@aura3d/animation 1.4.0 → 1.4.2

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 (46) hide show
  1. package/package.json +36 -3
  2. package/src/AnimationAction.ts +0 -166
  3. package/src/AnimationClip.ts +0 -63
  4. package/src/AnimationClipEvents.ts +0 -501
  5. package/src/AnimationClipRegistry.ts +0 -404
  6. package/src/AnimationController.ts +0 -1017
  7. package/src/AnimationEvents.ts +0 -34
  8. package/src/AnimationLayer.ts +0 -73
  9. package/src/AnimationMixer.ts +0 -402
  10. package/src/AnimationStateGraph.ts +0 -105
  11. package/src/AnimationStateMachine.ts +0 -265
  12. package/src/AnimationTrack.ts +0 -176
  13. package/src/BlendTree.ts +0 -89
  14. package/src/Bone.ts +0 -40
  15. package/src/CrowdAnimation.ts +0 -25
  16. package/src/ECSAnimationBridge.ts +0 -30
  17. package/src/FighterAnimationAdapter.ts +0 -124
  18. package/src/FootIk.ts +0 -251
  19. package/src/HumanoidBoneInference.ts +0 -323
  20. package/src/HumanoidRetargeting.ts +0 -700
  21. package/src/IK.ts +0 -117
  22. package/src/Inertialization.ts +0 -269
  23. package/src/Keyframe.ts +0 -159
  24. package/src/LocomotionController.ts +0 -155
  25. package/src/LocomotionKit.ts +0 -89
  26. package/src/MotionMatchingFixtures.ts +0 -254
  27. package/src/MotionQuality.ts +0 -182
  28. package/src/RigQuality.ts +0 -101
  29. package/src/RootMotion.ts +0 -124
  30. package/src/SceneAnimationBridge.ts +0 -80
  31. package/src/SecondaryAnimationFixtures.ts +0 -210
  32. package/src/Skeleton.ts +0 -78
  33. package/src/Skinning.ts +0 -18
  34. package/src/SpringBones.ts +0 -243
  35. package/src/browser-index.ts +0 -66
  36. package/src/index.ts +0 -66
  37. package/src/library/performanceStateGraph.ts +0 -173
  38. package/src/library/standardHumanoidClips.ts +0 -659
  39. package/src/threejs-compatibility/AnimationAction.ts +0 -27
  40. package/src/threejs-compatibility/AnimationClip.ts +0 -16
  41. package/src/threejs-compatibility/AnimationDiagnostics.ts +0 -42
  42. package/src/threejs-compatibility/AnimationMixer.ts +0 -24
  43. package/src/threejs-compatibility/MorphTargetMixer.ts +0 -16
  44. package/src/threejs-compatibility/Skeleton.ts +0 -9
  45. package/src/threejs-compatibility/SkinnedMesh.ts +0 -12
  46. package/src/threejs-compatibility/index.ts +0 -11
