@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.
Files changed (153) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/components/Avatar/Avatar.d.ts +4 -0
  3. package/dist/components/Avatar/Avatar.js +6 -6
  4. package/dist/components/Avatar/Avatar.js.map +1 -1
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +3 -0
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +3 -3
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js +1 -1
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +1 -1
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +48 -10
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.d.ts +4 -1
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.d.ts +17 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js +73 -0
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js.map +1 -0
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.d.ts +19 -0
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js +60 -0
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js.map +1 -0
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.d.ts +18 -0
  21. package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.js +26 -0
  22. package/dist/components/Avatar/AvatarView/AvatarComponent/components/constants.js.map +1 -0
  23. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +8 -7
  24. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +65 -78
  25. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  26. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
  27. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.d.ts +12 -0
  28. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +98 -0
  29. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -0
  30. package/dist/components/Avatar/AvatarView/index.d.ts +4 -1
  31. package/dist/components/Avatar/AvatarView/index.js +36 -13
  32. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  33. package/dist/components/Avatar/AvatarView/utils/hideHands.d.ts +2 -0
  34. package/dist/components/Avatar/AvatarView/utils/hideHands.js +14 -0
  35. package/dist/components/Avatar/AvatarView/utils/hideHands.js.map +1 -0
  36. package/dist/components/Chat/Chat.d.ts +1 -0
  37. package/dist/components/Chat/Chat.js +5 -2
  38. package/dist/components/Chat/Chat.js.map +1 -1
  39. package/dist/components/ChatBubble/ChatBubble.js +19 -4
  40. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  41. package/dist/components/MemoriWidget/MemoriWidget.js +27 -4
  42. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/dist/components/SettingsDrawer/SettingsDrawer.css +4 -2
  44. package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +6 -1
  45. package/dist/components/SettingsDrawer/SettingsDrawer.js +10 -4
  46. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  47. package/dist/components/layouts/Totem.js +1 -1
  48. package/dist/components/layouts/Totem.js.map +1 -1
  49. package/dist/components/layouts/totem.css +9 -9
  50. package/dist/components/ui/Button.css +4 -0
  51. package/dist/components/ui/Button.d.ts +1 -0
  52. package/dist/components/ui/Button.js +2 -1
  53. package/dist/components/ui/Button.js.map +1 -1
  54. package/dist/components/ui/Slider.css +177 -0
  55. package/dist/components/ui/Slider.d.ts +12 -0
  56. package/dist/components/ui/Slider.js +78 -0
  57. package/dist/components/ui/Slider.js.map +1 -0
  58. package/dist/locales/en.json +11 -0
  59. package/dist/locales/it.json +11 -0
  60. package/dist/styles.css +1 -0
  61. package/esm/components/Avatar/Avatar.d.ts +4 -0
  62. package/esm/components/Avatar/Avatar.js +6 -6
  63. package/esm/components/Avatar/Avatar.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +3 -0
  65. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +3 -3
  66. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  67. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js +1 -1
  68. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.js.map +1 -1
  69. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +1 -1
  70. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +45 -8
  71. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  72. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.d.ts +4 -1
  73. package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.d.ts +17 -0
  74. package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js +69 -0
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/MorphTargetController.js.map +1 -0
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.d.ts +19 -0
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js +56 -0
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/components/PositionController.js.map +1 -0
  79. package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.d.ts +18 -0
  80. package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.js +23 -0
  81. package/esm/components/Avatar/AvatarView/AvatarComponent/components/constants.js.map +1 -0
  82. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +8 -7
  83. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +66 -80
  84. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  85. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
  86. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.d.ts +12 -0
  87. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +95 -0
  88. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -0
  89. package/esm/components/Avatar/AvatarView/index.d.ts +4 -1
  90. package/esm/components/Avatar/AvatarView/index.js +38 -15
  91. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  92. package/esm/components/Avatar/AvatarView/utils/hideHands.d.ts +2 -0
  93. package/esm/components/Avatar/AvatarView/utils/hideHands.js +10 -0
  94. package/esm/components/Avatar/AvatarView/utils/hideHands.js.map +1 -0
  95. package/esm/components/Chat/Chat.d.ts +1 -0
  96. package/esm/components/Chat/Chat.js +5 -2
  97. package/esm/components/Chat/Chat.js.map +1 -1
  98. package/esm/components/ChatBubble/ChatBubble.js +19 -4
  99. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  100. package/esm/components/MemoriWidget/MemoriWidget.js +27 -4
  101. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  102. package/esm/components/SettingsDrawer/SettingsDrawer.css +4 -2
  103. package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +6 -1
  104. package/esm/components/SettingsDrawer/SettingsDrawer.js +10 -4
  105. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  106. package/esm/components/layouts/Totem.js +1 -1
  107. package/esm/components/layouts/Totem.js.map +1 -1
  108. package/esm/components/layouts/totem.css +9 -9
  109. package/esm/components/ui/Button.css +4 -0
  110. package/esm/components/ui/Button.d.ts +1 -0
  111. package/esm/components/ui/Button.js +2 -1
  112. package/esm/components/ui/Button.js.map +1 -1
  113. package/esm/components/ui/Slider.css +177 -0
  114. package/esm/components/ui/Slider.d.ts +12 -0
  115. package/esm/components/ui/Slider.js +75 -0
  116. package/esm/components/ui/Slider.js.map +1 -0
  117. package/esm/locales/en.json +11 -0
  118. package/esm/locales/it.json +11 -0
  119. package/esm/styles.css +1 -0
  120. package/package.json +3 -2
  121. package/src/components/Avatar/Avatar.test.tsx +8 -0
  122. package/src/components/Avatar/Avatar.tsx +19 -6
  123. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +17 -8
  124. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/AnimationController.ts +1 -1
  125. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +69 -18
  126. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.ts +4 -1
  127. package/src/components/Avatar/AvatarView/AvatarComponent/components/PositionController.ts +83 -0
  128. package/src/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/constants.ts → constants.ts} +2 -1
  129. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +106 -124
  130. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +99 -0
  131. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx +187 -0
  132. package/src/components/Avatar/AvatarView/index.tsx +115 -43
  133. package/src/components/Avatar/AvatarView/utils/hideHands.ts +11 -0
  134. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +32 -93
  135. package/src/components/Chat/Chat.test.tsx +11 -0
  136. package/src/components/Chat/Chat.tsx +8 -1
  137. package/src/components/ChatBubble/ChatBubble.tsx +46 -27
  138. package/src/components/MemoriWidget/MemoriWidget.tsx +42 -5
  139. package/src/components/SettingsDrawer/SettingsDrawer.css +4 -2
  140. package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +24 -0
  141. package/src/components/SettingsDrawer/SettingsDrawer.tsx +76 -4
  142. package/src/components/layouts/Totem.tsx +1 -1
  143. package/src/components/layouts/layouts.stories.tsx +111 -3
  144. package/src/components/layouts/totem.css +9 -9
  145. package/src/components/ui/Button.css +4 -0
  146. package/src/components/ui/Button.tsx +3 -0
  147. package/src/components/ui/Slider.css +177 -0
  148. package/src/components/ui/Slider.stories.tsx +63 -0
  149. package/src/components/ui/Slider.tsx +142 -0
  150. package/src/locales/en.json +11 -0
  151. package/src/locales/it.json +11 -0
  152. package/src/styles.css +1 -0
  153. /package/src/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.ts → MorphTargetController.ts} +0 -0
