@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.
Files changed (192) hide show
  1. package/dist/AutoMesher-CK47F6AV.js +17 -0
  2. package/dist/GPUBuffers-2LHBCD7X.js +9 -0
  3. package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
  4. package/dist/animation/index.cjs +38 -38
  5. package/dist/animation/index.d.cts +1 -1
  6. package/dist/animation/index.d.ts +1 -1
  7. package/dist/animation/index.js +1 -1
  8. package/dist/audio/index.cjs +16 -6
  9. package/dist/audio/index.d.cts +1 -1
  10. package/dist/audio/index.d.ts +1 -1
  11. package/dist/audio/index.js +1 -1
  12. package/dist/camera/index.cjs +23 -23
  13. package/dist/camera/index.d.cts +1 -1
  14. package/dist/camera/index.d.ts +1 -1
  15. package/dist/camera/index.js +1 -1
  16. package/dist/character/index.cjs +6 -4
  17. package/dist/character/index.js +1 -1
  18. package/dist/choreography/index.cjs +1194 -0
  19. package/dist/choreography/index.d.cts +687 -0
  20. package/dist/choreography/index.d.ts +687 -0
  21. package/dist/choreography/index.js +1156 -0
  22. package/dist/chunk-2CSNRI2N.js +217 -0
  23. package/dist/chunk-33T2WINR.js +266 -0
  24. package/dist/chunk-35R73OFM.js +1257 -0
  25. package/dist/chunk-4MMDSUNP.js +1256 -0
  26. package/dist/chunk-5V6HOU72.js +319 -0
  27. package/dist/chunk-6QOP6PYF.js +1038 -0
  28. package/dist/chunk-7KMJVHIL.js +8944 -0
  29. package/dist/chunk-7VPUC62U.js +1106 -0
  30. package/dist/chunk-A2Y6RCAT.js +1878 -0
  31. package/dist/chunk-AHM42MK6.js +8944 -0
  32. package/dist/chunk-BL7IDTHE.js +218 -0
  33. package/dist/chunk-CITOMSWL.js +10462 -0
  34. package/dist/chunk-CXDPKW2K.js +8944 -0
  35. package/dist/chunk-CXZPLD4S.js +223 -0
  36. package/dist/chunk-CZYJE7IH.js +5169 -0
  37. package/dist/chunk-D2OP7YC7.js +6325 -0
  38. package/dist/chunk-EDRVQHUU.js +1544 -0
  39. package/dist/chunk-EJSLOOW2.js +3589 -0
  40. package/dist/chunk-F53SFGW5.js +1878 -0
  41. package/dist/chunk-HCFPELPY.js +919 -0
  42. package/dist/chunk-HNEE36PY.js +93 -0
  43. package/dist/chunk-HYXNV36F.js +1256 -0
  44. package/dist/chunk-IB7KHVFY.js +821 -0
  45. package/dist/chunk-IBBO7YYG.js +690 -0
  46. package/dist/chunk-ILIBGINU.js +5470 -0
  47. package/dist/chunk-IS4MHLKN.js +5479 -0
  48. package/dist/chunk-JT2PFKWD.js +5479 -0
  49. package/dist/chunk-K4CUB4NY.js +1038 -0
  50. package/dist/chunk-KATDQXRJ.js +10462 -0
  51. package/dist/chunk-KBQE6ZFJ.js +8944 -0
  52. package/dist/chunk-KBVD5K7E.js +560 -0
  53. package/dist/chunk-KCDPVQRY.js +4088 -0
  54. package/dist/chunk-KN4QJPKN.js +8944 -0
  55. package/dist/chunk-KWJ3ROSI.js +8944 -0
  56. package/dist/chunk-L45VF6DD.js +919 -0
  57. package/dist/chunk-LY4T37YK.js +307 -0
  58. package/dist/chunk-MDN5WZXA.js +1544 -0
  59. package/dist/chunk-MGCDP6VU.js +928 -0
  60. package/dist/chunk-NCX7X6G2.js +8681 -0
  61. package/dist/chunk-OF54BPVD.js +913 -0
  62. package/dist/chunk-OWSN2Q3Q.js +690 -0
  63. package/dist/chunk-PRRB5TTA.js +406 -0
  64. package/dist/chunk-PXWVQF76.js +4086 -0
  65. package/dist/chunk-PYCOIDT2.js +812 -0
  66. package/dist/chunk-PZCSADOV.js +928 -0
  67. package/dist/chunk-Q2XBVS2K.js +1038 -0
  68. package/dist/chunk-QDZRXWN5.js +1776 -0
  69. package/dist/chunk-RNWOZ6WQ.js +913 -0
  70. package/dist/chunk-ROLFT4CJ.js +1693 -0
  71. package/dist/chunk-SLTJRZ2N.js +266 -0
  72. package/dist/chunk-SRUS5XSU.js +4088 -0
  73. package/dist/chunk-TKCA3WZ5.js +5409 -0
  74. package/dist/chunk-TNRMXYI2.js +1650 -0
  75. package/dist/chunk-TQB3GJGM.js +9763 -0
  76. package/dist/chunk-TUFGXG6K.js +510 -0
  77. package/dist/chunk-U6KMTGQJ.js +632 -0
  78. package/dist/chunk-VMGJQST6.js +8681 -0
  79. package/dist/chunk-X4F4TCG4.js +5470 -0
  80. package/dist/chunk-ZIFROE75.js +1544 -0
  81. package/dist/chunk-ZIJQYHSQ.js +1204 -0
  82. package/dist/combat/index.cjs +4 -4
  83. package/dist/combat/index.d.cts +1 -1
  84. package/dist/combat/index.d.ts +1 -1
  85. package/dist/combat/index.js +1 -1
  86. package/dist/ecs/index.cjs +1 -1
  87. package/dist/ecs/index.js +1 -1
  88. package/dist/environment/index.cjs +14 -14
  89. package/dist/environment/index.d.cts +1 -1
  90. package/dist/environment/index.d.ts +1 -1
  91. package/dist/environment/index.js +1 -1
  92. package/dist/gpu/index.cjs +4810 -0
  93. package/dist/gpu/index.js +3714 -0
  94. package/dist/hologram/index.cjs +27 -1
  95. package/dist/hologram/index.js +1 -1
  96. package/dist/index-B2PIsAmR.d.cts +2180 -0
  97. package/dist/index-B2PIsAmR.d.ts +2180 -0
  98. package/dist/index-BHySEPX7.d.cts +2921 -0
  99. package/dist/index-BJV21zuy.d.cts +341 -0
  100. package/dist/index-BJV21zuy.d.ts +341 -0
  101. package/dist/index-BQutTphC.d.cts +790 -0
  102. package/dist/index-ByIq2XrS.d.cts +3910 -0
  103. package/dist/index-BysHjDSO.d.cts +224 -0
  104. package/dist/index-BysHjDSO.d.ts +224 -0
  105. package/dist/index-CKwAJGck.d.ts +455 -0
  106. package/dist/index-CUl3QstQ.d.cts +3006 -0
  107. package/dist/index-CUl3QstQ.d.ts +3006 -0
  108. package/dist/index-CmYtNiI-.d.cts +953 -0
  109. package/dist/index-CmYtNiI-.d.ts +953 -0
  110. package/dist/index-CnRzWxi_.d.cts +522 -0
  111. package/dist/index-CnRzWxi_.d.ts +522 -0
  112. package/dist/index-CwRWbSC7.d.ts +2921 -0
  113. package/dist/index-CxKIBstO.d.ts +790 -0
  114. package/dist/index-DJ6-R8vh.d.cts +455 -0
  115. package/dist/index-DQKisbcI.d.cts +4968 -0
  116. package/dist/index-DQKisbcI.d.ts +4968 -0
  117. package/dist/index-DRT2zJez.d.ts +3910 -0
  118. package/dist/index-DfNLiAka.d.cts +192 -0
  119. package/dist/index-DfNLiAka.d.ts +192 -0
  120. package/dist/index-nMvkoRm8.d.cts +405 -0
  121. package/dist/index-nMvkoRm8.d.ts +405 -0
  122. package/dist/index-s9yOFU37.d.cts +604 -0
  123. package/dist/index-s9yOFU37.d.ts +604 -0
  124. package/dist/index.cjs +22966 -6960
  125. package/dist/index.d.cts +864 -20
  126. package/dist/index.d.ts +864 -20
  127. package/dist/index.js +3062 -48
  128. package/dist/input/index.cjs +1 -1
  129. package/dist/input/index.js +1 -1
  130. package/dist/orbital/index.cjs +3 -3
  131. package/dist/orbital/index.d.cts +1 -1
  132. package/dist/orbital/index.d.ts +1 -1
  133. package/dist/orbital/index.js +1 -1
  134. package/dist/particles/index.cjs +16 -16
  135. package/dist/particles/index.d.cts +1 -1
  136. package/dist/particles/index.d.ts +1 -1
  137. package/dist/particles/index.js +1 -1
  138. package/dist/physics/index.cjs +2377 -21
  139. package/dist/physics/index.d.cts +1 -1
  140. package/dist/physics/index.d.ts +1 -1
  141. package/dist/physics/index.js +35 -1
  142. package/dist/postfx/index.cjs +3491 -0
  143. package/dist/postfx/index.js +93 -0
  144. package/dist/procedural/index.cjs +1 -1
  145. package/dist/procedural/index.js +1 -1
  146. package/dist/puppeteer-5VF6KDVO.js +52197 -0
  147. package/dist/puppeteer-IZVZ3SG4.js +52197 -0
  148. package/dist/rendering/index.cjs +33 -32
  149. package/dist/rendering/index.d.cts +1 -1
  150. package/dist/rendering/index.d.ts +1 -1
  151. package/dist/rendering/index.js +8 -6
  152. package/dist/runtime/index.cjs +23 -13
  153. package/dist/runtime/index.d.cts +1 -1
  154. package/dist/runtime/index.d.ts +1 -1
  155. package/dist/runtime/index.js +8 -6
  156. package/dist/runtime/protocols/index.cjs +349 -0
  157. package/dist/runtime/protocols/index.js +15 -0
  158. package/dist/scene/index.cjs +8 -8
  159. package/dist/scene/index.d.cts +1 -1
  160. package/dist/scene/index.d.ts +1 -1
  161. package/dist/scene/index.js +1 -1
  162. package/dist/shader/index.cjs +3087 -0
  163. package/dist/shader/index.js +3044 -0
  164. package/dist/simulation/index.cjs +10680 -0
  165. package/dist/simulation/index.d.cts +3 -0
  166. package/dist/simulation/index.d.ts +3 -0
  167. package/dist/simulation/index.js +307 -0
  168. package/dist/spatial/index.cjs +2443 -0
  169. package/dist/spatial/index.d.cts +1545 -0
  170. package/dist/spatial/index.d.ts +1545 -0
  171. package/dist/spatial/index.js +2400 -0
  172. package/dist/terrain/index.cjs +1 -1
  173. package/dist/terrain/index.d.cts +1 -1
  174. package/dist/terrain/index.d.ts +1 -1
  175. package/dist/terrain/index.js +1 -1
  176. package/dist/transformers.node-4NKAPD5U.js +45620 -0
  177. package/dist/vm/index.cjs +7 -8
  178. package/dist/vm/index.d.cts +1 -1
  179. package/dist/vm/index.d.ts +1 -1
  180. package/dist/vm/index.js +1 -1
  181. package/dist/vm-bridge/index.cjs +2 -2
  182. package/dist/vm-bridge/index.d.cts +2 -2
  183. package/dist/vm-bridge/index.d.ts +2 -2
  184. package/dist/vm-bridge/index.js +1 -1
  185. package/dist/vr/index.cjs +6 -6
  186. package/dist/vr/index.js +1 -1
  187. package/dist/world/index.cjs +3 -3
  188. package/dist/world/index.d.cts +1 -1
  189. package/dist/world/index.d.ts +1 -1
  190. package/dist/world/index.js +1 -1
  191. package/package.json +53 -21
  192. 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.x,
