reze-engine 0.30.2 → 0.31.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.
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/locomotion.d.ts +94 -0
- package/dist/locomotion.d.ts.map +1 -1
- package/dist/locomotion.js +332 -3
- package/dist/model.d.ts +23 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +59 -1
- package/dist/state-machine.d.ts +64 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +145 -0
- package/package.json +1 -1
- package/src/index.ts +5 -1
- package/src/locomotion.ts +430 -3
- package/src/model.ts +65 -1
- package/src/state-machine.ts +201 -0
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAA6C,MAAM,QAAQ,CAAA;AAEpF,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAI5C,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EAEjB,UAAU,EAQX,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/dist/model.js
CHANGED
|
@@ -106,6 +106,10 @@ export class Model {
|
|
|
106
106
|
// envelopes — the background keeps advancing and is what the fade-out returns to.
|
|
107
107
|
this.oneShot = null;
|
|
108
108
|
this.oneShotEntries = [];
|
|
109
|
+
/** Time-triggered clip callbacks (footsteps, skill timing). Fired by every
|
|
110
|
+
* playback path when a clip's cursor crosses the event time with weight. */
|
|
111
|
+
this.clipEvents = new Map();
|
|
112
|
+
this.entryEventPrev = new WeakMap();
|
|
109
113
|
// Blended pose: declarative N-clip mix (setBlendPose) or a running crossfade.
|
|
110
114
|
// Cursor caches are per clip so sampling several clips in one frame doesn't
|
|
111
115
|
// thrash the single-clip caches above; WeakMap so removed clips can collect.
|
|
@@ -1079,6 +1083,52 @@ export class Model {
|
|
|
1079
1083
|
getOneShot() {
|
|
1080
1084
|
return this.oneShot?.name ?? null;
|
|
1081
1085
|
}
|
|
1086
|
+
/** Fire `callback` whenever `clip`'s playback crosses `time` (seconds) with at
|
|
1087
|
+
* least `minWeight` influence (default 0.1) — on any path: blend entries,
|
|
1088
|
+
* one-shots, crossfades, or plain play. Loop wraps fire correctly; hard cursor
|
|
1089
|
+
* jumps may occasionally fire or skip (events are for sfx-grade timing, not
|
|
1090
|
+
* logic). Returns an unsubscribe function. */
|
|
1091
|
+
addClipEvent(clip, time, callback, options) {
|
|
1092
|
+
let list = this.clipEvents.get(clip);
|
|
1093
|
+
if (!list) {
|
|
1094
|
+
list = [];
|
|
1095
|
+
this.clipEvents.set(clip, list);
|
|
1096
|
+
}
|
|
1097
|
+
const def = { time, minWeight: options?.minWeight ?? 0.1, callback };
|
|
1098
|
+
list.push(def);
|
|
1099
|
+
return () => {
|
|
1100
|
+
const i = list.indexOf(def);
|
|
1101
|
+
if (i >= 0)
|
|
1102
|
+
list.splice(i, 1);
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
fireClipEvents(name, prev, now, weight) {
|
|
1106
|
+
const evs = this.clipEvents.get(name);
|
|
1107
|
+
if (!evs || evs.length === 0)
|
|
1108
|
+
return;
|
|
1109
|
+
const wrapped = now < prev;
|
|
1110
|
+
for (let i = 0; i < evs.length; i++) {
|
|
1111
|
+
const ev = evs[i];
|
|
1112
|
+
if (weight < ev.minWeight)
|
|
1113
|
+
continue;
|
|
1114
|
+
const hit = wrapped ? ev.time > prev || ev.time <= now : ev.time > prev && ev.time <= now;
|
|
1115
|
+
if (hit)
|
|
1116
|
+
ev.callback({ clip: name, time: ev.time, weight });
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/** Per-entry cursor memory for event crossing detection; keyed on entry object
|
|
1120
|
+
* identity (controllers reuse their arrays, so this stays warm). */
|
|
1121
|
+
trackEntryEvents(e) {
|
|
1122
|
+
const prev = this.entryEventPrev.get(e);
|
|
1123
|
+
if (prev === undefined) {
|
|
1124
|
+
this.entryEventPrev.set(e, { name: e.name, time: e.time });
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (prev.name === e.name && prev.time !== e.time)
|
|
1128
|
+
this.fireClipEvents(e.name, prev.time, e.time, e.weight);
|
|
1129
|
+
prev.name = e.name;
|
|
1130
|
+
prev.time = e.time;
|
|
1131
|
+
}
|
|
1082
1132
|
/** Fade from the currently playing clip (or from the rest pose when nothing plays)
|
|
1083
1133
|
* into `name` over `seconds`. The target starts at frame 0 and becomes the current
|
|
1084
1134
|
* clip immediately — progress, looping and the camera clock report the target for
|
|
@@ -1335,6 +1385,8 @@ export class Model {
|
|
|
1335
1385
|
continue;
|
|
1336
1386
|
const w = e.weight * norm;
|
|
1337
1387
|
const frame = e.time * FPS;
|
|
1388
|
+
if (this.clipEvents.size > 0)
|
|
1389
|
+
this.trackEntryEvents(e);
|
|
1338
1390
|
let cursors = this.blendBoneCursors.get(clip);
|
|
1339
1391
|
if (!cursors) {
|
|
1340
1392
|
cursors = new Map();
|
|
@@ -1526,6 +1578,8 @@ export class Model {
|
|
|
1526
1578
|
this.tweenTimeMs += deltaTime * 1000;
|
|
1527
1579
|
// Update all active tweens (rotations, translations, morphs)
|
|
1528
1580
|
const tweensChangedMorphs = this.updateTweens();
|
|
1581
|
+
const evWatch = this.clipEvents.size > 0 ? this.animationState.getCurrentAnimation() : null;
|
|
1582
|
+
const evPrevFrame = evWatch !== null ? this.animationState.getCurrentFrame() : 0;
|
|
1529
1583
|
this.animationState.update(deltaTime);
|
|
1530
1584
|
if (!this.clipApplySuspended) {
|
|
1531
1585
|
if (this.oneShot !== null) {
|
|
@@ -1539,8 +1593,12 @@ export class Model {
|
|
|
1539
1593
|
}
|
|
1540
1594
|
else {
|
|
1541
1595
|
const clip = this.animationState.getCurrentClip();
|
|
1542
|
-
if (clip !== null)
|
|
1596
|
+
if (clip !== null) {
|
|
1543
1597
|
this.applyPoseFromClip(clip, this.animationState.getCurrentFrame());
|
|
1598
|
+
if (evWatch !== null && evWatch === this.animationState.getCurrentAnimation()) {
|
|
1599
|
+
this.fireClipEvents(evWatch, evPrevFrame / FPS, this.animationState.getCurrentFrame() / FPS, 1);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1544
1602
|
}
|
|
1545
1603
|
}
|
|
1546
1604
|
// Constant per-bone offsets compose after every pose source, before the
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Model } from "./model";
|
|
2
|
+
import type { BlendEntry } from "./animation";
|
|
3
|
+
export interface AnimStateDef {
|
|
4
|
+
/** Clip name previously loaded on the model. Mutually exclusive with `entries`. */
|
|
5
|
+
clip?: string;
|
|
6
|
+
/** Loop the clip (default true). Non-loop states hold their last frame. */
|
|
7
|
+
loop?: boolean;
|
|
8
|
+
/** Clip playback speed multiplier (default 1). */
|
|
9
|
+
speed?: number;
|
|
10
|
+
/** Delegate pose producer — return this frame's blend entries (e.g. a
|
|
11
|
+
* LocomotionController with autoApply: false). Return null to contribute
|
|
12
|
+
* nothing (the pose relaxes toward rest per setBlendPose semantics). */
|
|
13
|
+
entries?: (dt: number) => BlendEntry[] | null;
|
|
14
|
+
onEnter?: (from: string | null) => void;
|
|
15
|
+
onExit?: (to: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface AnimTransitionDef {
|
|
18
|
+
/** Source state name, or "*" for any state (never fires into itself). */
|
|
19
|
+
from: string | "*";
|
|
20
|
+
to: string;
|
|
21
|
+
/** Condition, checked every update. Omitted = unconditional. */
|
|
22
|
+
when?: () => boolean;
|
|
23
|
+
/** Fire only once the state has been active this many seconds. On a clip
|
|
24
|
+
* state with NO `when` and NO `exitTime`, the transition fires when the
|
|
25
|
+
* (non-looping) clip approaches its end — the "skill finished, back to
|
|
26
|
+
* locomotion" pattern. */
|
|
27
|
+
exitTime?: number;
|
|
28
|
+
/** Crossfade seconds (default: machine's defaultFade). */
|
|
29
|
+
fade?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface StateMachineOptions {
|
|
32
|
+
initial: string;
|
|
33
|
+
/** Crossfade used when a transition does not specify one (default 0.25). */
|
|
34
|
+
defaultFade?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare class AnimationStateMachine {
|
|
37
|
+
private readonly model;
|
|
38
|
+
private readonly states;
|
|
39
|
+
private readonly transitions;
|
|
40
|
+
private readonly defaultFade;
|
|
41
|
+
private current;
|
|
42
|
+
/** Outgoing state during a crossfade — keeps playing while it fades. */
|
|
43
|
+
private fading;
|
|
44
|
+
private readonly merged;
|
|
45
|
+
constructor(model: Model, states: Record<string, AnimStateDef>, transitions: AnimTransitionDef[], options: StateMachineOptions);
|
|
46
|
+
get state(): string;
|
|
47
|
+
/** Seconds the current state has been active. */
|
|
48
|
+
get stateTime(): number;
|
|
49
|
+
/** Force a transition now, regardless of the transition table. */
|
|
50
|
+
go(to: string, fade?: number): void;
|
|
51
|
+
update(dt: number): void;
|
|
52
|
+
private transitionReady;
|
|
53
|
+
private begin;
|
|
54
|
+
/** This frame's entries for a state: clip states own a one-entry pose;
|
|
55
|
+
* delegate states produce their own. Returns null for "no pose". */
|
|
56
|
+
private produce;
|
|
57
|
+
private readonly inScratch;
|
|
58
|
+
private readonly outScratch;
|
|
59
|
+
/** Merge up to two entry lists scaled by their group weights into the stable
|
|
60
|
+
* scratch array and hand it to the model. */
|
|
61
|
+
private apply;
|
|
62
|
+
private clipDuration;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=state-machine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAK7C,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;6EAEyE;IACzE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,UAAU,EAAE,GAAG,IAAI,CAAA;IAC7C,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACvC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,OAAO,CAAA;IACpB;;;+BAG2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAQD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,OAAO,CAAC,OAAO,CAAa;IAC5B,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAyE;IAIvF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,mBAAmB;IAW9H,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,iDAAiD;IACjD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,kEAAkE;IAClE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAInC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAgCxB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,KAAK;IAYb;yEACqE;IACrE,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmD;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmD;IAE9E;kDAC8C;IAC9C,OAAO,CAAC,KAAK;IAyBb,OAAO,CAAC,YAAY;CAIrB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { easeInOut } from "./math";
|
|
2
|
+
const FPS = 30;
|
|
3
|
+
export class AnimationStateMachine {
|
|
4
|
+
constructor(model, states, transitions, options) {
|
|
5
|
+
/** Outgoing state during a crossfade — keeps playing while it fades. */
|
|
6
|
+
this.fading = null;
|
|
7
|
+
// Scratch: per-slot entry objects are reused so downstream caches (blend
|
|
8
|
+
// cursors, clip-event trackers) keyed on entry identity stay warm.
|
|
9
|
+
this.merged = [];
|
|
10
|
+
this.inScratch = [{ name: "", time: 0, weight: 1 }];
|
|
11
|
+
this.outScratch = [{ name: "", time: 0, weight: 1 }];
|
|
12
|
+
this.model = model;
|
|
13
|
+
this.states = states;
|
|
14
|
+
this.transitions = transitions;
|
|
15
|
+
this.defaultFade = options.defaultFade ?? 0.25;
|
|
16
|
+
const def = states[options.initial];
|
|
17
|
+
if (!def)
|
|
18
|
+
throw new Error(`Unknown initial state "${options.initial}"`);
|
|
19
|
+
this.current = { name: options.initial, def, time: 0 };
|
|
20
|
+
def.onEnter?.(null);
|
|
21
|
+
}
|
|
22
|
+
get state() {
|
|
23
|
+
return this.current.name;
|
|
24
|
+
}
|
|
25
|
+
/** Seconds the current state has been active. */
|
|
26
|
+
get stateTime() {
|
|
27
|
+
return this.current.time;
|
|
28
|
+
}
|
|
29
|
+
/** Force a transition now, regardless of the transition table. */
|
|
30
|
+
go(to, fade) {
|
|
31
|
+
this.begin(to, fade ?? this.defaultFade);
|
|
32
|
+
}
|
|
33
|
+
update(dt) {
|
|
34
|
+
this.current.time += dt;
|
|
35
|
+
// Transitions are not interruptible mid-fade (go() still is).
|
|
36
|
+
if (this.fading === null) {
|
|
37
|
+
for (const t of this.transitions) {
|
|
38
|
+
if (t.to === this.current.name)
|
|
39
|
+
continue;
|
|
40
|
+
if (t.from !== "*" && t.from !== this.current.name)
|
|
41
|
+
continue;
|
|
42
|
+
if (!this.transitionReady(t))
|
|
43
|
+
continue;
|
|
44
|
+
this.begin(t.to, t.fade ?? this.defaultFade);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const inEntries = this.produce(this.current, dt, this.inScratch);
|
|
49
|
+
if (this.fading !== null) {
|
|
50
|
+
const f = this.fading;
|
|
51
|
+
f.elapsed += dt;
|
|
52
|
+
if (f.elapsed >= f.duration) {
|
|
53
|
+
this.fading = null;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
f.state.time += dt;
|
|
57
|
+
const outEntries = this.produce(f.state, dt, this.outScratch);
|
|
58
|
+
const w = easeInOut(f.elapsed / f.duration);
|
|
59
|
+
this.apply(outEntries, 1 - w, inEntries, w);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.apply(inEntries, 1, null, 0);
|
|
64
|
+
}
|
|
65
|
+
transitionReady(t) {
|
|
66
|
+
const def = this.current.def;
|
|
67
|
+
if (t.exitTime !== undefined) {
|
|
68
|
+
if (this.current.time < t.exitTime)
|
|
69
|
+
return false;
|
|
70
|
+
return t.when ? t.when() : true;
|
|
71
|
+
}
|
|
72
|
+
if (t.when)
|
|
73
|
+
return t.when();
|
|
74
|
+
// Unconditional, no exitTime: on a non-looping clip state this means
|
|
75
|
+
// "when the clip is about to end" (start the fade so it lands at the end).
|
|
76
|
+
if (def.clip && def.loop === false) {
|
|
77
|
+
const dur = this.clipDuration(def.clip) / (def.speed ?? 1);
|
|
78
|
+
return this.current.time >= Math.max(0, dur - (t.fade ?? this.defaultFade));
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
begin(to, fade) {
|
|
83
|
+
const def = this.states[to];
|
|
84
|
+
if (!def)
|
|
85
|
+
throw new Error(`Unknown state "${to}"`);
|
|
86
|
+
this.current.def.onExit?.(to);
|
|
87
|
+
// go() during an existing fade drops the older outgoing state (v1: no
|
|
88
|
+
// three-way mixes) — the current state becomes the outgoing one.
|
|
89
|
+
this.fading = fade > 0 ? { state: this.current, elapsed: 0, duration: fade } : null;
|
|
90
|
+
const from = this.current.name;
|
|
91
|
+
this.current = { name: to, def, time: 0 };
|
|
92
|
+
def.onEnter?.(from);
|
|
93
|
+
}
|
|
94
|
+
/** This frame's entries for a state: clip states own a one-entry pose;
|
|
95
|
+
* delegate states produce their own. Returns null for "no pose". */
|
|
96
|
+
produce(state, dt, scratch) {
|
|
97
|
+
const def = state.def;
|
|
98
|
+
if (def.entries)
|
|
99
|
+
return def.entries(dt);
|
|
100
|
+
if (!def.clip)
|
|
101
|
+
return null;
|
|
102
|
+
const dur = this.clipDuration(def.clip);
|
|
103
|
+
let t = state.time * (def.speed ?? 1);
|
|
104
|
+
if (dur > 0)
|
|
105
|
+
t = def.loop === false ? Math.min(t, dur) : t % dur;
|
|
106
|
+
scratch[0].name = def.clip;
|
|
107
|
+
scratch[0].time = t;
|
|
108
|
+
scratch[0].weight = 1;
|
|
109
|
+
return scratch;
|
|
110
|
+
}
|
|
111
|
+
/** Merge up to two entry lists scaled by their group weights into the stable
|
|
112
|
+
* scratch array and hand it to the model. */
|
|
113
|
+
apply(a, wa, b, wb) {
|
|
114
|
+
let n = 0;
|
|
115
|
+
const put = (src, scale) => {
|
|
116
|
+
if (src === null || scale <= 0)
|
|
117
|
+
return;
|
|
118
|
+
for (const e of src) {
|
|
119
|
+
if (!(e.weight > 1e-6))
|
|
120
|
+
continue;
|
|
121
|
+
let slot = this.merged[n];
|
|
122
|
+
if (!slot) {
|
|
123
|
+
slot = { name: "", time: 0, weight: 0 };
|
|
124
|
+
this.merged[n] = slot;
|
|
125
|
+
}
|
|
126
|
+
slot.name = e.name;
|
|
127
|
+
slot.time = e.time;
|
|
128
|
+
slot.weight = e.weight * scale;
|
|
129
|
+
n++;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
// Order: incoming FIRST so slot identities stay stable for a given state
|
|
133
|
+
// across the fade's start/end (cursor + event caches key on the objects).
|
|
134
|
+
put(b, wb);
|
|
135
|
+
put(a, wa);
|
|
136
|
+
for (let i = n; i < this.merged.length; i++)
|
|
137
|
+
this.merged[i].weight = 0;
|
|
138
|
+
if (n > 0)
|
|
139
|
+
this.model.setBlendPose(this.merged);
|
|
140
|
+
}
|
|
141
|
+
clipDuration(name) {
|
|
142
|
+
const frames = this.model.getClip(name)?.frameCount ?? 0;
|
|
143
|
+
return frames > 1 ? (frames - 1) / FPS : 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ export { BODY_GRAPH } from "./graph/presets/body"
|
|
|
52
52
|
export { STOCKINGS_GRAPH } from "./graph/presets/stockings"
|
|
53
53
|
export { EYE_GRAPH } from "./graph/presets/eye"
|
|
54
54
|
export { FACE_GRAPH } from "./graph/presets/face"
|
|
55
|
-
export { Model } from "./model"
|
|
55
|
+
export { Model, type ClipEventInfo } from "./model"
|
|
56
56
|
export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math"
|
|
57
57
|
export type {
|
|
58
58
|
AnimationClip,
|
|
@@ -71,7 +71,11 @@ export {
|
|
|
71
71
|
type LocomotionOptions,
|
|
72
72
|
type LocomotionPose,
|
|
73
73
|
type StrafeClipEntry,
|
|
74
|
+
type TurnClipEntry,
|
|
75
|
+
type RunTurnClipEntry,
|
|
76
|
+
type StopClipEntry,
|
|
74
77
|
} from "./locomotion"
|
|
78
|
+
export { AnimationStateMachine, type AnimStateDef, type AnimTransitionDef, type StateMachineOptions } from "./state-machine"
|
|
75
79
|
export {
|
|
76
80
|
FPS,
|
|
77
81
|
bezierInterpolate,
|