Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions src/components/Avatar/Avatar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
MaterialConfiguration,
SpawnState
} from 'src/types';
import { AnimationAction, Vector3 } from 'three';
import { AnimationAction, Vector3, Mesh } from 'three';

import Lights from 'src/components/Lights/Lights.component';
import { spawnState } from 'src/state/spawnAtom';
Expand Down Expand Up @@ -179,6 +179,22 @@ export interface AvatarProps extends LightingProps, EnvironmentProps, Omit<BaseM
*/
canvasConfig?: CanvasConfiguration;
animatedCameraSrc?: string;
/**
* Callback for when a mesh is clicked.
*/
onMeshClick?: (mesh: Mesh) => void;
/**
* Callback for when a mesh hover starts.
*/
onMeshHoverStart?: (mesh: Mesh) => void;
/**
* Callback for when a mesh hover ends.
*/
onMeshHoverEnd?: (mesh: Mesh) => void;
/**
* Callback for processing meshes on model load.
*/
meshCallback?: (mesh: Mesh) => void;
}

/**
Expand Down Expand Up @@ -229,7 +245,11 @@ const Avatar: FC<AvatarProps> = ({
materialConfig,
controlsMinDistance,
controlsMaxDistance,
canvasConfig
canvasConfig,
onMeshClick,
onMeshHoverStart,
onMeshHoverEnd,
meshCallback
}) => {
const setSpawnState = useSetAtom(spawnState);

Expand All @@ -254,6 +274,10 @@ const Avatar: FC<AvatarProps> = ({
bloom={effects?.bloom}
onAnimationEnd={onAnimationEnd}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
}
Expand All @@ -270,6 +294,10 @@ const Avatar: FC<AvatarProps> = ({
headMovement={headMovement}
bloom={effects?.bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
}
Expand All @@ -285,6 +313,10 @@ const Avatar: FC<AvatarProps> = ({
headMovement={headMovement}
bloom={effects?.bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
}
Expand All @@ -299,6 +331,10 @@ const Avatar: FC<AvatarProps> = ({
onLoaded={onLoaded}
bloom={effects?.bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
}
Expand All @@ -311,6 +347,10 @@ const Avatar: FC<AvatarProps> = ({
emotion={emotion}
bloom={effects?.bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
}, [
Expand All @@ -327,7 +367,11 @@ const Avatar: FC<AvatarProps> = ({
materialConfig,
onAnimationEnd,
idleRotation,
headMovement
headMovement,
onMeshClick,
onMeshHoverStart,
onMeshHoverEnd,
meshCallback
]);

useEffect(() => triggerCallback(onLoading), [modelSrc, animationSrc, onLoading]);
Expand Down
6 changes: 5 additions & 1 deletion src/components/Avatar/Avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ export default {
cameraInitialDistance: { control: { type: 'range', min: 0, max: 2.5, step: 0.01 } },
onLoaded: { control: false },
environment: { options: Object.keys(environmentPresets), control: { type: 'select' } },
fov: { control: { type: 'range', min: 30, max: 100, step: 1 } }
fov: { control: { type: 'range', min: 30, max: 100, step: 1 } },
onMeshClick: { control: false },
onMeshHoverStart: { control: false },
onMeshHoverEnd: { control: false },
meshCallback: { control: false }
}
};
86 changes: 84 additions & 2 deletions src/components/Avatar/Examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import React, { useCallback, useState } from 'react';
import { StoryFn } from '@storybook/react';
import { Sparkles as SparklesDrei } from '@react-three/drei';
import type { Meta } from '@storybook/react';
import { Avatar as AvatarWrapper, CAMERA } from 'src/components/Avatar';
import { getStoryAssetPath } from 'src/services';
import { ignoreArgTypesOnExamples, emotions, modelPresets, animationPresets } from 'src/services/Stories.service';
import { Vector3 } from 'three';
import { Mesh, MeshStandardMaterial, Vector3 } from 'three';
import { AvatarProps } from './Avatar.component';
import { Static } from './Avatar.stories';
import { BloomConfiguration } from '../../types';
Expand Down Expand Up @@ -154,3 +154,85 @@ IdleBlinking.argTypes = {
modelSrc: { options: Object.values({ seven: modelPresets.seven }), control: { type: 'select' } },
animationSrc: { options: Object.values(animationPresets), control: { type: 'select' } }
};

const highlightMaterial = new MeshStandardMaterial({ color: '#ff0000', transparent: false });

export const MouseActions: StoryFn<typeof Avatar> = (args) => {
const [originalMaterials] = useState(new Map());

const handleModelClick = (mesh: Mesh) => {
console.log('Clicked mesh:', mesh);
Comment thread
Kristokarp marked this conversation as resolved.
};

const dimClothMaterials = (mesh: Mesh) => {
const meshWhitelist = ['Wolf3D_Outfit_Top', 'Wolf3D_Outfit_Bottom', 'Wolf3D_Hair', 'Wolf3D_Outfit_Footwear'];

if (!meshWhitelist.includes(mesh.name)) {
return;
}

const material = mesh.material as MeshStandardMaterial;
material.color.setHex(0x404040);
material.roughness = 0.8;
material.metalness = 0.1;
material.emissive.setHex(0xffffff);
material.emissiveIntensity = 0.1;
};

const onMeshHoverStart = useCallback(
(mesh: Mesh) => {
const meshWhiteList = ['Wolf3D_Outfit_Top', 'Wolf3D_Outfit_Bottom', 'Wolf3D_Hair', 'Wolf3D_Outfit_Footwear'];

if (!meshWhiteList.includes(mesh?.name)) {
return;
}

document.body.style.cursor = 'pointer';

// Store original material and set hover highlight for the new mesh
if (!originalMaterials.has(mesh.name)) {
originalMaterials.set(mesh.name, mesh.material);

// eslint-disable-next-line no-param-reassign
mesh.material = highlightMaterial;
}
},
[originalMaterials]
);

const onMeshHoverEnd = useCallback(
(mesh: Mesh) => {
const originalMaterial = originalMaterials.get(mesh.name);

if (originalMaterial) {
document.body.style.cursor = 'default';
// eslint-disable-next-line no-param-reassign
mesh.material = originalMaterial;
originalMaterials.delete(mesh.name);
}
},
[originalMaterials]
);

return (
<Avatar
{...args}
onMeshClick={handleModelClick}
meshCallback={dimClothMaterials}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
/>
);
};

MouseActions.args = {
...Static.args,
modelSrc: getStoryAssetPath('male.glb'),
cameraTarget: CAMERA.TARGET.FULL_BODY.MALE,
cameraInitialDistance: CAMERA.CONTROLS.FULL_BODY.MAX_DISTANCE,
background: {
color: 'rgba(134, 157, 169, 1)'
}
};

MouseActions.argTypes = {};
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ export const AnimationModel: FC<AnimationModelProps> = ({
headMovement = false,
emotion,
bloom,
materialConfig
materialConfig,
onMeshClick,
onMeshHoverStart,
onMeshHoverEnd,
meshCallback
}) => {
const ref = useRef<Group>(null);
const [animationRunning, setAnimationRunning] = useState(true);
Expand All @@ -57,7 +61,7 @@ export const AnimationModel: FC<AnimationModelProps> = ({

assetMixerRef.current = null;
};
}, [scene]);
}, [scene, embeddedAnimations]);

const animationClip = useMemo(async () => {
const clip = await loadAnimationClips(animationSrc);
Expand Down Expand Up @@ -108,6 +112,10 @@ export const AnimationModel: FC<AnimationModelProps> = ({
onSpawnAnimationFinish={onSpawnAnimationFinish}
bloom={bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
};
25 changes: 23 additions & 2 deletions src/components/Models/FloatingModel/FloatingModel.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ export interface FloatingModelProps extends BaseModelProps {
scale?: number;
}

export const FloatingModel: FC<FloatingModelProps> = ({ modelSrc, scale = 1.0, onLoaded, bloom }) => {
export const FloatingModel: FC<FloatingModelProps> = ({
modelSrc,
scale = 1.0,
onLoaded,
bloom,
onMeshClick,
onMeshHoverStart,
onMeshHoverEnd,
meshCallback
}) => {
const ref = useRef<Group>(null);
const { scene } = useGltfLoader(modelSrc);

Expand All @@ -23,5 +32,17 @@ export const FloatingModel: FC<FloatingModelProps> = ({ modelSrc, scale = 1.0, o
}
});

return <Model modelRef={ref} scale={scale} scene={scene} onLoaded={onLoaded} bloom={bloom} />;
return (
<Model
modelRef={ref}
scale={scale}
scene={scene}
onLoaded={onLoaded}
bloom={bloom}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export const HalfBodyModel: FC<HalfBodyModelProps> = ({
onLoaded,
headMovement = false,
bloom,
materialConfig
materialConfig,
onMeshClick,
onMeshHoverStart,
onMeshHoverEnd,
meshCallback
}) => {
const ref = useRef<Group>(null);
const { scene } = useGltfLoader(modelSrc);
Expand Down Expand Up @@ -72,6 +76,10 @@ export const HalfBodyModel: FC<HalfBodyModelProps> = ({
onLoaded={onLoaded}
bloom={bloom}
materialConfig={materialConfig}
onMeshClick={onMeshClick}
onMeshHoverStart={onMeshHoverStart}
onMeshHoverEnd={onMeshHoverEnd}
meshCallback={meshCallback}
/>
);
};
Loading
Loading