@aura3d/animation 1.0.6

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 (187) hide show
  1. package/README.md +19 -0
  2. package/dist/AnimationAction.d.ts +40 -0
  3. package/dist/AnimationAction.d.ts.map +1 -0
  4. package/dist/AnimationAction.js +143 -0
  5. package/dist/AnimationAction.js.map +1 -0
  6. package/dist/AnimationClip.d.ts +24 -0
  7. package/dist/AnimationClip.d.ts.map +1 -0
  8. package/dist/AnimationClip.js +45 -0
  9. package/dist/AnimationClip.js.map +1 -0
  10. package/dist/AnimationClipEvents.d.ts +57 -0
  11. package/dist/AnimationClipEvents.d.ts.map +1 -0
  12. package/dist/AnimationClipEvents.js +171 -0
  13. package/dist/AnimationClipEvents.js.map +1 -0
  14. package/dist/AnimationClipRegistry.d.ts +76 -0
  15. package/dist/AnimationClipRegistry.d.ts.map +1 -0
  16. package/dist/AnimationClipRegistry.js +130 -0
  17. package/dist/AnimationClipRegistry.js.map +1 -0
  18. package/dist/AnimationController.d.ts +168 -0
  19. package/dist/AnimationController.d.ts.map +1 -0
  20. package/dist/AnimationController.js +619 -0
  21. package/dist/AnimationController.js.map +1 -0
  22. package/dist/AnimationEvents.d.ts +10 -0
  23. package/dist/AnimationEvents.d.ts.map +1 -0
  24. package/dist/AnimationEvents.js +26 -0
  25. package/dist/AnimationEvents.js.map +1 -0
  26. package/dist/AnimationLayer.d.ts +27 -0
  27. package/dist/AnimationLayer.d.ts.map +1 -0
  28. package/dist/AnimationLayer.js +53 -0
  29. package/dist/AnimationLayer.js.map +1 -0
  30. package/dist/AnimationMixer.d.ts +54 -0
  31. package/dist/AnimationMixer.d.ts.map +1 -0
  32. package/dist/AnimationMixer.js +319 -0
  33. package/dist/AnimationMixer.js.map +1 -0
  34. package/dist/AnimationStateGraph.d.ts +2 -0
  35. package/dist/AnimationStateGraph.d.ts.map +1 -0
  36. package/dist/AnimationStateGraph.js +2 -0
  37. package/dist/AnimationStateGraph.js.map +1 -0
  38. package/dist/AnimationStateMachine.d.ts +59 -0
  39. package/dist/AnimationStateMachine.d.ts.map +1 -0
  40. package/dist/AnimationStateMachine.js +154 -0
  41. package/dist/AnimationStateMachine.js.map +1 -0
  42. package/dist/AnimationTrack.d.ts +23 -0
  43. package/dist/AnimationTrack.d.ts.map +1 -0
  44. package/dist/AnimationTrack.js +124 -0
  45. package/dist/AnimationTrack.js.map +1 -0
  46. package/dist/BlendTree.d.ts +23 -0
  47. package/dist/BlendTree.d.ts.map +1 -0
  48. package/dist/BlendTree.js +68 -0
  49. package/dist/BlendTree.js.map +1 -0
  50. package/dist/Bone.d.ts +20 -0
  51. package/dist/Bone.d.ts.map +1 -0
  52. package/dist/Bone.js +24 -0
  53. package/dist/Bone.js.map +1 -0
  54. package/dist/CrowdAnimation.d.ts +13 -0
  55. package/dist/CrowdAnimation.d.ts.map +1 -0
  56. package/dist/CrowdAnimation.js +14 -0
  57. package/dist/CrowdAnimation.js.map +1 -0
  58. package/dist/ECSAnimationBridge.d.ts +9 -0
  59. package/dist/ECSAnimationBridge.d.ts.map +1 -0
  60. package/dist/ECSAnimationBridge.js +24 -0
  61. package/dist/ECSAnimationBridge.js.map +1 -0
  62. package/dist/HumanoidRetargeting.d.ts +76 -0
  63. package/dist/HumanoidRetargeting.d.ts.map +1 -0
  64. package/dist/HumanoidRetargeting.js +331 -0
  65. package/dist/HumanoidRetargeting.js.map +1 -0
  66. package/dist/IK.d.ts +24 -0
  67. package/dist/IK.d.ts.map +1 -0
  68. package/dist/IK.js +76 -0
  69. package/dist/IK.js.map +1 -0
  70. package/dist/Keyframe.d.ts +50 -0
  71. package/dist/Keyframe.d.ts.map +1 -0
  72. package/dist/Keyframe.js +118 -0
  73. package/dist/Keyframe.js.map +1 -0
  74. package/dist/LocomotionController.d.ts +47 -0
  75. package/dist/LocomotionController.d.ts.map +1 -0
  76. package/dist/LocomotionController.js +106 -0
  77. package/dist/LocomotionController.js.map +1 -0
  78. package/dist/MotionMatchingFixtures.d.ts +58 -0
  79. package/dist/MotionMatchingFixtures.d.ts.map +1 -0
  80. package/dist/MotionMatchingFixtures.js +175 -0
  81. package/dist/MotionMatchingFixtures.js.map +1 -0
  82. package/dist/MotionQuality.d.ts +36 -0
  83. package/dist/MotionQuality.d.ts.map +1 -0
  84. package/dist/MotionQuality.js +117 -0
  85. package/dist/MotionQuality.js.map +1 -0
  86. package/dist/Retargeting.d.ts +12 -0
  87. package/dist/Retargeting.d.ts.map +1 -0
  88. package/dist/Retargeting.js +19 -0
  89. package/dist/Retargeting.js.map +1 -0
  90. package/dist/RootMotion.d.ts +21 -0
  91. package/dist/RootMotion.d.ts.map +1 -0
  92. package/dist/RootMotion.js +84 -0
  93. package/dist/RootMotion.js.map +1 -0
  94. package/dist/SceneAnimationBridge.d.ts +18 -0
  95. package/dist/SceneAnimationBridge.d.ts.map +1 -0
  96. package/dist/SceneAnimationBridge.js +69 -0
  97. package/dist/SceneAnimationBridge.js.map +1 -0
  98. package/dist/SecondaryAnimationFixtures.d.ts +55 -0
  99. package/dist/SecondaryAnimationFixtures.d.ts.map +1 -0
  100. package/dist/SecondaryAnimationFixtures.js +147 -0
  101. package/dist/SecondaryAnimationFixtures.js.map +1 -0
  102. package/dist/Skeleton.d.ts +9 -0
  103. package/dist/Skeleton.d.ts.map +1 -0
  104. package/dist/Skeleton.js +31 -0
  105. package/dist/Skeleton.js.map +1 -0
  106. package/dist/Skinning.d.ts +7 -0
  107. package/dist/Skinning.d.ts.map +1 -0
  108. package/dist/Skinning.js +12 -0
  109. package/dist/Skinning.js.map +1 -0
  110. package/dist/browser-index.d.ts +39 -0
  111. package/dist/browser-index.d.ts.map +1 -0
  112. package/dist/browser-index.js +34 -0
  113. package/dist/browser-index.js.map +1 -0
  114. package/dist/index.d.ts +40 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +35 -0
  117. package/dist/index.js.map +1 -0
  118. package/dist/threejs-compatibility/AnimationAction.d.ts +16 -0
  119. package/dist/threejs-compatibility/AnimationAction.d.ts.map +1 -0
  120. package/dist/threejs-compatibility/AnimationAction.js +25 -0
  121. package/dist/threejs-compatibility/AnimationAction.js.map +1 -0
  122. package/dist/threejs-compatibility/AnimationClip.d.ts +14 -0
  123. package/dist/threejs-compatibility/AnimationClip.d.ts.map +1 -0
  124. package/dist/threejs-compatibility/AnimationClip.js +11 -0
  125. package/dist/threejs-compatibility/AnimationClip.js.map +1 -0
  126. package/dist/threejs-compatibility/AnimationDiagnostics.d.ts +44 -0
  127. package/dist/threejs-compatibility/AnimationDiagnostics.d.ts.map +1 -0
  128. package/dist/threejs-compatibility/AnimationDiagnostics.js +29 -0
  129. package/dist/threejs-compatibility/AnimationDiagnostics.js.map +1 -0
  130. package/dist/threejs-compatibility/AnimationMixer.d.ts +9 -0
  131. package/dist/threejs-compatibility/AnimationMixer.d.ts.map +1 -0
  132. package/dist/threejs-compatibility/AnimationMixer.js +24 -0
  133. package/dist/threejs-compatibility/AnimationMixer.js.map +1 -0
  134. package/dist/threejs-compatibility/MorphTargetMixer.d.ts +10 -0
  135. package/dist/threejs-compatibility/MorphTargetMixer.d.ts.map +1 -0
  136. package/dist/threejs-compatibility/MorphTargetMixer.js +10 -0
  137. package/dist/threejs-compatibility/MorphTargetMixer.js.map +1 -0
  138. package/dist/threejs-compatibility/Skeleton.d.ts +10 -0
  139. package/dist/threejs-compatibility/Skeleton.d.ts.map +1 -0
  140. package/dist/threejs-compatibility/Skeleton.js +8 -0
  141. package/dist/threejs-compatibility/Skeleton.js.map +1 -0
  142. package/dist/threejs-compatibility/SkinnedMesh.d.ts +11 -0
  143. package/dist/threejs-compatibility/SkinnedMesh.d.ts.map +1 -0
  144. package/dist/threejs-compatibility/SkinnedMesh.js +16 -0
  145. package/dist/threejs-compatibility/SkinnedMesh.js.map +1 -0
  146. package/dist/threejs-compatibility/index.d.ts +12 -0
  147. package/dist/threejs-compatibility/index.d.ts.map +1 -0
  148. package/dist/threejs-compatibility/index.js +8 -0
  149. package/dist/threejs-compatibility/index.js.map +1 -0
  150. package/package.json +17 -0
  151. package/src/AnimationAction.ts +166 -0
  152. package/src/AnimationClip.ts +63 -0
  153. package/src/AnimationClipEvents.ts +320 -0
  154. package/src/AnimationClipRegistry.ts +280 -0
  155. package/src/AnimationController.ts +967 -0
  156. package/src/AnimationEvents.ts +34 -0
  157. package/src/AnimationLayer.ts +73 -0
  158. package/src/AnimationMixer.ts +366 -0
  159. package/src/AnimationStateGraph.ts +8 -0
  160. package/src/AnimationStateMachine.ts +217 -0
  161. package/src/AnimationTrack.ts +176 -0
  162. package/src/BlendTree.ts +89 -0
  163. package/src/Bone.ts +35 -0
  164. package/src/CrowdAnimation.ts +25 -0
  165. package/src/ECSAnimationBridge.ts +30 -0
  166. package/src/HumanoidRetargeting.ts +476 -0
  167. package/src/IK.ts +107 -0
  168. package/src/Keyframe.ts +159 -0
  169. package/src/LocomotionController.ts +149 -0
  170. package/src/MotionMatchingFixtures.ts +254 -0
  171. package/src/MotionQuality.ts +156 -0
  172. package/src/Retargeting.ts +29 -0
  173. package/src/RootMotion.ts +124 -0
  174. package/src/SceneAnimationBridge.ts +80 -0
  175. package/src/SecondaryAnimationFixtures.ts +212 -0
  176. package/src/Skeleton.ts +34 -0
  177. package/src/Skinning.ts +18 -0
  178. package/src/browser-index.ts +55 -0
  179. package/src/index.ts +56 -0
  180. package/src/threejs-compatibility/AnimationAction.ts +27 -0
  181. package/src/threejs-compatibility/AnimationClip.ts +16 -0
  182. package/src/threejs-compatibility/AnimationDiagnostics.ts +42 -0
  183. package/src/threejs-compatibility/AnimationMixer.ts +24 -0
  184. package/src/threejs-compatibility/MorphTargetMixer.ts +16 -0
  185. package/src/threejs-compatibility/Skeleton.ts +9 -0
  186. package/src/threejs-compatibility/SkinnedMesh.ts +12 -0
  187. package/src/threejs-compatibility/index.ts +11 -0
