@holoscript/engine 6.0.3 → 6.0.4
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/AutoMesher-CK47F6AV.js +17 -0
- package/dist/GPUBuffers-2LHBCD7X.js +9 -0
- package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
- package/dist/animation/index.cjs +38 -38
- package/dist/animation/index.d.cts +1 -1
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.js +1 -1
- package/dist/audio/index.cjs +16 -6
- package/dist/audio/index.d.cts +1 -1
- package/dist/audio/index.d.ts +1 -1
- package/dist/audio/index.js +1 -1
- package/dist/camera/index.cjs +23 -23
- package/dist/camera/index.d.cts +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/character/index.cjs +6 -4
- package/dist/character/index.js +1 -1
- package/dist/choreography/index.cjs +1194 -0
- package/dist/choreography/index.d.cts +687 -0
- package/dist/choreography/index.d.ts +687 -0
- package/dist/choreography/index.js +1156 -0
- package/dist/chunk-2CSNRI2N.js +217 -0
- package/dist/chunk-33T2WINR.js +266 -0
- package/dist/chunk-35R73OFM.js +1257 -0
- package/dist/chunk-4MMDSUNP.js +1256 -0
- package/dist/chunk-5V6HOU72.js +319 -0
- package/dist/chunk-6QOP6PYF.js +1038 -0
- package/dist/chunk-7KMJVHIL.js +8944 -0
- package/dist/chunk-7VPUC62U.js +1106 -0
- package/dist/chunk-A2Y6RCAT.js +1878 -0
- package/dist/chunk-AHM42MK6.js +8944 -0
- package/dist/chunk-BL7IDTHE.js +218 -0
- package/dist/chunk-CITOMSWL.js +10462 -0
- package/dist/chunk-CXDPKW2K.js +8944 -0
- package/dist/chunk-CXZPLD4S.js +223 -0
- package/dist/chunk-CZYJE7IH.js +5169 -0
- package/dist/chunk-D2OP7YC7.js +6325 -0
- package/dist/chunk-EDRVQHUU.js +1544 -0
- package/dist/chunk-EJSLOOW2.js +3589 -0
- package/dist/chunk-F53SFGW5.js +1878 -0
- package/dist/chunk-HCFPELPY.js +919 -0
- package/dist/chunk-HNEE36PY.js +93 -0
- package/dist/chunk-HYXNV36F.js +1256 -0
- package/dist/chunk-IB7KHVFY.js +821 -0
- package/dist/chunk-IBBO7YYG.js +690 -0
- package/dist/chunk-ILIBGINU.js +5470 -0
- package/dist/chunk-IS4MHLKN.js +5479 -0
- package/dist/chunk-JT2PFKWD.js +5479 -0
- package/dist/chunk-K4CUB4NY.js +1038 -0
- package/dist/chunk-KATDQXRJ.js +10462 -0
- package/dist/chunk-KBQE6ZFJ.js +8944 -0
- package/dist/chunk-KBVD5K7E.js +560 -0
- package/dist/chunk-KCDPVQRY.js +4088 -0
- package/dist/chunk-KN4QJPKN.js +8944 -0
- package/dist/chunk-KWJ3ROSI.js +8944 -0
- package/dist/chunk-L45VF6DD.js +919 -0
- package/dist/chunk-LY4T37YK.js +307 -0
- package/dist/chunk-MDN5WZXA.js +1544 -0
- package/dist/chunk-MGCDP6VU.js +928 -0
- package/dist/chunk-NCX7X6G2.js +8681 -0
- package/dist/chunk-OF54BPVD.js +913 -0
- package/dist/chunk-OWSN2Q3Q.js +690 -0
- package/dist/chunk-PRRB5TTA.js +406 -0
- package/dist/chunk-PXWVQF76.js +4086 -0
- package/dist/chunk-PYCOIDT2.js +812 -0
- package/dist/chunk-PZCSADOV.js +928 -0
- package/dist/chunk-Q2XBVS2K.js +1038 -0
- package/dist/chunk-QDZRXWN5.js +1776 -0
- package/dist/chunk-RNWOZ6WQ.js +913 -0
- package/dist/chunk-ROLFT4CJ.js +1693 -0
- package/dist/chunk-SLTJRZ2N.js +266 -0
- package/dist/chunk-SRUS5XSU.js +4088 -0
- package/dist/chunk-TKCA3WZ5.js +5409 -0
- package/dist/chunk-TNRMXYI2.js +1650 -0
- package/dist/chunk-TQB3GJGM.js +9763 -0
- package/dist/chunk-TUFGXG6K.js +510 -0
- package/dist/chunk-U6KMTGQJ.js +632 -0
- package/dist/chunk-VMGJQST6.js +8681 -0
- package/dist/chunk-X4F4TCG4.js +5470 -0
- package/dist/chunk-ZIFROE75.js +1544 -0
- package/dist/chunk-ZIJQYHSQ.js +1204 -0
- package/dist/combat/index.cjs +4 -4
- package/dist/combat/index.d.cts +1 -1
- package/dist/combat/index.d.ts +1 -1
- package/dist/combat/index.js +1 -1
- package/dist/ecs/index.cjs +1 -1
- package/dist/ecs/index.js +1 -1
- package/dist/environment/index.cjs +14 -14
- package/dist/environment/index.d.cts +1 -1
- package/dist/environment/index.d.ts +1 -1
- package/dist/environment/index.js +1 -1
- package/dist/gpu/index.cjs +4810 -0
- package/dist/gpu/index.js +3714 -0
- package/dist/hologram/index.cjs +27 -1
- package/dist/hologram/index.js +1 -1
- package/dist/index-B2PIsAmR.d.cts +2180 -0
- package/dist/index-B2PIsAmR.d.ts +2180 -0
- package/dist/index-BHySEPX7.d.cts +2921 -0
- package/dist/index-BJV21zuy.d.cts +341 -0
- package/dist/index-BJV21zuy.d.ts +341 -0
- package/dist/index-BQutTphC.d.cts +790 -0
- package/dist/index-ByIq2XrS.d.cts +3910 -0
- package/dist/index-BysHjDSO.d.cts +224 -0
- package/dist/index-BysHjDSO.d.ts +224 -0
- package/dist/index-CKwAJGck.d.ts +455 -0
- package/dist/index-CUl3QstQ.d.cts +3006 -0
- package/dist/index-CUl3QstQ.d.ts +3006 -0
- package/dist/index-CmYtNiI-.d.cts +953 -0
- package/dist/index-CmYtNiI-.d.ts +953 -0
- package/dist/index-CnRzWxi_.d.cts +522 -0
- package/dist/index-CnRzWxi_.d.ts +522 -0
- package/dist/index-CwRWbSC7.d.ts +2921 -0
- package/dist/index-CxKIBstO.d.ts +790 -0
- package/dist/index-DJ6-R8vh.d.cts +455 -0
- package/dist/index-DQKisbcI.d.cts +4968 -0
- package/dist/index-DQKisbcI.d.ts +4968 -0
- package/dist/index-DRT2zJez.d.ts +3910 -0
- package/dist/index-DfNLiAka.d.cts +192 -0
- package/dist/index-DfNLiAka.d.ts +192 -0
- package/dist/index-nMvkoRm8.d.cts +405 -0
- package/dist/index-nMvkoRm8.d.ts +405 -0
- package/dist/index-s9yOFU37.d.cts +604 -0
- package/dist/index-s9yOFU37.d.ts +604 -0
- package/dist/index.cjs +22966 -6960
- package/dist/index.d.cts +864 -20
- package/dist/index.d.ts +864 -20
- package/dist/index.js +3062 -48
- package/dist/input/index.cjs +1 -1
- package/dist/input/index.js +1 -1
- package/dist/orbital/index.cjs +3 -3
- package/dist/orbital/index.d.cts +1 -1
- package/dist/orbital/index.d.ts +1 -1
- package/dist/orbital/index.js +1 -1
- package/dist/particles/index.cjs +16 -16
- package/dist/particles/index.d.cts +1 -1
- package/dist/particles/index.d.ts +1 -1
- package/dist/particles/index.js +1 -1
- package/dist/physics/index.cjs +2377 -21
- package/dist/physics/index.d.cts +1 -1
- package/dist/physics/index.d.ts +1 -1
- package/dist/physics/index.js +35 -1
- package/dist/postfx/index.cjs +3491 -0
- package/dist/postfx/index.js +93 -0
- package/dist/procedural/index.cjs +1 -1
- package/dist/procedural/index.js +1 -1
- package/dist/puppeteer-5VF6KDVO.js +52197 -0
- package/dist/puppeteer-IZVZ3SG4.js +52197 -0
- package/dist/rendering/index.cjs +33 -32
- package/dist/rendering/index.d.cts +1 -1
- package/dist/rendering/index.d.ts +1 -1
- package/dist/rendering/index.js +8 -6
- package/dist/runtime/index.cjs +23 -13
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +8 -6
- package/dist/runtime/protocols/index.cjs +349 -0
- package/dist/runtime/protocols/index.js +15 -0
- package/dist/scene/index.cjs +8 -8
- package/dist/scene/index.d.cts +1 -1
- package/dist/scene/index.d.ts +1 -1
- package/dist/scene/index.js +1 -1
- package/dist/shader/index.cjs +3087 -0
- package/dist/shader/index.js +3044 -0
- package/dist/simulation/index.cjs +10680 -0
- package/dist/simulation/index.d.cts +3 -0
- package/dist/simulation/index.d.ts +3 -0
- package/dist/simulation/index.js +307 -0
- package/dist/spatial/index.cjs +2443 -0
- package/dist/spatial/index.d.cts +1545 -0
- package/dist/spatial/index.d.ts +1545 -0
- package/dist/spatial/index.js +2400 -0
- package/dist/terrain/index.cjs +1 -1
- package/dist/terrain/index.d.cts +1 -1
- package/dist/terrain/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/transformers.node-4NKAPD5U.js +45620 -0
- package/dist/vm/index.cjs +7 -8
- package/dist/vm/index.d.cts +1 -1
- package/dist/vm/index.d.ts +1 -1
- package/dist/vm/index.js +1 -1
- package/dist/vm-bridge/index.cjs +2 -2
- package/dist/vm-bridge/index.d.cts +2 -2
- package/dist/vm-bridge/index.d.ts +2 -2
- package/dist/vm-bridge/index.js +1 -1
- package/dist/vr/index.cjs +6 -6
- package/dist/vr/index.js +1 -1
- package/dist/world/index.cjs +3 -3
- package/dist/world/index.d.cts +1 -1
- package/dist/world/index.d.ts +1 -1
- package/dist/world/index.js +1 -1
- package/package.json +53 -21
- package/LICENSE +0 -21
|
@@ -0,0 +1,1878 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-AKLW2MUS.js";
|
|
4
|
+
|
|
5
|
+
// src/animation/index.ts
|
|
6
|
+
var animation_exports = {};
|
|
7
|
+
__export(animation_exports, {
|
|
8
|
+
AnimClip: () => AnimClip,
|
|
9
|
+
AnimationEngine: () => AnimationEngine,
|
|
10
|
+
AnimationGraph: () => AnimationGraph,
|
|
11
|
+
AnimationTransitionSystem: () => AnimationTransitionSystem,
|
|
12
|
+
AvatarController: () => AvatarController,
|
|
13
|
+
BoneSystem: () => BoneSystem,
|
|
14
|
+
CutsceneBuilder: () => CutsceneBuilder,
|
|
15
|
+
CutsceneTimeline: () => CutsceneTimeline,
|
|
16
|
+
Easing: () => Easing,
|
|
17
|
+
IKSolver: () => IKSolver,
|
|
18
|
+
MorphTargetSystem: () => MorphTargetSystem,
|
|
19
|
+
SkeletalBlender: () => SkeletalBlender,
|
|
20
|
+
SpringAnimator: () => SpringAnimator,
|
|
21
|
+
SpringPresets: () => SpringPresets,
|
|
22
|
+
Timeline: () => Timeline,
|
|
23
|
+
TransitionSystem: () => TransitionSystem,
|
|
24
|
+
Vec3SpringAnimator: () => Vec3SpringAnimator,
|
|
25
|
+
animationTraitHandler: () => animationTraitHandler,
|
|
26
|
+
getSharedAnimationEngine: () => getSharedAnimationEngine
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/animation/AnimationClip.ts
|
|
30
|
+
var AnimClip = class {
|
|
31
|
+
id;
|
|
32
|
+
name;
|
|
33
|
+
tracks = [];
|
|
34
|
+
events = [];
|
|
35
|
+
_duration = 0;
|
|
36
|
+
loop = false;
|
|
37
|
+
speed = 1;
|
|
38
|
+
wrapMode = "once";
|
|
39
|
+
constructor(id, name, duration) {
|
|
40
|
+
this.id = id;
|
|
41
|
+
this.name = name;
|
|
42
|
+
this._duration = duration;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Configuration
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
setLoop(loop) {
|
|
48
|
+
this.loop = loop;
|
|
49
|
+
this.wrapMode = loop ? "loop" : "once";
|
|
50
|
+
}
|
|
51
|
+
isLooping() {
|
|
52
|
+
return this.loop;
|
|
53
|
+
}
|
|
54
|
+
setSpeed(speed) {
|
|
55
|
+
this.speed = Math.max(0.01, speed);
|
|
56
|
+
}
|
|
57
|
+
getSpeed() {
|
|
58
|
+
return this.speed;
|
|
59
|
+
}
|
|
60
|
+
setWrapMode(mode) {
|
|
61
|
+
this.wrapMode = mode;
|
|
62
|
+
}
|
|
63
|
+
getWrapMode() {
|
|
64
|
+
return this.wrapMode;
|
|
65
|
+
}
|
|
66
|
+
getDuration() {
|
|
67
|
+
return this._duration;
|
|
68
|
+
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Tracks
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
addTrack(track) {
|
|
73
|
+
this.tracks.push(track);
|
|
74
|
+
for (const kf of track.keyframes) {
|
|
75
|
+
if (kf.time > this._duration) this._duration = kf.time;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
getTrack(id) {
|
|
79
|
+
return this.tracks.find((t) => t.id === id);
|
|
80
|
+
}
|
|
81
|
+
getTracks() {
|
|
82
|
+
return [...this.tracks];
|
|
83
|
+
}
|
|
84
|
+
getTrackCount() {
|
|
85
|
+
return this.tracks.length;
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Events
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
addEvent(time, name, data = {}) {
|
|
91
|
+
this.events.push({ time, name, data });
|
|
92
|
+
this.events.sort((a, b) => a.time - b.time);
|
|
93
|
+
}
|
|
94
|
+
getEventsInRange(fromTime, toTime) {
|
|
95
|
+
return this.events.filter((e) => e.time >= fromTime && e.time < toTime);
|
|
96
|
+
}
|
|
97
|
+
getEvents() {
|
|
98
|
+
return [...this.events];
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Sampling
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
sample(time) {
|
|
104
|
+
const wrapped = this.wrapTime(time);
|
|
105
|
+
const result = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const track of this.tracks) {
|
|
107
|
+
const value = this.sampleTrack(track, wrapped);
|
|
108
|
+
const key = track.component ? `${track.targetPath}.${track.property}.${track.component}` : `${track.targetPath}.${track.property}`;
|
|
109
|
+
result.set(key, value);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
sampleTrack(track, time) {
|
|
114
|
+
const kfs = track.keyframes;
|
|
115
|
+
if (kfs.length === 0) return 0;
|
|
116
|
+
if (kfs.length === 1) return typeof kfs[0].value === "number" ? kfs[0].value : 0;
|
|
117
|
+
let i = 0;
|
|
118
|
+
for (; i < kfs.length - 1; i++) {
|
|
119
|
+
if (time < kfs[i + 1].time) break;
|
|
120
|
+
}
|
|
121
|
+
if (i >= kfs.length - 1) i = kfs.length - 2;
|
|
122
|
+
const k0 = kfs[i];
|
|
123
|
+
const k1 = kfs[i + 1];
|
|
124
|
+
const dt = k1.time - k0.time;
|
|
125
|
+
const t = dt > 0 ? (time - k0.time) / dt : 0;
|
|
126
|
+
const v0 = typeof k0.value === "number" ? k0.value : 0;
|
|
127
|
+
const v1 = typeof k1.value === "number" ? k1.value : 0;
|
|
128
|
+
switch (track.interpolation) {
|
|
129
|
+
case "step":
|
|
130
|
+
return v0;
|
|
131
|
+
case "linear":
|
|
132
|
+
return v0 + (v1 - v0) * t;
|
|
133
|
+
case "cubic": {
|
|
134
|
+
const m0 = k0.outTangent ?? 0;
|
|
135
|
+
const m1 = k1.inTangent ?? 0;
|
|
136
|
+
const t2 = t * t, t3 = t2 * t;
|
|
137
|
+
return (2 * t3 - 3 * t2 + 1) * v0 + (t3 - 2 * t2 + t) * m0 * dt + (-2 * t3 + 3 * t2) * v1 + (t3 - t2) * m1 * dt;
|
|
138
|
+
}
|
|
139
|
+
default:
|
|
140
|
+
return v0 + (v1 - v0) * t;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
wrapTime(time) {
|
|
144
|
+
if (this._duration <= 0) return 0;
|
|
145
|
+
const t = time * this.speed;
|
|
146
|
+
switch (this.wrapMode) {
|
|
147
|
+
case "once":
|
|
148
|
+
return Math.min(t, this._duration);
|
|
149
|
+
case "clamp":
|
|
150
|
+
return Math.max(0, Math.min(t, this._duration));
|
|
151
|
+
case "loop":
|
|
152
|
+
return (t % this._duration + this._duration) % this._duration;
|
|
153
|
+
case "ping-pong": {
|
|
154
|
+
const cycle = t / this._duration;
|
|
155
|
+
const phase = cycle % 2;
|
|
156
|
+
return phase < 1 ? phase * this._duration : (2 - phase) * this._duration;
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
return t;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Blending
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
static blend(a, b, weight) {
|
|
166
|
+
const result = /* @__PURE__ */ new Map();
|
|
167
|
+
const allKeys = /* @__PURE__ */ new Set([...a.keys(), ...b.keys()]);
|
|
168
|
+
for (const key of allKeys) {
|
|
169
|
+
const va = a.get(key) ?? 0;
|
|
170
|
+
const vb = b.get(key) ?? 0;
|
|
171
|
+
result.set(key, va * (1 - weight) + vb * weight);
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/animation/AnimationEngine.ts
|
|
178
|
+
var Easing = {
|
|
179
|
+
linear: (t) => t,
|
|
180
|
+
easeInQuad: (t) => t * t,
|
|
181
|
+
easeOutQuad: (t) => t * (2 - t),
|
|
182
|
+
easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
183
|
+
easeInCubic: (t) => t * t * t,
|
|
184
|
+
easeOutCubic: (t) => --t * t * t + 1,
|
|
185
|
+
easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
|
|
186
|
+
easeInExpo: (t) => t === 0 ? 0 : Math.pow(2, 10 * (t - 1)),
|
|
187
|
+
easeOutExpo: (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t),
|
|
188
|
+
easeInOutExpo: (t) => {
|
|
189
|
+
if (t === 0 || t === 1) return t;
|
|
190
|
+
return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
191
|
+
},
|
|
192
|
+
easeOutBack: (t) => {
|
|
193
|
+
const c1 = 1.70158;
|
|
194
|
+
const c3 = c1 + 1;
|
|
195
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
196
|
+
},
|
|
197
|
+
easeOutElastic: (t) => {
|
|
198
|
+
if (t === 0 || t === 1) return t;
|
|
199
|
+
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
200
|
+
},
|
|
201
|
+
easeOutBounce: (t) => {
|
|
202
|
+
const n1 = 7.5625;
|
|
203
|
+
const d1 = 2.75;
|
|
204
|
+
if (t < 1 / d1) return n1 * t * t;
|
|
205
|
+
else if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
206
|
+
else if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
207
|
+
else return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function lerp(a, b, t) {
|
|
211
|
+
return a + (b - a) * t;
|
|
212
|
+
}
|
|
213
|
+
function interpolateKeyframes(keyframes, t) {
|
|
214
|
+
if (keyframes.length === 0) return 0;
|
|
215
|
+
if (keyframes.length === 1) return keyframes[0].value;
|
|
216
|
+
t = Math.max(0, Math.min(1, t));
|
|
217
|
+
let prevKf = keyframes[0];
|
|
218
|
+
let nextKf = keyframes[keyframes.length - 1];
|
|
219
|
+
for (let i = 0; i < keyframes.length - 1; i++) {
|
|
220
|
+
if (t >= keyframes[i].time && t <= keyframes[i + 1].time) {
|
|
221
|
+
prevKf = keyframes[i];
|
|
222
|
+
nextKf = keyframes[i + 1];
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const segmentDuration = nextKf.time - prevKf.time;
|
|
227
|
+
const localT = segmentDuration > 0 ? (t - prevKf.time) / segmentDuration : 1;
|
|
228
|
+
const easingFn = nextKf.easing || Easing.linear;
|
|
229
|
+
const easedT = easingFn(localT);
|
|
230
|
+
return lerp(prevKf.value, nextKf.value, easedT);
|
|
231
|
+
}
|
|
232
|
+
var AnimationEngine = class {
|
|
233
|
+
animations = /* @__PURE__ */ new Map();
|
|
234
|
+
propertySetters = /* @__PURE__ */ new Map();
|
|
235
|
+
/**
|
|
236
|
+
* Register an animation clip and start playing it.
|
|
237
|
+
*/
|
|
238
|
+
play(clip, setter) {
|
|
239
|
+
this.animations.set(clip.id, {
|
|
240
|
+
clip,
|
|
241
|
+
elapsed: -clip.delay,
|
|
242
|
+
// Negative = waiting for delay
|
|
243
|
+
isPlaying: true,
|
|
244
|
+
isPaused: false,
|
|
245
|
+
direction: 1,
|
|
246
|
+
loopCount: 0
|
|
247
|
+
});
|
|
248
|
+
this.propertySetters.set(clip.id, setter);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Stop and remove an animation.
|
|
252
|
+
*/
|
|
253
|
+
stop(clipId) {
|
|
254
|
+
this.animations.delete(clipId);
|
|
255
|
+
this.propertySetters.delete(clipId);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Pause an animation.
|
|
259
|
+
*/
|
|
260
|
+
pause(clipId) {
|
|
261
|
+
const anim = this.animations.get(clipId);
|
|
262
|
+
if (anim) anim.isPaused = true;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Resume a paused animation.
|
|
266
|
+
*/
|
|
267
|
+
resume(clipId) {
|
|
268
|
+
const anim = this.animations.get(clipId);
|
|
269
|
+
if (anim) anim.isPaused = false;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Check if an animation is active.
|
|
273
|
+
*/
|
|
274
|
+
isActive(clipId) {
|
|
275
|
+
return this.animations.has(clipId);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get all active animation IDs.
|
|
279
|
+
*/
|
|
280
|
+
getActiveIds() {
|
|
281
|
+
return Array.from(this.animations.keys());
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Update all active animations. Call this every frame.
|
|
285
|
+
*/
|
|
286
|
+
update(delta) {
|
|
287
|
+
const toRemove = [];
|
|
288
|
+
for (const [id, anim] of this.animations) {
|
|
289
|
+
if (anim.isPaused || !anim.isPlaying) continue;
|
|
290
|
+
anim.elapsed += delta;
|
|
291
|
+
if (anim.elapsed < 0) continue;
|
|
292
|
+
const { clip } = anim;
|
|
293
|
+
const effectiveElapsed = anim.elapsed;
|
|
294
|
+
let normalizedT = clip.duration > 0 ? effectiveElapsed / clip.duration : 1;
|
|
295
|
+
if (normalizedT >= 1) {
|
|
296
|
+
if (clip.pingPong) {
|
|
297
|
+
const cycles = Math.floor(effectiveElapsed / clip.duration);
|
|
298
|
+
anim.loopCount += cycles;
|
|
299
|
+
anim.elapsed -= clip.duration;
|
|
300
|
+
anim.direction *= -1;
|
|
301
|
+
normalizedT = clip.duration > 0 ? anim.elapsed / clip.duration : 1;
|
|
302
|
+
} else if (clip.loop) {
|
|
303
|
+
anim.elapsed = effectiveElapsed % clip.duration;
|
|
304
|
+
anim.loopCount++;
|
|
305
|
+
normalizedT = clip.duration > 0 ? anim.elapsed / clip.duration : 0;
|
|
306
|
+
} else {
|
|
307
|
+
normalizedT = 1;
|
|
308
|
+
anim.isPlaying = false;
|
|
309
|
+
toRemove.push(id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const sampleT = anim.direction === -1 ? 1 - normalizedT : normalizedT;
|
|
313
|
+
const value = interpolateKeyframes(clip.keyframes, sampleT);
|
|
314
|
+
const setter = this.propertySetters.get(id);
|
|
315
|
+
if (setter) setter(value);
|
|
316
|
+
}
|
|
317
|
+
for (const id of toRemove) {
|
|
318
|
+
const anim = this.animations.get(id);
|
|
319
|
+
if (anim?.clip.onComplete) anim.clip.onComplete();
|
|
320
|
+
this.animations.delete(id);
|
|
321
|
+
this.propertySetters.delete(id);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Stop all animations.
|
|
326
|
+
*/
|
|
327
|
+
clear() {
|
|
328
|
+
this.animations.clear();
|
|
329
|
+
this.propertySetters.clear();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/animation/AnimationGraph.ts
|
|
334
|
+
var AnimationGraph = class {
|
|
335
|
+
clips = /* @__PURE__ */ new Map();
|
|
336
|
+
layers = [];
|
|
337
|
+
defaultGraph;
|
|
338
|
+
constructor() {
|
|
339
|
+
this.defaultGraph = {
|
|
340
|
+
states: /* @__PURE__ */ new Map(),
|
|
341
|
+
transitions: [],
|
|
342
|
+
currentState: "",
|
|
343
|
+
parameters: /* @__PURE__ */ new Map(),
|
|
344
|
+
activeTransition: null
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Clip Management
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
addClip(clip) {
|
|
351
|
+
this.clips.set(clip.id, clip);
|
|
352
|
+
}
|
|
353
|
+
getClip(id) {
|
|
354
|
+
return this.clips.get(id);
|
|
355
|
+
}
|
|
356
|
+
removeClip(id) {
|
|
357
|
+
return this.clips.delete(id);
|
|
358
|
+
}
|
|
359
|
+
getClipIds() {
|
|
360
|
+
return [...this.clips.keys()];
|
|
361
|
+
}
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// State Management
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
addState(id, clipId, options = {}) {
|
|
366
|
+
const state = {
|
|
367
|
+
id,
|
|
368
|
+
clipId,
|
|
369
|
+
speed: options.speed ?? 1,
|
|
370
|
+
loop: options.loop ?? true,
|
|
371
|
+
currentTime: 0,
|
|
372
|
+
weight: 0,
|
|
373
|
+
isPlaying: false
|
|
374
|
+
};
|
|
375
|
+
this.defaultGraph.states.set(id, state);
|
|
376
|
+
if (this.defaultGraph.states.size === 1) {
|
|
377
|
+
this.defaultGraph.currentState = id;
|
|
378
|
+
state.weight = 1;
|
|
379
|
+
state.isPlaying = true;
|
|
380
|
+
}
|
|
381
|
+
return state;
|
|
382
|
+
}
|
|
383
|
+
getState(id) {
|
|
384
|
+
return this.defaultGraph.states.get(id);
|
|
385
|
+
}
|
|
386
|
+
getCurrentState() {
|
|
387
|
+
return this.defaultGraph.currentState;
|
|
388
|
+
}
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Transitions
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
addTransition(transition) {
|
|
393
|
+
this.defaultGraph.transitions.push(transition);
|
|
394
|
+
}
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
// Parameters
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
setParameter(name, value) {
|
|
399
|
+
this.defaultGraph.parameters.set(name, value);
|
|
400
|
+
}
|
|
401
|
+
getParameter(name) {
|
|
402
|
+
return this.defaultGraph.parameters.get(name);
|
|
403
|
+
}
|
|
404
|
+
setTrigger(name) {
|
|
405
|
+
this.defaultGraph.parameters.set(name, true);
|
|
406
|
+
}
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// Update
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
update(dt) {
|
|
411
|
+
const graph = this.defaultGraph;
|
|
412
|
+
const output = /* @__PURE__ */ new Map();
|
|
413
|
+
if (!graph.activeTransition) {
|
|
414
|
+
for (const t of graph.transitions) {
|
|
415
|
+
if (t.fromState !== graph.currentState) continue;
|
|
416
|
+
if (this.evaluateCondition(t.condition, graph)) {
|
|
417
|
+
graph.activeTransition = {
|
|
418
|
+
from: t.fromState,
|
|
419
|
+
to: t.toState,
|
|
420
|
+
progress: 0,
|
|
421
|
+
duration: t.duration
|
|
422
|
+
};
|
|
423
|
+
const toState = graph.states.get(t.toState);
|
|
424
|
+
if (toState) {
|
|
425
|
+
toState.currentTime = 0;
|
|
426
|
+
toState.isPlaying = true;
|
|
427
|
+
}
|
|
428
|
+
if (t.condition.type === "trigger") {
|
|
429
|
+
graph.parameters.delete(t.condition.name);
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (graph.activeTransition) {
|
|
436
|
+
graph.activeTransition.progress += dt / graph.activeTransition.duration;
|
|
437
|
+
const fromState = graph.states.get(graph.activeTransition.from);
|
|
438
|
+
const toState = graph.states.get(graph.activeTransition.to);
|
|
439
|
+
const blend = Math.min(1, graph.activeTransition.progress);
|
|
440
|
+
if (fromState) fromState.weight = 1 - blend;
|
|
441
|
+
if (toState) toState.weight = blend;
|
|
442
|
+
if (graph.activeTransition.progress >= 1) {
|
|
443
|
+
if (fromState) {
|
|
444
|
+
fromState.weight = 0;
|
|
445
|
+
fromState.isPlaying = false;
|
|
446
|
+
}
|
|
447
|
+
if (toState) toState.weight = 1;
|
|
448
|
+
graph.currentState = graph.activeTransition.to;
|
|
449
|
+
graph.activeTransition = null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
for (const [, state] of graph.states) {
|
|
453
|
+
if (!state.isPlaying) continue;
|
|
454
|
+
const clip = this.clips.get(state.clipId);
|
|
455
|
+
if (!clip) continue;
|
|
456
|
+
state.currentTime += dt * state.speed;
|
|
457
|
+
if (state.loop) {
|
|
458
|
+
state.currentTime %= clip.duration;
|
|
459
|
+
} else {
|
|
460
|
+
state.currentTime = Math.min(state.currentTime, clip.duration);
|
|
461
|
+
}
|
|
462
|
+
for (const track of clip.tracks) {
|
|
463
|
+
const value = this.sampleTrack(track, state.currentTime);
|
|
464
|
+
const existing = output.get(track.targetProperty) || 0;
|
|
465
|
+
output.set(track.targetProperty, existing + value * state.weight);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return output;
|
|
469
|
+
}
|
|
470
|
+
// ---------------------------------------------------------------------------
|
|
471
|
+
// Layers
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
addLayer(layer) {
|
|
474
|
+
this.layers.push(layer);
|
|
475
|
+
}
|
|
476
|
+
getLayers() {
|
|
477
|
+
return [...this.layers];
|
|
478
|
+
}
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// Internal
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
evaluateCondition(condition, graph) {
|
|
483
|
+
switch (condition.type) {
|
|
484
|
+
case "finished": {
|
|
485
|
+
const currentState = graph.states.get(graph.currentState);
|
|
486
|
+
if (!currentState) return false;
|
|
487
|
+
const clip = this.clips.get(currentState.clipId);
|
|
488
|
+
return clip ? currentState.currentTime >= clip.duration : false;
|
|
489
|
+
}
|
|
490
|
+
case "trigger":
|
|
491
|
+
return graph.parameters.get(condition.name) === true;
|
|
492
|
+
case "parameter": {
|
|
493
|
+
const val = graph.parameters.get(condition.name);
|
|
494
|
+
if (val === void 0) return false;
|
|
495
|
+
switch (condition.comparator) {
|
|
496
|
+
case ">":
|
|
497
|
+
return val > condition.value;
|
|
498
|
+
case "<":
|
|
499
|
+
return val < condition.value;
|
|
500
|
+
case "==":
|
|
501
|
+
return val === condition.value;
|
|
502
|
+
case "!=":
|
|
503
|
+
return val !== condition.value;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
sampleTrack(track, time) {
|
|
510
|
+
const kfs = track.keyframes;
|
|
511
|
+
if (kfs.length === 0) return 0;
|
|
512
|
+
if (kfs.length === 1 || time <= kfs[0].time) return kfs[0].value;
|
|
513
|
+
if (time >= kfs[kfs.length - 1].time) return kfs[kfs.length - 1].value;
|
|
514
|
+
let i = 0;
|
|
515
|
+
while (i < kfs.length - 1 && kfs[i + 1].time < time) i++;
|
|
516
|
+
const kf0 = kfs[i];
|
|
517
|
+
const kf1 = kfs[i + 1];
|
|
518
|
+
const t = (time - kf0.time) / (kf1.time - kf0.time);
|
|
519
|
+
switch (track.interpolation) {
|
|
520
|
+
case "step":
|
|
521
|
+
return kf0.value;
|
|
522
|
+
case "cubic": {
|
|
523
|
+
const t2 = t * t;
|
|
524
|
+
const t3 = t2 * t;
|
|
525
|
+
const h1 = 2 * t3 - 3 * t2 + 1;
|
|
526
|
+
const h2 = t3 - 2 * t2 + t;
|
|
527
|
+
const h3 = -2 * t3 + 3 * t2;
|
|
528
|
+
const h4 = t3 - t2;
|
|
529
|
+
const dt = kf1.time - kf0.time;
|
|
530
|
+
return h1 * kf0.value + h2 * (kf0.outTangent || 0) * dt + h3 * kf1.value + h4 * (kf1.inTangent || 0) * dt;
|
|
531
|
+
}
|
|
532
|
+
case "linear":
|
|
533
|
+
default:
|
|
534
|
+
return kf0.value + (kf1.value - kf0.value) * t;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// src/animation/SpringAnimator.ts
|
|
540
|
+
var SpringPresets = {
|
|
541
|
+
/** Snappy, minimal bounce — good for buttons, toggles */
|
|
542
|
+
stiff: { stiffness: 300, damping: 30, mass: 1, precision: 0.01 },
|
|
543
|
+
/** Default responsive spring */
|
|
544
|
+
default: { stiffness: 170, damping: 26, mass: 1, precision: 0.01 },
|
|
545
|
+
/** Gentle, bouncy — good for dialogs, modals */
|
|
546
|
+
gentle: { stiffness: 120, damping: 14, mass: 1, precision: 0.01 },
|
|
547
|
+
/** Wobbly — fun/playful animations */
|
|
548
|
+
wobbly: { stiffness: 180, damping: 12, mass: 1, precision: 0.01 },
|
|
549
|
+
/** Slow, heavy — good for large panels */
|
|
550
|
+
slow: { stiffness: 80, damping: 20, mass: 2, precision: 0.01 },
|
|
551
|
+
/** Molasses — ultra-slow drift */
|
|
552
|
+
molasses: { stiffness: 40, damping: 30, mass: 3, precision: 5e-3 }
|
|
553
|
+
};
|
|
554
|
+
var SpringAnimator = class {
|
|
555
|
+
current;
|
|
556
|
+
target;
|
|
557
|
+
velocity = 0;
|
|
558
|
+
config;
|
|
559
|
+
atRest = true;
|
|
560
|
+
onUpdate;
|
|
561
|
+
onRest;
|
|
562
|
+
constructor(initialValue, config = {}, onUpdate, onRest) {
|
|
563
|
+
this.current = initialValue;
|
|
564
|
+
this.target = initialValue;
|
|
565
|
+
this.config = { ...SpringPresets.default, ...config };
|
|
566
|
+
this.onUpdate = onUpdate;
|
|
567
|
+
this.onRest = onRest;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Set a new target value. The spring will animate towards it.
|
|
571
|
+
*/
|
|
572
|
+
setTarget(target) {
|
|
573
|
+
this.target = target;
|
|
574
|
+
this.atRest = false;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Instantly jump to a value (no animation).
|
|
578
|
+
*/
|
|
579
|
+
setValue(value) {
|
|
580
|
+
this.current = value;
|
|
581
|
+
this.target = value;
|
|
582
|
+
this.velocity = 0;
|
|
583
|
+
this.atRest = true;
|
|
584
|
+
if (this.onUpdate) this.onUpdate(value);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Apply an impulse (instant velocity change).
|
|
588
|
+
*/
|
|
589
|
+
impulse(force) {
|
|
590
|
+
this.velocity += force;
|
|
591
|
+
this.atRest = false;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Step the spring simulation forward.
|
|
595
|
+
* Uses semi-implicit Euler integration.
|
|
596
|
+
*/
|
|
597
|
+
update(delta) {
|
|
598
|
+
if (this.atRest) return this.current;
|
|
599
|
+
const { stiffness, damping, mass, precision } = this.config;
|
|
600
|
+
const displacement = this.current - this.target;
|
|
601
|
+
const springForce = -stiffness * displacement;
|
|
602
|
+
const dampingForce = -damping * this.velocity;
|
|
603
|
+
const acceleration = (springForce + dampingForce) / mass;
|
|
604
|
+
this.velocity += acceleration * delta;
|
|
605
|
+
this.current += this.velocity * delta;
|
|
606
|
+
if (Math.abs(this.velocity) < precision && Math.abs(displacement) < precision) {
|
|
607
|
+
this.current = this.target;
|
|
608
|
+
this.velocity = 0;
|
|
609
|
+
this.atRest = true;
|
|
610
|
+
if (this.onUpdate) this.onUpdate(this.current);
|
|
611
|
+
if (this.onRest) this.onRest();
|
|
612
|
+
return this.current;
|
|
613
|
+
}
|
|
614
|
+
if (this.onUpdate) this.onUpdate(this.current);
|
|
615
|
+
return this.current;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get current value.
|
|
619
|
+
*/
|
|
620
|
+
getValue() {
|
|
621
|
+
return this.current;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Check if the spring has settled.
|
|
625
|
+
*/
|
|
626
|
+
isAtRest() {
|
|
627
|
+
return this.atRest;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Change spring configuration dynamically.
|
|
631
|
+
*/
|
|
632
|
+
setConfig(config) {
|
|
633
|
+
this.config = { ...this.config, ...config };
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var Vec3SpringAnimator = class {
|
|
637
|
+
x;
|
|
638
|
+
y;
|
|
639
|
+
z;
|
|
640
|
+
constructor(initial, config = {}, onUpdate) {
|
|
641
|
+
const notifyAll = () => {
|
|
642
|
+
if (onUpdate) {
|
|
643
|
+
onUpdate({ x: this.x.getValue(), y: this.y.getValue(), z: this.z.getValue() });
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
this.x = new SpringAnimator(initial.x, config, notifyAll);
|
|
647
|
+
this.y = new SpringAnimator(initial.y, config, notifyAll);
|
|
648
|
+
this.z = new SpringAnimator(initial.z, config, notifyAll);
|
|
649
|
+
}
|
|
650
|
+
setTarget(target) {
|
|
651
|
+
this.x.setTarget(target.x);
|
|
652
|
+
this.y.setTarget(target.y);
|
|
653
|
+
this.z.setTarget(target.z);
|
|
654
|
+
}
|
|
655
|
+
update(delta) {
|
|
656
|
+
return {
|
|
657
|
+
x: this.x.update(delta),
|
|
658
|
+
y: this.y.update(delta),
|
|
659
|
+
z: this.z.update(delta)
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
getValue() {
|
|
663
|
+
return { x: this.x.getValue(), y: this.y.getValue(), z: this.z.getValue() };
|
|
664
|
+
}
|
|
665
|
+
isAtRest() {
|
|
666
|
+
return this.x.isAtRest() && this.y.isAtRest() && this.z.isAtRest();
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/animation/AnimationTrait.ts
|
|
671
|
+
var sharedEngine = null;
|
|
672
|
+
function getSharedAnimationEngine() {
|
|
673
|
+
if (!sharedEngine) {
|
|
674
|
+
sharedEngine = new AnimationEngine();
|
|
675
|
+
}
|
|
676
|
+
return sharedEngine;
|
|
677
|
+
}
|
|
678
|
+
var nodeSpringMap = /* @__PURE__ */ new Map();
|
|
679
|
+
var easingLookup = {
|
|
680
|
+
linear: Easing.linear,
|
|
681
|
+
"ease-in": Easing.easeInQuad,
|
|
682
|
+
"ease-out": Easing.easeOutQuad,
|
|
683
|
+
"ease-in-out": Easing.easeInOutQuad,
|
|
684
|
+
"ease-in-cubic": Easing.easeInCubic,
|
|
685
|
+
"ease-out-cubic": Easing.easeOutCubic,
|
|
686
|
+
"ease-in-out-cubic": Easing.easeInOutCubic,
|
|
687
|
+
"ease-out-back": Easing.easeOutBack,
|
|
688
|
+
"ease-out-elastic": Easing.easeOutElastic,
|
|
689
|
+
"ease-out-bounce": Easing.easeOutBounce,
|
|
690
|
+
"ease-in-expo": Easing.easeInExpo,
|
|
691
|
+
"ease-out-expo": Easing.easeOutExpo
|
|
692
|
+
};
|
|
693
|
+
function resolveEasing(name) {
|
|
694
|
+
if (!name) return Easing.linear;
|
|
695
|
+
return easingLookup[name] || Easing.linear;
|
|
696
|
+
}
|
|
697
|
+
var animationTraitHandler = {
|
|
698
|
+
name: "animate",
|
|
699
|
+
defaultConfig: {
|
|
700
|
+
clips: [],
|
|
701
|
+
springs: [],
|
|
702
|
+
autoPlay: true
|
|
703
|
+
},
|
|
704
|
+
onAttach(node, config, _context) {
|
|
705
|
+
const engine = getSharedAnimationEngine();
|
|
706
|
+
const nodeId = node.id || "unknown";
|
|
707
|
+
if (config.autoPlay && config.clips) {
|
|
708
|
+
for (const clipDef of config.clips) {
|
|
709
|
+
const clip = {
|
|
710
|
+
id: `${nodeId}_${clipDef.property}`,
|
|
711
|
+
property: clipDef.property,
|
|
712
|
+
keyframes: clipDef.keyframes.map((kf) => ({
|
|
713
|
+
time: kf.time,
|
|
714
|
+
value: kf.value,
|
|
715
|
+
easing: resolveEasing(kf.easing)
|
|
716
|
+
})),
|
|
717
|
+
duration: clipDef.duration,
|
|
718
|
+
loop: clipDef.loop || false,
|
|
719
|
+
pingPong: clipDef.pingPong || false,
|
|
720
|
+
delay: clipDef.delay || 0
|
|
721
|
+
};
|
|
722
|
+
engine.play(clip, (value) => {
|
|
723
|
+
setNestedProperty(node, clipDef.property, value);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (config.springs) {
|
|
728
|
+
const springs = /* @__PURE__ */ new Map();
|
|
729
|
+
for (const springDef of config.springs) {
|
|
730
|
+
const presetConfig = springDef.preset ? SpringPresets[springDef.preset] : SpringPresets.default;
|
|
731
|
+
const mergedConfig = { ...presetConfig, ...springDef.config || {} };
|
|
732
|
+
const initial = getNestedProperty(node, springDef.property) ?? 0;
|
|
733
|
+
const spring = new SpringAnimator(initial, mergedConfig, (value) => {
|
|
734
|
+
setNestedProperty(node, springDef.property, value);
|
|
735
|
+
});
|
|
736
|
+
spring.setTarget(springDef.target);
|
|
737
|
+
springs.set(springDef.property, spring);
|
|
738
|
+
}
|
|
739
|
+
nodeSpringMap.set(nodeId, springs);
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
onDetach(node, _config, _context) {
|
|
743
|
+
const nodeId = node.id || "unknown";
|
|
744
|
+
const engine = getSharedAnimationEngine();
|
|
745
|
+
for (const id of engine.getActiveIds()) {
|
|
746
|
+
if (id.startsWith(nodeId)) {
|
|
747
|
+
engine.stop(id);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
nodeSpringMap.delete(nodeId);
|
|
751
|
+
},
|
|
752
|
+
onUpdate(node, _config, _context, delta) {
|
|
753
|
+
const nodeId = node.id || "unknown";
|
|
754
|
+
const springs = nodeSpringMap.get(nodeId);
|
|
755
|
+
if (springs) {
|
|
756
|
+
for (const spring of springs.values()) {
|
|
757
|
+
spring.update(delta);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
function setNestedProperty(node, path, value) {
|
|
763
|
+
if (!node.properties) return;
|
|
764
|
+
const parts = path.split(".");
|
|
765
|
+
let target = node.properties;
|
|
766
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
767
|
+
if (target[parts[i]] === void 0) target[parts[i]] = {};
|
|
768
|
+
target = target[parts[i]];
|
|
769
|
+
}
|
|
770
|
+
target[parts[parts.length - 1]] = value;
|
|
771
|
+
}
|
|
772
|
+
function getNestedProperty(node, path) {
|
|
773
|
+
if (!node.properties) return void 0;
|
|
774
|
+
const parts = path.split(".");
|
|
775
|
+
let target = node.properties;
|
|
776
|
+
for (const part of parts) {
|
|
777
|
+
if (target === void 0 || target === null) return void 0;
|
|
778
|
+
target = target[part];
|
|
779
|
+
}
|
|
780
|
+
return target;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/animation/AnimationTransitions.ts
|
|
784
|
+
var DEFAULT_CONFIG = {
|
|
785
|
+
duration: 0.5,
|
|
786
|
+
curve: "ease_in_out",
|
|
787
|
+
settleThreshold: 0.1
|
|
788
|
+
};
|
|
789
|
+
var AnimationTransitionSystem = class {
|
|
790
|
+
config;
|
|
791
|
+
activeBlends = /* @__PURE__ */ new Map();
|
|
792
|
+
constructor(config = {}) {
|
|
793
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
794
|
+
}
|
|
795
|
+
startAnimToRagdoll(entityId, currentPose) {
|
|
796
|
+
this.activeBlends.set(entityId, {
|
|
797
|
+
direction: "animation_to_ragdoll",
|
|
798
|
+
progress: 0,
|
|
799
|
+
duration: this.config.duration,
|
|
800
|
+
sourcePose: currentPose.map((p) => ({
|
|
801
|
+
...p,
|
|
802
|
+
position: { ...p.position },
|
|
803
|
+
rotation: { ...p.rotation }
|
|
804
|
+
})),
|
|
805
|
+
isComplete: false
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
startRagdollToAnim(entityId, currentPose) {
|
|
809
|
+
this.activeBlends.set(entityId, {
|
|
810
|
+
direction: "ragdoll_to_animation",
|
|
811
|
+
progress: 0,
|
|
812
|
+
duration: this.config.duration,
|
|
813
|
+
sourcePose: currentPose.map((p) => ({
|
|
814
|
+
...p,
|
|
815
|
+
position: { ...p.position },
|
|
816
|
+
rotation: { ...p.rotation }
|
|
817
|
+
})),
|
|
818
|
+
isComplete: false
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
update(dt, ragdollPoses, animPoses) {
|
|
822
|
+
const results = /* @__PURE__ */ new Map();
|
|
823
|
+
for (const [entityId, blend] of this.activeBlends) {
|
|
824
|
+
if (blend.isComplete) continue;
|
|
825
|
+
blend.progress = Math.min(1, blend.progress + dt / blend.duration);
|
|
826
|
+
const t = this.applyCurve(blend.progress);
|
|
827
|
+
const ragdoll = ragdollPoses.get(entityId) || blend.sourcePose;
|
|
828
|
+
const anim = animPoses.get(entityId) || blend.sourcePose;
|
|
829
|
+
const blended = [];
|
|
830
|
+
for (let i = 0; i < blend.sourcePose.length; i++) {
|
|
831
|
+
const source = blend.sourcePose[i];
|
|
832
|
+
const ragBone = ragdoll.find((b) => b.boneId === source.boneId) || source;
|
|
833
|
+
const animBone = anim.find((b) => b.boneId === source.boneId) || source;
|
|
834
|
+
const fromBone = blend.direction === "animation_to_ragdoll" ? animBone : ragBone;
|
|
835
|
+
const toBone = blend.direction === "animation_to_ragdoll" ? ragBone : animBone;
|
|
836
|
+
blended.push({
|
|
837
|
+
boneId: source.boneId,
|
|
838
|
+
position: this.lerpVec3(fromBone.position, toBone.position, t),
|
|
839
|
+
rotation: this.slerpQuat(fromBone.rotation, toBone.rotation, t)
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
results.set(entityId, blended);
|
|
843
|
+
if (blend.progress >= 1) {
|
|
844
|
+
blend.isComplete = true;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return results;
|
|
848
|
+
}
|
|
849
|
+
isTransitioning(entityId) {
|
|
850
|
+
const blend = this.activeBlends.get(entityId);
|
|
851
|
+
return blend !== void 0 && !blend.isComplete;
|
|
852
|
+
}
|
|
853
|
+
getBlendProgress(entityId) {
|
|
854
|
+
return this.activeBlends.get(entityId)?.progress ?? 0;
|
|
855
|
+
}
|
|
856
|
+
clearTransition(entityId) {
|
|
857
|
+
this.activeBlends.delete(entityId);
|
|
858
|
+
}
|
|
859
|
+
getActiveTransitionCount() {
|
|
860
|
+
let count = 0;
|
|
861
|
+
for (const [, blend] of this.activeBlends) {
|
|
862
|
+
if (!blend.isComplete) count++;
|
|
863
|
+
}
|
|
864
|
+
return count;
|
|
865
|
+
}
|
|
866
|
+
applyCurve(t) {
|
|
867
|
+
switch (this.config.curve) {
|
|
868
|
+
case "ease_in":
|
|
869
|
+
return t * t;
|
|
870
|
+
case "ease_out":
|
|
871
|
+
return 1 - (1 - t) * (1 - t);
|
|
872
|
+
case "ease_in_out":
|
|
873
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
874
|
+
case "linear":
|
|
875
|
+
default:
|
|
876
|
+
return t;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
lerpVec3(a, b, t) {
|
|
880
|
+
return {
|
|
881
|
+
x: a.x + (b.x - a.x) * t,
|
|
882
|
+
y: a.y + (b.y - a.y) * t,
|
|
883
|
+
z: a.z + (b.z - a.z) * t
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
slerpQuat(a, b, t) {
|
|
887
|
+
let dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
|
888
|
+
let bx = b.x, by = b.y, bz = b.z, bw = b.w;
|
|
889
|
+
if (dot < 0) {
|
|
890
|
+
bx = -bx;
|
|
891
|
+
by = -by;
|
|
892
|
+
bz = -bz;
|
|
893
|
+
bw = -bw;
|
|
894
|
+
dot = -dot;
|
|
895
|
+
}
|
|
896
|
+
const rx = a.x + (bx - a.x) * t;
|
|
897
|
+
const ry = a.y + (by - a.y) * t;
|
|
898
|
+
const rz = a.z + (bz - a.z) * t;
|
|
899
|
+
const rw = a.w + (bw - a.w) * t;
|
|
900
|
+
const len = Math.sqrt(rx * rx + ry * ry + rz * rz + rw * rw) || 1;
|
|
901
|
+
return { x: rx / len, y: ry / len, z: rz / len, w: rw / len };
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// src/animation/AvatarController.ts
|
|
906
|
+
var AvatarController = class {
|
|
907
|
+
solver;
|
|
908
|
+
bones;
|
|
909
|
+
calibrated = false;
|
|
910
|
+
// Configuration for VRIK
|
|
911
|
+
config = {
|
|
912
|
+
headOffset: { x: 0, y: -0.1, z: 0 },
|
|
913
|
+
// Neck pivot relative to HMD
|
|
914
|
+
shoulderWidth: 0.4,
|
|
915
|
+
spineLength: 0.6
|
|
916
|
+
};
|
|
917
|
+
constructor(solver, bones) {
|
|
918
|
+
this.solver = solver;
|
|
919
|
+
this.bones = bones;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Calibrate avatar scale based on user height
|
|
923
|
+
*/
|
|
924
|
+
calibrate(_userHeight) {
|
|
925
|
+
this.calibrated = true;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Update IK targets from VR input
|
|
929
|
+
*/
|
|
930
|
+
update(input) {
|
|
931
|
+
if (!this.calibrated) return;
|
|
932
|
+
this.solver.setTarget(
|
|
933
|
+
"leftArm",
|
|
934
|
+
input.leftHand.position[0],
|
|
935
|
+
input.leftHand.position[1],
|
|
936
|
+
input.leftHand.position[2]
|
|
937
|
+
);
|
|
938
|
+
this.solver.setTarget(
|
|
939
|
+
"rightArm",
|
|
940
|
+
input.rightHand.position[0],
|
|
941
|
+
input.rightHand.position[1],
|
|
942
|
+
input.rightHand.position[2]
|
|
943
|
+
);
|
|
944
|
+
const leftHandBone = this.bones.getBone("LeftHand");
|
|
945
|
+
if (leftHandBone) {
|
|
946
|
+
}
|
|
947
|
+
this.solver.solveAll();
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
// src/animation/BoneSystem.ts
|
|
952
|
+
var BoneSystem = class {
|
|
953
|
+
bones = /* @__PURE__ */ new Map();
|
|
954
|
+
roots = [];
|
|
955
|
+
dirty = true;
|
|
956
|
+
// ---------------------------------------------------------------------------
|
|
957
|
+
// Bone Management
|
|
958
|
+
// ---------------------------------------------------------------------------
|
|
959
|
+
addBone(id, name, parentId, local) {
|
|
960
|
+
const defaultTransform = () => ({
|
|
961
|
+
tx: 0,
|
|
962
|
+
ty: 0,
|
|
963
|
+
tz: 0,
|
|
964
|
+
rx: 0,
|
|
965
|
+
ry: 0,
|
|
966
|
+
rz: 0,
|
|
967
|
+
rw: 1,
|
|
968
|
+
sx: 1,
|
|
969
|
+
sy: 1,
|
|
970
|
+
sz: 1
|
|
971
|
+
});
|
|
972
|
+
const bone = {
|
|
973
|
+
id,
|
|
974
|
+
name,
|
|
975
|
+
parentId,
|
|
976
|
+
local: { ...defaultTransform(), ...local },
|
|
977
|
+
world: defaultTransform(),
|
|
978
|
+
bindInverse: defaultTransform(),
|
|
979
|
+
childIds: []
|
|
980
|
+
};
|
|
981
|
+
this.bones.set(id, bone);
|
|
982
|
+
if (parentId) {
|
|
983
|
+
const parent = this.bones.get(parentId);
|
|
984
|
+
if (parent) parent.childIds.push(id);
|
|
985
|
+
} else {
|
|
986
|
+
this.roots.push(id);
|
|
987
|
+
}
|
|
988
|
+
this.dirty = true;
|
|
989
|
+
}
|
|
990
|
+
getBone(id) {
|
|
991
|
+
return this.bones.get(id);
|
|
992
|
+
}
|
|
993
|
+
getBoneCount() {
|
|
994
|
+
return this.bones.size;
|
|
995
|
+
}
|
|
996
|
+
getRoots() {
|
|
997
|
+
return [...this.roots];
|
|
998
|
+
}
|
|
999
|
+
// ---------------------------------------------------------------------------
|
|
1000
|
+
// Pose Application
|
|
1001
|
+
// ---------------------------------------------------------------------------
|
|
1002
|
+
setLocalTransform(id, transform) {
|
|
1003
|
+
const bone = this.bones.get(id);
|
|
1004
|
+
if (!bone) return;
|
|
1005
|
+
Object.assign(bone.local, transform);
|
|
1006
|
+
this.dirty = true;
|
|
1007
|
+
}
|
|
1008
|
+
// ---------------------------------------------------------------------------
|
|
1009
|
+
// World-Space Update
|
|
1010
|
+
// ---------------------------------------------------------------------------
|
|
1011
|
+
updateWorldTransforms() {
|
|
1012
|
+
if (!this.dirty) return;
|
|
1013
|
+
for (const rootId of this.roots) this.updateBoneChain(rootId);
|
|
1014
|
+
this.dirty = false;
|
|
1015
|
+
}
|
|
1016
|
+
updateBoneChain(id) {
|
|
1017
|
+
const bone = this.bones.get(id);
|
|
1018
|
+
if (!bone) return;
|
|
1019
|
+
if (bone.parentId) {
|
|
1020
|
+
const parent = this.bones.get(bone.parentId);
|
|
1021
|
+
bone.world = this.combineTransforms(parent.world, bone.local);
|
|
1022
|
+
} else {
|
|
1023
|
+
bone.world = { ...bone.local };
|
|
1024
|
+
}
|
|
1025
|
+
for (const childId of bone.childIds) this.updateBoneChain(childId);
|
|
1026
|
+
}
|
|
1027
|
+
// ---------------------------------------------------------------------------
|
|
1028
|
+
// Bind Pose
|
|
1029
|
+
// ---------------------------------------------------------------------------
|
|
1030
|
+
captureBindPose() {
|
|
1031
|
+
this.updateWorldTransforms();
|
|
1032
|
+
for (const bone of this.bones.values()) {
|
|
1033
|
+
bone.bindInverse = this.invertTransform(bone.world);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
getSkinningMatrix(id) {
|
|
1037
|
+
const bone = this.bones.get(id);
|
|
1038
|
+
if (!bone) return null;
|
|
1039
|
+
this.updateWorldTransforms();
|
|
1040
|
+
return this.combineTransforms(bone.world, bone.bindInverse);
|
|
1041
|
+
}
|
|
1042
|
+
// ---------------------------------------------------------------------------
|
|
1043
|
+
// Transform Math (simplified)
|
|
1044
|
+
// ---------------------------------------------------------------------------
|
|
1045
|
+
combineTransforms(parent, child) {
|
|
1046
|
+
return {
|
|
1047
|
+
tx: parent.tx + child.tx * parent.sx,
|
|
1048
|
+
ty: parent.ty + child.ty * parent.sy,
|
|
1049
|
+
tz: parent.tz + child.tz * parent.sz,
|
|
1050
|
+
rx: child.rx,
|
|
1051
|
+
ry: child.ry,
|
|
1052
|
+
rz: child.rz,
|
|
1053
|
+
rw: child.rw,
|
|
1054
|
+
// Simplified — no quat multiply
|
|
1055
|
+
sx: parent.sx * child.sx,
|
|
1056
|
+
sy: parent.sy * child.sy,
|
|
1057
|
+
sz: parent.sz * child.sz
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
invertTransform(t) {
|
|
1061
|
+
const isx = t.sx !== 0 ? 1 / t.sx : 0;
|
|
1062
|
+
const isy = t.sy !== 0 ? 1 / t.sy : 0;
|
|
1063
|
+
const isz = t.sz !== 0 ? 1 / t.sz : 0;
|
|
1064
|
+
return {
|
|
1065
|
+
tx: -t.tx * isx,
|
|
1066
|
+
ty: -t.ty * isy,
|
|
1067
|
+
tz: -t.tz * isz,
|
|
1068
|
+
rx: -t.rx,
|
|
1069
|
+
ry: -t.ry,
|
|
1070
|
+
rz: -t.rz,
|
|
1071
|
+
rw: t.rw,
|
|
1072
|
+
// Conjugate
|
|
1073
|
+
sx: isx,
|
|
1074
|
+
sy: isy,
|
|
1075
|
+
sz: isz
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
// ---------------------------------------------------------------------------
|
|
1079
|
+
// Queries
|
|
1080
|
+
// ---------------------------------------------------------------------------
|
|
1081
|
+
getWorldPosition(id) {
|
|
1082
|
+
const bone = this.bones.get(id);
|
|
1083
|
+
if (!bone) return null;
|
|
1084
|
+
this.updateWorldTransforms();
|
|
1085
|
+
return { x: bone.world.tx, y: bone.world.ty, z: bone.world.tz };
|
|
1086
|
+
}
|
|
1087
|
+
getChain(leafId) {
|
|
1088
|
+
const chain = [];
|
|
1089
|
+
let current = leafId;
|
|
1090
|
+
while (current) {
|
|
1091
|
+
chain.unshift(current);
|
|
1092
|
+
current = this.bones.get(current)?.parentId ?? "";
|
|
1093
|
+
}
|
|
1094
|
+
return chain;
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// src/animation/CutsceneTimeline.ts
|
|
1099
|
+
var CutsceneTimeline = class {
|
|
1100
|
+
cutscenes = /* @__PURE__ */ new Map();
|
|
1101
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
1102
|
+
// ---------------------------------------------------------------------------
|
|
1103
|
+
// Cutscene Management
|
|
1104
|
+
// ---------------------------------------------------------------------------
|
|
1105
|
+
load(definition) {
|
|
1106
|
+
this.cutscenes.set(definition.id, {
|
|
1107
|
+
definition,
|
|
1108
|
+
currentTime: 0,
|
|
1109
|
+
isPlaying: false,
|
|
1110
|
+
isPaused: false,
|
|
1111
|
+
speed: 1,
|
|
1112
|
+
activeEvents: /* @__PURE__ */ new Set(),
|
|
1113
|
+
completedEvents: /* @__PURE__ */ new Set(),
|
|
1114
|
+
triggeredCallbacks: []
|
|
1115
|
+
});
|
|
1116
|
+
return definition.id;
|
|
1117
|
+
}
|
|
1118
|
+
play(id, startTime = 0) {
|
|
1119
|
+
const state = this.cutscenes.get(id);
|
|
1120
|
+
if (!state) return false;
|
|
1121
|
+
state.isPlaying = true;
|
|
1122
|
+
state.isPaused = false;
|
|
1123
|
+
state.currentTime = startTime;
|
|
1124
|
+
state.activeEvents.clear();
|
|
1125
|
+
state.completedEvents.clear();
|
|
1126
|
+
state.triggeredCallbacks = [];
|
|
1127
|
+
return true;
|
|
1128
|
+
}
|
|
1129
|
+
pause(id) {
|
|
1130
|
+
const state = this.cutscenes.get(id);
|
|
1131
|
+
if (state) state.isPaused = true;
|
|
1132
|
+
}
|
|
1133
|
+
resume(id) {
|
|
1134
|
+
const state = this.cutscenes.get(id);
|
|
1135
|
+
if (state) state.isPaused = false;
|
|
1136
|
+
}
|
|
1137
|
+
stop(id) {
|
|
1138
|
+
const state = this.cutscenes.get(id);
|
|
1139
|
+
if (!state) return;
|
|
1140
|
+
state.isPlaying = false;
|
|
1141
|
+
state.isPaused = false;
|
|
1142
|
+
state.currentTime = 0;
|
|
1143
|
+
state.activeEvents.clear();
|
|
1144
|
+
}
|
|
1145
|
+
setSpeed(id, speed) {
|
|
1146
|
+
const state = this.cutscenes.get(id);
|
|
1147
|
+
if (state) state.speed = Math.max(0, speed);
|
|
1148
|
+
}
|
|
1149
|
+
seek(id, time) {
|
|
1150
|
+
const state = this.cutscenes.get(id);
|
|
1151
|
+
if (!state) return;
|
|
1152
|
+
state.currentTime = Math.max(0, Math.min(time, state.definition.duration));
|
|
1153
|
+
state.completedEvents.clear();
|
|
1154
|
+
state.activeEvents.clear();
|
|
1155
|
+
state.triggeredCallbacks = [];
|
|
1156
|
+
}
|
|
1157
|
+
// ---------------------------------------------------------------------------
|
|
1158
|
+
// Callbacks
|
|
1159
|
+
// ---------------------------------------------------------------------------
|
|
1160
|
+
registerCallback(callbackId, fn) {
|
|
1161
|
+
this.callbacks.set(callbackId, fn);
|
|
1162
|
+
}
|
|
1163
|
+
unregisterCallback(callbackId) {
|
|
1164
|
+
this.callbacks.delete(callbackId);
|
|
1165
|
+
}
|
|
1166
|
+
// ---------------------------------------------------------------------------
|
|
1167
|
+
// Update
|
|
1168
|
+
// ---------------------------------------------------------------------------
|
|
1169
|
+
update(dt) {
|
|
1170
|
+
const activeEventsPerCutscene = /* @__PURE__ */ new Map();
|
|
1171
|
+
for (const [id, state] of this.cutscenes) {
|
|
1172
|
+
if (!state.isPlaying || state.isPaused) continue;
|
|
1173
|
+
state.currentTime += dt * state.speed;
|
|
1174
|
+
if (state.currentTime >= state.definition.duration) {
|
|
1175
|
+
if (state.definition.loop) {
|
|
1176
|
+
state.currentTime %= state.definition.duration;
|
|
1177
|
+
state.completedEvents.clear();
|
|
1178
|
+
state.triggeredCallbacks = [];
|
|
1179
|
+
} else {
|
|
1180
|
+
state.currentTime = state.definition.duration;
|
|
1181
|
+
state.isPlaying = false;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const currentActive = [];
|
|
1185
|
+
for (const track of state.definition.tracks) {
|
|
1186
|
+
if (track.muted) continue;
|
|
1187
|
+
for (const event of track.events) {
|
|
1188
|
+
const eventEnd = event.startTime + event.duration;
|
|
1189
|
+
const isActive = state.currentTime >= event.startTime && state.currentTime < eventEnd;
|
|
1190
|
+
if (isActive) {
|
|
1191
|
+
state.activeEvents.add(event.id);
|
|
1192
|
+
currentActive.push(event);
|
|
1193
|
+
if (event.type === "callback" && !state.triggeredCallbacks.includes(event.id)) {
|
|
1194
|
+
const callbackId = event.data.callbackId;
|
|
1195
|
+
const fn = this.callbacks.get(callbackId);
|
|
1196
|
+
if (fn) fn();
|
|
1197
|
+
state.triggeredCallbacks.push(event.id);
|
|
1198
|
+
}
|
|
1199
|
+
} else if (state.currentTime >= eventEnd) {
|
|
1200
|
+
state.activeEvents.delete(event.id);
|
|
1201
|
+
state.completedEvents.add(event.id);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
activeEventsPerCutscene.set(id, currentActive);
|
|
1206
|
+
}
|
|
1207
|
+
return activeEventsPerCutscene;
|
|
1208
|
+
}
|
|
1209
|
+
// ---------------------------------------------------------------------------
|
|
1210
|
+
// Queries
|
|
1211
|
+
// ---------------------------------------------------------------------------
|
|
1212
|
+
getState(id) {
|
|
1213
|
+
return this.cutscenes.get(id);
|
|
1214
|
+
}
|
|
1215
|
+
isPlaying(id) {
|
|
1216
|
+
return this.cutscenes.get(id)?.isPlaying ?? false;
|
|
1217
|
+
}
|
|
1218
|
+
getCurrentTime(id) {
|
|
1219
|
+
return this.cutscenes.get(id)?.currentTime ?? 0;
|
|
1220
|
+
}
|
|
1221
|
+
getProgress(id) {
|
|
1222
|
+
const state = this.cutscenes.get(id);
|
|
1223
|
+
if (!state) return 0;
|
|
1224
|
+
return state.currentTime / state.definition.duration;
|
|
1225
|
+
}
|
|
1226
|
+
removeCutscene(id) {
|
|
1227
|
+
return this.cutscenes.delete(id);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
var CutsceneBuilder = class {
|
|
1231
|
+
tracks = [];
|
|
1232
|
+
eventCounter = 0;
|
|
1233
|
+
id;
|
|
1234
|
+
name;
|
|
1235
|
+
constructor(id, name) {
|
|
1236
|
+
this.id = id;
|
|
1237
|
+
this.name = name;
|
|
1238
|
+
}
|
|
1239
|
+
addTrack(name, targetEntity) {
|
|
1240
|
+
this.tracks.push({
|
|
1241
|
+
id: `track_${this.tracks.length}`,
|
|
1242
|
+
name,
|
|
1243
|
+
targetEntity,
|
|
1244
|
+
events: [],
|
|
1245
|
+
muted: false
|
|
1246
|
+
});
|
|
1247
|
+
return this;
|
|
1248
|
+
}
|
|
1249
|
+
addEvent(trackIndex, type, startTime, duration, data = {}) {
|
|
1250
|
+
if (trackIndex < this.tracks.length) {
|
|
1251
|
+
this.tracks[trackIndex].events.push({
|
|
1252
|
+
id: `event_${this.eventCounter++}`,
|
|
1253
|
+
type,
|
|
1254
|
+
startTime,
|
|
1255
|
+
duration,
|
|
1256
|
+
data
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
return this;
|
|
1260
|
+
}
|
|
1261
|
+
build() {
|
|
1262
|
+
const maxEnd = this.tracks.reduce(
|
|
1263
|
+
(max, track) => Math.max(max, ...track.events.map((e) => e.startTime + e.duration)),
|
|
1264
|
+
0
|
|
1265
|
+
);
|
|
1266
|
+
return {
|
|
1267
|
+
id: this.id,
|
|
1268
|
+
name: this.name,
|
|
1269
|
+
duration: maxEnd,
|
|
1270
|
+
tracks: this.tracks,
|
|
1271
|
+
loop: false
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
// src/animation/IKSolver.ts
|
|
1277
|
+
var IKSolver = class {
|
|
1278
|
+
chains = /* @__PURE__ */ new Map();
|
|
1279
|
+
footConfig = {
|
|
1280
|
+
rayHeight: 1,
|
|
1281
|
+
rayLength: 2,
|
|
1282
|
+
footOffset: 0.05,
|
|
1283
|
+
blendSpeed: 10,
|
|
1284
|
+
enabled: false
|
|
1285
|
+
};
|
|
1286
|
+
footPositions = /* @__PURE__ */ new Map();
|
|
1287
|
+
// ---------------------------------------------------------------------------
|
|
1288
|
+
// Chain Management
|
|
1289
|
+
// ---------------------------------------------------------------------------
|
|
1290
|
+
addChain(chain) {
|
|
1291
|
+
this.chains.set(chain.id, chain);
|
|
1292
|
+
}
|
|
1293
|
+
removeChain(id) {
|
|
1294
|
+
return this.chains.delete(id);
|
|
1295
|
+
}
|
|
1296
|
+
getChain(id) {
|
|
1297
|
+
return this.chains.get(id);
|
|
1298
|
+
}
|
|
1299
|
+
getChainCount() {
|
|
1300
|
+
return this.chains.size;
|
|
1301
|
+
}
|
|
1302
|
+
setTarget(chainId, x, y, z) {
|
|
1303
|
+
const chain = this.chains.get(chainId);
|
|
1304
|
+
if (chain) chain.target = { x, y, z };
|
|
1305
|
+
}
|
|
1306
|
+
setPoleTarget(chainId, x, y, z) {
|
|
1307
|
+
const chain = this.chains.get(chainId);
|
|
1308
|
+
if (chain) chain.poleTarget = { x, y, z };
|
|
1309
|
+
}
|
|
1310
|
+
setWeight(chainId, weight) {
|
|
1311
|
+
const chain = this.chains.get(chainId);
|
|
1312
|
+
if (chain) chain.weight = Math.max(0, Math.min(1, weight));
|
|
1313
|
+
}
|
|
1314
|
+
// ---------------------------------------------------------------------------
|
|
1315
|
+
// Two-Bone IK
|
|
1316
|
+
// ---------------------------------------------------------------------------
|
|
1317
|
+
solveTwoBone(chainId) {
|
|
1318
|
+
const chain = this.chains.get(chainId);
|
|
1319
|
+
if (!chain || chain.bones.length < 2) return false;
|
|
1320
|
+
const root = chain.bones[0];
|
|
1321
|
+
const mid = chain.bones[1];
|
|
1322
|
+
const end = chain.bones.length > 2 ? chain.bones[2] : null;
|
|
1323
|
+
const a = root.length;
|
|
1324
|
+
const b = mid.length;
|
|
1325
|
+
const target = chain.target;
|
|
1326
|
+
const dx = target[0] - root.position[0];
|
|
1327
|
+
const dy = target[1] - root.position[1];
|
|
1328
|
+
const dz = target[2] - root.position[2];
|
|
1329
|
+
const distSq = dx * dx + dy * dy + dz * dz;
|
|
1330
|
+
const dist = Math.sqrt(distSq);
|
|
1331
|
+
const maxReach = a + b;
|
|
1332
|
+
const minReach = Math.abs(a - b);
|
|
1333
|
+
const clampedDist = Math.max(minReach + 1e-3, Math.min(maxReach - 1e-3, dist));
|
|
1334
|
+
const cosAngle = (a * a + b * b - clampedDist * clampedDist) / (2 * a * b);
|
|
1335
|
+
const _midAngle = Math.acos(Math.max(-1, Math.min(1, cosAngle)));
|
|
1336
|
+
const rootAngle = Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
|
|
1337
|
+
const cosA = (a * a + clampedDist * clampedDist - b * b) / (2 * a * clampedDist);
|
|
1338
|
+
const rootBendAngle = Math.acos(Math.max(-1, Math.min(1, cosA)));
|
|
1339
|
+
const totalRootAngle = rootAngle + rootBendAngle * chain.weight;
|
|
1340
|
+
mid.position = [
|
|
1341
|
+
root.position[0] + Math.cos(totalRootAngle) * a * (dx / (dist || 1)),
|
|
1342
|
+
root.position[1] + Math.sin(totalRootAngle) * a,
|
|
1343
|
+
root.position[2] + Math.cos(totalRootAngle) * a * (dz / (dist || 1))
|
|
1344
|
+
];
|
|
1345
|
+
if (end) {
|
|
1346
|
+
const blendedTarget = [
|
|
1347
|
+
end.position[0] + (target[0] - end.position[0]) * chain.weight,
|
|
1348
|
+
end.position[1] + (target[1] - end.position[1]) * chain.weight,
|
|
1349
|
+
end.position[2] + (target[2] - end.position[2]) * chain.weight
|
|
1350
|
+
];
|
|
1351
|
+
end.position = blendedTarget;
|
|
1352
|
+
}
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1355
|
+
// ---------------------------------------------------------------------------
|
|
1356
|
+
// CCD (Cyclic Coordinate Descent)
|
|
1357
|
+
// ---------------------------------------------------------------------------
|
|
1358
|
+
solveCCD(chainId) {
|
|
1359
|
+
const chain = this.chains.get(chainId);
|
|
1360
|
+
if (!chain || chain.bones.length < 2) return false;
|
|
1361
|
+
const target = chain.target;
|
|
1362
|
+
const bones = chain.bones;
|
|
1363
|
+
for (let iter = 0; iter < chain.iterations; iter++) {
|
|
1364
|
+
for (let i = bones.length - 2; i >= 0; i--) {
|
|
1365
|
+
const bone = bones[i];
|
|
1366
|
+
const endEffector = bones[bones.length - 1];
|
|
1367
|
+
const toEnd = {
|
|
1368
|
+
x: endEffector.position[0] - bone.position[0],
|
|
1369
|
+
y: endEffector.position[1] - bone.position[1],
|
|
1370
|
+
z: endEffector.position[2] - bone.position[2]
|
|
1371
|
+
};
|
|
1372
|
+
const toTarget = {
|
|
1373
|
+
x: target[0] - bone.position[0],
|
|
1374
|
+
y: target[1] - bone.position[1],
|
|
1375
|
+
z: target[2] - bone.position[2]
|
|
1376
|
+
};
|
|
1377
|
+
const angleEnd = Math.atan2(toEnd.y, toEnd.x);
|
|
1378
|
+
const angleTarget = Math.atan2(toTarget.y, toTarget.x);
|
|
1379
|
+
let angle = (angleTarget - angleEnd) * chain.weight;
|
|
1380
|
+
if (bone.minAngle !== void 0 && bone.maxAngle !== void 0) {
|
|
1381
|
+
angle = Math.max(bone.minAngle, Math.min(bone.maxAngle, angle));
|
|
1382
|
+
}
|
|
1383
|
+
const cosA = Math.cos(angle), sinA = Math.sin(angle);
|
|
1384
|
+
for (let j = i + 1; j < bones.length; j++) {
|
|
1385
|
+
const child = bones[j];
|
|
1386
|
+
const rx = child.position[0] - bone.position[0];
|
|
1387
|
+
const ry = child.position[1] - bone.position[1];
|
|
1388
|
+
child.position = [
|
|
1389
|
+
bone.position[0] + rx * cosA - ry * sinA,
|
|
1390
|
+
bone.position[1] + rx * sinA + ry * cosA,
|
|
1391
|
+
child.position[2]
|
|
1392
|
+
];
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
const end = bones[bones.length - 1];
|
|
1396
|
+
const dx = end.position[0] - target[0];
|
|
1397
|
+
const dy = end.position[1] - target[1];
|
|
1398
|
+
const dz = end.position[2] - target[2];
|
|
1399
|
+
if (dx * dx + dy * dy + dz * dz < 1e-3) return true;
|
|
1400
|
+
}
|
|
1401
|
+
return true;
|
|
1402
|
+
}
|
|
1403
|
+
// ---------------------------------------------------------------------------
|
|
1404
|
+
// Foot Placement
|
|
1405
|
+
// ---------------------------------------------------------------------------
|
|
1406
|
+
setFootPlacement(config) {
|
|
1407
|
+
Object.assign(this.footConfig, config);
|
|
1408
|
+
}
|
|
1409
|
+
getFootPlacement() {
|
|
1410
|
+
return { ...this.footConfig };
|
|
1411
|
+
}
|
|
1412
|
+
updateFootPlacement(footId, groundHeight, dt) {
|
|
1413
|
+
const current = this.footPositions.get(footId) ?? { x: 0, y: 0, z: 0 };
|
|
1414
|
+
const targetY = groundHeight + this.footConfig.footOffset;
|
|
1415
|
+
const blend = Math.min(1, this.footConfig.blendSpeed * dt);
|
|
1416
|
+
const result = {
|
|
1417
|
+
x: current.x,
|
|
1418
|
+
y: current.y + (targetY - current.y) * blend,
|
|
1419
|
+
z: current.z
|
|
1420
|
+
};
|
|
1421
|
+
this.footPositions.set(footId, result);
|
|
1422
|
+
return result;
|
|
1423
|
+
}
|
|
1424
|
+
// ---------------------------------------------------------------------------
|
|
1425
|
+
// Solve All
|
|
1426
|
+
// ---------------------------------------------------------------------------
|
|
1427
|
+
solveAll() {
|
|
1428
|
+
for (const chain of this.chains.values()) {
|
|
1429
|
+
if (chain.bones.length === 2 || chain.bones.length === 3) {
|
|
1430
|
+
this.solveTwoBone(chain.id);
|
|
1431
|
+
} else {
|
|
1432
|
+
this.solveCCD(chain.id);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
// src/animation/MorphTargets.ts
|
|
1439
|
+
var MorphTargetSystem = class {
|
|
1440
|
+
targets = /* @__PURE__ */ new Map();
|
|
1441
|
+
presets = /* @__PURE__ */ new Map();
|
|
1442
|
+
vertexCount;
|
|
1443
|
+
constructor(vertexCount) {
|
|
1444
|
+
this.vertexCount = vertexCount;
|
|
1445
|
+
}
|
|
1446
|
+
// ---------------------------------------------------------------------------
|
|
1447
|
+
// Target Management
|
|
1448
|
+
// ---------------------------------------------------------------------------
|
|
1449
|
+
addTarget(name, deltas) {
|
|
1450
|
+
this.targets.set(name, { name, deltas, weight: 0 });
|
|
1451
|
+
}
|
|
1452
|
+
removeTarget(name) {
|
|
1453
|
+
this.targets.delete(name);
|
|
1454
|
+
}
|
|
1455
|
+
setWeight(name, weight) {
|
|
1456
|
+
const target = this.targets.get(name);
|
|
1457
|
+
if (target) target.weight = Math.max(0, Math.min(1, weight));
|
|
1458
|
+
}
|
|
1459
|
+
getWeight(name) {
|
|
1460
|
+
return this.targets.get(name)?.weight ?? 0;
|
|
1461
|
+
}
|
|
1462
|
+
// ---------------------------------------------------------------------------
|
|
1463
|
+
// Preset Management
|
|
1464
|
+
// ---------------------------------------------------------------------------
|
|
1465
|
+
addPreset(name, weights) {
|
|
1466
|
+
this.presets.set(name, { name, weights });
|
|
1467
|
+
}
|
|
1468
|
+
applyPreset(name) {
|
|
1469
|
+
const preset = this.presets.get(name);
|
|
1470
|
+
if (!preset) return;
|
|
1471
|
+
for (const [targetName, weight] of preset.weights) {
|
|
1472
|
+
this.setWeight(targetName, weight);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
// ---------------------------------------------------------------------------
|
|
1476
|
+
// Vertex Computation
|
|
1477
|
+
// ---------------------------------------------------------------------------
|
|
1478
|
+
computeDeformedPositions(basePositions) {
|
|
1479
|
+
const result = new Float32Array(basePositions);
|
|
1480
|
+
for (const target of this.targets.values()) {
|
|
1481
|
+
if (target.weight <= 0) continue;
|
|
1482
|
+
for (const delta of target.deltas) {
|
|
1483
|
+
const idx = delta.vertexIndex * 3;
|
|
1484
|
+
if (idx + 2 < result.length) {
|
|
1485
|
+
result[idx] += delta.dx * target.weight;
|
|
1486
|
+
result[idx + 1] += delta.dy * target.weight;
|
|
1487
|
+
result[idx + 2] += delta.dz * target.weight;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return result;
|
|
1492
|
+
}
|
|
1493
|
+
// ---------------------------------------------------------------------------
|
|
1494
|
+
// Interpolation
|
|
1495
|
+
// ---------------------------------------------------------------------------
|
|
1496
|
+
lerpWeights(targetWeights, t) {
|
|
1497
|
+
for (const [name, targetWeight] of targetWeights) {
|
|
1498
|
+
const current = this.getWeight(name);
|
|
1499
|
+
this.setWeight(name, current + (targetWeight - current) * t);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
// ---------------------------------------------------------------------------
|
|
1503
|
+
// Queries
|
|
1504
|
+
// ---------------------------------------------------------------------------
|
|
1505
|
+
getTargetCount() {
|
|
1506
|
+
return this.targets.size;
|
|
1507
|
+
}
|
|
1508
|
+
getActiveTargets() {
|
|
1509
|
+
return [...this.targets.values()].filter((t) => t.weight > 0).map((t) => t.name);
|
|
1510
|
+
}
|
|
1511
|
+
getVertexCount() {
|
|
1512
|
+
return this.vertexCount;
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
// src/animation/SkeletalBlender.ts
|
|
1517
|
+
var SkeletalBlender = class {
|
|
1518
|
+
layers = [];
|
|
1519
|
+
blendedPoses = /* @__PURE__ */ new Map();
|
|
1520
|
+
// ---------------------------------------------------------------------------
|
|
1521
|
+
// Layer Management
|
|
1522
|
+
// ---------------------------------------------------------------------------
|
|
1523
|
+
addLayer(layer) {
|
|
1524
|
+
this.layers.push(layer);
|
|
1525
|
+
}
|
|
1526
|
+
removeLayer(id) {
|
|
1527
|
+
this.layers = this.layers.filter((l) => l.id !== id);
|
|
1528
|
+
}
|
|
1529
|
+
setLayerWeight(id, weight) {
|
|
1530
|
+
const layer = this.layers.find((l) => l.id === id);
|
|
1531
|
+
if (layer) layer.weight = Math.max(0, Math.min(1, weight));
|
|
1532
|
+
}
|
|
1533
|
+
getLayerWeight(id) {
|
|
1534
|
+
return this.layers.find((l) => l.id === id)?.weight ?? 0;
|
|
1535
|
+
}
|
|
1536
|
+
// ---------------------------------------------------------------------------
|
|
1537
|
+
// Blending
|
|
1538
|
+
// ---------------------------------------------------------------------------
|
|
1539
|
+
blend() {
|
|
1540
|
+
this.blendedPoses.clear();
|
|
1541
|
+
for (const layer of this.layers) {
|
|
1542
|
+
if (layer.weight <= 0) continue;
|
|
1543
|
+
for (const pose of layer.poses) {
|
|
1544
|
+
if (layer.mask && !layer.mask.has(pose.boneId)) continue;
|
|
1545
|
+
const existing = this.blendedPoses.get(pose.boneId);
|
|
1546
|
+
if (!existing || layer.mode === "override") {
|
|
1547
|
+
const base = existing ?? {
|
|
1548
|
+
boneId: pose.boneId,
|
|
1549
|
+
tx: 0,
|
|
1550
|
+
ty: 0,
|
|
1551
|
+
tz: 0,
|
|
1552
|
+
sx: 1,
|
|
1553
|
+
sy: 1,
|
|
1554
|
+
sz: 1
|
|
1555
|
+
};
|
|
1556
|
+
this.blendedPoses.set(pose.boneId, this.lerpPose(base, pose, layer.weight));
|
|
1557
|
+
} else {
|
|
1558
|
+
this.blendedPoses.set(pose.boneId, {
|
|
1559
|
+
boneId: pose.boneId,
|
|
1560
|
+
tx: existing.tx + pose.tx * layer.weight,
|
|
1561
|
+
ty: existing.ty + pose.ty * layer.weight,
|
|
1562
|
+
tz: existing.tz + pose.tz * layer.weight,
|
|
1563
|
+
sx: existing.sx * (1 + (pose.sx - 1) * layer.weight),
|
|
1564
|
+
sy: existing.sy * (1 + (pose.sy - 1) * layer.weight),
|
|
1565
|
+
sz: existing.sz * (1 + (pose.sz - 1) * layer.weight)
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return new Map(this.blendedPoses);
|
|
1571
|
+
}
|
|
1572
|
+
// ---------------------------------------------------------------------------
|
|
1573
|
+
// Crossfade
|
|
1574
|
+
// ---------------------------------------------------------------------------
|
|
1575
|
+
crossfade(fromId, toId, t) {
|
|
1576
|
+
this.setLayerWeight(fromId, 1 - t);
|
|
1577
|
+
this.setLayerWeight(toId, t);
|
|
1578
|
+
}
|
|
1579
|
+
// ---------------------------------------------------------------------------
|
|
1580
|
+
// Helpers
|
|
1581
|
+
// ---------------------------------------------------------------------------
|
|
1582
|
+
lerpPose(a, b, t) {
|
|
1583
|
+
return {
|
|
1584
|
+
boneId: a.boneId,
|
|
1585
|
+
tx: a.tx + (b.tx - a.tx) * t,
|
|
1586
|
+
ty: a.ty + (b.ty - a.ty) * t,
|
|
1587
|
+
tz: a.tz + (b.tz - a.tz) * t,
|
|
1588
|
+
sx: a.sx + (b.sx - a.sx) * t,
|
|
1589
|
+
sy: a.sy + (b.sy - a.sy) * t,
|
|
1590
|
+
sz: a.sz + (b.sz - a.sz) * t
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
getLayerCount() {
|
|
1594
|
+
return this.layers.length;
|
|
1595
|
+
}
|
|
1596
|
+
getBlendedPose(boneId) {
|
|
1597
|
+
return this.blendedPoses.get(boneId);
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1601
|
+
// src/animation/Timeline.ts
|
|
1602
|
+
var Timeline = class {
|
|
1603
|
+
entries = [];
|
|
1604
|
+
config;
|
|
1605
|
+
engine;
|
|
1606
|
+
elapsed = 0;
|
|
1607
|
+
isPlaying = false;
|
|
1608
|
+
isPaused = false;
|
|
1609
|
+
currentLoop = 0;
|
|
1610
|
+
direction = 1;
|
|
1611
|
+
totalDuration = 0;
|
|
1612
|
+
constructor(config = {}, engine) {
|
|
1613
|
+
this.config = {
|
|
1614
|
+
mode: "sequential",
|
|
1615
|
+
loop: false,
|
|
1616
|
+
loopCount: 1,
|
|
1617
|
+
pingPong: false,
|
|
1618
|
+
delay: 0,
|
|
1619
|
+
...config
|
|
1620
|
+
};
|
|
1621
|
+
this.engine = engine || new AnimationEngine();
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Add an animation to the timeline.
|
|
1625
|
+
*/
|
|
1626
|
+
add(clip, setter, startOffset) {
|
|
1627
|
+
this.entries.push({ clip, setter, startOffset });
|
|
1628
|
+
this.recalcDuration();
|
|
1629
|
+
return this;
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Start playing the timeline.
|
|
1633
|
+
*/
|
|
1634
|
+
play() {
|
|
1635
|
+
this.elapsed = -this.config.delay;
|
|
1636
|
+
this.isPlaying = true;
|
|
1637
|
+
this.isPaused = false;
|
|
1638
|
+
this.currentLoop = 0;
|
|
1639
|
+
this.direction = 1;
|
|
1640
|
+
}
|
|
1641
|
+
pause() {
|
|
1642
|
+
this.isPaused = true;
|
|
1643
|
+
}
|
|
1644
|
+
resume() {
|
|
1645
|
+
this.isPaused = false;
|
|
1646
|
+
}
|
|
1647
|
+
stop() {
|
|
1648
|
+
this.isPlaying = false;
|
|
1649
|
+
this.engine.clear();
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Update the timeline. Call every frame.
|
|
1653
|
+
*/
|
|
1654
|
+
update(delta) {
|
|
1655
|
+
if (!this.isPlaying || this.isPaused) return;
|
|
1656
|
+
this.elapsed += delta * this.direction;
|
|
1657
|
+
if (this.elapsed < 0) return;
|
|
1658
|
+
const _t = this.totalDuration > 0 ? this.elapsed / this.totalDuration : 1;
|
|
1659
|
+
if (this.config.mode === "sequential") {
|
|
1660
|
+
this.updateSequential(this.elapsed);
|
|
1661
|
+
} else {
|
|
1662
|
+
this.updateParallel(this.elapsed);
|
|
1663
|
+
}
|
|
1664
|
+
this.engine.update(delta);
|
|
1665
|
+
if (this.elapsed >= this.totalDuration) {
|
|
1666
|
+
this.handleCompletion();
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
updateSequential(elapsed) {
|
|
1670
|
+
let cumulativeTime = 0;
|
|
1671
|
+
for (const entry of this.entries) {
|
|
1672
|
+
const clipStart = cumulativeTime + (entry.clip.delay || 0);
|
|
1673
|
+
const clipEnd = clipStart + entry.clip.duration;
|
|
1674
|
+
if (elapsed >= clipStart && elapsed < clipEnd) {
|
|
1675
|
+
if (!this.engine.isActive(entry.clip.id)) {
|
|
1676
|
+
this.engine.play(entry.clip, entry.setter);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
cumulativeTime = clipEnd;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
updateParallel(elapsed) {
|
|
1683
|
+
for (const entry of this.entries) {
|
|
1684
|
+
const offset = entry.startOffset || 0;
|
|
1685
|
+
if (elapsed >= offset && !this.engine.isActive(entry.clip.id)) {
|
|
1686
|
+
const adjustedClip = {
|
|
1687
|
+
...entry.clip,
|
|
1688
|
+
delay: Math.max(0, offset - elapsed)
|
|
1689
|
+
};
|
|
1690
|
+
this.engine.play(adjustedClip, entry.setter);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
handleCompletion() {
|
|
1695
|
+
this.currentLoop++;
|
|
1696
|
+
if (this.config.pingPong) {
|
|
1697
|
+
this.direction *= -1;
|
|
1698
|
+
this.elapsed = this.direction === -1 ? this.totalDuration : 0;
|
|
1699
|
+
this.engine.clear();
|
|
1700
|
+
if (this.config.onLoop) this.config.onLoop(this.currentLoop);
|
|
1701
|
+
if (!this.config.loop && this.currentLoop >= this.config.loopCount * 2) {
|
|
1702
|
+
this.isPlaying = false;
|
|
1703
|
+
if (this.config.onComplete) this.config.onComplete();
|
|
1704
|
+
}
|
|
1705
|
+
} else if (this.config.loop) {
|
|
1706
|
+
if (this.config.loopCount !== -1 && this.currentLoop >= this.config.loopCount) {
|
|
1707
|
+
this.isPlaying = false;
|
|
1708
|
+
if (this.config.onComplete) this.config.onComplete();
|
|
1709
|
+
} else {
|
|
1710
|
+
this.elapsed = 0;
|
|
1711
|
+
this.engine.clear();
|
|
1712
|
+
if (this.config.onLoop) this.config.onLoop(this.currentLoop);
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
this.isPlaying = false;
|
|
1716
|
+
if (this.config.onComplete) this.config.onComplete();
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
recalcDuration() {
|
|
1720
|
+
if (this.config.mode === "sequential") {
|
|
1721
|
+
this.totalDuration = this.entries.reduce(
|
|
1722
|
+
(sum, e) => sum + e.clip.duration + (e.clip.delay || 0),
|
|
1723
|
+
0
|
|
1724
|
+
);
|
|
1725
|
+
} else {
|
|
1726
|
+
this.totalDuration = this.entries.reduce(
|
|
1727
|
+
(max, e) => Math.max(max, (e.startOffset || 0) + e.clip.duration),
|
|
1728
|
+
0
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Get total duration of the timeline.
|
|
1734
|
+
*/
|
|
1735
|
+
getDuration() {
|
|
1736
|
+
return this.totalDuration;
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Get current elapsed time.
|
|
1740
|
+
*/
|
|
1741
|
+
getElapsed() {
|
|
1742
|
+
return Math.max(0, this.elapsed);
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Get normalized progress (0-1).
|
|
1746
|
+
*/
|
|
1747
|
+
getProgress() {
|
|
1748
|
+
return this.totalDuration > 0 ? Math.max(0, Math.min(1, this.elapsed / this.totalDuration)) : 0;
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
// src/animation/TransitionSystem.ts
|
|
1753
|
+
var TransitionSystem = class {
|
|
1754
|
+
engine;
|
|
1755
|
+
constructor(engine) {
|
|
1756
|
+
this.engine = engine || new AnimationEngine();
|
|
1757
|
+
}
|
|
1758
|
+
getEngine() {
|
|
1759
|
+
return this.engine;
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Fade in/out (opacity 0 <-> 1)
|
|
1763
|
+
*/
|
|
1764
|
+
fade(nodeId, direction, setter, options = {}) {
|
|
1765
|
+
const { duration = 0.3, easing = Easing.easeOutQuad, delay = 0, onComplete } = options;
|
|
1766
|
+
const from = direction === "in" ? 0 : 1;
|
|
1767
|
+
const to = direction === "in" ? 1 : 0;
|
|
1768
|
+
this.engine.play(
|
|
1769
|
+
{
|
|
1770
|
+
id: `${nodeId}_fade`,
|
|
1771
|
+
property: "opacity",
|
|
1772
|
+
keyframes: [
|
|
1773
|
+
{ time: 0, value: from },
|
|
1774
|
+
{ time: 1, value: to, easing }
|
|
1775
|
+
],
|
|
1776
|
+
duration,
|
|
1777
|
+
loop: false,
|
|
1778
|
+
pingPong: false,
|
|
1779
|
+
delay,
|
|
1780
|
+
onComplete
|
|
1781
|
+
},
|
|
1782
|
+
setter
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Scale in/out (scale 0 <-> 1)
|
|
1787
|
+
*/
|
|
1788
|
+
scale(nodeId, direction, setter, options = {}) {
|
|
1789
|
+
const { duration = 0.35, easing = Easing.easeOutBack, delay = 0, onComplete } = options;
|
|
1790
|
+
const from = direction === "in" ? 0 : 1;
|
|
1791
|
+
const to = direction === "in" ? 1 : 0;
|
|
1792
|
+
this.engine.play(
|
|
1793
|
+
{
|
|
1794
|
+
id: `${nodeId}_scale`,
|
|
1795
|
+
property: "scale",
|
|
1796
|
+
keyframes: [
|
|
1797
|
+
{ time: 0, value: from },
|
|
1798
|
+
{ time: 1, value: to, easing }
|
|
1799
|
+
],
|
|
1800
|
+
duration,
|
|
1801
|
+
loop: false,
|
|
1802
|
+
pingPong: false,
|
|
1803
|
+
delay,
|
|
1804
|
+
onComplete
|
|
1805
|
+
},
|
|
1806
|
+
setter
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Slide from a direction (e.g., slide up from below)
|
|
1811
|
+
*/
|
|
1812
|
+
slide(nodeId, direction, axis, distance, setter, options = {}) {
|
|
1813
|
+
const { duration = 0.4, easing = Easing.easeOutCubic, delay = 0, onComplete } = options;
|
|
1814
|
+
const from = direction === "in" ? distance : 0;
|
|
1815
|
+
const to = direction === "in" ? 0 : distance;
|
|
1816
|
+
this.engine.play(
|
|
1817
|
+
{
|
|
1818
|
+
id: `${nodeId}_slide_${axis}`,
|
|
1819
|
+
property: `slide_${axis}`,
|
|
1820
|
+
keyframes: [
|
|
1821
|
+
{ time: 0, value: from },
|
|
1822
|
+
{ time: 1, value: to, easing }
|
|
1823
|
+
],
|
|
1824
|
+
duration,
|
|
1825
|
+
loop: false,
|
|
1826
|
+
pingPong: false,
|
|
1827
|
+
delay,
|
|
1828
|
+
onComplete
|
|
1829
|
+
},
|
|
1830
|
+
setter
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Combined: Scale + Fade (common for dialogs/menus)
|
|
1835
|
+
*/
|
|
1836
|
+
popIn(nodeId, scaleSetter, opacitySetter, options = {}) {
|
|
1837
|
+
const { duration = 0.35, delay = 0, onComplete } = options;
|
|
1838
|
+
this.scale(nodeId, "in", scaleSetter, { duration, delay, easing: Easing.easeOutBack });
|
|
1839
|
+
this.fade(nodeId, "in", opacitySetter, { duration: duration * 0.6, delay, onComplete });
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Combined: Scale + Fade out
|
|
1843
|
+
*/
|
|
1844
|
+
popOut(nodeId, scaleSetter, opacitySetter, options = {}) {
|
|
1845
|
+
const { duration = 0.25, delay = 0, onComplete } = options;
|
|
1846
|
+
this.scale(nodeId, "out", scaleSetter, { duration, delay, easing: Easing.easeInQuad });
|
|
1847
|
+
this.fade(nodeId, "out", opacitySetter, { duration, delay, onComplete });
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Update all transitions. Must be called every frame.
|
|
1851
|
+
*/
|
|
1852
|
+
update(delta) {
|
|
1853
|
+
this.engine.update(delta);
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
export {
|
|
1858
|
+
AnimClip,
|
|
1859
|
+
Easing,
|
|
1860
|
+
AnimationEngine,
|
|
1861
|
+
AnimationGraph,
|
|
1862
|
+
SpringPresets,
|
|
1863
|
+
SpringAnimator,
|
|
1864
|
+
Vec3SpringAnimator,
|
|
1865
|
+
getSharedAnimationEngine,
|
|
1866
|
+
animationTraitHandler,
|
|
1867
|
+
AnimationTransitionSystem,
|
|
1868
|
+
AvatarController,
|
|
1869
|
+
BoneSystem,
|
|
1870
|
+
CutsceneTimeline,
|
|
1871
|
+
CutsceneBuilder,
|
|
1872
|
+
IKSolver,
|
|
1873
|
+
MorphTargetSystem,
|
|
1874
|
+
SkeletalBlender,
|
|
1875
|
+
Timeline,
|
|
1876
|
+
TransitionSystem,
|
|
1877
|
+
animation_exports
|
|
1878
|
+
};
|