@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,176 @@
1
+ import { cloneAnimationValue, deserializeKeyframe, lerpNumber, lerpVec3, serializeKeyframe, slerpQuat, validateKeyframes, type AnimationValue, type Keyframe, type SerializedKeyframe } from "./Keyframe.js";
2
+
3
+ export type TrackValueType = "scalar" | "vector3" | "quaternion" | "number-array" | "boolean" | "string";
4
+
5
+ export type AnimationTrackDescriptor<T extends AnimationValue = AnimationValue> = {
6
+ readonly target: string;
7
+ readonly valueType: TrackValueType;
8
+ readonly keyframes: readonly Keyframe<T>[];
9
+ };
10
+
11
+ export type SerializedAnimationTrack<T extends AnimationValue = AnimationValue> = {
12
+ readonly target: string;
13
+ readonly valueType: TrackValueType;
14
+ readonly keyframes: readonly SerializedKeyframe<T>[];
15
+ };
16
+
17
+ export class AnimationTrack<T extends AnimationValue = AnimationValue> {
18
+ readonly target: string;
19
+ readonly valueType: TrackValueType;
20
+ readonly keyframes: readonly Keyframe<T>[];
21
+ readonly duration: number;
22
+
23
+ constructor(descriptor: AnimationTrackDescriptor<T>) {
24
+ if (descriptor.target.trim().length === 0) {
25
+ throw new Error("AnimationTrack target cannot be empty.");
26
+ }
27
+ validateKeyframes(descriptor.keyframes);
28
+ this.target = descriptor.target;
29
+ this.valueType = descriptor.valueType;
30
+ this.keyframes = descriptor.keyframes.map((keyframe) => ({ ...keyframe, value: cloneAnimationValue(keyframe.value) }));
31
+ this.duration = this.keyframes[this.keyframes.length - 1]!.time;
32
+ }
33
+
34
+ sample(time: number): T {
35
+ if (!Number.isFinite(time)) {
36
+ throw new Error("AnimationTrack sample time must be finite.");
37
+ }
38
+ if (time <= this.keyframes[0]!.time) {
39
+ return cloneAnimationValue(this.keyframes[0]!.value);
40
+ }
41
+ const last = this.keyframes[this.keyframes.length - 1]!;
42
+ if (time >= last.time) {
43
+ return cloneAnimationValue(last.value);
44
+ }
45
+ for (let index = 0; index < this.keyframes.length - 1; index += 1) {
46
+ const a = this.keyframes[index]!;
47
+ const b = this.keyframes[index + 1]!;
48
+ if (time >= a.time && time <= b.time) {
49
+ if (a.interpolation === "step" || b.time === a.time) {
50
+ return cloneAnimationValue(a.value);
51
+ }
52
+ const t = (time - a.time) / (b.time - a.time);
53
+ if (a.interpolation === "cubicspline") {
54
+ return interpolateCubicSpline(this.valueType, a.value, b.value, a.outTangent, b.inTangent, t, b.time - a.time) as T;
55
+ }
56
+ return interpolate(this.valueType, a.value, b.value, t) as T;
57
+ }
58
+ }
59
+ return cloneAnimationValue(last.value);
60
+ }
61
+
62
+ toJSON(): SerializedAnimationTrack<T> {
63
+ return {
64
+ target: this.target,
65
+ valueType: this.valueType,
66
+ keyframes: this.keyframes.map((keyframe) => serializeKeyframe(keyframe))
67
+ };
68
+ }
69
+
70
+ static fromJSON<T extends AnimationValue = AnimationValue>(serialized: SerializedAnimationTrack<T>): AnimationTrack<T> {
71
+ return new AnimationTrack({
72
+ target: serialized.target,
73
+ valueType: serialized.valueType,
74
+ keyframes: serialized.keyframes.map((keyframe) => deserializeKeyframe(keyframe))
75
+ });
76
+ }
77
+ }
78
+
79
+ function interpolateCubicSpline(
80
+ type: TrackValueType,
81
+ a: AnimationValue,
82
+ b: AnimationValue,
83
+ outTangent: AnimationValue | undefined,
84
+ inTangent: AnimationValue | undefined,
85
+ t: number,
86
+ deltaTime: number
87
+ ): AnimationValue {
88
+ if (outTangent === undefined || inTangent === undefined) {
89
+ throw new Error("CUBICSPLINE keyframes require outTangent on the first keyframe and inTangent on the next keyframe.");
90
+ }
91
+ switch (type) {
92
+ case "scalar":
93
+ return cubicScalar(a as number, b as number, outTangent as number, inTangent as number, t, deltaTime);
94
+ case "vector3":
95
+ return [
96
+ cubicScalar((a as [number, number, number])[0], (b as [number, number, number])[0], (outTangent as [number, number, number])[0], (inTangent as [number, number, number])[0], t, deltaTime),
97
+ cubicScalar((a as [number, number, number])[1], (b as [number, number, number])[1], (outTangent as [number, number, number])[1], (inTangent as [number, number, number])[1], t, deltaTime),
98
+ cubicScalar((a as [number, number, number])[2], (b as [number, number, number])[2], (outTangent as [number, number, number])[2], (inTangent as [number, number, number])[2], t, deltaTime)
99
+ ];
100
+ case "quaternion":
101
+ return normalizeCubicQuaternion(a as [number, number, number, number], b as [number, number, number, number], outTangent as [number, number, number, number], inTangent as [number, number, number, number], t, deltaTime);
102
+ case "number-array":
103
+ return cubicNumberArray(a as readonly number[], b as readonly number[], outTangent as readonly number[], inTangent as readonly number[], t, deltaTime);
104
+ case "boolean":
105
+ case "string":
106
+ throw new Error(`CUBICSPLINE interpolation is not supported for ${type} animation tracks.`);
107
+ }
108
+ }
109
+
110
+ function cubicScalar(a: number, b: number, outTangent: number, inTangent: number, t: number, deltaTime: number): number {
111
+ const t2 = t * t;
112
+ const t3 = t2 * t;
113
+ return (
114
+ (2 * t3 - 3 * t2 + 1) * a +
115
+ (t3 - 2 * t2 + t) * deltaTime * outTangent +
116
+ (-2 * t3 + 3 * t2) * b +
117
+ (t3 - t2) * deltaTime * inTangent
118
+ );
119
+ }
120
+
121
+ function cubicNumberArray(
122
+ a: readonly number[],
123
+ b: readonly number[],
124
+ outTangent: readonly number[],
125
+ inTangent: readonly number[],
126
+ t: number,
127
+ deltaTime: number
128
+ ): readonly number[] {
129
+ if (a.length !== b.length || a.length !== outTangent.length || b.length !== inTangent.length) {
130
+ throw new Error("CUBICSPLINE number-array keyframes require matching value and tangent lengths.");
131
+ }
132
+ return a.map((value, index) => cubicScalar(value, b[index]!, outTangent[index]!, inTangent[index]!, t, deltaTime));
133
+ }
134
+
135
+ function normalizeCubicQuaternion(
136
+ a: readonly [number, number, number, number],
137
+ b: readonly [number, number, number, number],
138
+ outTangent: readonly [number, number, number, number],
139
+ inTangent: readonly [number, number, number, number],
140
+ t: number,
141
+ deltaTime: number
142
+ ): readonly [number, number, number, number] {
143
+ return slerpQuat(
144
+ [0, 0, 0, 1],
145
+ [
146
+ cubicScalar(a[0], b[0], outTangent[0], inTangent[0], t, deltaTime),
147
+ cubicScalar(a[1], b[1], outTangent[1], inTangent[1], t, deltaTime),
148
+ cubicScalar(a[2], b[2], outTangent[2], inTangent[2], t, deltaTime),
149
+ cubicScalar(a[3], b[3], outTangent[3], inTangent[3], t, deltaTime)
150
+ ],
151
+ 1
152
+ );
153
+ }
154
+
155
+ function interpolate(type: TrackValueType, a: AnimationValue, b: AnimationValue, t: number): AnimationValue {
156
+ switch (type) {
157
+ case "scalar":
158
+ return lerpNumber(a as number, b as number, t);
159
+ case "vector3":
160
+ return lerpVec3(a as [number, number, number], b as [number, number, number], t);
161
+ case "quaternion":
162
+ return slerpQuat(a as [number, number, number, number], b as [number, number, number, number], t);
163
+ case "number-array":
164
+ return interpolateNumberArray(a as readonly number[], b as readonly number[], t);
165
+ case "boolean":
166
+ case "string":
167
+ return t < 1 ? a : b;
168
+ }
169
+ }
170
+
171
+ function interpolateNumberArray(a: readonly number[], b: readonly number[], t: number): readonly number[] {
172
+ if (a.length !== b.length) {
173
+ throw new Error("number-array animation keyframes require matching lengths.");
174
+ }
175
+ return a.map((value, index) => lerpNumber(value, b[index]!, t));
176
+ }
@@ -0,0 +1,89 @@
1
+ export type BlendTreeChild<T> = {
2
+ readonly value: T;
3
+ readonly threshold: number;
4
+ };
5
+
6
+ export type BlendTree2DChild<T> = {
7
+ readonly value: T;
8
+ readonly position: readonly [number, number];
9
+ };
10
+
11
+ export type BlendTreeWeight<T> = {
12
+ readonly value: T;
13
+ readonly weight: number;
14
+ };
15
+
16
+ export class BlendTree1D<T> {
17
+ readonly children: readonly BlendTreeChild<T>[];
18
+
19
+ constructor(children: readonly BlendTreeChild<T>[]) {
20
+ if (children.length === 0) {
21
+ throw new Error("BlendTree1D requires at least one child.");
22
+ }
23
+ this.children = [...children].sort((a, b) => a.threshold - b.threshold);
24
+ }
25
+
26
+ weights(parameter: number): readonly BlendTreeWeight<T>[] {
27
+ if (!Number.isFinite(parameter)) {
28
+ throw new Error("Blend parameter must be finite.");
29
+ }
30
+ if (this.children.length === 1 || parameter <= this.children[0]!.threshold) {
31
+ return [{ value: this.children[0]!.value, weight: 1 }];
32
+ }
33
+ const last = this.children[this.children.length - 1]!;
34
+ if (parameter >= last.threshold) {
35
+ return [{ value: last.value, weight: 1 }];
36
+ }
37
+ for (let index = 0; index < this.children.length - 1; index += 1) {
38
+ const a = this.children[index]!;
39
+ const b = this.children[index + 1]!;
40
+ if (parameter >= a.threshold && parameter <= b.threshold) {
41
+ const span = b.threshold - a.threshold;
42
+ const t = span === 0 ? 0 : (parameter - a.threshold) / span;
43
+ return [{ value: a.value, weight: 1 - t }, { value: b.value, weight: t }];
44
+ }
45
+ }
46
+ return [{ value: last.value, weight: 1 }];
47
+ }
48
+ }
49
+
50
+ export class BlendTree2D<T> {
51
+ readonly children: readonly BlendTree2DChild<T>[];
52
+
53
+ constructor(children: readonly BlendTree2DChild<T>[]) {
54
+ if (children.length === 0) {
55
+ throw new Error("BlendTree2D requires at least one child.");
56
+ }
57
+ for (const child of children) {
58
+ if (!Number.isFinite(child.position[0]) || !Number.isFinite(child.position[1])) {
59
+ throw new Error("BlendTree2D child positions must be finite.");
60
+ }
61
+ }
62
+ this.children = [...children];
63
+ }
64
+
65
+ weights(parameter: readonly [number, number]): readonly BlendTreeWeight<T>[] {
66
+ if (!Number.isFinite(parameter[0]) || !Number.isFinite(parameter[1])) {
67
+ throw new Error("BlendTree2D parameters must be finite.");
68
+ }
69
+ if (this.children.length === 1) {
70
+ return [{ value: this.children[0]!.value, weight: 1 }];
71
+ }
72
+
73
+ const distances = this.children.map((child) => {
74
+ const dx = parameter[0] - child.position[0];
75
+ const dy = parameter[1] - child.position[1];
76
+ return Math.hypot(dx, dy);
77
+ });
78
+ const exactIndex = distances.findIndex((distance) => distance === 0);
79
+ if (exactIndex >= 0) {
80
+ return [{ value: this.children[exactIndex]!.value, weight: 1 }];
81
+ }
82
+
83
+ const inverseDistances = distances.map((distance) => 1 / distance);
84
+ const total = inverseDistances.reduce((sum, weight) => sum + weight, 0);
85
+ return this.children
86
+ .map((child, index) => ({ value: child.value, weight: inverseDistances[index]! / total }))
87
+ .filter((weight) => weight.weight > 0);
88
+ }
89
+ }
package/src/Bone.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { composeMat4, identityMat4, type Mat4, type Quat, type Vec3 } from "./Keyframe.js";
2
+
3
+ export type BoneDescriptor = {
4
+ readonly name: string;
5
+ readonly parentIndex: number;
6
+ readonly translation?: Vec3;
7
+ readonly rotation?: Quat;
8
+ readonly scale?: Vec3;
9
+ readonly inverseBindMatrix?: Mat4;
10
+ };
11
+
12
+ export class Bone {
13
+ readonly name: string;
14
+ readonly parentIndex: number;
15
+ translation: Vec3;
16
+ rotation: Quat;
17
+ scale: Vec3;
18
+ inverseBindMatrix: Mat4;
19
+
20
+ constructor(descriptor: BoneDescriptor) {
21
+ if (descriptor.name.trim().length === 0) {
22
+ throw new Error("Bone name cannot be empty.");
23
+ }
24
+ this.name = descriptor.name;
25
+ this.parentIndex = descriptor.parentIndex;
26
+ this.translation = descriptor.translation ?? [0, 0, 0];
27
+ this.rotation = descriptor.rotation ?? [0, 0, 0, 1];
28
+ this.scale = descriptor.scale ?? [1, 1, 1];
29
+ this.inverseBindMatrix = descriptor.inverseBindMatrix ?? identityMat4();
30
+ }
31
+
32
+ localMatrix(): Mat4 {
33
+ return composeMat4(this.translation, this.rotation, this.scale);
34
+ }
35
+ }
@@ -0,0 +1,25 @@
1
+ export interface CrowdAnimationAgent {
2
+ readonly id: string;
3
+ readonly clip: string;
4
+ readonly phase: number;
5
+ readonly speed: number;
6
+ }
7
+
8
+ export interface CrowdAnimationSample {
9
+ readonly id: string;
10
+ readonly clip: string;
11
+ readonly time: number;
12
+ }
13
+
14
+ export function sampleCrowdAnimation(agents: readonly CrowdAnimationAgent[], elapsedSeconds: number): readonly CrowdAnimationSample[] {
15
+ if (!Number.isFinite(elapsedSeconds) || elapsedSeconds < 0) {
16
+ throw new Error("Crowd animation elapsedSeconds must be finite and non-negative.");
17
+ }
18
+ return agents.map((agent) => {
19
+ if (!agent.id.trim() || !agent.clip.trim()) throw new Error("Crowd animation agents require id and clip.");
20
+ if (!Number.isFinite(agent.phase) || !Number.isFinite(agent.speed) || agent.speed < 0) {
21
+ throw new Error(`Crowd animation agent ${agent.id} has invalid phase or speed.`);
22
+ }
23
+ return { id: agent.id, clip: agent.clip, time: agent.phase + elapsedSeconds * agent.speed };
24
+ });
25
+ }
@@ -0,0 +1,30 @@
1
+ import type { AnimationValue } from "./Keyframe.js";
2
+
3
+ export type ECSAnimationComponent = Record<string, AnimationValue | undefined>;
4
+
5
+ export class ECSAnimationBridge {
6
+ private readonly components = new Map<string, ECSAnimationComponent>();
7
+
8
+ register(entityName: string, component: ECSAnimationComponent): void {
9
+ if (entityName.trim().length === 0) {
10
+ throw new Error("ECS animation target name cannot be empty.");
11
+ }
12
+ this.components.set(entityName, component);
13
+ }
14
+
15
+ unregister(entityName: string): void {
16
+ this.components.delete(entityName);
17
+ }
18
+
19
+ setAnimationValue(target: string, value: AnimationValue): void {
20
+ const [entityName, property] = target.split(".");
21
+ if (!entityName || !property) {
22
+ throw new Error(`Invalid ECS animation target ${target}.`);
23
+ }
24
+ const component = this.components.get(entityName);
25
+ if (!component) {
26
+ throw new Error(`Missing ECS animation target ${entityName}.`);
27
+ }
28
+ component[property] = Array.isArray(value) ? ([...value] as unknown as AnimationValue) : value;
29
+ }
30
+ }