@@ -0,0 +1,159 @@
1
+ export type Vec3 = readonly [number, number, number];
2
+ export type Quat = readonly [number, number, number, number];
3
+ export type Mat4 = readonly [
4
+ number, number, number, number,
5
+ number, number, number, number,
6
+ number, number, number, number,
7
+ number, number, number, number
8
+ ];
9
+
10
+ export type NumberArray = readonly number[];
11
+
12
+ export type AnimationValue = number | boolean | string | Vec3 | Quat | NumberArray;
13
+
14
+ export type InterpolationMode = "step" | "linear" | "cubicspline";
15
+
16
+ export type Keyframe<T extends AnimationValue = AnimationValue> = {
17
+ readonly time: number;
18
+ readonly value: T;
19
+ readonly interpolation?: InterpolationMode;
20
+ readonly inTangent?: T;
21
+ readonly outTangent?: T;
22
+ };
23
+
24
+ export type SerializedKeyframe<T extends AnimationValue = AnimationValue> = {
25
+ readonly time: number;
26
+ readonly value: T;
27
+ readonly interpolation?: InterpolationMode;
28
+ readonly inTangent?: T;
29
+ readonly outTangent?: T;
30
+ };
31
+
32
+ export function validateKeyframes<T extends AnimationValue>(keyframes: readonly Keyframe<T>[]): void {
33
+ if (keyframes.length === 0) {
34
+ throw new Error("Animation track requires at least one keyframe.");
35
+ }
36
+ let previous = -Number.MAX_VALUE;
37
+ for (const keyframe of keyframes) {
38
+ if (!Number.isFinite(keyframe.time) || keyframe.time < 0) {
39
+ throw new Error("Keyframe time must be finite and non-negative.");
40
+ }
41
+ if (keyframe.time <= previous) {
42
+ throw new Error("Keyframes must be sorted by strictly increasing time.");
43
+ }
44
+ previous = keyframe.time;
45
+ }
46
+ }
47
+
48
+ export function serializeKeyframe<T extends AnimationValue>(keyframe: Keyframe<T>): SerializedKeyframe<T> {
49
+ return {
50
+ time: keyframe.time,
51
+ value: cloneAnimationValue(keyframe.value),
52
+ ...(keyframe.interpolation ? { interpolation: keyframe.interpolation } : {}),
53
+ ...(keyframe.inTangent !== undefined ? { inTangent: cloneAnimationValue(keyframe.inTangent) } : {}),
54
+ ...(keyframe.outTangent !== undefined ? { outTangent: cloneAnimationValue(keyframe.outTangent) } : {})
55
+ };
56
+ }
57
+
58
+ export function deserializeKeyframe<T extends AnimationValue>(keyframe: SerializedKeyframe<T>): Keyframe<T> {
59
+ return serializeKeyframe(keyframe);
60
+ }
61
+
62
+ export function cloneAnimationValue<T extends AnimationValue>(value: T): T {
63
+ return Array.isArray(value) ? ([...value] as unknown as T) : value;
64
+ }
65
+
66
+ export function lerpNumber(a: number, b: number, t: number): number {
67
+ return a + (b - a) * t;
68
+ }
69
+
70
+ export function lerpVec3(a: Vec3, b: Vec3, t: number): Vec3 {
71
+ return [lerpNumber(a[0], b[0], t), lerpNumber(a[1], b[1], t), lerpNumber(a[2], b[2], t)];
72
+ }
73
+
74
+ export function normalizeQuat(q: Quat): Quat {
75
+ const length = Math.hypot(q[0], q[1], q[2], q[3]);
76
+ if (length <= 1e-9) {
77
+ return [0, 0, 0, 1];
78
+ }
79
+ return [q[0] / length, q[1] / length, q[2] / length, q[3] / length];
80
+ }
81
+
82
+ export function slerpQuat(a: Quat, b: Quat, t: number): Quat {
83
+ let bx = b[0];
84
+ let by = b[1];
85
+ let bz = b[2];
86
+ let bw = b[3];
87
+ let cos = a[0] * bx + a[1] * by + a[2] * bz + a[3] * bw;
88
+ if (cos < 0) {
89
+ cos = -cos;
90
+ bx = -bx;
91
+ by = -by;
92
+ bz = -bz;
93
+ bw = -bw;
94
+ }
95
+ if (cos > 0.9995) {
96
+ return normalizeQuat([lerpNumber(a[0], bx, t), lerpNumber(a[1], by, t), lerpNumber(a[2], bz, t), lerpNumber(a[3], bw, t)]);
97
+ }
98
+ const theta = Math.acos(Math.max(-1, Math.min(1, cos)));
99
+ const sinTheta = Math.sin(theta);
100
+ const wa = Math.sin((1 - t) * theta) / sinTheta;
101
+ const wb = Math.sin(t * theta) / sinTheta;
102
+ return [a[0] * wa + bx * wb, a[1] * wa + by * wb, a[2] * wa + bz * wb, a[3] * wa + bw * wb];
103
+ }
104
+
105
+ export function identityMat4(): Mat4 {
106
+ return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
107
+ }
108
+
109
+ export function composeMat4(translation: Vec3 = [0, 0, 0], rotation: Quat = [0, 0, 0, 1], scale: Vec3 = [1, 1, 1]): Mat4 {
110
+ const [x, y, z, w] = normalizeQuat(rotation);
111
+ const x2 = x + x;
112
+ const y2 = y + y;
113
+ const z2 = z + z;
114
+ const xx = x * x2;
115
+ const xy = x * y2;
116
+ const xz = x * z2;
117
+ const yy = y * y2;
118
+ const yz = y * z2;
119
+ const zz = z * z2;
120
+ const wx = w * x2;
121
+ const wy = w * y2;
122
+ const wz = w * z2;
123
+ return [
124
+ (1 - (yy + zz)) * scale[0], (xy + wz) * scale[0], (xz - wy) * scale[0], 0,
125
+ (xy - wz) * scale[1], (1 - (xx + zz)) * scale[1], (yz + wx) * scale[1], 0,
126
+ (xz + wy) * scale[2], (yz - wx) * scale[2], (1 - (xx + yy)) * scale[2], 0,
127
+ translation[0], translation[1], translation[2], 1
128
+ ];
129
+ }
130
+
131
+ export function multiplyMat4(a: Mat4, b: Mat4): Mat4 {
132
+ const out = new Array<number>(16).fill(0);
133
+ for (let row = 0; row < 4; row += 1) {
134
+ for (let col = 0; col < 4; col += 1) {
135
+ const a0 = a[row * 4]!;
136
+ const a1 = a[row * 4 + 1]!;
137
+ const a2 = a[row * 4 + 2]!;
138
+ const a3 = a[row * 4 + 3]!;
139
+ out[col + row * 4] =
140
+ a0 * b[col]! +
141
+ a1 * b[col + 4]! +
142
+ a2 * b[col + 8]! +
143
+ a3 * b[col + 12]!;
144
+ }
145
+ }
146
+ return out as unknown as Mat4;
147
+ }
148
+
149
+ export function invertTranslationMat4(matrix: Mat4): Mat4 {
150
+ return [
151
+ matrix[0], matrix[4], matrix[8], 0,
152
+ matrix[1], matrix[5], matrix[9], 0,
153
+ matrix[2], matrix[6], matrix[10], 0,
154
+ -(matrix[12] * matrix[0] + matrix[13] * matrix[1] + matrix[14] * matrix[2]),
155
+ -(matrix[12] * matrix[4] + matrix[13] * matrix[5] + matrix[14] * matrix[6]),
156
+ -(matrix[12] * matrix[8] + matrix[13] * matrix[9] + matrix[14] * matrix[10]),
157
+ 1
158
+ ];
159
+ }
@@ -0,0 +1,149 @@
1
+ import { AnimationClip } from "./AnimationClip.js";
2
+ import { AnimationTrack } from "./AnimationTrack.js";
3
+ import { applyRootMotion, extractRootMotion } from "./RootMotion.js";
4
+
5
+ export interface LocomotionControllerState {
6
+ speed: number;
7
+ inPlace: boolean;
8
+ paused: boolean;
9
+ pathRadius: number;
10
+ }
11
+
12
+ export interface LocomotionControllerSample {
13
+ clipName: string;
14
+ clipTime: number;
15
+ worldX: number;
16
+ worldZ: number;
17
+ heading: number;
18
+ stride: number;
19
+ rootMotionDistance: number;
20
+ }
21
+
22
+ export interface LocomotionControllerOptions {
23
+ readonly clip: AnimationClip;
24
+ readonly rootMotionTrack?: string;
25
+ readonly speed?: number;
26
+ readonly inPlace?: boolean;
27
+ readonly paused?: boolean;
28
+ readonly pathRadius?: number;
29
+ readonly strideAmplitude?: number;
30
+ readonly rootMotionScale?: number;
31
+ }
32
+
33
+ export interface ProceduralWalkClipOptions {
34
+ readonly name?: string;
35
+ readonly duration?: number;
36
+ readonly distance?: number;
37
+ readonly rootMotionTrack?: string;
38
+ }
39
+
40
+ export class LocomotionController {
41
+ readonly clip: AnimationClip;
42
+ readonly rootMotionTrack: string;
43
+ readonly strideAmplitude: number;
44
+ readonly rootMotionScale: number;
45
+ readonly state: LocomotionControllerState;
46
+
47
+ constructor(options: LocomotionControllerOptions) {
48
+ if (!Number.isFinite(options.clip.duration) || options.clip.duration <= 0) {
49
+ throw new Error("LocomotionController requires a clip with positive finite duration.");
50
+ }
51
+ this.clip = options.clip;
52
+ this.rootMotionTrack = options.rootMotionTrack ?? "root.position";
53
+ this.strideAmplitude = options.strideAmplitude ?? 1;
54
+ this.rootMotionScale = options.rootMotionScale ?? 1;
55
+ this.state = {
56
+ speed: options.speed ?? 1,
57
+ inPlace: options.inPlace ?? false,
58
+ paused: options.paused ?? false,
59
+ pathRadius: options.pathRadius ?? 1
60
+ };
61
+ }
62
+
63
+ setSpeed(speed: number): this {
64
+ if (!Number.isFinite(speed) || speed < 0) {
65
+ throw new Error("LocomotionController speed must be finite and non-negative.");
66
+ }
67
+ this.state.speed = speed;
68
+ return this;
69
+ }
70
+
71
+ setInPlace(inPlace: boolean): this {
72
+ this.state.inPlace = inPlace;
73
+ return this;
74
+ }
75
+
76
+ setPaused(paused: boolean): this {
77
+ this.state.paused = paused;
78
+ return this;
79
+ }
80
+
81
+ setPathRadius(pathRadius: number): this {
82
+ if (!Number.isFinite(pathRadius) || pathRadius <= 0) {
83
+ throw new Error("LocomotionController pathRadius must be positive and finite.");
84
+ }
85
+ this.state.pathRadius = pathRadius;
86
+ return this;
87
+ }
88
+
89
+ sample(elapsedSeconds: number): LocomotionControllerSample {
90
+ if (!Number.isFinite(elapsedSeconds) || elapsedSeconds < 0) {
91
+ throw new Error("LocomotionController elapsed time must be finite and non-negative.");
92
+ }
93
+ const scaledTime = this.state.paused ? 0 : elapsedSeconds * this.state.speed;
94
+ const previousTime = Math.max(0, scaledTime - 1 / 60);
95
+ const clipTime = wrapTime(scaledTime, this.clip.duration);
96
+ const previousClipTime = wrapTime(previousTime, this.clip.duration);
97
+ const target = { position: [0, 0, 0] as [number, number, number] };
98
+ const rootMotion = extractRootMotion(this.clip, {
99
+ target: this.rootMotionTrack,
100
+ fromTime: previousClipTime,
101
+ toTime: clipTime,
102
+ loop: true
103
+ });
104
+ applyRootMotion(target, rootMotion, this.state.inPlace ? 0 : this.rootMotionScale);
105
+ const rootMotionDistance = this.state.inPlace ? 0 : scaledTime * this.rootMotionScale;
106
+ const pathAngle = this.state.inPlace ? -0.9 : rootMotionDistance / Math.max(0.1, this.state.pathRadius);
107
+ return {
108
+ clipName: this.clip.name,
109
+ clipTime,
110
+ worldX: this.state.inPlace ? 0 : Math.sin(pathAngle) * this.state.pathRadius,
111
+ worldZ: this.state.inPlace ? 0 : Math.cos(pathAngle) * this.state.pathRadius,
112
+ heading: this.state.inPlace ? 0 : pathAngle + Math.PI / 2,
113
+ stride: Math.sin((clipTime / this.clip.duration) * Math.PI * 2) * this.strideAmplitude,
114
+ rootMotionDistance: Math.hypot(target.position[0], target.position[2]) + rootMotionDistance
115
+ };
116
+ }
117
+ }
118
+
119
+ export function createRootMotionWalkClip(options: ProceduralWalkClipOptions = {}): AnimationClip {
120
+ const name = options.name ?? "a3d-procedural-root-walk";
121
+ const duration = options.duration ?? 1.2;
122
+ const distance = options.distance ?? 1.28;
123
+ const target = options.rootMotionTrack ?? "root.position";
124
+ if (!Number.isFinite(duration) || duration <= 0 || !Number.isFinite(distance) || distance <= 0) {
125
+ throw new Error("Root-motion walk clip duration and distance must be positive finite values.");
126
+ }
127
+ return new AnimationClip({
128
+ name,
129
+ duration,
130
+ tracks: [
131
+ new AnimationTrack({
132
+ target,
133
+ valueType: "vector3",
134
+ keyframes: [
135
+ { time: 0, value: [0, 0, 0], interpolation: "linear" },
136
+ { time: duration * 0.25, value: [0, 0, distance * 0.25], interpolation: "linear" },
137
+ { time: duration * 0.5, value: [0, 0, distance * 0.5], interpolation: "linear" },
138
+ { time: duration * 0.75, value: [0, 0, distance * 0.75], interpolation: "linear" },
139
+ { time: duration, value: [0, 0, distance], interpolation: "linear" }
140
+ ]
141
+ })
142
+ ]
143
+ });
144
+ }
145
+
146
+ function wrapTime(time: number, duration: number): number {
147
+ const wrapped = time % duration;
148
+ return wrapped < 0 ? wrapped + duration : wrapped;
149
+ }
@@ -0,0 +1,254 @@
1
+ import type { Vec3 } from "./Keyframe.js";
2
+
3
+ export interface MotionMatchingFixtureOptions {
4
+ readonly currentPosition?: Vec3;
5
+ readonly moveDirection?: Vec3;
6
+ readonly facingDirection?: Vec3;
7
+ readonly speed?: number;
8
+ readonly elapsedSeconds?: number;
9
+ readonly previousPoseId?: string;
10
+ readonly seed?: number;
11
+ }
12
+
13
+ export interface MotionMatchingTrajectorySample {
14
+ readonly time: number;
15
+ readonly position: Vec3;
16
+ readonly direction: Vec3;
17
+ }
18
+
19
+ export interface MotionMatchingPoseCandidate {
20
+ readonly id: string;
21
+ readonly clip: "idle" | "walk" | "run" | "strafe" | "turn" | "jump";
22
+ readonly time: number;
23
+ readonly tags: readonly string[];
24
+ readonly rootVelocity: Vec3;
25
+ readonly footLeft: Vec3;
26
+ readonly footRight: Vec3;
27
+ readonly trajectory: readonly MotionMatchingTrajectorySample[];
28
+ }
29
+
30
+ export interface MotionMatchingCandidateScore {
31
+ readonly poseId: string;
32
+ readonly clip: MotionMatchingPoseCandidate["clip"];
33
+ readonly tags: readonly string[];
34
+ readonly velocityCost: number;
35
+ readonly facingCost: number;
36
+ readonly trajectoryCost: number;
37
+ readonly continuityCost: number;
38
+ readonly totalCost: number;
39
+ }
40
+
41
+ export interface MotionMatchingFixtureSample {
42
+ readonly id: "external-parity-old-branch-motion-matching-fixture";
43
+ readonly source: "origin-master-motion-matching-system-adapted";
44
+ readonly databasePoseCount: number;
45
+ readonly candidateScores: readonly MotionMatchingCandidateScore[];
46
+ readonly selectedPoseId: string;
47
+ readonly selectedClip: MotionMatchingPoseCandidate["clip"];
48
+ readonly selectedTags: readonly string[];
49
+ readonly selectedTime: number;
50
+ readonly transitioned: boolean;
51
+ readonly blendWeight: number;
52
+ readonly transitionDurationSeconds: number;
53
+ readonly queryTrajectory: readonly MotionMatchingTrajectorySample[];
54
+ readonly querySpeed: number;
55
+ readonly queryFacingAlignment: number;
56
+ readonly bestCost: number;
57
+ readonly secondBestCost: number;
58
+ readonly costMargin: number;
59
+ readonly hash: string;
60
+ readonly claimBoundary: string;
61
+ }
62
+
63
+ const predictionTimes = [0.2, 0.45, 0.75] as const;
64
+
65
+ export function sampleMotionMatchingFixture(options: MotionMatchingFixtureOptions = {}): MotionMatchingFixtureSample {
66
+ const seed = options.seed ?? 0x3d2025;
67
+ if (!Number.isInteger(seed)) throw new RangeError("Motion matching fixture seed must be an integer.");
68
+ const currentPosition = vec3(options.currentPosition ?? [0, 0, 0], "currentPosition");
69
+ const moveDirection = normalize(vec3(options.moveDirection ?? [1, 0, 0.18], "moveDirection"));
70
+ const facingDirection = normalize(vec3(options.facingDirection ?? [1, 0, 0], "facingDirection"));
71
+ const speed = clamp(finite(options.speed ?? 0.74, "speed"), 0, 1);
72
+ const elapsedSeconds = Math.max(0, finite(options.elapsedSeconds ?? 0, "elapsedSeconds"));
73
+ const maxSpeed = 6.2;
74
+ const queryVelocity = scale(moveDirection, speed * maxSpeed);
75
+ const queryTrajectory = predictionTimes.map((time) => ({
76
+ time,
77
+ position: roundVec3(add(currentPosition, scale(queryVelocity, time))),
78
+ direction: roundVec3(normalize(lerpVec3(moveDirection, facingDirection, Math.min(1, time * 0.8))))
79
+ }));
80
+ const database = createMotionDatabase(seed);
81
+ const previousPoseId = options.previousPoseId ?? database[0]?.id ?? "idle-0";
82
+ const scored = database
83
+ .map((candidate) => scoreCandidate(candidate, queryVelocity, facingDirection, queryTrajectory, previousPoseId))
84
+ .sort((left, right) => left.totalCost - right.totalCost);
85
+ const selected = scored[0];
86
+ if (!selected) throw new Error("Motion matching fixture database is empty.");
87
+ const selectedPose = database.find((candidate) => candidate.id === selected.poseId);
88
+ if (!selectedPose) throw new Error("Motion matching fixture selected an unknown pose.");
89
+ const secondBestCost = scored[1]?.totalCost ?? selected.totalCost;
90
+ const transitioned = selected.poseId !== previousPoseId;
91
+ const transitionDurationSeconds = 0.18;
92
+ const blendWeight = transitioned ? clamp(elapsedSeconds / transitionDurationSeconds, 0, 1) : 1;
93
+ return {
94
+ id: "external-parity-old-branch-motion-matching-fixture",
95
+ source: "origin-master-motion-matching-system-adapted",
96
+ databasePoseCount: database.length,
97
+ candidateScores: scored.slice(0, 6),
98
+ selectedPoseId: selected.poseId,
99
+ selectedClip: selected.clip,
100
+ selectedTags: selected.tags,
101
+ selectedTime: selectedPose.time,
102
+ transitioned,
103
+ blendWeight: Number(blendWeight.toFixed(4)),
104
+ transitionDurationSeconds,
105
+ queryTrajectory,
106
+ querySpeed: Number(length(queryVelocity).toFixed(4)),
107
+ queryFacingAlignment: Number(dot(moveDirection, facingDirection).toFixed(4)),
108
+ bestCost: selected.totalCost,
109
+ secondBestCost,
110
+ costMargin: Number((secondBestCost - selected.totalCost).toFixed(5)),
111
+ hash: hashMotionFixture(scored, queryTrajectory),
112
+ claimBoundary: "Deterministic pose-database scoring, trajectory prediction, tag filtering, continuity cost, and transition telemetry adapted from the old motion-matching system; this is bounded runtime evidence, not a full animation database, inertialization, pose application, foot locking, or Unity/Unreal animation middleware parity."
113
+ };
114
+ }
115
+
116
+ function createMotionDatabase(seed: number): readonly MotionMatchingPoseCandidate[] {
117
+ const clips: readonly MotionMatchingPoseCandidate["clip"][] = ["idle", "walk", "run", "strafe", "turn", "jump"];
118
+ return clips.flatMap((clip, clipIndex) => {
119
+ const speed = clip === "idle" ? 0 : clip === "walk" ? 2.1 : clip === "run" ? 5.8 : clip === "strafe" ? 3.2 : clip === "turn" ? 0.9 : 3.8;
120
+ const angle = clip === "strafe" ? Math.PI / 2 : clip === "turn" ? 0.62 : seeded01(seed + clipIndex * 41) * 0.14 - 0.07;
121
+ const direction: Vec3 = [Math.cos(angle), 0, Math.sin(angle)];
122
+ return [0, 1, 2].map((poseIndex) => {
123
+ const time = Number((poseIndex * 0.18 + seeded01(seed + clipIndex * 101 + poseIndex) * 0.04).toFixed(4));
124
+ const phase = poseIndex / 3;
125
+ return {
126
+ id: `${clip}-${poseIndex}`,
127
+ clip,
128
+ time,
129
+ tags: clip === "idle" ? ["idle"] : clip === "jump" ? ["action", "airborne"] : ["locomotion", clip],
130
+ rootVelocity: roundVec3(scale(direction, speed * (0.92 + phase * 0.12))),
131
+ footLeft: roundVec3([-0.16, Math.sin(phase * Math.PI * 2) * 0.08, 0.08]),
132
+ footRight: roundVec3([0.16, Math.sin((phase + 0.5) * Math.PI * 2) * 0.08, -0.08]),
133
+ trajectory: predictionTimes.map((predictionTime) => ({
134
+ time: predictionTime,
135
+ position: roundVec3(scale(direction, speed * predictionTime)),
136
+ direction: roundVec3(direction)
137
+ }))
138
+ };
139
+ });
140
+ });
141
+ }
142
+
143
+ function scoreCandidate(
144
+ candidate: MotionMatchingPoseCandidate,
145
+ queryVelocity: Vec3,
146
+ facingDirection: Vec3,
147
+ queryTrajectory: readonly MotionMatchingTrajectorySample[],
148
+ previousPoseId: string
149
+ ): MotionMatchingCandidateScore {
150
+ const velocityCost = distance(candidate.rootVelocity, queryVelocity);
151
+ const facingCost = 1 - clamp(dot(normalize(candidate.trajectory[0]?.direction ?? [1, 0, 0]), facingDirection), -1, 1);
152
+ const trajectoryCost = queryTrajectory.reduce((sum, query, index) => {
153
+ const candidateSample = candidate.trajectory[index] ?? candidate.trajectory[candidate.trajectory.length - 1]!;
154
+ return sum + distance(candidateSample.position, subtract(query.position, queryTrajectory[0]?.position ?? [0, 0, 0]));
155
+ }, 0) / Math.max(1, queryTrajectory.length);
156
+ const continuityCost = candidate.id === previousPoseId ? 0 : candidate.clip === previousPoseId.split("-")[0] ? 0.12 : 0.34;
157
+ const totalCost = velocityCost * 0.46 + facingCost * 1.2 + trajectoryCost * 0.38 + continuityCost;
158
+ return {
159
+ poseId: candidate.id,
160
+ clip: candidate.clip,
161
+ tags: candidate.tags,
162
+ velocityCost: Number(velocityCost.toFixed(5)),
163
+ facingCost: Number(facingCost.toFixed(5)),
164
+ trajectoryCost: Number(trajectoryCost.toFixed(5)),
165
+ continuityCost: Number(continuityCost.toFixed(5)),
166
+ totalCost: Number(totalCost.toFixed(5))
167
+ };
168
+ }
169
+
170
+ function hashMotionFixture(scores: readonly MotionMatchingCandidateScore[], trajectory: readonly MotionMatchingTrajectorySample[]): string {
171
+ let hash = 0x811c9dc5;
172
+ for (const score of scores) {
173
+ for (const char of `${score.poseId}:${score.totalCost}:${score.velocityCost}`) {
174
+ hash ^= char.charCodeAt(0);
175
+ hash = Math.imul(hash, 0x01000193) >>> 0;
176
+ }
177
+ }
178
+ for (const sample of trajectory) {
179
+ for (const value of [sample.time, ...sample.position, ...sample.direction]) {
180
+ const scaled = Math.round(value * 10_000);
181
+ hash ^= scaled & 0xff;
182
+ hash = Math.imul(hash, 0x01000193) >>> 0;
183
+ hash ^= (scaled >>> 8) & 0xff;
184
+ hash = Math.imul(hash, 0x01000193) >>> 0;
185
+ }
186
+ }
187
+ return hash.toString(16).padStart(8, "0");
188
+ }
189
+
190
+ function vec3(value: readonly number[], label: string): Vec3 {
191
+ if (value.length !== 3 || value.some((entry) => !Number.isFinite(entry))) {
192
+ throw new RangeError(`Motion matching fixture ${label} must contain three finite numbers.`);
193
+ }
194
+ return [value[0]!, value[1]!, value[2]!];
195
+ }
196
+
197
+ function roundVec3(value: Vec3): Vec3 {
198
+ return [Number(value[0].toFixed(4)), Number(value[1].toFixed(4)), Number(value[2].toFixed(4))];
199
+ }
200
+
201
+ function add(left: Vec3, right: Vec3): Vec3 {
202
+ return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
203
+ }
204
+
205
+ function subtract(left: Vec3, right: Vec3): Vec3 {
206
+ return [left[0] - right[0], left[1] - right[1], left[2] - right[2]];
207
+ }
208
+
209
+ function scale(value: Vec3, scalar: number): Vec3 {
210
+ return [value[0] * scalar, value[1] * scalar, value[2] * scalar];
211
+ }
212
+
213
+ function lerpVec3(left: Vec3, right: Vec3, weight: number): Vec3 {
214
+ return [
215
+ left[0] + (right[0] - left[0]) * weight,
216
+ left[1] + (right[1] - left[1]) * weight,
217
+ left[2] + (right[2] - left[2]) * weight
218
+ ];
219
+ }
220
+
221
+ function dot(left: Vec3, right: Vec3): number {
222
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
223
+ }
224
+
225
+ function length(value: Vec3): number {
226
+ return Math.hypot(value[0], value[1], value[2]);
227
+ }
228
+
229
+ function distance(left: Vec3, right: Vec3): number {
230
+ return length(subtract(left, right));
231
+ }
232
+
233
+ function normalize(value: Vec3): Vec3 {
234
+ const valueLength = length(value);
235
+ if (valueLength <= 1e-6) return [1, 0, 0];
236
+ return scale(value, 1 / valueLength);
237
+ }
238
+
239
+ function finite(value: number, label: string): number {
240
+ if (!Number.isFinite(value)) throw new RangeError(`Motion matching fixture ${label} must be finite.`);
241
+ return value;
242
+ }
243
+
244
+ function clamp(value: number, min: number, max: number): number {
245
+ return Math.max(min, Math.min(max, value));
246
+ }
247
+
248
+ function seeded01(seed: number): number {
249
+ let value = seed >>> 0;
250
+ value ^= value << 13;
251
+ value ^= value >>> 17;
252
+ value ^= value << 5;
253
+ return ((value >>> 0) % 10_000) / 10_000;
254
+ }