@memori.ai/memori-react 7.19.1 → 7.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/dist/components/Avatar/Avatar.js +3 -3
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +19 -77
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +95 -70
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +747 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +60 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +68 -70
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
- package/dist/components/Avatar/AvatarView/index.d.ts +1 -1
- package/dist/components/Avatar/AvatarView/index.js +2 -2
- package/dist/components/Avatar/AvatarView/index.js.map +1 -1
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.js +12 -9
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
- package/dist/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
- package/dist/components/LoginDrawer/LoginDrawer.js +6 -6
- package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +143 -64
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/SignupForm/SignupForm.js +4 -4
- package/dist/components/SignupForm/SignupForm.js.map +1 -1
- package/dist/components/StartPanel/StartPanel.js +5 -5
- package/dist/components/StartPanel/StartPanel.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +2 -2
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/dist/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
- package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
- package/dist/context/visemeContext.js +0 -39
- package/dist/context/visemeContext.js.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/locales/de.json +1 -0
- package/dist/locales/en.json +1 -0
- package/dist/locales/es.json +1 -0
- package/dist/locales/fr.json +1 -0
- package/dist/locales/it.json +1 -0
- package/esm/components/Avatar/Avatar.js +3 -3
- package/esm/components/Avatar/Avatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +20 -78
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +99 -74
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +743 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +61 -3
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +67 -69
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
- package/esm/components/Avatar/AvatarView/index.d.ts +1 -1
- package/esm/components/Avatar/AvatarView/index.js +2 -2
- package/esm/components/Avatar/AvatarView/index.js.map +1 -1
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +12 -9
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
- package/esm/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
- package/esm/components/LoginDrawer/LoginDrawer.js +6 -6
- package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +143 -64
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/SignupForm/SignupForm.js +4 -4
- package/esm/components/SignupForm/SignupForm.js.map +1 -1
- package/esm/components/StartPanel/StartPanel.js +5 -5
- package/esm/components/StartPanel/StartPanel.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +2 -2
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/esm/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
- package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
- package/esm/context/visemeContext.js +0 -39
- package/esm/context/visemeContext.js.map +1 -1
- package/esm/index.js +4 -3
- package/esm/index.js.map +1 -1
- package/esm/locales/de.json +1 -0
- package/esm/locales/en.json +1 -0
- package/esm/locales/es.json +1 -0
- package/esm/locales/fr.json +1 -0
- package/esm/locales/it.json +1 -0
- package/package.json +2 -2
- package/src/components/Avatar/Avatar.tsx +3 -3
- package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +15 -8
- package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +64 -219
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +221 -124
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +1250 -0
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +164 -8
- package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +19 -17
- package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +80 -79
- package/src/components/Avatar/AvatarView/index.tsx +1 -7
- package/src/components/Chat/Chat.tsx +2 -2
- package/src/components/ChatBubble/ChatBubble.tsx +37 -26
- package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +1 -1
- package/src/components/LoginDrawer/LoginDrawer.tsx +6 -6
- package/src/components/MemoriWidget/MemoriWidget.tsx +184 -78
- package/src/components/SignupForm/SignupForm.tsx +5 -5
- package/src/components/StartPanel/StartPanel.tsx +5 -5
- package/src/components/UploadButton/UploadButton.tsx +4 -4
- package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +1 -1
- package/src/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +44 -3
- package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +128 -8
- package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +28 -3
- package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +15 -1
- package/src/components/layouts/layouts.stories.tsx +0 -8
- package/src/context/visemeContext.tsx +40 -41
- package/src/index.stories.tsx +63 -65
- package/src/index.tsx +5 -3
- package/src/locales/de.json +1 -0
- package/src/locales/en.json +1 -0
- package/src/locales/es.json +1 -0
- package/src/locales/fr.json +1 -0
- package/src/locales/it.json +1 -0
- package/src/mocks/data.ts +3 -9
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.ts +0 -308
- package/src/helpers/tenant.ts +0 -47
|
@@ -1,56 +1,95 @@
|
|
|
1
|
-
import { useEffect, useRef, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
SkinnedMesh,
|
|
5
|
-
Object3D,
|
|
6
|
-
AnimationAction,
|
|
7
|
-
Vector3
|
|
8
|
-
} from 'three';
|
|
9
|
-
import { useAnimations, useGLTF } from '@react-three/drei';
|
|
1
|
+
import { useEffect, useRef, useMemo, useCallback, useState } from 'react';
|
|
2
|
+
import { AnimationAction, Vector3, Scene, SkinnedMesh, Object3D } from 'three';
|
|
3
|
+
import { useGLTF, useAnimations } from '@react-three/drei';
|
|
10
4
|
import { useFrame } from '@react-three/fiber';
|
|
11
|
-
import { AnimationState, FullbodyAvatarProps } from './types';
|
|
12
|
-
import { AnimationController } from '../controllers/AnimationController';
|
|
13
5
|
import { MorphTargetController } from '../controllers/MorphTargetController';
|
|
14
6
|
import { AvatarPositionController } from '../controllers/AvatarPositionController';
|
|
7
|
+
import { AvatarAnimator } from '../controllers/AvatarAnimator';
|
|
15
8
|
import {
|
|
9
|
+
ANIMATION_URLS,
|
|
16
10
|
AVATAR_POSITION,
|
|
17
11
|
AVATAR_ROTATION,
|
|
18
|
-
AVATAR_POSITION_ZOOMED,
|
|
19
|
-
ANIMATION_URLS,
|
|
20
|
-
DEFAULT_CONFIG,
|
|
21
12
|
SCALE_LERP_FACTOR,
|
|
22
13
|
} from '../../constants';
|
|
23
14
|
import DynamicShadow from '../../Shadow/DynamicShadow';
|
|
24
15
|
|
|
16
|
+
// Enhanced props interface for FullbodyAvatar
|
|
17
|
+
export interface FullbodyAvatarProps {
|
|
18
|
+
url: string;
|
|
19
|
+
sex: 'MALE' | 'FEMALE';
|
|
20
|
+
eyeBlink: boolean;
|
|
21
|
+
updateCurrentViseme: (
|
|
22
|
+
currentTime: number
|
|
23
|
+
) => { name: string; weight: number } | null;
|
|
24
|
+
avatarHeight?: number;
|
|
25
|
+
avatarDepth?: number;
|
|
26
|
+
onCameraZChange?: (value: number) => void;
|
|
27
|
+
chatEmission: any;
|
|
28
|
+
loading: boolean;
|
|
29
|
+
// New prop to expose animator to parent component
|
|
30
|
+
setAnimatorRef?: (animator: AvatarAnimator | null) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export function FullbodyAvatar({
|
|
26
34
|
url,
|
|
27
35
|
sex,
|
|
28
|
-
setIsRpm,
|
|
29
|
-
currentBaseAction,
|
|
30
|
-
timeScale,
|
|
31
36
|
eyeBlink,
|
|
32
37
|
updateCurrentViseme,
|
|
33
|
-
setMorphTargetDictionary,
|
|
34
|
-
setMorphTargetInfluences,
|
|
35
|
-
emotionMorphTargets,
|
|
36
38
|
avatarHeight = 50,
|
|
37
39
|
avatarDepth = 0,
|
|
38
40
|
onCameraZChange,
|
|
41
|
+
chatEmission,
|
|
42
|
+
loading,
|
|
43
|
+
setAnimatorRef,
|
|
39
44
|
}: FullbodyAvatarProps) {
|
|
40
|
-
|
|
41
|
-
const { animations: baseAnimations } = useGLTF(url);
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
// Load the avatar model and its animations
|
|
46
|
+
const { scene, animations: baseAnimations } = useGLTF(url);
|
|
47
|
+
|
|
48
|
+
// Load additional animations based on sex (fallback animations)
|
|
49
|
+
const { animations: additionalAnimations } = useGLTF(ANIMATION_URLS[sex]);
|
|
50
|
+
|
|
51
|
+
// Check if avatar needs additional animations
|
|
52
|
+
const needsAdditionalAnimations = useMemo(() => {
|
|
53
|
+
let found = false;
|
|
54
|
+
scene.traverse((object: Object3D) => {
|
|
55
|
+
if (
|
|
56
|
+
object instanceof SkinnedMesh &&
|
|
57
|
+
(object.name === 'GBNL__Head' ||
|
|
58
|
+
object.name === 'Wolf3D_Avatar' ||
|
|
59
|
+
object.name === 'Wolf3D_Avatar006_1')
|
|
60
|
+
) {
|
|
61
|
+
found = true;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return found;
|
|
65
|
+
}, [scene]);
|
|
66
|
+
|
|
67
|
+
// Merge base and additional animations only if needed
|
|
68
|
+
const mergedAnimations = useMemo(
|
|
69
|
+
() => needsAdditionalAnimations ? [...baseAnimations, ...additionalAnimations] : baseAnimations,
|
|
70
|
+
[baseAnimations, additionalAnimations, needsAdditionalAnimations]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Create animation actions from the merged animations
|
|
44
74
|
const { actions } = useAnimations(mergedAnimations, scene);
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
const morphTargetControllerRef = useRef<MorphTargetController>();
|
|
48
|
-
const positionControllerRef = useRef<AvatarPositionController>();
|
|
76
|
+
// System controllers - use refs to maintain instance across renders
|
|
77
|
+
const morphTargetControllerRef = useRef<MorphTargetController | null>(null);
|
|
78
|
+
const positionControllerRef = useRef<AvatarPositionController | null>(null);
|
|
79
|
+
const animatorRef = useRef<AvatarAnimator | null>(null);
|
|
80
|
+
|
|
81
|
+
// Reference to track initialization status
|
|
82
|
+
const isInitializedRef = useRef<boolean>(false);
|
|
83
|
+
|
|
84
|
+
// For position tracking and updates
|
|
49
85
|
const lastPositionRef = useRef<Vector3>(AVATAR_POSITION.clone());
|
|
50
86
|
const positionUpdateThrottleRef = useRef<number>(0);
|
|
51
87
|
const POSITION_UPDATE_INTERVAL = 1; // ~1000fps for more frequent updates
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
const [isRpm, setIsRpm] = useState(false);
|
|
91
|
+
|
|
92
|
+
// For eye blinking
|
|
54
93
|
const blinkStateRef = useRef({
|
|
55
94
|
isBlinking: false,
|
|
56
95
|
lastBlinkTime: 0,
|
|
@@ -58,157 +97,215 @@ export function FullbodyAvatar({
|
|
|
58
97
|
blinkStartTime: 0,
|
|
59
98
|
});
|
|
60
99
|
|
|
61
|
-
//
|
|
100
|
+
// Find head mesh and initialize morph target controller - only run when scene changes
|
|
62
101
|
useEffect(() => {
|
|
63
|
-
if (!
|
|
64
|
-
positionControllerRef.current = new AvatarPositionController(AVATAR_POSITION);
|
|
65
|
-
}
|
|
102
|
+
if (!scene) return;
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
// Find head mesh
|
|
105
|
+
let headMesh: SkinnedMesh | null = null;
|
|
106
|
+
scene.traverse((object: Object3D) => {
|
|
107
|
+
if (
|
|
108
|
+
object instanceof SkinnedMesh &&
|
|
109
|
+
(object.name === 'GBNL__Head' ||
|
|
110
|
+
object.name === 'Wolf3D_Avatar' ||
|
|
111
|
+
object.name === 'Wolf3D_Avatar006_1')
|
|
112
|
+
) {
|
|
68
113
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
if(object.name === 'Wolf3D_Avatar' ||
|
|
115
|
+
object.name === 'Wolf3D_Avatar006_1') {
|
|
116
|
+
setIsRpm(true);
|
|
117
|
+
console.log('RPM avatar detected');
|
|
118
|
+
}
|
|
119
|
+
headMesh = object;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
75
122
|
|
|
123
|
+
// Initialize morph target controller if head mesh found
|
|
76
124
|
if (headMesh) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
setMorphTargetDictionary(headMesh.morphTargetDictionary);
|
|
81
|
-
const initialInfluences = Object.keys(headMesh.morphTargetDictionary)
|
|
82
|
-
.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
83
|
-
setMorphTargetInfluences(initialInfluences);
|
|
125
|
+
// Only create a new controller if one doesn't exist
|
|
126
|
+
if (!morphTargetControllerRef.current) {
|
|
127
|
+
morphTargetControllerRef.current = new MorphTargetController(headMesh);
|
|
84
128
|
}
|
|
85
129
|
}
|
|
130
|
+
}, [scene]); // Only re-run if scene changes
|
|
86
131
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
132
|
+
// Initialize position controller once
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!positionControllerRef.current) {
|
|
135
|
+
positionControllerRef.current = new AvatarPositionController(
|
|
136
|
+
AVATAR_POSITION
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Cleanup function
|
|
141
|
+
return () => {
|
|
142
|
+
// No need to dispose the position controller here
|
|
143
|
+
};
|
|
144
|
+
}, []); // Empty dependency array means this runs once
|
|
93
145
|
|
|
146
|
+
// Handle avatar height changes
|
|
94
147
|
useEffect(() => {
|
|
95
148
|
if (positionControllerRef.current) {
|
|
96
149
|
positionControllerRef.current.updateHeight(avatarHeight, false);
|
|
97
150
|
}
|
|
98
151
|
}, [avatarHeight]);
|
|
99
152
|
|
|
153
|
+
// Handle avatar depth changes
|
|
100
154
|
useEffect(() => {
|
|
101
155
|
if (positionControllerRef.current && onCameraZChange) {
|
|
102
|
-
const newCameraZ = positionControllerRef.current.updateDepth(
|
|
156
|
+
const newCameraZ = positionControllerRef.current.updateDepth(
|
|
157
|
+
avatarDepth,
|
|
158
|
+
false
|
|
159
|
+
);
|
|
103
160
|
onCameraZChange(newCameraZ);
|
|
104
161
|
}
|
|
105
162
|
}, [avatarDepth, onCameraZChange]);
|
|
106
163
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
scene
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
164
|
+
// Initialize animator only once and cleanup properly
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
// Only initialize if not already initialized and dependencies are available
|
|
167
|
+
if (!scene || !actions || isInitializedRef.current) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log('Initializing animator');
|
|
172
|
+
|
|
173
|
+
// Create the animator only once
|
|
174
|
+
if (!animatorRef.current) {
|
|
175
|
+
animatorRef.current = new AvatarAnimator();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const animator = animatorRef.current;
|
|
179
|
+
|
|
180
|
+
const initWithPreloadedAnimations = async () => {
|
|
181
|
+
try {
|
|
182
|
+
// Prevent multiple initializations
|
|
183
|
+
if (animator.isInitialized()) {
|
|
184
|
+
console.log('Animator already initialized, skipping initialization');
|
|
185
|
+
return;
|
|
119
186
|
}
|
|
120
|
-
|
|
187
|
+
|
|
188
|
+
// Initialize animator with avatar animations first, then fallback animations
|
|
189
|
+
await animator.initialize(
|
|
190
|
+
scene as unknown as Scene,
|
|
191
|
+
actions as Record<string, AnimationAction>,
|
|
192
|
+
mergedAnimations,
|
|
193
|
+
isRpm ? 'RPM' : 'CUSTOM_GLB'
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
console.log(
|
|
197
|
+
'AvatarAnimator initialized with',
|
|
198
|
+
Object.keys(actions).length,
|
|
199
|
+
'animations'
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Expose animator to parent component if callback provided
|
|
203
|
+
if (setAnimatorRef) {
|
|
204
|
+
setAnimatorRef(animator);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Mark as initialized
|
|
208
|
+
isInitializedRef.current = true;
|
|
209
|
+
|
|
210
|
+
// Set initial time scale
|
|
211
|
+
animator.setTimeScale(0.8);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('Error initializing AvatarAnimator:', error);
|
|
121
214
|
}
|
|
122
|
-
}
|
|
123
|
-
return foundMesh;
|
|
124
|
-
}, [scene]);
|
|
215
|
+
};
|
|
125
216
|
|
|
126
|
-
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (!animationControllerRef.current) return;
|
|
217
|
+
initWithPreloadedAnimations();
|
|
129
218
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
} else if (currentBaseAction.action.includes('->')) {
|
|
137
|
-
animationControllerRef.current.playSequence(currentBaseAction.action);
|
|
138
|
-
} else if (currentBaseAction.action.startsWith('Idle')) {
|
|
139
|
-
animationControllerRef.current.transitionTo(AnimationState.IDLE);
|
|
140
|
-
} else {
|
|
141
|
-
animationControllerRef.current.transitionTo(
|
|
142
|
-
AnimationState.EMOTION,
|
|
143
|
-
currentBaseAction.action
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
}, [currentBaseAction]);
|
|
219
|
+
// Cleanup on unmount
|
|
220
|
+
return () => {
|
|
221
|
+
// Only clean up if we created it in this component
|
|
222
|
+
if (animatorRef.current && isInitializedRef.current) {
|
|
223
|
+
console.log('Cleaning up animator');
|
|
147
224
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
225
|
+
// Dispose mixer if needed
|
|
226
|
+
if ('mixer' in animatorRef.current && animatorRef.current['mixer']) {
|
|
227
|
+
(animatorRef.current['mixer'] as any).stopAllAction();
|
|
228
|
+
}
|
|
152
229
|
|
|
153
|
-
|
|
154
|
-
|
|
230
|
+
// Clear reference in parent component
|
|
231
|
+
if (setAnimatorRef) {
|
|
232
|
+
setAnimatorRef(null);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Reset references
|
|
236
|
+
isInitializedRef.current = false;
|
|
237
|
+
// Don't null out animatorRef here to prevent flickering during re-renders
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}, [scene, actions, mergedAnimations, sex, setAnimatorRef]);
|
|
241
|
+
|
|
242
|
+
// Memoize the frame callback to prevent creating a new function every render
|
|
243
|
+
const frameCallback = useCallback((state: any, delta: number) => {
|
|
155
244
|
const currentTime = state.clock.elapsedTime * 1000;
|
|
156
245
|
|
|
157
|
-
// Update
|
|
158
|
-
|
|
246
|
+
// Update animation system
|
|
247
|
+
if (animatorRef.current && isInitializedRef.current) {
|
|
248
|
+
animatorRef.current.update(delta);
|
|
249
|
+
}
|
|
159
250
|
|
|
160
|
-
// Update morph targets
|
|
251
|
+
// Update morph targets for facial expressions and visemes
|
|
161
252
|
if (morphTargetControllerRef.current) {
|
|
162
253
|
const currentViseme = updateCurrentViseme(currentTime / 1000);
|
|
254
|
+
|
|
163
255
|
morphTargetControllerRef.current.updateMorphTargets(
|
|
164
256
|
currentTime,
|
|
165
|
-
|
|
257
|
+
chatEmission,
|
|
258
|
+
loading,
|
|
166
259
|
currentViseme,
|
|
167
|
-
eyeBlink
|
|
260
|
+
eyeBlink,
|
|
168
261
|
blinkStateRef.current
|
|
169
262
|
);
|
|
170
263
|
}
|
|
171
264
|
|
|
172
|
-
// Update
|
|
265
|
+
// Update position and scale
|
|
173
266
|
if (scene && positionControllerRef.current) {
|
|
174
|
-
|
|
267
|
+
// Update scale
|
|
268
|
+
const newScale =
|
|
269
|
+
positionControllerRef.current.updateScale(SCALE_LERP_FACTOR);
|
|
175
270
|
scene.scale.copy(newScale);
|
|
176
271
|
|
|
177
272
|
// High frequency position update check
|
|
178
|
-
if (
|
|
273
|
+
if (
|
|
274
|
+
currentTime - positionUpdateThrottleRef.current >=
|
|
275
|
+
POSITION_UPDATE_INTERVAL
|
|
276
|
+
) {
|
|
179
277
|
const currentPosition = positionControllerRef.current.getPosition();
|
|
180
|
-
|
|
278
|
+
|
|
181
279
|
// Round to 6 decimal places for high precision
|
|
182
280
|
currentPosition.setX(Number(currentPosition.x.toFixed(6)));
|
|
183
281
|
currentPosition.setY(Number(currentPosition.y.toFixed(6)));
|
|
184
282
|
currentPosition.setZ(Number(currentPosition.z.toFixed(6)));
|
|
185
|
-
|
|
186
|
-
const positionDelta = currentPosition.distanceTo(lastPositionRef.current);
|
|
187
|
-
|
|
283
|
+
|
|
188
284
|
lastPositionRef.current.copy(currentPosition);
|
|
189
|
-
|
|
190
285
|
positionUpdateThrottleRef.current = currentTime;
|
|
191
286
|
}
|
|
192
287
|
}
|
|
193
|
-
});
|
|
288
|
+
}, [scene, updateCurrentViseme, chatEmission, loading, eyeBlink]);
|
|
289
|
+
|
|
290
|
+
// Use the memoized callback in useFrame
|
|
291
|
+
useFrame(frameCallback);
|
|
194
292
|
|
|
195
|
-
// Get current position from controller with fallback
|
|
293
|
+
// Get current position from controller with fallback - memoize to prevent recreation
|
|
196
294
|
const position = useMemo(() => {
|
|
197
|
-
return
|
|
198
|
-
|
|
295
|
+
return (
|
|
296
|
+
positionControllerRef.current?.getPosition() || AVATAR_POSITION.clone()
|
|
297
|
+
);
|
|
298
|
+
}, []);
|
|
199
299
|
|
|
200
300
|
return (
|
|
201
301
|
<>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<group
|
|
207
|
-
position={position}
|
|
208
|
-
rotation={AVATAR_ROTATION}
|
|
209
|
-
>
|
|
210
|
-
<primitive object={scene} />
|
|
211
|
-
</group>
|
|
302
|
+
<DynamicShadow animator={animatorRef.current} avatarPosition={position} />
|
|
303
|
+
<group position={position} rotation={AVATAR_ROTATION}>
|
|
304
|
+
<primitive object={scene} />
|
|
305
|
+
</group>
|
|
212
306
|
</>
|
|
213
307
|
);
|
|
214
|
-
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Avoid using React.memo here as it's exported as a named function
|
|
311
|
+
// The parent component should handle memoization if needed
|