@aura3d/animation 1.1.0 → 1.3.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 (121) hide show
  1. package/dist/AnimationClipEvents.d.ts +55 -0
  2. package/dist/AnimationClipEvents.d.ts.map +1 -1
  3. package/dist/AnimationClipEvents.js +136 -0
  4. package/dist/AnimationClipEvents.js.map +1 -1
  5. package/dist/AnimationClipRegistry.d.ts +9 -3
  6. package/dist/AnimationClipRegistry.d.ts.map +1 -1
  7. package/dist/AnimationClipRegistry.js +52 -5
  8. package/dist/AnimationClipRegistry.js.map +1 -1
  9. package/dist/AnimationController.d.ts +6 -6
  10. package/dist/AnimationController.d.ts.map +1 -1
  11. package/dist/AnimationController.js +3 -3
  12. package/dist/AnimationController.js.map +1 -1
  13. package/dist/AnimationMixer.d.ts +10 -0
  14. package/dist/AnimationMixer.d.ts.map +1 -1
  15. package/dist/AnimationMixer.js +35 -0
  16. package/dist/AnimationMixer.js.map +1 -1
  17. package/dist/AnimationStateGraph.d.ts +14 -5
  18. package/dist/AnimationStateGraph.d.ts.map +1 -1
  19. package/dist/AnimationStateGraph.js +25 -2
  20. package/dist/AnimationStateGraph.js.map +1 -1
  21. package/dist/AnimationStateMachine.d.ts +26 -0
  22. package/dist/AnimationStateMachine.d.ts.map +1 -1
  23. package/dist/AnimationStateMachine.js +32 -0
  24. package/dist/AnimationStateMachine.js.map +1 -1
  25. package/dist/Bone.d.ts +4 -0
  26. package/dist/Bone.d.ts.map +1 -1
  27. package/dist/Bone.js +3 -0
  28. package/dist/Bone.js.map +1 -1
  29. package/dist/FighterAnimationAdapter.d.ts +49 -0
  30. package/dist/FighterAnimationAdapter.d.ts.map +1 -0
  31. package/dist/FighterAnimationAdapter.js +71 -0
  32. package/dist/FighterAnimationAdapter.js.map +1 -0
  33. package/dist/FootIk.d.ts +77 -0
  34. package/dist/FootIk.d.ts.map +1 -0
  35. package/dist/FootIk.js +153 -0
  36. package/dist/FootIk.js.map +1 -0
  37. package/dist/HumanoidBoneInference.d.ts +61 -0
  38. package/dist/HumanoidBoneInference.d.ts.map +1 -0
  39. package/dist/HumanoidBoneInference.js +239 -0
  40. package/dist/HumanoidBoneInference.js.map +1 -0
  41. package/dist/HumanoidRetargeting.d.ts +48 -4
  42. package/dist/HumanoidRetargeting.d.ts.map +1 -1
  43. package/dist/HumanoidRetargeting.js +149 -14
  44. package/dist/HumanoidRetargeting.js.map +1 -1
  45. package/dist/IK.d.ts +5 -0
  46. package/dist/IK.d.ts.map +1 -1
  47. package/dist/IK.js +9 -0
  48. package/dist/IK.js.map +1 -1
  49. package/dist/Inertialization.d.ts +65 -0
  50. package/dist/Inertialization.d.ts.map +1 -0
  51. package/dist/Inertialization.js +211 -0
  52. package/dist/Inertialization.js.map +1 -0
  53. package/dist/LocomotionController.d.ts +5 -0
  54. package/dist/LocomotionController.d.ts.map +1 -1
  55. package/dist/LocomotionController.js +3 -0
  56. package/dist/LocomotionController.js.map +1 -1
  57. package/dist/LocomotionKit.d.ts +52 -0
  58. package/dist/LocomotionKit.d.ts.map +1 -0
  59. package/dist/LocomotionKit.js +38 -0
  60. package/dist/LocomotionKit.js.map +1 -0
  61. package/dist/MotionQuality.d.ts +3 -3
  62. package/dist/MotionQuality.d.ts.map +1 -1
  63. package/dist/MotionQuality.js +2 -2
  64. package/dist/MotionQuality.js.map +1 -1
  65. package/dist/RigQuality.d.ts +31 -0
  66. package/dist/RigQuality.d.ts.map +1 -0
  67. package/dist/RigQuality.js +70 -0
  68. package/dist/RigQuality.js.map +1 -0
  69. package/dist/SecondaryAnimationFixtures.d.ts.map +1 -1
  70. package/dist/SecondaryAnimationFixtures.js +1 -3
  71. package/dist/SecondaryAnimationFixtures.js.map +1 -1
  72. package/dist/Skeleton.d.ts +16 -1
  73. package/dist/Skeleton.d.ts.map +1 -1
  74. package/dist/Skeleton.js +42 -0
  75. package/dist/Skeleton.js.map +1 -1
  76. package/dist/SpringBones.d.ts +60 -0
  77. package/dist/SpringBones.d.ts.map +1 -0
  78. package/dist/SpringBones.js +173 -0
  79. package/dist/SpringBones.js.map +1 -0
  80. package/dist/browser-index.d.ts +12 -3
  81. package/dist/browser-index.d.ts.map +1 -1
  82. package/dist/browser-index.js +11 -2
  83. package/dist/browser-index.js.map +1 -1
  84. package/dist/index.d.ts +11 -3
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +10 -2
  87. package/dist/index.js.map +1 -1
  88. package/dist/library/performanceStateGraph.d.ts +42 -0
  89. package/dist/library/performanceStateGraph.d.ts.map +1 -0
  90. package/dist/library/performanceStateGraph.js +101 -0
  91. package/dist/library/performanceStateGraph.js.map +1 -0
  92. package/dist/library/standardHumanoidClips.d.ts +27 -0
  93. package/dist/library/standardHumanoidClips.d.ts.map +1 -0
  94. package/dist/library/standardHumanoidClips.js +357 -0
  95. package/dist/library/standardHumanoidClips.js.map +1 -0
  96. package/package.json +3 -3
  97. package/src/AnimationClipEvents.ts +181 -0
  98. package/src/AnimationClipRegistry.ts +65 -9
  99. package/src/AnimationController.ts +11 -11
  100. package/src/AnimationMixer.ts +36 -0
  101. package/src/AnimationStateGraph.ts +34 -5
  102. package/src/AnimationStateMachine.ts +48 -0
  103. package/src/Bone.ts +5 -0
  104. package/src/FighterAnimationAdapter.ts +124 -0
  105. package/src/FootIk.ts +251 -0
  106. package/src/HumanoidBoneInference.ts +323 -0
  107. package/src/HumanoidRetargeting.ts +180 -19
  108. package/src/IK.ts +10 -0
  109. package/src/Inertialization.ts +269 -0
  110. package/src/LocomotionController.ts +6 -0
  111. package/src/LocomotionKit.ts +89 -0
  112. package/src/MotionQuality.ts +5 -5
  113. package/src/RigQuality.ts +101 -0
  114. package/src/SecondaryAnimationFixtures.ts +1 -3
  115. package/src/Skeleton.ts +45 -1
  116. package/src/SpringBones.ts +243 -0
  117. package/src/browser-index.ts +13 -4
  118. package/src/index.ts +12 -4
  119. package/src/library/performanceStateGraph.ts +117 -0
  120. package/src/library/standardHumanoidClips.ts +442 -0
  121. package/src/Retargeting.ts +0 -29