@@ -1,124 +0,0 @@
1
- // Shared fighter animation adapter: maps a combat state to a clip key + playback speed, and
2
- // computes deterministic transition weights on state change. Pure (no engine/render import) so both
3
- // the Aura Clash arena and the fighting-game template can reuse the same mapping instead of each
4
- // re-deriving it inline. Transitions default to the inertialized (critically-damped) weight curve;
5
- // the linear crossfade is retained as an explicit fallback.
6
-
7
- import { inertializedTransitionWeight, DEFAULT_INERTIALIZATION_HALF_LIFE } from "./Inertialization.js";
8
-
9
- export type FighterClipKey =
10
- | "idle"
11
- | "walk"
12
- | "run"
13
- | "air"
14
- | "down"
15
- | "guard"
16
- | "hurt"
17
- | "ko"
18
- | "light"
19
- | "heavy"
20
- | "special";
21
-
22
- export interface FighterAnimationStateInput {
23
- /** High-level action label from the combat sim. */
24
- readonly action: string;
25
- /** Whether the fighter is on the ground (airborne overrides locomotion). */
26
- readonly grounded: boolean;
27
- /** Active attack clip key, if mid-attack (overrides everything except KO). */
28
- readonly attackClipKey?: FighterClipKey | undefined;
29
- /** KO takes precedence over all other states. */
30
- readonly knockedOut?: boolean | undefined;
31
- }
32
-
33
- export interface FighterClipResolution {
34
- readonly clipKey: FighterClipKey;
35
- readonly speed: number;
36
- }
37
-
38
- export interface FighterAnimationSpeedTable {
39
- readonly light?: number;
40
- readonly heavy?: number;
41
- readonly special?: number;
42
- readonly run?: number;
43
- }
44
-
45
- const defaultSpeeds: Required<FighterAnimationSpeedTable> = { light: 1.45, heavy: 1.06, special: 0.94, run: 1.18 };
46
-
47
- const locomotionByAction: Readonly<Record<string, FighterClipKey>> = {
48
- idle: "idle",
49
- walk: "walk",
50
- run: "run",
51
- dash: "run",
52
- guard: "guard",
53
- block: "guard",
54
- down: "down",
55
- crouch: "down",
56
- hurt: "hurt",
57
- hit: "hurt"
58
- };
59
-
60
- /** Resolve the clip key + speed for a fighter state. Mirrors the Aura Clash arena cascade. */
61
- export function resolveFighterClip(input: FighterAnimationStateInput, speeds: FighterAnimationSpeedTable = {}): FighterClipResolution {
62
- const table = { ...defaultSpeeds, ...speeds };
63
- if (input.knockedOut) return { clipKey: "ko", speed: 1 };
64
- if (input.attackClipKey) {
65
- const speed = input.attackClipKey === "light" ? table.light : input.attackClipKey === "heavy" ? table.heavy : input.attackClipKey === "special" ? table.special : 1;
66
- return { clipKey: input.attackClipKey, speed };
67
- }
68
- if (!input.grounded) return { clipKey: "air", speed: 1 };
69
- const action = input.action.toLowerCase();
70
- const clipKey = locomotionByAction[action] ?? "idle";
71
- const speed = clipKey === "run" ? table.run : 1;
72
- return { clipKey, speed };
73
- }
74
-
75
- export interface FighterCrossfade<TClip extends string = string> {
76
- readonly from: TClip;
77
- readonly to: TClip;
78
- readonly weights: readonly [number, number];
79
- readonly done: boolean;
80
- }
81
-
82
- /**
83
- * Deterministic crossfade weights from a previous clip to the current clip over a transition
84
- * window. `elapsed`/`duration` are in seconds. Returns clamped [fromWeight, toWeight] summing to 1.
85
- * Generic over clip identifier so callers using clip keys OR raw clip names can both use it.
86
- */
87
- export function fighterCrossfadeWeights<TClip extends string = string>(from: TClip, to: TClip, elapsed: number, duration: number): FighterCrossfade<TClip> {
88
- if (from === to || duration <= 0) {
89
- return { from, to, weights: [0, 1], done: true };
90
- }
91
- const t = Math.max(0, Math.min(1, elapsed / duration));
92
- return { from, to, weights: [1 - t, t], done: t >= 1 };
93
- }
94
-
95
- /**
96
- * Inertialized transition weights from a previous clip to the current clip. This is the **default**
97
- * fighter transition: the source weight follows a critically-damped decay (zero initial slope,
98
- * momentum-preserving) instead of the straight linear ramp of {@link fighterCrossfadeWeights}, so
99
- * move swaps carry through smoothly rather than dissolving. Weights still sum to 1, output is
100
- * deterministic (pure function of `elapsed`/`duration`/`halfLife`), so deterministic combat replay
101
- * is unaffected. `halfLife` defaults to ~40% of the transition window so the source has effectively
102
- * faded by the time the window closes; the linear path remains available as a fallback.
103
- */
104
- export function fighterInertializedWeights<TClip extends string = string>(
105
- from: TClip,
106
- to: TClip,
107
- elapsed: number,
108
- duration: number,
109
- halfLife?: number
110
- ): FighterCrossfade<TClip> {
111
- if (from === to || duration <= 0) {
112
- return { from, to, weights: [0, 1], done: true };
113
- }
114
- const done = elapsed >= duration;
115
- if (done) {
116
- return { from, to, weights: [0, 1], done: true };
117
- }
118
- const life = halfLife ?? Math.max(1e-4, duration * 0.4);
119
- const fromWeight = inertializedTransitionWeight(Math.max(0, elapsed), life);
120
- return { from, to, weights: [fromWeight, 1 - fromWeight], done: false };
121
- }
122
-
123
- /** Default transition half-life re-exported for callers that want to align their own timing. */
124
- export { DEFAULT_INERTIALIZATION_HALF_LIFE };
package/src/FootIk.ts DELETED
@@ -1,251 +0,0 @@
1
- // Runtime foot IK + foot-lock, built on the existing two-bone solver (`solveTwoBoneIk`) plus a
2
- // ground query (a downward ray). This is "wire up what we already have" — no new solver. It removes
3
- // foot sliding (foot-lock holds a planted foot in world space during stance and releases it on
4
- // lift) and grounds characters on uneven terrain (each ankle is pulled to the ground height under
5
- // it, the hip drops so the lower foot can reach).
6
- //
7
- // The animation package stays dependency-free of @aura3d/physics: callers inject a `GroundRaycaster`
8
- // (the physics package's `groundHeightRaycaster` adapter satisfies this interface, or use the
9
- // analytic `createHeightFieldGround` here). Pure and deterministic: given the same pose stream and
10
- // ground, the solved feet and lock state are identical every run.
11
-
12
- import { solveTwoBoneIk, type TwoBoneIkResult } from "./IK.js";
13
- import type { Vec3 } from "./Keyframe.js";
14
- // `FootIkSample` is the single source-of-truth data shape, defined in (and exported from)
15
- // SecondaryAnimationFixtures; the runtime here produces that exact shape.
16
- import type { FootIkSample } from "./SecondaryAnimationFixtures.js";
17
-
18
- /** Result of a downward ground query: where the ground is and its surface normal. */
19
- export interface GroundSample {
20
- readonly point: Vec3;
21
- readonly normal: Vec3;
22
- readonly distance: number;
23
- }
24
-
25
- /** Minimal ground query the rig needs: cast a downward ray and report the first ground hit. */
26
- export interface GroundRaycaster {
27
- raycastDown(origin: Vec3, maxDistance: number): GroundSample | undefined;
28
- }
29
-
30
- /**
31
- * Analytic ground query from a height function — deterministic and physics-free, ideal for tests
32
- * and procedural terrain. `heightAt` returns the ground height (and optional normal) at an (x, z).
33
- */
34
- export function createHeightFieldGround(heightAt: (x: number, z: number) => { height: number; normal?: Vec3 }): GroundRaycaster {
35
- return {
36
- raycastDown(origin: Vec3, maxDistance: number): GroundSample | undefined {
37
- const { height, normal } = heightAt(origin[0], origin[2]);
38
- const distance = origin[1] - height;
39
- if (distance < 0 || distance > maxDistance) return undefined;
40
- return { point: [origin[0], height, origin[2]], normal: normal ?? [0, 1, 0], distance };
41
- }
42
- };
43
- }
44
-
45
- /** One leg: the hip (root), knee (mid), ankle (end), and an optional pole/knee hint. */
46
- export interface FootLegInput {
47
- readonly side: "left" | "right";
48
- readonly hip: Vec3;
49
- readonly knee: Vec3;
50
- readonly ankle: Vec3;
51
- readonly pole?: Vec3;
52
- }
53
-
54
- export interface FootIkRigOptions {
55
- readonly legs: readonly FootLegInput[];
56
- readonly raycaster: GroundRaycaster;
57
- /** Height the ankle joint sits above the ground contact point (default 0.035). */
58
- readonly ankleHeight?: number;
59
- /** How far above the ankle to start the downward ray (default 0.6). */
60
- readonly rayStartHeight?: number;
61
- /** Max ray distance (default 2). */
62
- readonly maxRayDistance?: number;
63
- /** Foot is in stance (locks) when its height above the ground target is ≤ this (default 0.02). */
64
- readonly plantThreshold?: number;
65
- /** Hip-drop factor applied to the deepest required correction (default 0.72, matches fixture). */
66
- readonly hipDropFactor?: number;
67
- }
68
-
69
- export interface SolvedLeg {
70
- readonly side: "left" | "right";
71
- readonly sample: FootIkSample;
72
- /** Solved joint chain (write these back onto the skeleton). */
73
- readonly hip: Vec3;
74
- readonly knee: Vec3;
75
- readonly ankle: Vec3;
76
- /** True while the foot is locked to a world position (stance). */
77
- readonly locked: boolean;
78
- readonly reached: boolean;
79
- }
80
-
81
- export interface FootIkSolveResult {
82
- readonly feet: readonly SolvedLeg[];
83
- /** Vertical hip offset (≤ 0) so the lower planted foot can reach the ground. */
84
- readonly hipOffset: number;
85
- readonly groundedFeet: number;
86
- readonly averageTargetError: number;
87
- }
88
-
89
- interface LegLockState {
90
- locked: boolean;
91
- lockedPosition: Vec3;
92
- }
93
-
94
- export interface FootIkRig {
95
- /**
96
- * Solve foot placement for the current pose. Pass updated leg transforms (e.g. sampled from the
97
- * animation) via `pose.legs`, or omit to reuse the legs the rig was created with. Optionally
98
- * override the ground query for this frame.
99
- */
100
- solveFootPlacement(pose?: { legs?: readonly FootLegInput[] }, groundQuery?: GroundRaycaster): FootIkSolveResult;
101
- /** Whether a given side is currently foot-locked. */
102
- isLocked(side: "left" | "right"): boolean;
103
- /** Clear all foot-lock state (e.g. on teleport / respawn). */
104
- reset(): void;
105
- }
106
-
107
- /** Create a foot-IK rig. Foot-lock state persists across `solveFootPlacement` calls. */
108
- export function createFootIkRig(options: FootIkRigOptions): FootIkRig {
109
- const ankleHeight = options.ankleHeight ?? 0.035;
110
- const rayStartHeight = options.rayStartHeight ?? 0.6;
111
- const maxRayDistance = options.maxRayDistance ?? 2;
112
- const plantThreshold = options.plantThreshold ?? 0.02;
113
- const hipDropFactor = options.hipDropFactor ?? 0.72;
114
- let legs = options.legs;
115
- const locks = new Map<"left" | "right", LegLockState>();
116
-
117
- function lockFor(side: "left" | "right"): LegLockState {
118
- let state = locks.get(side);
119
- if (!state) {
120
- state = { locked: false, lockedPosition: [0, 0, 0] };
121
- locks.set(side, state);
122
- }
123
- return state;
124
- }
125
-
126
- return {
127
- solveFootPlacement(pose, groundQuery) {
128
- const raycaster = groundQuery ?? options.raycaster;
129
- if (pose?.legs) legs = pose.legs;
130
- const solved: SolvedLeg[] = [];
131
- let deepestCorrection = 0;
132
- for (const leg of legs) {
133
- const lock = lockFor(leg.side);
134
- const result = solveLeg(leg, lock, raycaster, { ankleHeight, rayStartHeight, maxRayDistance, plantThreshold });
135
- solved.push(result);
136
- deepestCorrection = Math.max(deepestCorrection, Math.max(0, result.sample.verticalCorrection));
137
- }
138
- const hipOffset = round(-deepestCorrection * hipDropFactor);
139
- const groundedFeet = solved.filter((leg) => leg.sample.grounded).length;
140
- const averageTargetError = solved.length === 0
141
- ? 0
142
- : round(solved.reduce((sum, leg) => sum + leg.sample.targetError, 0) / solved.length);
143
- return { feet: solved, hipOffset, groundedFeet, averageTargetError };
144
- },
145
- isLocked(side) {
146
- return locks.get(side)?.locked ?? false;
147
- },
148
- reset() {
149
- locks.clear();
150
- }
151
- };
152
- }
153
-
154
- interface SolveParams {
155
- readonly ankleHeight: number;
156
- readonly rayStartHeight: number;
157
- readonly maxRayDistance: number;
158
- readonly plantThreshold: number;
159
- }
160
-
161
- function solveLeg(leg: FootLegInput, lock: LegLockState, raycaster: GroundRaycaster, params: SolveParams): SolvedLeg {
162
- const sourceAnkle = leg.ankle;
163
- const ground = raycaster.raycastDown(
164
- [sourceAnkle[0], sourceAnkle[1] + params.rayStartHeight, sourceAnkle[2]],
165
- params.rayStartHeight + params.maxRayDistance
166
- );
167
-
168
- if (!ground) {
169
- // No ground under the foot (airborne / off the edge): release the lock and pass the pose through.
170
- lock.locked = false;
171
- return {
172
- side: leg.side,
173
- sample: {
174
- side: leg.side,
175
- sourceFoot: sourceAnkle,
176
- plantedFoot: sourceAnkle,
177
- groundNormal: [0, 1, 0],
178
- grounded: false,
179
- verticalCorrection: 0,
180
- targetError: 0
181
- },
182
- hip: leg.hip,
183
- knee: leg.knee,
184
- ankle: sourceAnkle,
185
- locked: false,
186
- reached: true
187
- };
188
- }
189
-
190
- const groundTargetY = ground.point[1] + params.ankleHeight;
191
- const heightAboveGround = sourceAnkle[1] - groundTargetY;
192
- const inStance = heightAboveGround <= params.plantThreshold;
193
-
194
- let target: Vec3;
195
- if (inStance) {
196
- if (!lock.locked) {
197
- // Just entered stance: plant the foot at the ground point under it.
198
- lock.locked = true;
199
- lock.lockedPosition = [sourceAnkle[0], groundTargetY, sourceAnkle[2]];
200
- }
201
- // Hold the locked world position so the foot does not slide while it should be stationary.
202
- target = lock.lockedPosition;
203
- } else {
204
- // Swing phase: release the lock; the foot follows above the ground under it.
205
- lock.locked = false;
206
- target = [sourceAnkle[0], Math.max(sourceAnkle[1], groundTargetY), sourceAnkle[2]];
207
- }
208
-
209
- // Blend IK in fully during stance, fade out across the swing so the leg returns to the clip pose.
210
- const weight = inStance ? 1 : clamp(1 - heightAboveGround / 0.25, 0, 1);
211
- const solved: TwoBoneIkResult = solveTwoBoneIk({
212
- root: leg.hip,
213
- mid: leg.knee,
214
- end: leg.ankle,
215
- target,
216
- pole: leg.pole,
217
- weight
218
- });
219
-
220
- const verticalCorrection = round(sourceAnkle[1] - target[1]);
221
- const targetError = round(solved.endDistanceToTarget);
222
- return {
223
- side: leg.side,
224
- sample: {
225
- side: leg.side,
226
- sourceFoot: roundVec(sourceAnkle),
227
- plantedFoot: roundVec(target),
228
- groundNormal: roundVec(ground.normal),
229
- grounded: inStance,
230
- verticalCorrection,
231
- targetError
232
- },
233
- hip: leg.hip,
234
- knee: roundVec(solved.mid),
235
- ankle: roundVec(solved.end),
236
- locked: lock.locked,
237
- reached: solved.reached
238
- };
239
- }
240
-
241
- function clamp(value: number, min: number, max: number): number {
242
- return Math.max(min, Math.min(max, value));
243
- }
244
-
245
- function round(value: number): number {
246
- return Number(value.toFixed(4));
247
- }
248
-
249
- function roundVec(value: Vec3): Vec3 {
250
- return [round(value[0]), round(value[1]), round(value[2])];
251
- }