@@ -1,38 +1,38 @@
1
- import React, { useEffect, useRef, useMemo, useCallback } from 'react';
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 './MorhTargetController';
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
- } from './constants';
20
+ SCALE_LERP_FACTOR,
21
+ } from '../constants';
24
22
 
25
- export default function FullbodyAvatar({
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.traverse((object: Object3D) => {
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
- // Initialize controllers
65
- useEffect(() => {
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
- // Animation update loop
120
- useFrame(state => {
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(state.clock.getDelta());
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={isZoomed ? AVATAR_POSITION_ZOOMED : AVATAR_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
- isZoomed?: boolean;
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 React, { useEffect, useMemo, useRef } from 'react';
2
- import { Object3D, SkinnedMesh, Vector3 } from 'three';
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 { useGraph, dispose, useFrame } from '@react-three/fiber';
6
- import { useAvatarBlink } from '../../utils/useEyeBlink';
7
- import useHeadMovement from '../../utils/useHeadMovement';
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 { AnimationMixer, MathUtils } from 'three';
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
- morphTargetSmoothing?: number;
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 mixer = useRef(new AnimationMixer(scene));
44
- const avatarMeshRef = useRef<SkinnedMesh | null>(null);
44
+ const { camera } = useThree();
45
45
 
46
- // Blink state
47
- const lastBlinkTime = useRef(0);
48
- const nextBlinkTime = useRef(0);
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
- useEffect(() => {
55
- correctMaterials(materials);
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
- scene.traverse((object: Object3D) => {
58
- if (object instanceof SkinnedMesh) {
59
- if (object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar') {
60
- headMeshRef.current = object;
61
- if (object.morphTargetDictionary && object.morphTargetInfluences) {
62
- setMorphTargetDictionary(object.morphTargetDictionary);
63
-
64
- const initialInfluences = Object.keys(
65
- object.morphTargetDictionary
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
- onLoaded?.();
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
- const currentViseme = updateCurrentViseme(currentTime / 1000);
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
- // Update morph targets
125
- Object.entries(headMeshRef.current.morphTargetDictionary).forEach(
126
- ([key, index]) => {
127
- if (typeof index === 'number') {
128
- let targetValue = 0;
129
-
130
- // Handle visemes (additive layer)
131
- if (currentViseme && key === currentViseme.name) {
132
- targetValue += currentViseme.weight * 1.3; // Amplify the effect
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
- // Update the animation mixer
159
- mixer.current.update(0.01); // Fixed delta time for consistent animation speed
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={AVATAR_POSITION}>
146
+ <group position={position}>
165
147
  <primitive object={scene} />
166
148
  </group>
167
149
  );
168
- }
150
+ }
@@ -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
+ }