@@ -0,0 +1,117 @@
1
+ import { AnimationStateMachine, type AnimationState, type StateTransition } from "../AnimationStateMachine.js";
2
+ import { STANDARD_CLIP_IDS, type StandardClipId } from "./standardHumanoidClips.js";
3
+
4
+ export interface PerformanceStateGraphOptions {
5
+ /** Default / resting state (must be one of the standard clip ids). Defaults to `"idle"`. */
6
+ readonly idleState?: StandardClipId;
7
+ /** Duration (seconds) of the one-shot performance states before they auto-return to idle. */
8
+ readonly oneShotDuration?: number;
9
+ }
10
+
11
+ /**
12
+ * Boolean/number state-machine parameters the studio sets per beat to drive the performance graph.
13
+ * The looping conversational states (talk) are held while their flag is truthy; the one-shot states
14
+ * (gesture/point/nod/react) are triggered by a truthy pulse that is consumed on entry and auto-return
15
+ * to idle. Locomotion (walk/run) is driven by `isMoving` / `isRunning`, mirroring
16
+ * {@link createLocomotionAnimationStateGraph} so the same parameters work for both.
17
+ */
18
+ export const PERFORMANCE_GRAPH_PARAMETERS = {
19
+ talk: "isSpeaking",
20
+ gesture: "gesture",
21
+ point: "point",
22
+ nod: "nod",
23
+ react: "react",
24
+ walk: "isMoving",
25
+ run: "isRunning"
26
+ } as const satisfies Partial<Record<StandardClipId, string>>;
27
+
28
+ function trigger(
29
+ to: StandardClipId,
30
+ parameter: string,
31
+ priority: number,
32
+ consume = false,
33
+ expected: boolean = true
34
+ ): StateTransition {
35
+ return {
36
+ to,
37
+ priority,
38
+ label: `${parameter}->${to}`,
39
+ ...(consume ? { consumeParameters: [parameter] } : {}),
40
+ condition: (parameters) => {
41
+ const value = parameters[parameter];
42
+ if (expected !== true) return value !== true && !truthy(value);
43
+ return truthy(value);
44
+ }
45
+ };
46
+ }
47
+
48
+ function truthy(value: number | boolean | string | undefined): boolean {
49
+ if (typeof value === "boolean") return value;
50
+ if (typeof value === "number") return value > 0;
51
+ if (typeof value === "string") return value.trim().length > 0;
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * State graph over the 8 standard performance clip ids ({@link STANDARD_CLIP_IDS}) with `idle` as the
57
+ * default state. The studio requests a state per beat and the machine crossfades via the inertialized
58
+ * {@link AnimationStateMachine.stateBlend}.
59
+ *
60
+ * Topology:
61
+ * - `idle` (default) can go to `talk`, `gesture`, `point`, `nod`, `react`, `walk`, `run`.
62
+ * - `talk` is a held loop (while `isSpeaking`) that returns to idle when the flag clears; from talk
63
+ * you can still fire one-shots and start moving.
64
+ * - `gesture` / `point` / `nod` / `react` are one-shots that auto-return to idle on completion.
65
+ * - `walk` <-> `run` <-> `idle` form the locomotion blend (`isMoving` / `isRunning`).
66
+ *
67
+ * Priorities: locomotion-stop/one-shots take precedence so a requested beat wins over the held loop.
68
+ */
69
+ export function createPerformanceStateGraph(options: PerformanceStateGraphOptions = {}): AnimationStateMachine {
70
+ const idle = options.idleState ?? "idle";
71
+ const oneShot = options.oneShotDuration ?? 0.6;
72
+ const P = PERFORMANCE_GRAPH_PARAMETERS;
73
+
74
+ const toTalk = trigger("talk", P.talk, 30);
75
+ const toWalk = trigger("walk", P.walk, 40);
76
+ const toRun = trigger("run", P.run, 60);
77
+ const toGesture = trigger("gesture", P.gesture, 80, true);
78
+ const toPoint = trigger("point", P.point, 80, true);
79
+ const toNod = trigger("nod", P.nod, 80, true);
80
+ const toReact = trigger("react", P.react, 90, true);
81
+
82
+ const states: readonly AnimationState[] = [
83
+ {
84
+ name: idle,
85
+ transitions: [toReact, toGesture, toPoint, toNod, toRun, toWalk, toTalk]
86
+ },
87
+ {
88
+ name: "talk",
89
+ transitions: [
90
+ toReact,
91
+ toGesture,
92
+ toPoint,
93
+ toNod,
94
+ toRun,
95
+ toWalk,
96
+ trigger(idle, P.talk, 0, false, false)
97
+ ]
98
+ },
99
+ { name: "gesture", duration: oneShot, oneShot: true, onComplete: idle },
100
+ { name: "point", duration: oneShot, oneShot: true, onComplete: idle },
101
+ { name: "nod", duration: oneShot, oneShot: true, onComplete: idle },
102
+ { name: "react", duration: oneShot, oneShot: true, onComplete: idle },
103
+ {
104
+ name: "walk",
105
+ transitions: [toRun, trigger(idle, P.walk, 0, false, false)]
106
+ },
107
+ {
108
+ name: "run",
109
+ transitions: [trigger("walk", P.run, 0, false, false), trigger(idle, P.walk, 0, false, false)]
110
+ }
111
+ ];
112
+
113
+ return new AnimationStateMachine(states, idle);
114
+ }
115
+
116
+ /** The full set of state names the performance graph exposes (equals the standard clip vocabulary). */
117
+ export const PERFORMANCE_GRAPH_STATES: readonly StandardClipId[] = STANDARD_CLIP_IDS;
@@ -0,0 +1,442 @@
1
+ import { AnimationTrack } from "../AnimationTrack.js";
2
+ import {
3
+ AnimationClipRegistry,
4
+ createAnimationClipRegistry,
5
+ type AnimationClipDefinition
6
+ } from "../AnimationClipRegistry.js";
7
+ import { HUMANOID_BONES, type HumanoidBoneName, type HumanoidRigDefinition } from "../HumanoidRetargeting.js";
8
+
9
+ /**
10
+ * The STANDARD performance vocabulary (Phase 2.4). These 8 ids are the contract the Animation
11
+ * Studio drives — every shared library, retarget target, and state graph speaks exactly these.
12
+ */
13
+ export const STANDARD_CLIP_IDS = ["idle", "talk", "gesture", "point", "nod", "walk", "run", "react"] as const;
14
+ export type StandardClipId = typeof STANDARD_CLIP_IDS[number];
15
+
16
+ /** Locomotion subset of the vocabulary (used by the locomotion blend / studio clip-map check). */
17
+ export const STANDARD_LOCOMOTION_CLIP_IDS = ["idle", "walk", "run"] as const;
18
+
19
+ type Quat = readonly [number, number, number, number];
20
+ type Vec3 = readonly [number, number, number];
21
+
22
+ const FRAME_RATE = 30;
23
+
24
+ /**
25
+ * The rig-neutral humanoid these clips are authored against. Bone names are the canonical
26
+ * {@link HUMANOID_BONES} (identity bindings), so an identity retarget map is a no-op and the clips
27
+ * can be retargeted onto any inferred character rig via {@link createHumanoidRetargetingMap}.
28
+ */
29
+ export const STANDARD_LIBRARY_RIG: HumanoidRigDefinition = {
30
+ id: "aura3d.standard-humanoid",
31
+ name: "Aura3D Standard Humanoid (shared clip library rig)",
32
+ units: "meters",
33
+ facingAxis: "z",
34
+ scale: 1,
35
+ bones: Object.fromEntries(
36
+ HUMANOID_BONES.map((bone) => [bone, { name: bone }])
37
+ ) as HumanoidRigDefinition["bones"],
38
+ metadata: {
39
+ library: "aura3d.standard-humanoid-clips",
40
+ clipVocabulary: [...STANDARD_CLIP_IDS]
41
+ }
42
+ };
43
+
44
+ /** Euler (radians, XYZ) -> quaternion. Compact authoring helper for the procedural clips. */
45
+ function euler(x: number, y: number, z: number): Quat {
46
+ const cx = Math.cos(x / 2);
47
+ const sx = Math.sin(x / 2);
48
+ const cy = Math.cos(y / 2);
49
+ const sy = Math.sin(y / 2);
50
+ const cz = Math.cos(z / 2);
51
+ const sz = Math.sin(z / 2);
52
+ return [
53
+ sx * cy * cz - cx * sy * sz,
54
+ cx * sy * cz + sx * cy * sz,
55
+ cx * cy * sz - sx * sy * cz,
56
+ cx * cy * cz + sx * sy * sz
57
+ ];
58
+ }
59
+
60
+ const IDENTITY: Quat = [0, 0, 0, 1];
61
+
62
+ /** Build a looping quaternion (rotation) track for a bone from per-frame Euler samples. */
63
+ function rotationTrack(
64
+ bone: HumanoidBoneName,
65
+ duration: number,
66
+ samples: readonly { readonly t: number; readonly e: Vec3 }[]
67
+ ): AnimationTrack {
68
+ const keyframes = samples.map((sample) => ({
69
+ time: sample.t,
70
+ value: euler(sample.e[0], sample.e[1], sample.e[2]) as [number, number, number, number],
71
+ interpolation: "linear" as const
72
+ }));
73
+ // Pad to duration so the clip loops cleanly back to its first pose.
74
+ if (keyframes[keyframes.length - 1]!.time < duration) {
75
+ keyframes.push({ time: duration, value: [...keyframes[0]!.value], interpolation: "linear" });
76
+ }
77
+ return new AnimationTrack({ target: `${bone}.rotation`, valueType: "quaternion", keyframes });
78
+ }
79
+
80
+ /** Build a looping vector3 (translation) track for a bone, used for hip bob / sway. */
81
+ function translationTrack(
82
+ bone: HumanoidBoneName,
83
+ duration: number,
84
+ samples: readonly { readonly t: number; readonly p: Vec3 }[]
85
+ ): AnimationTrack {
86
+ const keyframes = samples.map((sample) => ({
87
+ time: sample.t,
88
+ value: [...sample.p] as [number, number, number],
89
+ interpolation: "linear" as const
90
+ }));
91
+ if (keyframes[keyframes.length - 1]!.time < duration) {
92
+ keyframes.push({ time: duration, value: [...keyframes[0]!.value], interpolation: "linear" });
93
+ }
94
+ return new AnimationTrack({ target: `${bone}.translation`, valueType: "vector3", keyframes });
95
+ }
96
+
97
+ function clip(
98
+ id: StandardClipId,
99
+ duration: number,
100
+ tracks: readonly AnimationTrack[],
101
+ loop: boolean,
102
+ tags: readonly string[]
103
+ ): AnimationClipDefinition<StandardClipId> {
104
+ return {
105
+ id,
106
+ name: id,
107
+ duration,
108
+ frameRate: FRAME_RATE,
109
+ loop,
110
+ tags,
111
+ tracks,
112
+ source: "aura3d.standard-humanoid-clips",
113
+ metadata: { rig: STANDARD_LIBRARY_RIG.id, procedural: true }
114
+ };
115
+ }
116
+
117
+ // --- idle: visible breathing + head turns + weight-shift sway -----------------------------------
118
+ // Amplitudes are deliberately readable (not the old ±2° "alive but invisible" idle): spine breathes
119
+ // ~0.16 rad, head turns ~0.34 rad peak-to-peak, hips shift ~6 cm laterally so a viewer can tell the
120
+ // character is standing/living rather than frozen.
121
+ function idleClip(): AnimationClipDefinition<StandardClipId> {
122
+ const d = 4;
123
+ return clip(
124
+ "idle",
125
+ d,
126
+ [
127
+ // Weight shift left -> right plus a clear breathing bob.
128
+ translationTrack("hips", d, [
129
+ { t: 0, p: [0.03, 0, 0] },
130
+ { t: 1, p: [0.02, 0.03, 0] },
131
+ { t: 2, p: [-0.03, 0.01, 0] },
132
+ { t: 3, p: [-0.02, 0.03, 0] }
133
+ ]),
134
+ // Breathing: spine flexes/extends well past the visibility floor (~0.16 rad swing).
135
+ rotationTrack("spine", d, [
136
+ { t: 0, e: [0.04, 0, 0.04] },
137
+ { t: 1, e: [0.18, 0, 0.03] },
138
+ { t: 2, e: [0.05, 0, -0.05] },
139
+ { t: 3, e: [0.16, 0, -0.03] }
140
+ ]),
141
+ rotationTrack("chest", d, [
142
+ { t: 0, e: [0, 0, 0] },
143
+ { t: 1, e: [0.12, 0, 0.02] },
144
+ { t: 2, e: [0.02, 0, 0] },
145
+ { t: 3, e: [0.1, 0, -0.02] }
146
+ ]),
147
+ // Idle head turns: a slow look-around, ~0.34 rad peak-to-peak yaw + a little pitch.
148
+ rotationTrack("head", d, [
149
+ { t: 0, e: [0.02, 0.18, 0] },
150
+ { t: 1, e: [0.06, 0.1, 0.04] },
151
+ { t: 2, e: [0.04, -0.16, 0] },
152
+ { t: 3, e: [-0.04, -0.06, -0.04] }
153
+ ]),
154
+ rotationTrack("neck", d, [
155
+ { t: 0, e: [0, 0.08, 0] },
156
+ { t: 2, e: [0, -0.08, 0] }
157
+ ])
158
+ ],
159
+ true,
160
+ ["idle", "performance", "loop"]
161
+ );
162
+ }
163
+
164
+ // --- talk: real conversational hand gestures + torso + head -------------------------------------
165
+ // A speaking beat must read as "talking with the hands": both arms swing through ~0.5+ rad, the
166
+ // forearms beat with the cadence, the chest counter-rotates, and the head punctuates.
167
+ function talkClip(): AnimationClipDefinition<StandardClipId> {
168
+ const d = 2;
169
+ return clip(
170
+ "talk",
171
+ d,
172
+ [
173
+ rotationTrack("head", d, [
174
+ { t: 0, e: [0, 0.04, 0] },
175
+ { t: 0.5, e: [0.16, 0.12, 0] },
176
+ { t: 1, e: [-0.1, -0.1, 0] },
177
+ { t: 1.5, e: [0.14, 0.06, 0] }
178
+ ]),
179
+ rotationTrack("neck", d, [
180
+ { t: 0, e: [0, 0, 0] },
181
+ { t: 0.5, e: [0.09, 0.05, 0] },
182
+ { t: 1.5, e: [-0.07, -0.05, 0] }
183
+ ]),
184
+ rotationTrack("chest", d, [
185
+ { t: 0, e: [0, -0.06, 0] },
186
+ { t: 0.6, e: [0.04, 0.12, 0] },
187
+ { t: 1.3, e: [0.02, -0.1, 0] }
188
+ ]),
189
+ // Right hand does the lead gesture: up-and-out beats (~0.55 rad swing in Z plus pitch).
190
+ rotationTrack("rightUpperArm", d, [
191
+ { t: 0, e: [0, 0, -0.2] },
192
+ { t: 0.6, e: [0.45, -0.1, -0.62] },
193
+ { t: 1.2, e: [0.1, 0, -0.25] },
194
+ { t: 1.7, e: [0.35, -0.05, -0.5] }
195
+ ]),
196
+ rotationTrack("rightLowerArm", d, [
197
+ { t: 0, e: [0, 0, -0.35] },
198
+ { t: 0.6, e: [0, -0.2, -0.95] },
199
+ { t: 1.2, e: [0, 0, -0.4] },
200
+ { t: 1.7, e: [0, -0.15, -0.8] }
201
+ ]),
202
+ // Left hand supports with its own beat (~0.5 rad swing).
203
+ rotationTrack("leftUpperArm", d, [
204
+ { t: 0, e: [0, 0, 0.2] },
205
+ { t: 0.8, e: [0.35, 0.1, 0.6] },
206
+ { t: 1.5, e: [0.1, 0, 0.28] }
207
+ ]),
208
+ rotationTrack("leftLowerArm", d, [
209
+ { t: 0, e: [0, 0, 0.3] },
210
+ { t: 0.8, e: [0, 0.15, 0.75] },
211
+ { t: 1.5, e: [0, 0, 0.35] }
212
+ ])
213
+ ],
214
+ true,
215
+ ["talk", "performance", "loop", "upper-body"]
216
+ );
217
+ }
218
+
219
+ // --- gesture: one-shot BIG expressive both-arm sweep --------------------------------------------
220
+ // A full presentational sweep: the lead arm raises ~1.1 rad and opens out, the off arm mirrors, the
221
+ // torso opens with it. Unmistakably a "big gesture", not a twitch.
222
+ function gestureClip(): AnimationClipDefinition<StandardClipId> {
223
+ const d = 1.1;
224
+ return clip(
225
+ "gesture",
226
+ d,
227
+ [
228
+ rotationTrack("rightUpperArm", d, [
229
+ { t: 0, e: [0, 0, -0.2] },
230
+ { t: 0.45, e: [1.1, -0.35, -1.15] },
231
+ { t: 0.8, e: [0.7, -0.2, -0.85] }
232
+ ]),
233
+ rotationTrack("rightLowerArm", d, [
234
+ { t: 0, e: [0, 0, -0.3] },
235
+ { t: 0.45, e: [0, -0.6, -1.2] },
236
+ { t: 0.8, e: [0, -0.3, -0.8] }
237
+ ]),
238
+ rotationTrack("leftUpperArm", d, [
239
+ { t: 0, e: [0, 0, 0.2] },
240
+ { t: 0.5, e: [0.95, 0.35, 1.1] },
241
+ { t: 0.85, e: [0.6, 0.2, 0.8] }
242
+ ]),
243
+ rotationTrack("leftLowerArm", d, [
244
+ { t: 0, e: [0, 0, 0.3] },
245
+ { t: 0.5, e: [0, 0.5, 1.1] }
246
+ ]),
247
+ rotationTrack("chest", d, [
248
+ { t: 0, e: [0, 0, 0] },
249
+ { t: 0.45, e: [-0.08, 0.28, 0] }
250
+ ]),
251
+ rotationTrack("head", d, [
252
+ { t: 0, e: [0, 0, 0] },
253
+ { t: 0.45, e: [-0.12, 0.1, 0] }
254
+ ])
255
+ ],
256
+ false,
257
+ ["gesture", "performance", "one-shot", "upper-body"]
258
+ );
259
+ }
260
+
261
+ // --- point: one-shot directional arm extension --------------------------------------------------
262
+ function pointClip(): AnimationClipDefinition<StandardClipId> {
263
+ const d = 0.9;
264
+ return clip(
265
+ "point",
266
+ d,
267
+ [
268
+ rotationTrack("rightShoulder", d, [
269
+ { t: 0, e: [0, 0, 0] },
270
+ { t: 0.35, e: [0, -0.15, 0] }
271
+ ]),
272
+ rotationTrack("rightUpperArm", d, [
273
+ { t: 0, e: [0, 0, -0.2] },
274
+ { t: 0.35, e: [0, -0.25, -1.55] },
275
+ { t: 0.7, e: [0, -0.25, -1.55] }
276
+ ]),
277
+ rotationTrack("rightLowerArm", d, [
278
+ { t: 0, e: [0, 0, -0.35] },
279
+ { t: 0.35, e: [0, 0, 0] },
280
+ { t: 0.7, e: [0, 0, 0] }
281
+ ]),
282
+ rotationTrack("head", d, [
283
+ { t: 0, e: [0, 0, 0] },
284
+ { t: 0.35, e: [0, -0.35, 0] },
285
+ { t: 0.7, e: [0, -0.3, 0] }
286
+ ])
287
+ ],
288
+ false,
289
+ ["point", "performance", "one-shot", "upper-body"]
290
+ );
291
+ }
292
+
293
+ // --- nod: one-shot affirmative head nod ---------------------------------------------------------
294
+ function nodClip(): AnimationClipDefinition<StandardClipId> {
295
+ const d = 0.8;
296
+ return clip(
297
+ "nod",
298
+ d,
299
+ [
300
+ rotationTrack("head", d, [
301
+ { t: 0, e: [-0.06, 0, 0] },
302
+ { t: 0.2, e: [0.42, 0, 0] },
303
+ { t: 0.4, e: [-0.1, 0, 0] },
304
+ { t: 0.6, e: [0.34, 0, 0] }
305
+ ]),
306
+ rotationTrack("neck", d, [
307
+ { t: 0, e: [-0.02, 0, 0] },
308
+ { t: 0.2, e: [0.18, 0, 0] },
309
+ { t: 0.4, e: [-0.02, 0, 0] },
310
+ { t: 0.6, e: [0.14, 0, 0] }
311
+ ])
312
+ ],
313
+ false,
314
+ ["nod", "performance", "one-shot"]
315
+ );
316
+ }
317
+
318
+ // --- react: one-shot surprised recoil (torso + arms + head) -------------------------------------
319
+ function reactClip(): AnimationClipDefinition<StandardClipId> {
320
+ const d = 1;
321
+ return clip(
322
+ "react",
323
+ d,
324
+ [
325
+ rotationTrack("spine", d, [
326
+ { t: 0, e: [0, 0, 0] },
327
+ { t: 0.18, e: [-0.5, 0, 0] },
328
+ { t: 0.6, e: [-0.18, 0, 0] }
329
+ ]),
330
+ rotationTrack("head", d, [
331
+ { t: 0, e: [0, 0, 0] },
332
+ { t: 0.18, e: [-0.4, 0, 0] },
333
+ { t: 0.6, e: [-0.12, 0, 0] }
334
+ ]),
335
+ rotationTrack("leftUpperArm", d, [
336
+ { t: 0, e: [0, 0, 0.2] },
337
+ { t: 0.2, e: [-0.7, 0, 1.1] },
338
+ { t: 0.6, e: [-0.3, 0, 0.6] }
339
+ ]),
340
+ rotationTrack("rightUpperArm", d, [
341
+ { t: 0, e: [0, 0, -0.2] },
342
+ { t: 0.2, e: [-0.7, 0, -1.1] },
343
+ { t: 0.6, e: [-0.3, 0, -0.6] }
344
+ ]),
345
+ translationTrack("hips", d, [
346
+ { t: 0, p: [0, 0, 0] },
347
+ { t: 0.18, p: [0, 0, -0.12] },
348
+ { t: 0.6, p: [0, 0, -0.04] }
349
+ ])
350
+ ],
351
+ false,
352
+ ["react", "performance", "one-shot"]
353
+ );
354
+ }
355
+
356
+ /** Build a symmetric two-step leg/arm cycle for walk or run. */
357
+ function legCycle(id: "walk" | "run", duration: number, amplitude: number, lift: number, bob: number): AnimationClipDefinition<StandardClipId> {
358
+ const half = duration / 2;
359
+ return clip(
360
+ id,
361
+ duration,
362
+ [
363
+ // Hip vertical bob (two bobs per stride).
364
+ translationTrack("hips", duration, [
365
+ { t: 0, p: [0, 0, 0] },
366
+ { t: half / 2, p: [0, bob, 0] },
367
+ { t: half, p: [0, 0, 0] },
368
+ { t: half + half / 2, p: [0, bob, 0] }
369
+ ]),
370
+ // Legs swing in opposition.
371
+ rotationTrack("leftUpperLeg", duration, [
372
+ { t: 0, e: [amplitude, 0, 0] },
373
+ { t: half, e: [-amplitude, 0, 0] }
374
+ ]),
375
+ rotationTrack("rightUpperLeg", duration, [
376
+ { t: 0, e: [-amplitude, 0, 0] },
377
+ { t: half, e: [amplitude, 0, 0] }
378
+ ]),
379
+ rotationTrack("leftLowerLeg", duration, [
380
+ { t: 0, e: [0, 0, 0] },
381
+ { t: half / 2, e: [lift, 0, 0] },
382
+ { t: half, e: [0, 0, 0] }
383
+ ]),
384
+ rotationTrack("rightLowerLeg", duration, [
385
+ { t: 0, e: [lift, 0, 0] },
386
+ { t: half / 2, e: [0, 0, 0] },
387
+ { t: half, e: [lift, 0, 0] }
388
+ ]),
389
+ // Arms counter-swing to the legs.
390
+ rotationTrack("leftUpperArm", duration, [
391
+ { t: 0, e: [-amplitude * 0.8, 0, 0.15] },
392
+ { t: half, e: [amplitude * 0.8, 0, 0.15] }
393
+ ]),
394
+ rotationTrack("rightUpperArm", duration, [
395
+ { t: 0, e: [amplitude * 0.8, 0, -0.15] },
396
+ { t: half, e: [-amplitude * 0.8, 0, -0.15] }
397
+ ]),
398
+ rotationTrack("spine", duration, [
399
+ { t: 0, e: [0.05, amplitude * 0.15, 0] },
400
+ { t: half, e: [0.05, -amplitude * 0.15, 0] }
401
+ ])
402
+ ],
403
+ true,
404
+ [id, "locomotion", "loop", "lower-body"]
405
+ );
406
+ }
407
+
408
+ // Walk: strong staged stride. Upper-leg swing of ±0.55 rad gives a 1.1 rad peak-to-peak step that
409
+ // reads clearly even on a previz rig; knees lift, arms counter-swing, torso twists.
410
+ function walkClip(): AnimationClipDefinition<StandardClipId> {
411
+ return legCycle("walk", 1, 0.55, 0.7, 0.04);
412
+ }
413
+
414
+ // Run: bigger and faster (rig permitting) — ±0.9 rad upper-leg swing, deep knee lift, strong bob.
415
+ function runClip(): AnimationClipDefinition<StandardClipId> {
416
+ return legCycle("run", 0.6, 0.9, 1.1, 0.07);
417
+ }
418
+
419
+ /** Ordered list of all standard library clip definitions (idle first). */
420
+ export function createStandardHumanoidClipDefinitions(): readonly AnimationClipDefinition<StandardClipId>[] {
421
+ return [
422
+ idleClip(),
423
+ talkClip(),
424
+ gestureClip(),
425
+ pointClip(),
426
+ nodClip(),
427
+ walkClip(),
428
+ runClip(),
429
+ reactClip()
430
+ ];
431
+ }
432
+
433
+ /**
434
+ * Shared, rig-neutral {@link AnimationClipRegistry} populated with the 8 standard performance clips
435
+ * authored on {@link STANDARD_LIBRARY_RIG} (canonical {@link HUMANOID_BONES}). The clips are COMPACT
436
+ * procedural keyframe loops/one-shots — real, samplable {@link AnimationTrack} data, not mocap and
437
+ * not placeholders. They give the studio a guaranteed, retargetable performance vocabulary even when
438
+ * a source GLB ships only one (or zero) usable clips.
439
+ */
440
+ export function createStandardHumanoidClipRegistry(): AnimationClipRegistry<StandardClipId> {
441
+ return createAnimationClipRegistry<StandardClipId>(createStandardHumanoidClipDefinitions());
442
+ }
@@ -1,29 +0,0 @@
1
- import { Matrix4 } from "@aura3d/math";
2
-
3
- export interface RetargetBoneMap {
4
- readonly source: string;
5
- readonly target: string;
6
- readonly scale?: number;
7
- }
8
-
9
- export interface RetargetPoseInput {
10
- readonly sourcePose: ReadonlyMap<string, Matrix4>;
11
- readonly boneMap: readonly RetargetBoneMap[];
12
- }
13
-
14
- export function retargetPose(input: RetargetPoseInput): Map<string, Matrix4> {
15
- const output = new Map<string, Matrix4>();
16
- for (const mapping of input.boneMap) {
17
- const source = input.sourcePose.get(mapping.source);
18
- if (!source) continue;
19
- const elements = [...source.elements];
20
- const scale = mapping.scale ?? 1;
21
- if (scale !== 1) {
22
- elements[12] *= scale;
23
- elements[13] *= scale;
24
- elements[14] *= scale;
25
- }
26
- output.set(mapping.target, new Matrix4(elements as unknown as Matrix4["elements"]));
27
- }
28
- return output;
29
- }