talking-head-studio 0.4.11 → 0.4.12

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 (142) hide show
  1. package/README.md +279 -193
  2. package/dist/TalkingHead.d.ts +28 -3
  3. package/dist/TalkingHead.js +21 -2
  4. package/dist/TalkingHead.web.d.ts +31 -4
  5. package/dist/TalkingHead.web.js +11 -1
  6. package/dist/TalkingHeadVisualization.d.ts +22 -0
  7. package/dist/TalkingHeadVisualization.js +30 -10
  8. package/dist/api/studioApi.d.ts +12 -1
  9. package/dist/api/studioApi.js +16 -2
  10. package/dist/contract.d.ts +14 -0
  11. package/dist/contract.js +30 -0
  12. package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
  13. package/dist/core/avatar/avatarCapabilities.js +100 -0
  14. package/dist/core/avatar/backends/gaussian.js +6 -4
  15. package/dist/core/avatar/motion.d.ts +1713 -0
  16. package/dist/core/avatar/motion.js +550 -0
  17. package/dist/core/avatar/motionRuntime.d.ts +46 -0
  18. package/dist/core/avatar/motionRuntime.js +84 -0
  19. package/dist/core/avatar/schema.d.ts +33 -5
  20. package/dist/core/avatar/visemes.d.ts +16 -1
  21. package/dist/core/avatar/visemes.js +48 -1
  22. package/dist/editor/AvatarCanvas.js +92 -1
  23. package/dist/editor/AvatarEditor.native.js +1 -0
  24. package/dist/editor/AvatarModel.js +1 -0
  25. package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
  26. package/dist/editor/FaceSqueezeEditor.js +176 -112
  27. package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
  28. package/dist/editor/FaceSqueezeEditor.web.js +30 -28
  29. package/dist/editor/RigidAccessory.js +17 -2
  30. package/dist/editor/SkinnedClothing.js +1 -0
  31. package/dist/editor/boneLockedDrag.d.ts +11 -0
  32. package/dist/editor/boneLockedDrag.js +68 -0
  33. package/dist/editor/boneSnap.web.d.ts +27 -0
  34. package/dist/editor/boneSnap.web.js +99 -0
  35. package/dist/editor/index.web.d.ts +10 -0
  36. package/dist/editor/index.web.js +26 -0
  37. package/dist/editor/sounds/haha.wav +0 -0
  38. package/dist/editor/sounds/owie.wav +0 -0
  39. package/dist/editor/sounds/stop.wav +0 -0
  40. package/dist/editor/studioTheme.d.ts +14 -14
  41. package/dist/editor/studioTheme.js +17 -14
  42. package/dist/editor/types.d.ts +1 -0
  43. package/dist/html/accessories.d.ts +7 -0
  44. package/dist/html/accessories.js +149 -0
  45. package/dist/html/motion.d.ts +1 -0
  46. package/dist/html/motion.js +189 -0
  47. package/dist/html/visemes.d.ts +7 -0
  48. package/dist/html/visemes.js +348 -0
  49. package/dist/html.d.ts +1 -1
  50. package/dist/html.js +55 -732
  51. package/dist/index.d.ts +7 -3
  52. package/dist/index.js +17 -1
  53. package/dist/index.web.d.ts +18 -1
  54. package/dist/index.web.js +36 -3
  55. package/dist/sketchfab/api.js +1 -0
  56. package/dist/sketchfab/glbInspect.d.ts +22 -0
  57. package/dist/sketchfab/glbInspect.js +58 -0
  58. package/dist/sketchfab/index.d.ts +3 -0
  59. package/dist/sketchfab/index.js +8 -1
  60. package/dist/sketchfab/inspectRemote.d.ts +13 -0
  61. package/dist/sketchfab/inspectRemote.js +77 -0
  62. package/dist/sketchfab/types.d.ts +10 -0
  63. package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
  64. package/dist/studio/AccessoryBrowserScreen.js +626 -0
  65. package/dist/studio/AccessoryPanel.d.ts +10 -0
  66. package/dist/studio/AccessoryPanel.js +396 -0
  67. package/dist/studio/AppearancePanel.d.ts +9 -0
  68. package/dist/studio/AppearancePanel.js +77 -0
  69. package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
  70. package/dist/studio/AvatarCreatorScreen.js +806 -0
  71. package/dist/studio/AvatarEditorScreen.d.ts +14 -0
  72. package/dist/studio/AvatarEditorScreen.js +510 -0
  73. package/dist/studio/AvatarGrid.d.ts +23 -0
  74. package/dist/studio/AvatarGrid.js +257 -0
  75. package/dist/studio/ColorSwatch.d.ts +8 -0
  76. package/dist/studio/ColorSwatch.js +100 -0
  77. package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
  78. package/dist/studio/CreateVoiceProfileSheet.js +242 -0
  79. package/dist/studio/DetailsPanel.d.ts +15 -0
  80. package/dist/studio/DetailsPanel.js +239 -0
  81. package/dist/studio/FilamentEditor.d.ts +2 -0
  82. package/dist/studio/FilamentEditor.js +6 -0
  83. package/dist/studio/PrecisionPanel.d.ts +2 -0
  84. package/dist/studio/PrecisionPanel.js +7 -0
  85. package/dist/studio/PublicGalleryScreen.d.ts +5 -0
  86. package/dist/studio/PublicGalleryScreen.js +358 -0
  87. package/dist/studio/SketchfabModelCard.d.ts +20 -0
  88. package/dist/studio/SketchfabModelCard.js +104 -0
  89. package/dist/studio/StudioBrowseHeader.d.ts +9 -0
  90. package/dist/studio/StudioBrowseHeader.js +28 -0
  91. package/dist/studio/StudioEmptyState.d.ts +8 -0
  92. package/dist/studio/StudioEmptyState.js +29 -0
  93. package/dist/studio/StudioFloatingAction.d.ts +13 -0
  94. package/dist/studio/StudioFloatingAction.js +42 -0
  95. package/dist/studio/StudioSectionHeader.d.ts +7 -0
  96. package/dist/studio/StudioSectionHeader.js +27 -0
  97. package/dist/studio/StudioSurfaceCard.d.ts +8 -0
  98. package/dist/studio/StudioSurfaceCard.js +20 -0
  99. package/dist/studio/VoicePanel.d.ts +15 -0
  100. package/dist/studio/VoicePanel.js +305 -0
  101. package/dist/studio/constants.d.ts +3 -0
  102. package/dist/studio/constants.js +6 -0
  103. package/dist/studio/index.d.ts +29 -0
  104. package/dist/studio/index.js +54 -0
  105. package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
  106. package/dist/studio/useSketchfabCapabilities.js +82 -0
  107. package/dist/tts/useDirectVisemeStream.js +15 -10
  108. package/dist/utils/avatarUtils.js +92 -5
  109. package/dist/utils/faceLandmarkerToShapeWeights.js +2 -4
  110. package/dist/voice/useAudioPlayer.js +17 -4
  111. package/dist/voice/useVoicePreview.js +4 -2
  112. package/dist/wardrobe/index.d.ts +1 -0
  113. package/dist/wardrobe/index.js +6 -1
  114. package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
  115. package/dist/wardrobe/useAccessoryGestures.js +94 -0
  116. package/dist/wardrobe/useAvatarWardrobeHydration.js +8 -2
  117. package/dist/wardrobe/useStudioAvatar.js +11 -2
  118. package/dist/wardrobe/wardrobeStore.d.ts +2 -0
  119. package/dist/wardrobe/wardrobeStore.js +12 -2
  120. package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
  121. package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
  122. package/dist/wgpu/WgpuAvatar.d.ts +26 -2
  123. package/dist/wgpu/WgpuAvatar.js +296 -39
  124. package/dist/wgpu/accessoryDefaults.d.ts +12 -0
  125. package/dist/wgpu/accessoryDefaults.js +19 -0
  126. package/dist/wgpu/blobShim.d.ts +2 -0
  127. package/dist/wgpu/blobShim.js +191 -0
  128. package/dist/wgpu/index.d.ts +1 -0
  129. package/dist/wgpu/index.js +4 -1
  130. package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
  131. package/dist/wgpu/loadGLTFFromUri.js +75 -0
  132. package/dist/wgpu/morphTables.js +21 -10
  133. package/dist/wgpu/motionState.d.ts +20 -0
  134. package/dist/wgpu/motionState.js +31 -0
  135. package/dist/wgpu/patchThreeForRN.d.ts +28 -0
  136. package/dist/wgpu/patchThreeForRN.js +292 -0
  137. package/dist/wgpu/scenePlacement.d.ts +5 -0
  138. package/dist/wgpu/scenePlacement.js +50 -0
  139. package/dist/wgpu/useAuthedModelUri.js +4 -2
  140. package/dist/wgpu/useNativeGLTF.d.ts +7 -0
  141. package/dist/wgpu/useNativeGLTF.js +36 -0
  142. package/package.json +97 -31