935
+ input.leftHand.position.y,
936
+ input.leftHand.position.z
937
+ );
938
+ this.solver.setTarget(
939
+ "rightArm",
940
+ input.rightHand.position.x,
941
+ input.rightHand.position.y,
942
+ input.rightHand.position.z
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.x - root.position.x;
1327
+ const dy = target.y - root.position.y;
1328
+ const dz = target.z - root.position.z;
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
+ x: root.position.x + Math.cos(totalRootAngle) * a * (dx / (dist || 1)),
1342
+ y: root.position.y + Math.sin(totalRootAngle) * a,
1343
+ z: root.position.z + Math.cos(totalRootAngle) * a * (dz / (dist || 1))
1344
+ };
1345
+ if (end) {
1346
+ const blendedTarget = {
1347
+ x: end.position.x + (target.x - end.position.x) * chain.weight,
1348
+ y: end.position.y + (target.y - end.position.y) * chain.weight,
1349
+ z: end.position.z + (target.z - end.position.z) * 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.x - bone.position.x,
1369
+ y: endEffector.position.y - bone.position.y,
1370
+ z: endEffector.position.z - bone.position.z
1371
+ };
1372
+ const toTarget = {
1373
+ x: target.x - bone.position.x,
1374
+ y: target.y - bone.position.y,
1375
+ z: target.z - bone.position.z
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.x - bone.position.x;
1387
+ const ry = child.position.y - bone.position.y;
1388
+ child.position = {
1389
+ x: bone.position.x + rx * cosA - ry * sinA,
1390
+ y: bone.position.y + rx * sinA + ry * cosA,
1391
+ z: child.position.z
1392
+ };
1393
+ }
1394
+ }
1395
+ const end = bones[bones.length - 1];
1396
+ const dx = end.position.x - target.x;
1397
+ const dy = end.position.y - target.y;
1398
+ const dz = end.position.z - target.z;
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
+ };