@memori.ai/memori-react 7.8.7 → 7.9.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 +61 -0
- package/dist/components/Avatar/Avatar.d.ts +4 -0
- package/dist/components/Avatar/Avatar.js +6 -6
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +3 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +3 -3
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +48 -10
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.d.ts +4 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.d.ts +17 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js +73 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js.map +1 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.d.ts +19 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js +60 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js.map +1 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.d.ts +18 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.js +26 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.js.map +1 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +8 -7
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +65 -78
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.d.ts +12 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +98 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -0
- package/dist/components/Avatar/AvatarView/index.d.ts +4 -1
- package/dist/components/Avatar/AvatarView/index.js +36 -13
- package/dist/components/Avatar/AvatarView/index.js.map +1 -1
- package/dist/components/Avatar/AvatarView/utils/hideHands.d.ts +2 -0
- package/dist/components/Avatar/AvatarView/utils/hideHands.js +14 -0
- package/dist/components/Avatar/AvatarView/utils/hideHands.js.map +1 -0
- package/dist/components/Chat/Chat.d.ts +1 -0
- package/dist/components/Chat/Chat.js +5 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.js +19 -4
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +27 -4
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/SettingsDrawer/SettingsDrawer.css +4 -2
- package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +6 -1
- package/dist/components/SettingsDrawer/SettingsDrawer.js +10 -4
- package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/dist/components/layouts/Totem.js +1 -1
- package/dist/components/layouts/Totem.js.map +1 -1
- package/dist/components/layouts/totem.css +9 -9
- package/dist/components/ui/Button.css +4 -0
- package/dist/components/ui/Button.d.ts +1 -0
- package/dist/components/ui/Button.js +2 -1
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Slider.css +177 -0
- package/dist/components/ui/Slider.d.ts +12 -0
- package/dist/components/ui/Slider.js +78 -0
- package/dist/components/ui/Slider.js.map +1 -0
- package/dist/locales/en.json +11 -0
- package/dist/locales/it.json +11 -0
- package/dist/styles.css +1 -0
- package/esm/components/Avatar/Avatar.d.ts +4 -0
- package/esm/components/Avatar/Avatar.js +6 -6
- package/esm/components/Avatar/Avatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +3 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +3 -3
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +45 -8
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.d.ts +4 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.d.ts +17 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js +69 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js.map +1 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.d.ts +19 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js +56 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js.map +1 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.d.ts +18 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.js +23 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.js.map +1 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +8 -7
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +66 -80
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.d.ts +12 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +95 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -0
- package/esm/components/Avatar/AvatarView/index.d.ts +4 -1
- package/esm/components/Avatar/AvatarView/index.js +38 -15
- package/esm/components/Avatar/AvatarView/index.js.map +1 -1
- package/esm/components/Avatar/AvatarView/utils/hideHands.d.ts +2 -0
- package/esm/components/Avatar/AvatarView/utils/hideHands.js +10 -0
- package/esm/components/Avatar/AvatarView/utils/hideHands.js.map +1 -0
- package/esm/components/Chat/Chat.d.ts +1 -0
- package/esm/components/Chat/Chat.js +5 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +19 -4
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +27 -4
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/SettingsDrawer/SettingsDrawer.css +4 -2
- package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +6 -1
- package/esm/components/SettingsDrawer/SettingsDrawer.js +10 -4
- package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/esm/components/layouts/Totem.js +1 -1
- package/esm/components/layouts/Totem.js.map +1 -1
- package/esm/components/layouts/totem.css +9 -9
- package/esm/components/ui/Button.css +4 -0
- package/esm/components/ui/Button.d.ts +1 -0
- package/esm/components/ui/Button.js +2 -1
- package/esm/components/ui/Button.js.map +1 -1
- package/esm/components/ui/Slider.css +177 -0
- package/esm/components/ui/Slider.d.ts +12 -0
- package/esm/components/ui/Slider.js +75 -0
- package/esm/components/ui/Slider.js.map +1 -0
- package/esm/locales/en.json +11 -0
- package/esm/locales/it.json +11 -0
- package/esm/styles.css +1 -0
- package/package.json +3 -2
- package/src/components/Avatar/Avatar.test.tsx +8 -0
- package/src/components/Avatar/Avatar.tsx +19 -6
- package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +17 -8
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.ts +1 -1
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +69 -18
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.ts +4 -1
- package/src/components/Avatar/AvatarView/AvatarComponent/components/PositionController.ts +83 -0
- package/src/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/constants.ts → constants.ts} +2 -1
- package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +106 -124
- package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
- package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx +187 -0
- package/src/components/Avatar/AvatarView/index.tsx +115 -43
- package/src/components/Avatar/AvatarView/utils/hideHands.ts +11 -0
- package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +32 -93
- package/src/components/Chat/Chat.test.tsx +11 -0
- package/src/components/Chat/Chat.tsx +8 -1
- package/src/components/ChatBubble/ChatBubble.tsx +46 -27
- package/src/components/MemoriWidget/MemoriWidget.tsx +42 -5
- package/src/components/SettingsDrawer/SettingsDrawer.css +4 -2
- package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +24 -0
- package/src/components/SettingsDrawer/SettingsDrawer.tsx +76 -4
- package/src/components/layouts/Totem.tsx +1 -1
- package/src/components/layouts/layouts.stories.tsx +111 -3
- package/src/components/layouts/totem.css +9 -9
- package/src/components/ui/Button.css +4 -0
- package/src/components/ui/Button.tsx +3 -0
- package/src/components/ui/Slider.css +177 -0
- package/src/components/ui/Slider.stories.tsx +63 -0
- package/src/components/ui/Slider.tsx +142 -0
- package/src/locales/en.json +11 -0
- package/src/locales/it.json +11 -0
- package/src/styles.css +1 -0
- /package/src/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.ts → MorphTargetController.ts} +0 -0
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useRef, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
Vector3,
|
|
4
|
-
Euler,
|
|
5
3
|
AnimationMixer,
|
|
6
4
|
SkinnedMesh,
|
|
7
5
|
Object3D,
|
|
8
|
-
MathUtils,
|
|
9
6
|
AnimationAction,
|
|
10
|
-
LoopOnce,
|
|
11
7
|
} from 'three';
|
|
12
8
|
import { useAnimations, useGLTF } from '@react-three/drei';
|
|
13
9
|
import { useFrame } from '@react-three/fiber';
|
|
14
10
|
import { AnimationState, FullbodyAvatarProps } from './types';
|
|
15
11
|
import { AnimationController } from './AnimationController';
|
|
16
|
-
import { MorphTargetController } from '
|
|
12
|
+
import { MorphTargetController } from '../MorphTargetController';
|
|
13
|
+
import { AvatarPositionController } from '../PositionController';
|
|
17
14
|
import {
|
|
18
15
|
AVATAR_POSITION,
|
|
19
16
|
AVATAR_ROTATION,
|
|
20
17
|
AVATAR_POSITION_ZOOMED,
|
|
21
18
|
ANIMATION_URLS,
|
|
22
19
|
DEFAULT_CONFIG,
|
|
23
|
-
|
|
20
|
+
SCALE_LERP_FACTOR,
|
|
21
|
+
} from '../constants';
|
|
24
22
|
|
|
25
|
-
export
|
|
23
|
+
export function FullbodyAvatar({
|
|
26
24
|
url,
|
|
27
25
|
sex,
|
|
28
26
|
currentBaseAction,
|
|
29
27
|
timeScale,
|
|
30
|
-
isZoomed,
|
|
31
28
|
eyeBlink,
|
|
32
29
|
updateCurrentViseme,
|
|
33
30
|
setMorphTargetDictionary,
|
|
34
31
|
setMorphTargetInfluences,
|
|
35
32
|
emotionMorphTargets,
|
|
33
|
+
avatarHeight = 50,
|
|
34
|
+
avatarDepth = 0,
|
|
35
|
+
onCameraZChange,
|
|
36
36
|
}: FullbodyAvatarProps) {
|
|
37
37
|
const { scene } = useGLTF(url);
|
|
38
38
|
const { animations } = useGLTF(ANIMATION_URLS[sex]);
|
|
@@ -40,6 +40,8 @@ export default function FullbodyAvatar({
|
|
|
40
40
|
|
|
41
41
|
const animationControllerRef = useRef<AnimationController>();
|
|
42
42
|
const morphTargetControllerRef = useRef<MorphTargetController>();
|
|
43
|
+
const positionControllerRef = useRef<AvatarPositionController>();
|
|
44
|
+
|
|
43
45
|
const blinkStateRef = useRef({
|
|
44
46
|
isBlinking: false,
|
|
45
47
|
lastBlinkTime: 0,
|
|
@@ -47,10 +49,49 @@ export default function FullbodyAvatar({
|
|
|
47
49
|
blinkStartTime: 0,
|
|
48
50
|
});
|
|
49
51
|
|
|
52
|
+
// Initialize controllers
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!positionControllerRef.current) {
|
|
55
|
+
positionControllerRef.current = new AvatarPositionController(AVATAR_POSITION);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!actions || !scene) return;
|
|
59
|
+
|
|
60
|
+
const mixer = new AnimationMixer(scene);
|
|
61
|
+
animationControllerRef.current = new AnimationController(
|
|
62
|
+
mixer,
|
|
63
|
+
actions as Record<string, AnimationAction>,
|
|
64
|
+
{ ...DEFAULT_CONFIG }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (headMesh) {
|
|
68
|
+
morphTargetControllerRef.current = new MorphTargetController(headMesh);
|
|
69
|
+
|
|
70
|
+
if (headMesh.morphTargetDictionary && headMesh.morphTargetInfluences) {
|
|
71
|
+
setMorphTargetDictionary(headMesh.morphTargetDictionary);
|
|
72
|
+
const initialInfluences = Object.keys(headMesh.morphTargetDictionary)
|
|
73
|
+
.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
74
|
+
setMorphTargetInfluences(initialInfluences);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, [actions, scene]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (positionControllerRef.current) {
|
|
80
|
+
positionControllerRef.current.updateHeight(avatarHeight, false);
|
|
81
|
+
}
|
|
82
|
+
}, [avatarHeight]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (positionControllerRef.current && onCameraZChange) {
|
|
86
|
+
const newCameraZ = positionControllerRef.current.updateDepth(avatarDepth, false);
|
|
87
|
+
onCameraZChange(newCameraZ);
|
|
88
|
+
}
|
|
89
|
+
}, [avatarDepth, onCameraZChange]);
|
|
90
|
+
|
|
50
91
|
// Find head mesh
|
|
51
92
|
const headMesh = useMemo(() => {
|
|
52
93
|
let foundMesh: SkinnedMesh | undefined;
|
|
53
|
-
scene
|
|
94
|
+
scene?.traverse((object: Object3D) => {
|
|
54
95
|
if (
|
|
55
96
|
object instanceof SkinnedMesh &&
|
|
56
97
|
(object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar')
|
|
@@ -61,8 +102,8 @@ export default function FullbodyAvatar({
|
|
|
61
102
|
return foundMesh;
|
|
62
103
|
}, [scene]);
|
|
63
104
|
|
|
64
|
-
|
|
65
|
-
|
|
105
|
+
// Initialize controllers
|
|
106
|
+
useEffect(() => {
|
|
66
107
|
if (!actions || !headMesh) return;
|
|
67
108
|
|
|
68
109
|
const mixer = new AnimationMixer(scene);
|
|
@@ -96,6 +137,7 @@ export default function FullbodyAvatar({
|
|
|
96
137
|
if (!animationControllerRef.current) return;
|
|
97
138
|
|
|
98
139
|
if (currentBaseAction.action.startsWith('Loading')) {
|
|
140
|
+
animationControllerRef.current.updateIsChatAlreadyStarted(true);
|
|
99
141
|
animationControllerRef.current.transitionTo(
|
|
100
142
|
AnimationState.LOADING,
|
|
101
143
|
currentBaseAction.action
|
|
@@ -103,7 +145,6 @@ export default function FullbodyAvatar({
|
|
|
103
145
|
} else if (currentBaseAction.action.startsWith('Idle')) {
|
|
104
146
|
animationControllerRef.current.transitionTo(AnimationState.IDLE);
|
|
105
147
|
} else {
|
|
106
|
-
animationControllerRef.current.updateIsChatAlreadyStarted(true);
|
|
107
148
|
animationControllerRef.current.transitionTo(
|
|
108
149
|
AnimationState.EMOTION,
|
|
109
150
|
currentBaseAction.action
|
|
@@ -116,12 +157,13 @@ export default function FullbodyAvatar({
|
|
|
116
157
|
animationControllerRef.current?.setTimeScale(timeScale);
|
|
117
158
|
}, [timeScale]);
|
|
118
159
|
|
|
119
|
-
|
|
120
|
-
|
|
160
|
+
|
|
161
|
+
// Animation and scaling update loop
|
|
162
|
+
useFrame((state, delta) => {
|
|
121
163
|
const currentTime = state.clock.elapsedTime * 1000;
|
|
122
164
|
|
|
123
165
|
// Update animations
|
|
124
|
-
animationControllerRef.current?.update(
|
|
166
|
+
animationControllerRef.current?.update(delta);
|
|
125
167
|
|
|
126
168
|
// Update morph targets
|
|
127
169
|
if (morphTargetControllerRef.current) {
|
|
@@ -134,14 +176,23 @@ export default function FullbodyAvatar({
|
|
|
134
176
|
blinkStateRef.current
|
|
135
177
|
);
|
|
136
178
|
}
|
|
179
|
+
|
|
180
|
+
// Update scale with smooth transition
|
|
181
|
+
if (scene && positionControllerRef.current) {
|
|
182
|
+
const newScale = positionControllerRef.current.updateScale(SCALE_LERP_FACTOR);
|
|
183
|
+
scene.scale.copy(newScale);
|
|
184
|
+
}
|
|
137
185
|
});
|
|
138
186
|
|
|
187
|
+
// Get current position from controller
|
|
188
|
+
const position = positionControllerRef.current?.getPosition() || AVATAR_POSITION;
|
|
189
|
+
|
|
139
190
|
return (
|
|
140
191
|
<group
|
|
141
|
-
position={
|
|
192
|
+
position={position}
|
|
142
193
|
rotation={AVATAR_ROTATION}
|
|
143
194
|
>
|
|
144
195
|
<primitive object={scene} />
|
|
145
196
|
</group>
|
|
146
197
|
);
|
|
147
|
-
}
|
|
198
|
+
}
|
|
@@ -20,8 +20,10 @@ export interface FullbodyAvatarProps {
|
|
|
20
20
|
weight: number;
|
|
21
21
|
};
|
|
22
22
|
timeScale: number;
|
|
23
|
-
|
|
23
|
+
onCameraZChange: (value: number) => void;
|
|
24
24
|
eyeBlink?: boolean;
|
|
25
|
+
avatarDepth?: number;
|
|
26
|
+
avatarHeight?: number;
|
|
25
27
|
stopProcessing: () => void;
|
|
26
28
|
resetVisemeQueue: () => void;
|
|
27
29
|
updateCurrentViseme: (
|
|
@@ -37,4 +39,5 @@ export interface FullbodyAvatarProps {
|
|
|
37
39
|
morphTargetInfluences: Record<string, number>
|
|
38
40
|
) => void;
|
|
39
41
|
emotionMorphTargets: Record<string, number>;
|
|
42
|
+
halfBody: boolean;
|
|
40
43
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Vector3, MathUtils } from 'three';
|
|
2
|
+
import { AVATAR_POSITION, AVATAR_POSITION_ZOOMED } from './constants';
|
|
3
|
+
|
|
4
|
+
export class AvatarPositionController {
|
|
5
|
+
private currentScale: Vector3;
|
|
6
|
+
private targetScale: Vector3;
|
|
7
|
+
private currentPosition: Vector3;
|
|
8
|
+
private basePosition: Vector3;
|
|
9
|
+
private defaultPosition: Vector3;
|
|
10
|
+
private zoomedPosition: Vector3;
|
|
11
|
+
private initialCameraPosition: Vector3;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
defaultPosition: Vector3 = AVATAR_POSITION.clone(),
|
|
15
|
+
zoomedPosition: Vector3 = AVATAR_POSITION_ZOOMED.clone(),
|
|
16
|
+
initialCameraZ: number = 0.6
|
|
17
|
+
) {
|
|
18
|
+
this.defaultPosition = defaultPosition;
|
|
19
|
+
this.zoomedPosition = zoomedPosition;
|
|
20
|
+
this.currentScale = new Vector3(1, 1, 1);
|
|
21
|
+
this.targetScale = new Vector3(1, 1, 1);
|
|
22
|
+
this.currentPosition = defaultPosition.clone();
|
|
23
|
+
this.basePosition = defaultPosition.clone();
|
|
24
|
+
this.initialCameraPosition = new Vector3(0, 0, initialCameraZ);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Map height slider value (0 to 100) to scale (0.8 to 1.2)
|
|
28
|
+
private mapHeightToScale(sliderValue: number, isHalfBody: boolean): number {
|
|
29
|
+
// Convert slider value to scale factor
|
|
30
|
+
if (isHalfBody) {
|
|
31
|
+
return MathUtils.lerp(1.4, 2.1, sliderValue / 100);
|
|
32
|
+
} else {
|
|
33
|
+
return MathUtils.lerp(0.5, 1.5, sliderValue / 100);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Map depth slider value (0 to 100) to camera Z position
|
|
38
|
+
private mapDepthToCamera(depthValue: number, isHalfBody: boolean): number {
|
|
39
|
+
const baseZ = this.initialCameraPosition.z;
|
|
40
|
+
if (isHalfBody) {
|
|
41
|
+
return MathUtils.lerp(baseZ, baseZ + 3, depthValue / 100);
|
|
42
|
+
} else {
|
|
43
|
+
return MathUtils.lerp(baseZ, baseZ + 3, depthValue / 100);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Update height from GUI control (0-100)
|
|
48
|
+
updateHeight(heightValue: number, isHalfBody: boolean): void {
|
|
49
|
+
const heightScale = this.mapHeightToScale(heightValue, isHalfBody);
|
|
50
|
+
this.targetScale.set(heightScale, heightScale, heightScale);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Update depth and return new camera Z position
|
|
54
|
+
updateDepth(depthValue: number, isHalfBody: boolean): number {
|
|
55
|
+
return this.mapDepthToCamera(depthValue, isHalfBody);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Update base position for zooming
|
|
59
|
+
updateBasePosition(isZoomed: boolean): void {
|
|
60
|
+
const newPosition = isZoomed ? this.zoomedPosition : this.defaultPosition;
|
|
61
|
+
this.basePosition.copy(newPosition);
|
|
62
|
+
this.currentPosition.copy(newPosition);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Smoothly interpolate current scale to target scale
|
|
66
|
+
updateScale(lerpFactor: number): Vector3 {
|
|
67
|
+
this.currentScale.lerp(this.targetScale, lerpFactor);
|
|
68
|
+
return this.currentScale;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get current position
|
|
72
|
+
getPosition(): Vector3 {
|
|
73
|
+
return this.currentPosition;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Reset to default values
|
|
77
|
+
reset(): void {
|
|
78
|
+
this.currentScale.set(1, 1, 1);
|
|
79
|
+
this.targetScale.set(1, 1, 1);
|
|
80
|
+
this.currentPosition.copy(this.defaultPosition);
|
|
81
|
+
this.basePosition.copy(this.defaultPosition);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Vector3, Euler } from 'three';
|
|
2
|
-
import { AnimationConfig } from './types';
|
|
2
|
+
import { AnimationConfig } from './FullbodyAvatar/types';
|
|
3
3
|
|
|
4
4
|
export const AVATAR_POSITION = new Vector3(0, -1, 0);
|
|
5
5
|
export const AVATAR_ROTATION = new Euler(0.175, 0, 0);
|
|
6
6
|
export const AVATAR_POSITION_ZOOMED = new Vector3(0, -1.45, 0);
|
|
7
|
+
export const SCALE_LERP_FACTOR = 0.1;
|
|
7
8
|
|
|
8
9
|
export const ANIMATION_URLS = {
|
|
9
10
|
MALE: 'https://assets.memori.ai/api/v2/asset/2c5e88a4-cf62-408b-9ef0-518b099dfcb2.glb',
|
|
@@ -1,168 +1,150 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Object3D, SkinnedMesh
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Object3D, SkinnedMesh } from 'three';
|
|
3
3
|
import { useGLTF } from '@react-three/drei';
|
|
4
|
+
import { useGraph, useFrame, useThree } from '@react-three/fiber';
|
|
4
5
|
import { correctMaterials, isSkinnedMesh } from '../../../../../helpers/utils';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { MorphTargetController } from './MorphTargetController';
|
|
7
|
+
import { AvatarPositionController } from './PositionController';
|
|
8
|
+
import {
|
|
9
|
+
AVATAR_POSITION,
|
|
10
|
+
SCALE_LERP_FACTOR,
|
|
11
|
+
AVATAR_POSITION_ZOOMED,
|
|
12
|
+
} from './constants';
|
|
8
13
|
import { hideHands } from '../../utils/utils';
|
|
9
|
-
import
|
|
14
|
+
import useHeadMovement from '../../utils/useHeadMovement';
|
|
10
15
|
|
|
11
16
|
interface HalfBodyAvatarProps {
|
|
12
17
|
url: string;
|
|
13
18
|
setMorphTargetInfluences: (morphTargetInfluences: any) => void;
|
|
14
|
-
headMovement?: boolean;
|
|
15
|
-
speaking?: boolean;
|
|
16
|
-
onLoaded?: () => void;
|
|
17
19
|
setMorphTargetDictionary: (morphTargetDictionary: any) => void;
|
|
18
|
-
eyeBlink?: boolean;
|
|
19
|
-
morphTargetInfluences: any;
|
|
20
20
|
updateCurrentViseme: (currentTime: number) => any;
|
|
21
|
-
|
|
21
|
+
eyeBlink?: boolean;
|
|
22
|
+
heightValue?: number; // 0-100 slider value
|
|
23
|
+
avatarHeight: number;
|
|
24
|
+
avatarDepth: number;
|
|
25
|
+
onLoaded?: () => void;
|
|
26
|
+
onCameraZChange: (value: number) => void;
|
|
27
|
+
headMovement?: boolean;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
const AVATAR_POSITION = new Vector3(0, -0.6, 0);
|
|
25
|
-
// Blink configuration
|
|
26
|
-
const BLINK_CONFIG = {
|
|
27
|
-
minInterval: 1000,
|
|
28
|
-
maxInterval: 5000,
|
|
29
|
-
blinkDuration: 150,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
30
|
export default function HalfBodyAvatar({
|
|
33
31
|
url,
|
|
34
32
|
setMorphTargetInfluences,
|
|
35
33
|
setMorphTargetDictionary,
|
|
36
|
-
eyeBlink,
|
|
37
|
-
onLoaded,
|
|
38
|
-
morphTargetSmoothing = 0.5,
|
|
39
34
|
updateCurrentViseme,
|
|
35
|
+
eyeBlink = false,
|
|
36
|
+
avatarHeight = 50,
|
|
37
|
+
avatarDepth = 0,
|
|
38
|
+
headMovement = false,
|
|
39
|
+
onLoaded,
|
|
40
|
+
onCameraZChange,
|
|
40
41
|
}: HalfBodyAvatarProps) {
|
|
41
42
|
const { scene } = useGLTF(url);
|
|
42
43
|
const { nodes, materials } = useGraph(scene);
|
|
43
|
-
const
|
|
44
|
-
const avatarMeshRef = useRef<SkinnedMesh | null>(null);
|
|
44
|
+
const { camera } = useThree();
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const isBlinking = useRef(false);
|
|
50
|
-
const blinkStartTime = useRef(0);
|
|
46
|
+
const morphTargetControllerRef = useRef<MorphTargetController>();
|
|
47
|
+
const positionControllerRef = useRef<AvatarPositionController>();
|
|
48
|
+
const targetCameraZRef = useRef(camera.position.z);
|
|
51
49
|
|
|
52
|
-
const headMeshRef = useRef<SkinnedMesh>();
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
useHeadMovement(headMovement, nodes);
|
|
52
|
+
|
|
53
|
+
const blinkStateRef = useRef({
|
|
54
|
+
isBlinking: false,
|
|
55
|
+
lastBlinkTime: 0,
|
|
56
|
+
nextBlinkTime: 0,
|
|
57
|
+
blinkStartTime: 0,
|
|
58
|
+
});
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
67
|
-
setMorphTargetInfluences(initialInfluences);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
60
|
+
// Find head mesh
|
|
61
|
+
const headMesh = useMemo(() => {
|
|
62
|
+
let foundMesh: SkinnedMesh | undefined;
|
|
63
|
+
scene?.traverse((object: Object3D) => {
|
|
64
|
+
if (
|
|
65
|
+
object instanceof SkinnedMesh &&
|
|
66
|
+
(object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar')
|
|
67
|
+
) {
|
|
68
|
+
foundMesh = object;
|
|
70
69
|
}
|
|
71
70
|
});
|
|
71
|
+
return foundMesh;
|
|
72
|
+
}, [scene]);
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
// Initialize controllers
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!positionControllerRef.current) {
|
|
77
|
+
positionControllerRef.current = new AvatarPositionController(
|
|
78
|
+
AVATAR_POSITION,
|
|
79
|
+
AVATAR_POSITION_ZOOMED,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
74
82
|
|
|
83
|
+
if (headMesh) {
|
|
84
|
+
morphTargetControllerRef.current = new MorphTargetController(headMesh);
|
|
85
|
+
|
|
86
|
+
if (headMesh.morphTargetDictionary && headMesh.morphTargetInfluences) {
|
|
87
|
+
setMorphTargetDictionary(headMesh.morphTargetDictionary);
|
|
88
|
+
const initialInfluences = Object.keys(headMesh.morphTargetDictionary)
|
|
89
|
+
.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
90
|
+
setMorphTargetInfluences(initialInfluences);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
correctMaterials(materials);
|
|
95
|
+
onLoaded?.();
|
|
96
|
+
hideHands(nodes);
|
|
97
|
+
|
|
75
98
|
return () => {
|
|
76
99
|
Object.values(materials).forEach(material => material.dispose());
|
|
77
100
|
Object.values(nodes)
|
|
78
101
|
.filter(isSkinnedMesh)
|
|
79
102
|
.forEach(mesh => mesh.geometry.dispose());
|
|
80
103
|
};
|
|
81
|
-
}, [materials, nodes, url, onLoaded, scene]);
|
|
82
|
-
useFrame(state => {
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
headMeshRef.current &&
|
|
86
|
-
headMeshRef.current.morphTargetDictionary &&
|
|
87
|
-
headMeshRef.current.morphTargetInfluences
|
|
88
|
-
) {
|
|
89
|
-
const currentTime = state.clock.getElapsedTime() * 1000; // Convert to milliseconds
|
|
90
|
-
|
|
91
|
-
// Handle blinking
|
|
92
|
-
let blinkValue = 0;
|
|
93
|
-
if (eyeBlink) {
|
|
94
|
-
if (currentTime >= nextBlinkTime.current && !isBlinking.current) {
|
|
95
|
-
isBlinking.current = true;
|
|
96
|
-
blinkStartTime.current = currentTime;
|
|
97
|
-
lastBlinkTime.current = currentTime;
|
|
98
|
-
nextBlinkTime.current =
|
|
99
|
-
currentTime +
|
|
100
|
-
Math.random() *
|
|
101
|
-
(BLINK_CONFIG.maxInterval - BLINK_CONFIG.minInterval) +
|
|
102
|
-
BLINK_CONFIG.minInterval;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (isBlinking.current) {
|
|
106
|
-
const blinkProgress =
|
|
107
|
-
(currentTime - blinkStartTime.current) / BLINK_CONFIG.blinkDuration;
|
|
108
|
-
if (blinkProgress <= 0.5) {
|
|
109
|
-
// Eyes closing
|
|
110
|
-
blinkValue = blinkProgress * 2;
|
|
111
|
-
} else if (blinkProgress <= 1) {
|
|
112
|
-
// Eyes opening
|
|
113
|
-
blinkValue = 2 - blinkProgress * 2;
|
|
114
|
-
} else {
|
|
115
|
-
// Blink finished
|
|
116
|
-
isBlinking.current = false;
|
|
117
|
-
blinkValue = 0;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
104
|
+
}, [materials, nodes, url, onLoaded, scene, headMesh]);
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (positionControllerRef.current) {
|
|
108
|
+
positionControllerRef.current.updateHeight(avatarHeight, true);
|
|
109
|
+
}
|
|
110
|
+
}, [avatarHeight]);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (positionControllerRef.current && onCameraZChange) {
|
|
114
|
+
const newCameraZ = positionControllerRef.current.updateDepth(avatarDepth, true);
|
|
115
|
+
onCameraZChange(newCameraZ);
|
|
116
|
+
}
|
|
117
|
+
}, [avatarDepth, onCameraZChange]);
|
|
118
|
+
|
|
119
|
+
// Animation and morphing update loop
|
|
120
|
+
useFrame((state) => {
|
|
121
|
+
const currentTime = state.clock.elapsedTime * 1000;
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Handle blinking (additive layer, only for 'eyesClosed')
|
|
136
|
-
if (key === 'eyesClosed' && eyeBlink) {
|
|
137
|
-
targetValue += blinkValue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Clamp the final value between 0 and 1
|
|
141
|
-
targetValue = MathUtils.clamp(targetValue, 0, 1);
|
|
142
|
-
|
|
143
|
-
// Apply smoothing
|
|
144
|
-
if (
|
|
145
|
-
headMeshRef.current &&
|
|
146
|
-
headMeshRef.current.morphTargetInfluences
|
|
147
|
-
) {
|
|
148
|
-
headMeshRef.current.morphTargetInfluences[index] = MathUtils.lerp(
|
|
149
|
-
headMeshRef.current.morphTargetInfluences[index],
|
|
150
|
-
targetValue,
|
|
151
|
-
morphTargetSmoothing
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
123
|
+
// Update morph targets
|
|
124
|
+
if (morphTargetControllerRef.current) {
|
|
125
|
+
const currentViseme = updateCurrentViseme(currentTime / 1000);
|
|
126
|
+
morphTargetControllerRef.current.updateMorphTargets(
|
|
127
|
+
currentTime,
|
|
128
|
+
{},
|
|
129
|
+
currentViseme,
|
|
130
|
+
eyeBlink,
|
|
131
|
+
blinkStateRef.current,
|
|
156
132
|
);
|
|
133
|
+
}
|
|
157
134
|
|
|
158
|
-
|
|
159
|
-
|
|
135
|
+
// Update scale with smooth transition
|
|
136
|
+
if (scene && positionControllerRef.current) {
|
|
137
|
+
const newScale = positionControllerRef.current.updateScale(SCALE_LERP_FACTOR);
|
|
138
|
+
scene.scale.copy(newScale);
|
|
160
139
|
}
|
|
161
140
|
});
|
|
162
141
|
|
|
142
|
+
// Get current position from controller
|
|
143
|
+
const position = positionControllerRef.current?.getPosition() || AVATAR_POSITION;
|
|
144
|
+
|
|
163
145
|
return (
|
|
164
|
-
<group position={
|
|
146
|
+
<group position={position}>
|
|
165
147
|
<primitive object={scene} />
|
|
166
148
|
</group>
|
|
167
149
|
);
|
|
168
|
-
}
|
|
150
|
+
}
|
package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
.memori--position-controls {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 1000;
|
|
4
|
+
top: auto; /* Remove top positioning */
|
|
5
|
+
top: 65px; /* Position from bottom to avoid overlapping with buttons */
|
|
6
|
+
left: 15px; /* Remove left positioning */
|
|
7
|
+
display: flex;
|
|
8
|
+
width: 350px; /* Fixed width */
|
|
9
|
+
max-width: 90%; /* Maximum width relative to screen */
|
|
10
|
+
height: 300px;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
padding: 16px;
|
|
13
|
+
border-radius: 16px;
|
|
14
|
+
-webkit-backdrop-filter: blur(10px);
|
|
15
|
+
backdrop-filter: blur(10px);
|
|
16
|
+
background-color: var(--memori-inner-bg, #fff);
|
|
17
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
18
|
+
gap: 0.5rem;
|
|
19
|
+
text-align: left;
|
|
20
|
+
transform: none; /* Remove transform */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Slider adjustments */
|
|
24
|
+
.memori--slider-container {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
padding: 8px 0;
|
|
28
|
+
gap: 0.75rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.memori--slider-label {
|
|
32
|
+
margin-bottom: 4px;
|
|
33
|
+
font-size: 1rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Button adjustments */
|
|
37
|
+
.memori--preset-buttons {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: row;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
margin-top: 8px;
|
|
42
|
+
gap: 8px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.memori--preset-buttons button {
|
|
46
|
+
min-height: 36px;
|
|
47
|
+
flex: 1;
|
|
48
|
+
padding: 8px 4px;
|
|
49
|
+
font-size: 0.9rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Close button */
|
|
53
|
+
.memori--position-controls-close {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: -25px;
|
|
56
|
+
right: -30px;
|
|
57
|
+
border-radius: 50%;
|
|
58
|
+
background-color: var(--memori-inner-bg, #fff);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.memori--position-controls-close-button {
|
|
62
|
+
min-width: 36px;
|
|
63
|
+
min-height: 36px;
|
|
64
|
+
padding: 8px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Totem specific adjustments */
|
|
68
|
+
@media screen and (max-width: 480px) {
|
|
69
|
+
.memori--position-controls {
|
|
70
|
+
right: 10px;
|
|
71
|
+
bottom: 80px;
|
|
72
|
+
width: 280px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.memori--preset-buttons button {
|
|
76
|
+
padding: 6px 4px;
|
|
77
|
+
font-size: 0.85rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.memori--slider-container {
|
|
81
|
+
padding: 6px 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Portrait/vertical orientation */
|
|
86
|
+
@media screen and (min-height: 800px) {
|
|
87
|
+
.memori--position-controls {
|
|
88
|
+
bottom: 120px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.memori--slider-label {
|
|
92
|
+
font-size: 1.1rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.memori--preset-buttons button {
|
|
96
|
+
padding: 10px 6px;
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
}
|
|
99
|
+
}
|