@@ -0,0 +1,550 @@
1
+ "use strict";
2
+ /**
3
+ * Shared, framework-free motion engine data + math.
4
+ *
5
+ * This is the single source of truth for procedural body motions (groove,
6
+ * wave, attack, defend, …). It is consumed two ways:
7
+ * 1. The WebView avatar serializes MOTION_DEFS into the generated HTML
8
+ * (see src/html/motion.ts) and runs the math inside the render loop.
9
+ * 2. The native WebGPU avatar (src/wgpu/WgpuAvatar.tsx) imports it directly
10
+ * and drives bones from inside an R3F useFrame callback.
11
+ *
12
+ * Keep this module dependency-free (no three.js, no React) so both runtimes
13
+ * can share it. The quaternion helpers below are intentionally minimal.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.TALKINGHEAD_POSES = exports.TALKINGHEAD_GESTURES = exports.MOTION_KEYS = exports.MOTION_DEFS = exports.MOTION_BONE_SEARCH = void 0;
17
+ exports.isMotionKey = isMotionKey;
18
+ /**
19
+ * Bone name map: logical key -> keywords to match in an armature.
20
+ * Matching normalizes by lowercasing and stripping punctuation, so RPM names
21
+ * like LeftShoulder, RightArm, LeftForeArm all match correctly.
22
+ */
23
+ exports.MOTION_BONE_SEARCH = {
24
+ hips: ['hips', 'pelvis', 'hip', 'root'],
25
+ spine: ['spine', 'spine0', 'spine_0', 'spine_01', 'spine1_'],
26
+ spine2: ['spine2', 'spine_02', 'upperchest', 'chest', 'spine1'],
27
+ neck: ['neck'],
28
+ head: ['head'],
29
+ leftShoulder: ['leftshoulder', 'l_shoulder', 'shoulderleft', 'leftclavicle', 'l_clavicle'],
30
+ rightShoulder: ['rightshoulder', 'r_shoulder', 'shoulderright', 'rightclavicle', 'r_clavicle'],
31
+ leftArm: ['leftarm', 'l_arm', 'armleft', 'leftupperarm', 'l_upperarm'],
32
+ rightArm: ['rightarm', 'r_arm', 'armright', 'rightupperarm', 'r_upperarm'],
33
+ leftForeArm: ['leftforearm', 'l_forearm', 'forearmleft', 'leftlowerarm'],
34
+ rightForeArm: ['rightforearm', 'r_forearm', 'forearmright', 'rightlowerarm'],
35
+ };
36
+ exports.MOTION_DEFS = {
37
+ groove: {
38
+ bpm: 120,
39
+ smile: 0.55,
40
+ label: 'Groove',
41
+ bones: {
42
+ hips: [
43
+ { ax: 0, ay: 0, az: 1, amp: 0.1, freq: 1.0, phase: 0 },
44
+ { ax: 0, ay: 1, az: 0, amp: 0.05, freq: 0.5, phase: 0 },
45
+ ],
46
+ spine: [
47
+ { ax: 0, ay: 0, az: 1, amp: 0.08, freq: 1.0, phase: 0.3 },
48
+ { ax: 1, ay: 0, az: 0, amp: 0.03, freq: 2.0, phase: 0 },
49
+ ],
50
+ spine2: [{ ax: 0, ay: 0, az: 1, amp: 0.06, freq: 1.0, phase: 0.6 }],
51
+ neck: [{ ax: 0, ay: 0, az: 1, amp: 0.05, freq: 1.0, phase: Math.PI }],
52
+ head: [
53
+ { ax: 1, ay: 0, az: 0, amp: 0.06, freq: 2.0, phase: 0 },
54
+ { ax: 0, ay: 0, az: 1, amp: 0.04, freq: 1.0, phase: Math.PI },
55
+ ],
56
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.22, freq: 1.0, phase: Math.PI / 2 }],
57
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: 0.22, freq: 1.0, phase: -Math.PI / 2 }],
58
+ },
59
+ },
60
+ wave: {
61
+ bpm: 90,
62
+ smile: 0.75,
63
+ label: 'Wave',
64
+ bones: {
65
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0.5, phase: 0 }],
66
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0.5, phase: 0.2 }],
67
+ head: [
68
+ { ax: 0, ay: 1, az: 0, amp: 0.12, freq: 0.5, phase: 0 },
69
+ { ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0.5, phase: 0.5 },
70
+ ],
71
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.6, freq: 0, phase: 0 }],
72
+ rightArm: [
73
+ { ax: 0, ay: 0, az: 1, amp: -0.55, freq: 0, phase: 0 },
74
+ { ax: 1, ay: 0, az: 0, amp: 0.35, freq: 1.5, phase: 0 },
75
+ ],
76
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.4, freq: 1.5, phase: Math.PI / 3 }],
77
+ },
78
+ },
79
+ nod: {
80
+ bpm: 100,
81
+ smile: 0.4,
82
+ label: 'Head Bop',
83
+ bones: {
84
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0.83, phase: 0 }],
85
+ spine: [
86
+ { ax: 1, ay: 0, az: 0, amp: 0.04, freq: 0.83, phase: 0.2 },
87
+ { ax: 0, ay: 0, az: 1, amp: 0.03, freq: 0.83, phase: 0.4 },
88
+ ],
89
+ neck: [{ ax: 1, ay: 0, az: 0, amp: 0.12, freq: 1.67, phase: 0 }],
90
+ head: [
91
+ { ax: 1, ay: 0, az: 0, amp: 0.16, freq: 1.67, phase: Math.PI / 6 },
92
+ { ax: 0, ay: 1, az: 0, amp: 0.05, freq: 0.83, phase: 0 },
93
+ ],
94
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.12, freq: 0.83, phase: 0 }],
95
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: 0.12, freq: 0.83, phase: Math.PI }],
96
+ },
97
+ },
98
+ idle: {
99
+ bpm: 60,
100
+ smile: 0.15,
101
+ label: 'Idle Sway',
102
+ bones: {
103
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0.035, freq: 0.5, phase: 0 }],
104
+ spine: [
105
+ { ax: 0, ay: 0, az: 1, amp: 0.028, freq: 0.5, phase: 0.3 },
106
+ { ax: 1, ay: 0, az: 0, amp: 0.012, freq: 0.25, phase: 0 },
107
+ ],
108
+ spine2: [{ ax: 0, ay: 0, az: 1, amp: 0.018, freq: 0.5, phase: 0.5 }],
109
+ head: [
110
+ { ax: 1, ay: 0, az: 0, amp: 0.025, freq: 0.25, phase: 0 },
111
+ { ax: 0, ay: 1, az: 0, amp: 0.015, freq: 0.5, phase: 0.8 },
112
+ ],
113
+ },
114
+ },
115
+ attack: {
116
+ bpm: 160,
117
+ smile: 0.0,
118
+ label: 'Attack',
119
+ // Hold the lunge briefly, then return to guard so combat loops chain.
120
+ autoReturnMs: 450,
121
+ bones: {
122
+ hips: [{ ax: 1, ay: 0, az: 0, amp: 0.2, freq: 0, phase: 0 }],
123
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.25, freq: 0, phase: 0 }],
124
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.15, freq: 0, phase: 0 }],
125
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.14, freq: 0, phase: 0 }],
126
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.65, freq: 0, phase: 0 }],
127
+ rightArm: [
128
+ { ax: 1, ay: 0, az: 0, amp: 0.85, freq: 0, phase: 0 },
129
+ { ax: 0, ay: 0, az: 1, amp: -0.35, freq: 0, phase: 0 },
130
+ ],
131
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.55, freq: 0, phase: 0 }],
132
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.3, freq: 0, phase: 0 }],
133
+ },
134
+ },
135
+ defend: {
136
+ bpm: 80,
137
+ smile: 0.0,
138
+ label: 'Defend',
139
+ // A guard is a held stance; longer hold than attack but still auto-returns
140
+ // so the avatar relaxes if the engine forgets to clear it.
141
+ autoReturnMs: 1200,
142
+ bones: {
143
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.1, freq: 0, phase: 0 }],
144
+ spine: [{ ax: 1, ay: 0, az: 0, amp: -0.12, freq: 0, phase: 0 }],
145
+ head: [
146
+ { ax: 1, ay: 0, az: 0, amp: -0.06, freq: 0, phase: 0 },
147
+ { ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0, phase: 0 },
148
+ ],
149
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.55, freq: 0, phase: 0 }],
150
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.55, freq: 0, phase: 0 }],
151
+ leftArm: [
152
+ { ax: 1, ay: 0, az: 0, amp: 0.6, freq: 0, phase: 0 },
153
+ { ax: 0, ay: 0, az: 1, amp: 0.4, freq: 0, phase: 0 },
154
+ ],
155
+ rightArm: [
156
+ { ax: 1, ay: 0, az: 0, amp: 0.6, freq: 0, phase: 0 },
157
+ { ax: 0, ay: 0, az: 1, amp: -0.4, freq: 0, phase: 0 },
158
+ ],
159
+ leftForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.35, freq: 0, phase: 0 }],
160
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.35, freq: 0, phase: 0 }],
161
+ },
162
+ },
163
+ cast: {
164
+ bpm: 120,
165
+ smile: 0.25,
166
+ label: 'Cast',
167
+ autoReturnMs: 700,
168
+ bones: {
169
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.08, freq: 0, phase: 0 }],
170
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.04, freq: 0, phase: 0 }],
171
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.34, freq: 0, phase: 0 }],
172
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.34, freq: 0, phase: 0 }],
173
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: 0.52, freq: 0, phase: 0 }],
174
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.52, freq: 0, phase: 0 }],
175
+ },
176
+ },
177
+ hurt: {
178
+ bpm: 90,
179
+ smile: 0,
180
+ label: 'Hurt',
181
+ autoReturnMs: 520,
182
+ bones: {
183
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.12, freq: 0, phase: 0 }],
184
+ spine: [
185
+ { ax: 1, ay: 0, az: 0, amp: -0.18, freq: 0, phase: 0 },
186
+ { ax: 0, ay: 0, az: 1, amp: 0.08, freq: 0, phase: 0 },
187
+ ],
188
+ head: [
189
+ { ax: 1, ay: 0, az: 0, amp: -0.12, freq: 0, phase: 0 },
190
+ { ax: 0, ay: 0, az: 1, amp: -0.08, freq: 0, phase: 0 },
191
+ ],
192
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.2, freq: 0, phase: 0 }],
193
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.2, freq: 0, phase: 0 }],
194
+ },
195
+ },
196
+ die: {
197
+ bpm: 40,
198
+ smile: 0,
199
+ label: 'Die',
200
+ bones: {
201
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.5, freq: 0, phase: 0 }],
202
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.45, freq: 0, phase: 0 }],
203
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.25, freq: 0, phase: 0 }],
204
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.22, freq: 0, phase: 0 }],
205
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.35, freq: 0, phase: 0 }],
206
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.35, freq: 0, phase: 0 }],
207
+ },
208
+ },
209
+ windup: {
210
+ bpm: 110,
211
+ smile: 0.05,
212
+ label: 'Windup',
213
+ autoReturnMs: 800,
214
+ bones: {
215
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.08, freq: 0, phase: 0 }],
216
+ spine: [{ ax: 1, ay: 0, az: 0, amp: -0.18, freq: 0, phase: 0 }],
217
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.08, freq: 0, phase: 0 }],
218
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.62, freq: 0, phase: 0 }],
219
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.9, freq: 0, phase: 0 }],
220
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.4, freq: 0, phase: 0 }],
221
+ },
222
+ },
223
+ victory: {
224
+ bpm: 140,
225
+ smile: 0.9,
226
+ label: 'Victory',
227
+ bones: {
228
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0.12, freq: 1.1, phase: 0 }],
229
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0.08, freq: 1.1, phase: 0.3 }],
230
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.08, freq: 2.2, phase: 0 }],
231
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.52, freq: 0, phase: 0 }],
232
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.52, freq: 0, phase: 0 }],
233
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: 0.4, freq: 2.2, phase: 0 }],
234
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.4, freq: 2.2, phase: Math.PI }],
235
+ },
236
+ },
237
+ pulled: {
238
+ bpm: 70,
239
+ smile: 0,
240
+ label: 'Pulled',
241
+ autoReturnMs: 900,
242
+ bones: {
243
+ hips: [{ ax: 1, ay: 0, az: 0, amp: 0.18, freq: 0, phase: 0 }],
244
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.22, freq: 0, phase: 0 }],
245
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.12, freq: 0, phase: 0 }],
246
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: -0.18, freq: 0, phase: 0 }],
247
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: 0.18, freq: 0, phase: 0 }],
248
+ },
249
+ },
250
+ eaten: {
251
+ bpm: 45,
252
+ smile: 0,
253
+ label: 'Eaten',
254
+ bones: {
255
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.62, freq: 0, phase: 0 }],
256
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.36, freq: 0, phase: 0 }],
257
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.2, freq: 0, phase: 0 }],
258
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.3, freq: 0, phase: 0 }],
259
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: -0.32, freq: 0, phase: 0 }],
260
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: -0.32, freq: 0, phase: 0 }],
261
+ },
262
+ },
263
+ handup: {
264
+ bpm: 90,
265
+ smile: 0.45,
266
+ label: 'Hand Up',
267
+ autoReturnMs: 1600,
268
+ bones: {
269
+ spine: [{ ax: 0, ay: 0, az: 1, amp: -0.04, freq: 0, phase: 0 }],
270
+ head: [{ ax: 0, ay: 1, az: 0, amp: 0.08, freq: 0, phase: 0 }],
271
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.72, freq: 0, phase: 0 }],
272
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.95, freq: 0, phase: 0 }],
273
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.42, freq: 0, phase: 0 }],
274
+ },
275
+ },
276
+ index: {
277
+ bpm: 90,
278
+ smile: 0.35,
279
+ label: 'Index',
280
+ autoReturnMs: 1500,
281
+ bones: {
282
+ spine: [{ ax: 0, ay: 1, az: 0, amp: -0.08, freq: 0, phase: 0 }],
283
+ head: [{ ax: 0, ay: 1, az: 0, amp: -0.1, freq: 0, phase: 0 }],
284
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.48, freq: 0, phase: 0 }],
285
+ rightArm: [
286
+ { ax: 1, ay: 0, az: 0, amp: 0.35, freq: 0, phase: 0 },
287
+ { ax: 0, ay: 1, az: 0, amp: -0.22, freq: 0, phase: 0 },
288
+ ],
289
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.18, freq: 0, phase: 0 }],
290
+ },
291
+ },
292
+ ok: {
293
+ bpm: 100,
294
+ smile: 0.65,
295
+ label: 'OK',
296
+ autoReturnMs: 1400,
297
+ bones: {
298
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.04, freq: 0, phase: 0 }],
299
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.5, freq: 0, phase: 0 }],
300
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.55, freq: 0, phase: 0 }],
301
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.62, freq: 0, phase: 0 }],
302
+ },
303
+ },
304
+ thumbup: {
305
+ bpm: 100,
306
+ smile: 0.8,
307
+ label: 'Thumb Up',
308
+ autoReturnMs: 1500,
309
+ bones: {
310
+ spine: [{ ax: 0, ay: 0, az: 1, amp: -0.03, freq: 0, phase: 0 }],
311
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.42, freq: 0, phase: 0 }],
312
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.28, freq: 0, phase: 0 }],
313
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.75, freq: 0, phase: 0 }],
314
+ },
315
+ },
316
+ thumbdown: {
317
+ bpm: 80,
318
+ smile: 0.0,
319
+ label: 'Thumb Down',
320
+ autoReturnMs: 1500,
321
+ bones: {
322
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0.03, freq: 0, phase: 0 }],
323
+ head: [{ ax: 1, ay: 0, az: 0, amp: -0.07, freq: 0, phase: 0 }],
324
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.24, freq: 0, phase: 0 }],
325
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: -0.18, freq: 0, phase: 0 }],
326
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: -0.72, freq: 0, phase: 0 }],
327
+ },
328
+ },
329
+ side: {
330
+ bpm: 70,
331
+ smile: 0.25,
332
+ label: 'Side',
333
+ bones: {
334
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0.16, freq: 0, phase: 0 }],
335
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0.18, freq: 0, phase: 0 }],
336
+ spine2: [{ ax: 0, ay: 0, az: 1, amp: 0.1, freq: 0, phase: 0 }],
337
+ head: [{ ax: 0, ay: 0, az: 1, amp: -0.09, freq: 0, phase: 0 }],
338
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.25, freq: 0, phase: 0 }],
339
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.25, freq: 0, phase: 0 }],
340
+ },
341
+ },
342
+ shrug: {
343
+ bpm: 100,
344
+ smile: 0.2,
345
+ label: 'Shrug',
346
+ autoReturnMs: 1300,
347
+ bones: {
348
+ spine: [{ ax: 1, ay: 0, az: 0, amp: -0.08, freq: 0, phase: 0 }],
349
+ head: [
350
+ { ax: 1, ay: 0, az: 0, amp: 0.08, freq: 0, phase: 0 },
351
+ { ax: 0, ay: 0, az: 1, amp: 0.04, freq: 0, phase: 0 },
352
+ ],
353
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.38, freq: 0, phase: 0 }],
354
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.38, freq: 0, phase: 0 }],
355
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: -0.24, freq: 0, phase: 0 }],
356
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: -0.24, freq: 0, phase: 0 }],
357
+ leftForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.34, freq: 0, phase: 0 }],
358
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.34, freq: 0, phase: 0 }],
359
+ },
360
+ },
361
+ namaste: {
362
+ bpm: 70,
363
+ smile: 0.55,
364
+ label: 'Namaste',
365
+ autoReturnMs: 1800,
366
+ bones: {
367
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.07, freq: 0, phase: 0 }],
368
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.1, freq: 0, phase: 0 }],
369
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.35, freq: 0, phase: 0 }],
370
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.35, freq: 0, phase: 0 }],
371
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: 0.6, freq: 0, phase: 0 }],
372
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: 0.6, freq: 0, phase: 0 }],
373
+ leftForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.65, freq: 0, phase: 0 }],
374
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.65, freq: 0, phase: 0 }],
375
+ },
376
+ },
377
+ hip: {
378
+ bpm: 60,
379
+ smile: 0.35,
380
+ label: 'Hip',
381
+ bones: {
382
+ hips: [{ ax: 0, ay: 0, az: 1, amp: -0.2, freq: 0, phase: 0 }],
383
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0.1, freq: 0, phase: 0 }],
384
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.42, freq: 0, phase: 0 }],
385
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.18, freq: 0, phase: 0 }],
386
+ },
387
+ },
388
+ turn: {
389
+ bpm: 60,
390
+ smile: 0.2,
391
+ label: 'Turn',
392
+ bones: {
393
+ hips: [{ ax: 0, ay: 1, az: 0, amp: 0.24, freq: 0, phase: 0 }],
394
+ spine: [{ ax: 0, ay: 1, az: 0, amp: 0.18, freq: 0, phase: 0 }],
395
+ spine2: [{ ax: 0, ay: 1, az: 0, amp: 0.12, freq: 0, phase: 0 }],
396
+ head: [{ ax: 0, ay: 1, az: 0, amp: -0.1, freq: 0, phase: 0 }],
397
+ },
398
+ },
399
+ bend: {
400
+ bpm: 50,
401
+ smile: 0.1,
402
+ label: 'Bend',
403
+ bones: {
404
+ hips: [{ ax: 1, ay: 0, az: 0, amp: 0.2, freq: 0, phase: 0 }],
405
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.38, freq: 0, phase: 0 }],
406
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.22, freq: 0, phase: 0 }],
407
+ neck: [{ ax: 1, ay: 0, az: 0, amp: -0.08, freq: 0, phase: 0 }],
408
+ head: [{ ax: 1, ay: 0, az: 0, amp: -0.1, freq: 0, phase: 0 }],
409
+ },
410
+ },
411
+ back: {
412
+ bpm: 50,
413
+ smile: 0.1,
414
+ label: 'Back',
415
+ bones: {
416
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.16, freq: 0, phase: 0 }],
417
+ spine: [{ ax: 1, ay: 0, az: 0, amp: -0.2, freq: 0, phase: 0 }],
418
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: -0.12, freq: 0, phase: 0 }],
419
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.1, freq: 0, phase: 0 }],
420
+ },
421
+ },
422
+ straight: {
423
+ bpm: 60,
424
+ smile: 0.15,
425
+ label: 'Straight',
426
+ bones: {
427
+ hips: [{ ax: 0, ay: 0, az: 1, amp: 0, freq: 0, phase: 0 }],
428
+ spine: [{ ax: 0, ay: 0, az: 1, amp: 0, freq: 0, phase: 0 }],
429
+ head: [{ ax: 0, ay: 0, az: 1, amp: 0, freq: 0, phase: 0 }],
430
+ },
431
+ },
432
+ wide: {
433
+ bpm: 60,
434
+ smile: 0.35,
435
+ label: 'Wide',
436
+ bones: {
437
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.05, freq: 0, phase: 0 }],
438
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.38, freq: 0, phase: 0 }],
439
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.38, freq: 0, phase: 0 }],
440
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.55, freq: 0, phase: 0 }],
441
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.55, freq: 0, phase: 0 }],
442
+ },
443
+ },
444
+ oneknee: {
445
+ bpm: 40,
446
+ smile: 0.05,
447
+ label: 'One Knee',
448
+ bones: {
449
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.42, freq: 0, phase: 0 }],
450
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.2, freq: 0, phase: 0 }],
451
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.08, freq: 0, phase: 0 }],
452
+ leftArm: [{ ax: 0, ay: 0, az: 1, amp: 0.3, freq: 0, phase: 0 }],
453
+ rightArm: [{ ax: 0, ay: 0, az: 1, amp: -0.18, freq: 0, phase: 0 }],
454
+ },
455
+ },
456
+ kneel: {
457
+ bpm: 40,
458
+ smile: 0.05,
459
+ label: 'Kneel',
460
+ bones: {
461
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.55, freq: 0, phase: 0 }],
462
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.28, freq: 0, phase: 0 }],
463
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.12, freq: 0, phase: 0 }],
464
+ head: [{ ax: 1, ay: 0, az: 0, amp: 0.1, freq: 0, phase: 0 }],
465
+ },
466
+ },
467
+ sitting: {
468
+ bpm: 40,
469
+ smile: 0.2,
470
+ label: 'Sitting',
471
+ bones: {
472
+ hips: [{ ax: 1, ay: 0, az: 0, amp: -0.7, freq: 0, phase: 0 }],
473
+ spine: [{ ax: 1, ay: 0, az: 0, amp: 0.16, freq: 0, phase: 0 }],
474
+ spine2: [{ ax: 1, ay: 0, az: 0, amp: 0.06, freq: 0, phase: 0 }],
475
+ leftArm: [{ ax: 1, ay: 0, az: 0, amp: -0.2, freq: 0, phase: 0 }],
476
+ rightArm: [{ ax: 1, ay: 0, az: 0, amp: -0.2, freq: 0, phase: 0 }],
477
+ },
478
+ },
479
+ celebrate: {
480
+ bpm: 140,
481
+ smile: 0.95,
482
+ label: 'Celebrate',
483
+ bones: {
484
+ hips: [
485
+ { ax: 0, ay: 0, az: 1, amp: 0.14, freq: 1.17, phase: 0 },
486
+ { ax: 1, ay: 0, az: 0, amp: 0.06, freq: 2.33, phase: 0 },
487
+ ],
488
+ spine: [
489
+ { ax: 0, ay: 0, az: 1, amp: 0.1, freq: 1.17, phase: 0.4 },
490
+ { ax: 1, ay: 0, az: 0, amp: 0.05, freq: 2.33, phase: 0.2 },
491
+ ],
492
+ spine2: [{ ax: 0, ay: 0, az: 1, amp: 0.07, freq: 1.17, phase: 0.7 }],
493
+ neck: [{ ax: 1, ay: 0, az: 0, amp: 0.06, freq: 2.33, phase: 0 }],
494
+ head: [
495
+ { ax: 1, ay: 0, az: 0, amp: 0.1, freq: 2.33, phase: 0.15 },
496
+ { ax: 0, ay: 0, az: 1, amp: 0.06, freq: 1.17, phase: Math.PI },
497
+ ],
498
+ leftShoulder: [{ ax: 0, ay: 0, az: 1, amp: 0.5, freq: 0, phase: 0 }],
499
+ rightShoulder: [{ ax: 0, ay: 0, az: 1, amp: -0.5, freq: 0, phase: 0 }],
500
+ leftArm: [
501
+ { ax: 0, ay: 0, az: 1, amp: 0.55, freq: 0, phase: 0 },
502
+ { ax: 1, ay: 0, az: 0, amp: 0.3, freq: 2.33, phase: 0 },
503
+ ],
504
+ rightArm: [
505
+ { ax: 0, ay: 0, az: 1, amp: -0.55, freq: 0, phase: 0 },
506
+ { ax: 1, ay: 0, az: 0, amp: 0.3, freq: 2.33, phase: Math.PI },
507
+ ],
508
+ leftForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.25, freq: 2.33, phase: 0.4 }],
509
+ rightForeArm: [{ ax: 1, ay: 0, az: 0, amp: 0.25, freq: 2.33, phase: 0.4 }],
510
+ },
511
+ },
512
+ };
513
+ /** All available motion keys (runtime list for pickers / validation). */
514
+ exports.MOTION_KEYS = Object.keys(exports.MOTION_DEFS);
515
+ /** Returns true if `key` names a known motion. */
516
+ function isMotionKey(key) {
517
+ return Object.prototype.hasOwnProperty.call(exports.MOTION_DEFS, key);
518
+ }
519
+ /**
520
+ * Gesture names supported by the upstream met4citizen TalkingHead (master).
521
+ * Passing anything outside this set to playGesture() is a silent no-op there,
522
+ * so consumers (and the editor) can validate against this list.
523
+ */
524
+ exports.TALKINGHEAD_GESTURES = [
525
+ 'handup',
526
+ 'index',
527
+ 'ok',
528
+ 'thumbup',
529
+ 'thumbdown',
530
+ 'side',
531
+ 'shrug',
532
+ 'namaste',
533
+ ];
534
+ /**
535
+ * Built-in pose template names from upstream TalkingHead (master). playPose()
536
+ * accepts either one of these names OR a URL to a pose animation file — so
537
+ * combat states like a kneeling "defeated" pose need no hosted asset.
538
+ */
539
+ exports.TALKINGHEAD_POSES = [
540
+ 'side',
541
+ 'hip',
542
+ 'turn',
543
+ 'bend',
544
+ 'back',
545
+ 'straight',
546
+ 'wide',
547
+ 'oneknee',
548
+ 'kneel',
549
+ 'sitting',
550
+ ];
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Framework-free runtime for the procedural motion engine.
3
+ *
4
+ * Operates on a minimal structural bone interface (anything exposing a
5
+ * settable `quaternion` with x/y/z/w) so it works against three.js Object3D
6
+ * in the native path without importing three.js here. The generated WebView
7
+ * uses an inlined copy of the same math (src/html/motion.ts) for zero-dep
8
+ * execution inside the iframe.
9
+ */
10
+ import { type MotionBoneKey, type MotionKey } from './motion';
11
+ export interface Quat {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ w: number;
16
+ }
17
+ /** Minimal three.js-compatible bone shape. */
18
+ export interface MotionBone {
19
+ name: string;
20
+ quaternion: {
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ w: number;
25
+ set: (x: number, y: number, z: number, w: number) => void;
26
+ };
27
+ }
28
+ export declare function quatFromAxisAngle(ax: number, ay: number, az: number, angle: number): Quat;
29
+ export declare function quatMultiply(a: Quat, b: Quat): Quat;
30
+ /**
31
+ * Walks a skeleton (via a traverse callback) and maps logical bone keys to the
32
+ * matching bones plus their rest quaternions. Pass a `traverse` that invokes
33
+ * the callback for every bone node (three.js Object3D.traverse works directly).
34
+ */
35
+ export declare function scanMotionBones(traverse: (visit: (bone: MotionBone) => void) => void): {
36
+ bones: Partial<Record<MotionBoneKey, MotionBone>>;
37
+ rest: Partial<Record<MotionBoneKey, Quat>>;
38
+ };
39
+ /**
40
+ * Applies one frame of a motion onto the mapped bones. `tSec` is seconds
41
+ * elapsed since the motion started. Static oscillators (freq === 0) contribute
42
+ * a constant offset (held pose); sinusoidal ones animate.
43
+ */
44
+ export declare function applyMotionFrame(motionKey: MotionKey, tSec: number, bones: Partial<Record<MotionBoneKey, MotionBone>>, rest: Partial<Record<MotionBoneKey, Quat>>): void;
45
+ /** Restores mapped bones to their captured rest quaternions. */
46
+ export declare function restoreMotionBones(bones: Partial<Record<MotionBoneKey, MotionBone>>, rest: Partial<Record<MotionBoneKey, Quat>>): void;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quatFromAxisAngle = quatFromAxisAngle;
4
+ exports.quatMultiply = quatMultiply;
5
+ exports.scanMotionBones = scanMotionBones;
6
+ exports.applyMotionFrame = applyMotionFrame;
7
+ exports.restoreMotionBones = restoreMotionBones;
8
+ /**
9
+ * Framework-free runtime for the procedural motion engine.
10
+ *
11
+ * Operates on a minimal structural bone interface (anything exposing a
12
+ * settable `quaternion` with x/y/z/w) so it works against three.js Object3D
13
+ * in the native path without importing three.js here. The generated WebView
14
+ * uses an inlined copy of the same math (src/html/motion.ts) for zero-dep
15
+ * execution inside the iframe.
16
+ */
17
+ const motion_1 = require("./motion");
18
+ function quatFromAxisAngle(ax, ay, az, angle) {
19
+ const s = Math.sin(angle / 2);
20
+ return { x: ax * s, y: ay * s, z: az * s, w: Math.cos(angle / 2) };
21
+ }
22
+ function quatMultiply(a, b) {
23
+ return {
24
+ x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
25
+ y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
26
+ z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
27
+ w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
28
+ };
29
+ }
30
+ const norm = (s) => s.toLowerCase().replace(/[_\s\-.]/g, '');
31
+ /**
32
+ * Walks a skeleton (via a traverse callback) and maps logical bone keys to the
33
+ * matching bones plus their rest quaternions. Pass a `traverse` that invokes
34
+ * the callback for every bone node (three.js Object3D.traverse works directly).
35
+ */
36
+ function scanMotionBones(traverse) {
37
+ const bones = {};
38
+ const rest = {};
39
+ const entries = Object.entries(motion_1.MOTION_BONE_SEARCH);
40
+ traverse((bone) => {
41
+ const lk = norm(bone.name);
42
+ for (const [key, kws] of entries) {
43
+ if (bones[key])
44
+ continue;
45
+ if (kws.some((kw) => lk.includes(norm(kw)))) {
46
+ bones[key] = bone;
47
+ const q = bone.quaternion;
48
+ rest[key] = { x: q.x, y: q.y, z: q.z, w: q.w };
49
+ }
50
+ }
51
+ });
52
+ return { bones, rest };
53
+ }
54
+ /**
55
+ * Applies one frame of a motion onto the mapped bones. `tSec` is seconds
56
+ * elapsed since the motion started. Static oscillators (freq === 0) contribute
57
+ * a constant offset (held pose); sinusoidal ones animate.
58
+ */
59
+ function applyMotionFrame(motionKey, tSec, bones, rest) {
60
+ const def = motion_1.MOTION_DEFS[motionKey];
61
+ if (!def)
62
+ return;
63
+ for (const [boneName, oscillators] of Object.entries(def.bones)) {
64
+ const key = boneName;
65
+ const bone = bones[key];
66
+ const restQuat = rest[key];
67
+ if (!bone || !restQuat || !oscillators)
68
+ continue;
69
+ let q = { ...restQuat };
70
+ for (const osc of oscillators) {
71
+ const angle = osc.freq === 0 ? osc.amp : osc.amp * Math.sin(2 * Math.PI * osc.freq * tSec + osc.phase);
72
+ q = quatMultiply(q, quatFromAxisAngle(osc.ax, osc.ay, osc.az, angle));
73
+ }
74
+ bone.quaternion.set(q.x, q.y, q.z, q.w);
75
+ }
76
+ }
77
+ /** Restores mapped bones to their captured rest quaternions. */
78
+ function restoreMotionBones(bones, rest) {
79
+ for (const [key, bone] of Object.entries(bones)) {
80
+ const restQuat = rest[key];
81
+ if (bone && restQuat)
82
+ bone.quaternion.set(restQuat.x, restQuat.y, restQuat.z, restQuat.w);
83
+ }
84
+ }