@nice2dev/game-engine 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,835 @@
1
+ /**
2
+ * @file AnimationSystem.ts
3
+ * @description Animation System for Nice2Dev Game Engine
4
+ * PRO-1.11.4: Animation System
5
+ *
6
+ * Features:
7
+ * - Skeletal animation
8
+ * - Blend trees
9
+ * - Animation state machine
10
+ * - Inverse Kinematics (IK)
11
+ * - Procedural animation
12
+ * - Morph targets
13
+ * - Animation events
14
+ */
15
+ // ============================================================
16
+ // Animator Controller
17
+ // ============================================================
18
+ class AnimatorController {
19
+ constructor(boneDescriptors) {
20
+ this.clips = new Map();
21
+ this.layers = [];
22
+ this.stateMachine = null;
23
+ this.parameters = new Map();
24
+ this.ikChains = new Map();
25
+ this.morphTargets = null;
26
+ this.eventCallbacks = new Map();
27
+ this.skeleton = new SkeletonImpl(boneDescriptors);
28
+ }
29
+ // ─── Clips ─────────────────────────────────────────────
30
+ addClip(descriptor) {
31
+ const clip = new AnimationClipImpl(descriptor);
32
+ this.clips.set(descriptor.name, clip);
33
+ return clip;
34
+ }
35
+ getClip(name) {
36
+ return this.clips.get(name);
37
+ }
38
+ removeClip(name) {
39
+ this.clips.delete(name);
40
+ }
41
+ // ─── Layers ────────────────────────────────────────────
42
+ addLayer(descriptor) {
43
+ const layer = new AnimationLayerImpl(descriptor, this.skeleton, this.clips, this.parameters);
44
+ this.layers.push(layer);
45
+ return layer;
46
+ }
47
+ getLayer(nameOrIndex) {
48
+ if (typeof nameOrIndex === 'number') {
49
+ return this.layers[nameOrIndex];
50
+ }
51
+ return this.layers.find((l) => l.name === nameOrIndex);
52
+ }
53
+ getLayerCount() {
54
+ return this.layers.length;
55
+ }
56
+ // ─── State Machine ─────────────────────────────────────
57
+ createStateMachine(descriptor) {
58
+ this.stateMachine = new AnimationStateMachine(descriptor, this.clips, this.parameters);
59
+ // Initialize parameters
60
+ if (descriptor.parameters) {
61
+ for (const param of descriptor.parameters) {
62
+ this.parameters.set(param.name, param.defaultValue ?? (param.type === 'bool' || param.type === 'trigger' ? false : 0));
63
+ }
64
+ }
65
+ return this.stateMachine;
66
+ }
67
+ getStateMachine() {
68
+ return this.stateMachine;
69
+ }
70
+ // ─── Parameters ────────────────────────────────────────
71
+ setFloat(name, value) {
72
+ this.parameters.set(name, value);
73
+ }
74
+ getFloat(name) {
75
+ return this.parameters.get(name) ?? 0;
76
+ }
77
+ setInt(name, value) {
78
+ this.parameters.set(name, Math.floor(value));
79
+ }
80
+ getInt(name) {
81
+ return Math.floor(this.parameters.get(name)) ?? 0;
82
+ }
83
+ setBool(name, value) {
84
+ this.parameters.set(name, value);
85
+ }
86
+ getBool(name) {
87
+ return this.parameters.get(name) ?? false;
88
+ }
89
+ setTrigger(name) {
90
+ this.parameters.set(name, true);
91
+ }
92
+ resetTrigger(name) {
93
+ this.parameters.set(name, false);
94
+ }
95
+ // ─── IK ────────────────────────────────────────────────
96
+ addIKChain(name, descriptor) {
97
+ const chain = new IKChainImpl(descriptor);
98
+ this.ikChains.set(name, chain);
99
+ return chain;
100
+ }
101
+ getIKChain(name) {
102
+ return this.ikChains.get(name);
103
+ }
104
+ removeIKChain(name) {
105
+ this.ikChains.delete(name);
106
+ }
107
+ // ─── Morph Targets ─────────────────────────────────────
108
+ createMorphTargetManager() {
109
+ this.morphTargets = new MorphTargetManagerImpl();
110
+ return this.morphTargets;
111
+ }
112
+ getMorphTargetManager() {
113
+ return this.morphTargets;
114
+ }
115
+ // ─── Events ────────────────────────────────────────────
116
+ onEvent(eventName, callback) {
117
+ if (!this.eventCallbacks.has(eventName)) {
118
+ this.eventCallbacks.set(eventName, []);
119
+ }
120
+ this.eventCallbacks.get(eventName).push(callback);
121
+ }
122
+ offEvent(eventName, callback) {
123
+ const callbacks = this.eventCallbacks.get(eventName);
124
+ if (callbacks) {
125
+ const idx = callbacks.indexOf(callback);
126
+ if (idx >= 0) {
127
+ callbacks.splice(idx, 1);
128
+ }
129
+ }
130
+ }
131
+ dispatchEvent(eventName, data) {
132
+ const callbacks = this.eventCallbacks.get(eventName);
133
+ if (callbacks) {
134
+ for (const cb of callbacks) {
135
+ cb(data);
136
+ }
137
+ }
138
+ }
139
+ // ─── Update ────────────────────────────────────────────
140
+ update(deltaTime) {
141
+ // Update state machine
142
+ if (this.stateMachine) {
143
+ this.stateMachine.update(deltaTime);
144
+ // Check for animation events
145
+ const events = this.stateMachine.getTriggeredEvents();
146
+ for (const event of events) {
147
+ this.dispatchEvent(event.name, event.data);
148
+ }
149
+ }
150
+ // Update layers
151
+ for (const layer of this.layers) {
152
+ layer.evaluate(deltaTime);
153
+ }
154
+ // Apply state machine to skeleton
155
+ if (this.stateMachine) {
156
+ this.stateMachine.sample(this.skeleton);
157
+ }
158
+ // Apply layers to skeleton
159
+ for (const _layer of this.layers) {
160
+ // Layer application happens in evaluate
161
+ }
162
+ // Compute world transforms
163
+ this.skeleton.computeWorldTransforms();
164
+ // Apply IK
165
+ for (const chain of this.ikChains.values()) {
166
+ if (chain.isEnabled()) {
167
+ chain.solve(this.skeleton);
168
+ }
169
+ }
170
+ // Reset triggers
171
+ for (const [name, value] of this.parameters.entries()) {
172
+ if (value === true && this.isTrigger(name)) {
173
+ this.parameters.set(name, false);
174
+ }
175
+ }
176
+ }
177
+ isTrigger(_name) {
178
+ // Check if parameter is a trigger type
179
+ return false; // Would check against parameter definitions
180
+ }
181
+ // ─── Output ────────────────────────────────────────────
182
+ getSkeleton() {
183
+ return this.skeleton;
184
+ }
185
+ getJointMatrices() {
186
+ return this.skeleton.getJointMatrices();
187
+ }
188
+ }
189
+ // ============================================================
190
+ // Animation State Machine
191
+ // ============================================================
192
+ class AnimationStateMachine {
193
+ constructor(descriptor, clips, parameters) {
194
+ this.states = new Map();
195
+ this.transitions = [];
196
+ this.currentState = null;
197
+ this.activeTransition = null;
198
+ this.previousTime = 0;
199
+ this.triggeredEvents = [];
200
+ this.parameters = parameters;
201
+ // Create states
202
+ for (const stateDesc of descriptor.states) {
203
+ const clip = stateDesc.clip ? (clips.get(stateDesc.clip) ?? null) : null;
204
+ const state = new AnimationStateImpl(stateDesc, clip);
205
+ this.states.set(stateDesc.name, state);
206
+ }
207
+ // Create transitions
208
+ for (const transDesc of descriptor.transitions) {
209
+ const from = this.states.get(transDesc.from);
210
+ const to = this.states.get(transDesc.to);
211
+ if (from && to) {
212
+ this.transitions.push(new AnimationTransitionImpl(from, to, transDesc));
213
+ }
214
+ }
215
+ // Set default state
216
+ if (descriptor.defaultState) {
217
+ this.currentState = this.states.get(descriptor.defaultState) ?? null;
218
+ this.currentState?.enter();
219
+ }
220
+ else if (this.states.size > 0) {
221
+ this.currentState = this.states.values().next().value ?? null;
222
+ this.currentState?.enter();
223
+ }
224
+ }
225
+ getCurrentState() {
226
+ return this.currentState;
227
+ }
228
+ isInTransition() {
229
+ return this.activeTransition !== null;
230
+ }
231
+ getTransitionProgress() {
232
+ return this.activeTransition?.progress ?? 0;
233
+ }
234
+ update(deltaTime) {
235
+ this.triggeredEvents = [];
236
+ // Update active transition
237
+ if (this.activeTransition) {
238
+ this.activeTransition.update(deltaTime);
239
+ if (this.activeTransition.isComplete()) {
240
+ this.currentState = this.activeTransition.to;
241
+ this.activeTransition = null;
242
+ }
243
+ }
244
+ else {
245
+ // Check for transitions
246
+ if (this.currentState) {
247
+ for (const transition of this.transitions) {
248
+ if (transition.from === this.currentState && transition.check(this.parameters)) {
249
+ // Check exit time
250
+ if (transition.hasExitTime) {
251
+ if (this.currentState.normalizedTime >= transition.exitTime) {
252
+ this.startTransition(transition);
253
+ break;
254
+ }
255
+ }
256
+ else {
257
+ this.startTransition(transition);
258
+ break;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ // Update current state
265
+ const prevTime = this.currentState?.time ?? 0;
266
+ this.currentState?.update(deltaTime);
267
+ const currTime = this.currentState?.time ?? 0;
268
+ // Check for events
269
+ if (this.currentState?.clip) {
270
+ const event = this.currentState.clip.getEvent(currTime, prevTime);
271
+ if (event) {
272
+ this.triggeredEvents.push(event);
273
+ }
274
+ }
275
+ }
276
+ startTransition(transition) {
277
+ this.currentState?.exit();
278
+ transition.to.enter();
279
+ transition.to.time = transition.offset * transition.to.clip.duration;
280
+ transition.progress = 0;
281
+ this.activeTransition = transition;
282
+ }
283
+ sample(skeleton) {
284
+ if (this.activeTransition) {
285
+ // Blend between states
286
+ 1 - this.activeTransition.progress;
287
+ const toWeight = this.activeTransition.progress;
288
+ // Sample from state
289
+ this.activeTransition.from.sample(skeleton);
290
+ // Would need to blend, simplified here
291
+ if (toWeight > 0.5) {
292
+ this.activeTransition.to.sample(skeleton);
293
+ }
294
+ }
295
+ else if (this.currentState) {
296
+ this.currentState.sample(skeleton);
297
+ }
298
+ }
299
+ getTriggeredEvents() {
300
+ return this.triggeredEvents;
301
+ }
302
+ forceState(stateName) {
303
+ const state = this.states.get(stateName);
304
+ if (state) {
305
+ this.currentState?.exit();
306
+ this.currentState = state;
307
+ this.currentState.enter();
308
+ this.activeTransition = null;
309
+ }
310
+ }
311
+ }
312
+ // ============================================================
313
+ // Internal Implementations
314
+ // ============================================================
315
+ class SkeletonImpl {
316
+ constructor(descriptors) {
317
+ this.boneMap = new Map();
318
+ this.bones = descriptors.map((desc, index) => ({
319
+ name: desc.name,
320
+ index,
321
+ parentIndex: desc.parentIndex,
322
+ children: [],
323
+ localPosition: { ...desc.position },
324
+ localRotation: { ...desc.rotation },
325
+ localScale: { ...desc.scale },
326
+ worldPosition: { ...desc.position },
327
+ worldRotation: { ...desc.rotation },
328
+ worldScale: { ...desc.scale },
329
+ inverseBindMatrix: new Float32Array(16),
330
+ }));
331
+ // Build bone map and children
332
+ for (let i = 0; i < this.bones.length; i++) {
333
+ const bone = this.bones[i];
334
+ this.boneMap.set(bone.name, i);
335
+ if (bone.parentIndex >= 0) {
336
+ this.bones[bone.parentIndex].children.push(i);
337
+ }
338
+ }
339
+ this.jointMatrices = new Float32Array(this.bones.length * 16);
340
+ this.computeWorldTransforms();
341
+ }
342
+ getBone(nameOrIndex) {
343
+ if (typeof nameOrIndex === 'number') {
344
+ return this.bones[nameOrIndex];
345
+ }
346
+ const index = this.boneMap.get(nameOrIndex);
347
+ return index !== undefined ? this.bones[index] : undefined;
348
+ }
349
+ getBoneIndex(name) {
350
+ return this.boneMap.get(name) ?? -1;
351
+ }
352
+ getBoneCount() {
353
+ return this.bones.length;
354
+ }
355
+ setLocalPose(boneIndex, position, rotation, scale) {
356
+ const bone = this.bones[boneIndex];
357
+ if (bone) {
358
+ bone.localPosition = { ...position };
359
+ bone.localRotation = { ...rotation };
360
+ bone.localScale = { ...scale };
361
+ }
362
+ }
363
+ getLocalPose(boneIndex) {
364
+ const bone = this.bones[boneIndex];
365
+ return {
366
+ position: { ...bone.localPosition },
367
+ rotation: { ...bone.localRotation },
368
+ scale: { ...bone.localScale },
369
+ };
370
+ }
371
+ computeWorldTransforms() {
372
+ for (const bone of this.bones) {
373
+ if (bone.parentIndex < 0) {
374
+ // Root bone
375
+ bone.worldPosition = { ...bone.localPosition };
376
+ bone.worldRotation = { ...bone.localRotation };
377
+ bone.worldScale = { ...bone.localScale };
378
+ }
379
+ else {
380
+ const parent = this.bones[bone.parentIndex];
381
+ // Transform local by parent world
382
+ bone.worldPosition = this.transformPoint(bone.localPosition, parent);
383
+ bone.worldRotation = this.multiplyQuaternions(parent.worldRotation, bone.localRotation);
384
+ bone.worldScale = {
385
+ x: parent.worldScale.x * bone.localScale.x,
386
+ y: parent.worldScale.y * bone.localScale.y,
387
+ z: parent.worldScale.z * bone.localScale.z,
388
+ };
389
+ }
390
+ // Update joint matrix
391
+ this.updateJointMatrix(bone);
392
+ }
393
+ }
394
+ transformPoint(point, parent) {
395
+ // Simplified - would use proper quaternion rotation
396
+ return {
397
+ x: parent.worldPosition.x + point.x * parent.worldScale.x,
398
+ y: parent.worldPosition.y + point.y * parent.worldScale.y,
399
+ z: parent.worldPosition.z + point.z * parent.worldScale.z,
400
+ };
401
+ }
402
+ multiplyQuaternions(a, b) {
403
+ return {
404
+ x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
405
+ y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
406
+ z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
407
+ w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
408
+ };
409
+ }
410
+ updateJointMatrix(bone) {
411
+ const offset = bone.index * 16;
412
+ // Simplified - would compose TRS matrix
413
+ // Final = WorldMatrix * InverseBindMatrix
414
+ this.jointMatrices[offset] = 1;
415
+ this.jointMatrices[offset + 5] = 1;
416
+ this.jointMatrices[offset + 10] = 1;
417
+ this.jointMatrices[offset + 15] = 1;
418
+ this.jointMatrices[offset + 12] = bone.worldPosition.x;
419
+ this.jointMatrices[offset + 13] = bone.worldPosition.y;
420
+ this.jointMatrices[offset + 14] = bone.worldPosition.z;
421
+ }
422
+ getJointMatrices() {
423
+ return this.jointMatrices;
424
+ }
425
+ }
426
+ class AnimationClipImpl {
427
+ constructor(descriptor) {
428
+ this.name = descriptor.name;
429
+ this.duration = descriptor.duration;
430
+ this.tracks = descriptor.tracks;
431
+ this.events = descriptor.events ?? [];
432
+ }
433
+ sample(time, skeleton) {
434
+ for (const track of this.tracks) {
435
+ const boneIndex = skeleton.getBoneIndex(track.targetBone);
436
+ if (boneIndex < 0) {
437
+ continue;
438
+ }
439
+ const value = this.sampleTrack(track, time);
440
+ const pose = skeleton.getLocalPose(boneIndex);
441
+ switch (track.property) {
442
+ case 'position':
443
+ skeleton.setLocalPose(boneIndex, value, pose.rotation, pose.scale);
444
+ break;
445
+ case 'rotation':
446
+ skeleton.setLocalPose(boneIndex, pose.position, value, pose.scale);
447
+ break;
448
+ case 'scale':
449
+ skeleton.setLocalPose(boneIndex, pose.position, pose.rotation, value);
450
+ break;
451
+ }
452
+ }
453
+ }
454
+ sampleTrack(track, time) {
455
+ const keyframes = track.keyframes;
456
+ if (keyframes.length === 0) {
457
+ return track.property === 'rotation' ? { x: 0, y: 0, z: 0, w: 1 } : { x: 0, y: 0, z: 0 };
458
+ }
459
+ if (keyframes.length === 1 || time <= keyframes[0].time) {
460
+ return keyframes[0].value;
461
+ }
462
+ if (time >= keyframes[keyframes.length - 1].time) {
463
+ return keyframes[keyframes.length - 1].value;
464
+ }
465
+ // Find surrounding keyframes
466
+ let i = 0;
467
+ while (i < keyframes.length - 1 && keyframes[i + 1].time < time) {
468
+ i++;
469
+ }
470
+ const k0 = keyframes[i];
471
+ const k1 = keyframes[i + 1];
472
+ const t = (time - k0.time) / (k1.time - k0.time);
473
+ // Interpolate
474
+ if (track.property === 'rotation') {
475
+ return this.slerpQuaternion(k0.value, k1.value, t);
476
+ }
477
+ else {
478
+ return this.lerpVec3(k0.value, k1.value, t);
479
+ }
480
+ }
481
+ lerpVec3(a, b, t) {
482
+ return {
483
+ x: a.x + (b.x - a.x) * t,
484
+ y: a.y + (b.y - a.y) * t,
485
+ z: a.z + (b.z - a.z) * t,
486
+ };
487
+ }
488
+ slerpQuaternion(a, b, t) {
489
+ // Simplified slerp
490
+ let dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
491
+ if (dot < 0) {
492
+ b = { x: -b.x, y: -b.y, z: -b.z, w: -b.w };
493
+ dot = -dot;
494
+ }
495
+ if (dot > 0.9995) {
496
+ // Linear interpolation for very close quaternions
497
+ return this.normalizeQuaternion({
498
+ x: a.x + (b.x - a.x) * t,
499
+ y: a.y + (b.y - a.y) * t,
500
+ z: a.z + (b.z - a.z) * t,
501
+ w: a.w + (b.w - a.w) * t,
502
+ });
503
+ }
504
+ const theta0 = Math.acos(dot);
505
+ const theta = theta0 * t;
506
+ const sinTheta = Math.sin(theta);
507
+ const sinTheta0 = Math.sin(theta0);
508
+ const s0 = Math.cos(theta) - (dot * sinTheta) / sinTheta0;
509
+ const s1 = sinTheta / sinTheta0;
510
+ return {
511
+ x: a.x * s0 + b.x * s1,
512
+ y: a.y * s0 + b.y * s1,
513
+ z: a.z * s0 + b.z * s1,
514
+ w: a.w * s0 + b.w * s1,
515
+ };
516
+ }
517
+ normalizeQuaternion(q) {
518
+ const len = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
519
+ return { x: q.x / len, y: q.y / len, z: q.z / len, w: q.w / len };
520
+ }
521
+ getEvent(time, previousTime) {
522
+ for (const event of this.events) {
523
+ if (event.time > previousTime && event.time <= time) {
524
+ return event;
525
+ }
526
+ }
527
+ return null;
528
+ }
529
+ }
530
+ class AnimationLayerImpl {
531
+ constructor(descriptor, _skeleton, _clips, _parameters) {
532
+ this._mask = null;
533
+ this._currentState = null;
534
+ this.name = descriptor.name;
535
+ this.blendMode = descriptor.blendMode ?? 'override';
536
+ this._weight = descriptor.weight ?? 1;
537
+ if (descriptor.mask) {
538
+ this._mask = new AvatarMaskImpl(descriptor.mask);
539
+ }
540
+ }
541
+ getWeight() {
542
+ return this._weight;
543
+ }
544
+ setWeight(weight) {
545
+ this._weight = Math.max(0, Math.min(1, weight));
546
+ }
547
+ getBlendMode() {
548
+ return this.blendMode;
549
+ }
550
+ setBlendMode(mode) {
551
+ this.blendMode = mode;
552
+ }
553
+ getMask() {
554
+ return this._mask;
555
+ }
556
+ setMask(mask) {
557
+ this._mask = mask;
558
+ }
559
+ getCurrentState() {
560
+ return this._currentState;
561
+ }
562
+ setCurrentState(state) {
563
+ this._currentState = state;
564
+ }
565
+ evaluate(_deltaTime) {
566
+ // Update and blend animations for this layer
567
+ }
568
+ }
569
+ class AvatarMaskImpl {
570
+ constructor(bones) {
571
+ this.weights = new Map();
572
+ for (const bone of bones) {
573
+ this.weights.set(bone, 1);
574
+ }
575
+ }
576
+ getBoneWeight(boneName) {
577
+ return this.weights.get(boneName) ?? 0;
578
+ }
579
+ setBoneWeight(boneName, weight) {
580
+ this.weights.set(boneName, weight);
581
+ }
582
+ setAllWeights(weight) {
583
+ for (const bone of this.weights.keys()) {
584
+ this.weights.set(bone, weight);
585
+ }
586
+ }
587
+ }
588
+ class AnimationStateImpl {
589
+ constructor(descriptor, clip) {
590
+ this.blendTree = null;
591
+ this.normalizedTime = 0;
592
+ this.time = 0;
593
+ this.name = descriptor.name;
594
+ this.type = descriptor.type;
595
+ this.clip = clip;
596
+ this.speed = descriptor.speed ?? 1;
597
+ this.wrapMode = descriptor.wrapMode ?? 'loop';
598
+ }
599
+ enter() {
600
+ this.time = 0;
601
+ this.normalizedTime = 0;
602
+ }
603
+ exit() {
604
+ // Cleanup
605
+ }
606
+ update(deltaTime) {
607
+ this.time += deltaTime * this.speed;
608
+ if (this.clip) {
609
+ const duration = this.clip.duration;
610
+ switch (this.wrapMode) {
611
+ case 'once':
612
+ if (this.time > duration) {
613
+ this.time = duration;
614
+ }
615
+ break;
616
+ case 'loop':
617
+ while (this.time > duration) {
618
+ this.time -= duration;
619
+ }
620
+ break;
621
+ case 'pingpong':
622
+ // Would implement ping-pong
623
+ break;
624
+ case 'clamp':
625
+ this.time = Math.max(0, Math.min(this.time, duration));
626
+ break;
627
+ }
628
+ this.normalizedTime = duration > 0 ? this.time / duration : 0;
629
+ }
630
+ }
631
+ sample(skeleton) {
632
+ if (this.clip) {
633
+ this.clip.sample(this.time, skeleton);
634
+ }
635
+ else if (this.blendTree) {
636
+ this.blendTree.sample(skeleton);
637
+ }
638
+ }
639
+ }
640
+ class AnimationTransitionImpl {
641
+ constructor(from, to, descriptor) {
642
+ this.progress = 0;
643
+ this.from = from;
644
+ this.to = to;
645
+ this.duration = descriptor.duration ?? 0.25;
646
+ this.offset = descriptor.offset ?? 0;
647
+ this.conditions = descriptor.conditions ?? [];
648
+ this.hasExitTime = descriptor.hasExitTime ?? false;
649
+ this.exitTime = descriptor.exitTime ?? 1;
650
+ }
651
+ check(parameters) {
652
+ if (this.conditions.length === 0) {
653
+ return true;
654
+ }
655
+ for (const condition of this.conditions) {
656
+ const value = parameters.get(condition.parameter);
657
+ if (value === undefined) {
658
+ continue;
659
+ }
660
+ let result = false;
661
+ switch (condition.mode) {
662
+ case 'equals':
663
+ result = value === condition.value;
664
+ break;
665
+ case 'not-equals':
666
+ result = value !== condition.value;
667
+ break;
668
+ case 'greater':
669
+ result = value > condition.value;
670
+ break;
671
+ case 'less':
672
+ result = value < condition.value;
673
+ break;
674
+ case 'if':
675
+ result = value === true;
676
+ break;
677
+ case 'if-not':
678
+ result = value === false;
679
+ break;
680
+ }
681
+ if (!result) {
682
+ return false;
683
+ }
684
+ }
685
+ return true;
686
+ }
687
+ update(deltaTime) {
688
+ if (this.duration > 0) {
689
+ this.progress += deltaTime / this.duration;
690
+ this.progress = Math.min(1, this.progress);
691
+ }
692
+ else {
693
+ this.progress = 1;
694
+ }
695
+ }
696
+ isComplete() {
697
+ return this.progress >= 1;
698
+ }
699
+ }
700
+ class IKChainImpl {
701
+ constructor(descriptor) {
702
+ this._target = { x: 0, y: 0, z: 0 };
703
+ this._poleTarget = null;
704
+ this._enabled = true;
705
+ this.type = descriptor.type;
706
+ this.rootBone = descriptor.rootBone;
707
+ this.tipBone = descriptor.tipBone;
708
+ this._weight = descriptor.weight ?? 1;
709
+ this._iterations = descriptor.iterations ?? 10;
710
+ this._tolerance = descriptor.tolerance ?? 0.001;
711
+ this._poleTarget = descriptor.poleTarget ?? null;
712
+ }
713
+ getTarget() {
714
+ return { ...this._target };
715
+ }
716
+ setTarget(target) {
717
+ this._target = { ...target };
718
+ }
719
+ getPoleTarget() {
720
+ return this._poleTarget ? { ...this._poleTarget } : null;
721
+ }
722
+ setPoleTarget(target) {
723
+ this._poleTarget = target ? { ...target } : null;
724
+ }
725
+ getWeight() {
726
+ return this._weight;
727
+ }
728
+ setWeight(weight) {
729
+ this._weight = Math.max(0, Math.min(1, weight));
730
+ }
731
+ isEnabled() {
732
+ return this._enabled;
733
+ }
734
+ setEnabled(enabled) {
735
+ this._enabled = enabled;
736
+ }
737
+ solve(skeleton) {
738
+ if (!this._enabled || this._weight <= 0) {
739
+ return;
740
+ }
741
+ switch (this.type) {
742
+ case 'two-bone':
743
+ this.solveTwoBone(skeleton);
744
+ break;
745
+ case 'fabrik':
746
+ this.solveFABRIK(skeleton);
747
+ break;
748
+ case 'ccd':
749
+ this.solveCCD(skeleton);
750
+ break;
751
+ }
752
+ }
753
+ solveTwoBone(skeleton) {
754
+ // Two-bone analytical IK (common for arms/legs)
755
+ const root = skeleton.getBone(this.rootBone);
756
+ const tip = skeleton.getBone(this.tipBone);
757
+ if (!root || !tip) {
758
+ return;
759
+ }
760
+ // Would implement two-bone IK solver
761
+ }
762
+ solveFABRIK(_skeleton) {
763
+ // Forward And Backward Reaching Inverse Kinematics
764
+ for (let i = 0; i < this._iterations; i++) {
765
+ // Forward pass
766
+ // Backward pass
767
+ }
768
+ }
769
+ solveCCD(_skeleton) {
770
+ // Cyclic Coordinate Descent
771
+ for (let i = 0; i < this._iterations; i++) {
772
+ // Iterate through chain from tip to root
773
+ }
774
+ }
775
+ }
776
+ class MorphTargetManagerImpl {
777
+ constructor() {
778
+ this.targets = new Map();
779
+ }
780
+ getMorphTarget(name) {
781
+ return this.targets.get(name);
782
+ }
783
+ getMorphTargets() {
784
+ return Array.from(this.targets.values());
785
+ }
786
+ addMorphTarget(descriptor) {
787
+ const target = new MorphTargetImpl(descriptor);
788
+ this.targets.set(descriptor.name, target);
789
+ return target;
790
+ }
791
+ removeMorphTarget(name) {
792
+ this.targets.delete(name);
793
+ }
794
+ getWeight(name) {
795
+ return this.targets.get(name)?.weight ?? 0;
796
+ }
797
+ setWeight(name, weight) {
798
+ const target = this.targets.get(name);
799
+ if (target) {
800
+ target.weight = Math.max(0, Math.min(1, weight));
801
+ }
802
+ }
803
+ getBlendedVertices(baseVertices) {
804
+ const result = new Float32Array(baseVertices);
805
+ for (const target of this.targets.values()) {
806
+ if (target.weight <= 0) {
807
+ continue;
808
+ }
809
+ for (let i = 0; i < result.length; i++) {
810
+ result[i] += target.vertices[i] * target.weight;
811
+ }
812
+ }
813
+ return result;
814
+ }
815
+ }
816
+ class MorphTargetImpl {
817
+ constructor(descriptor) {
818
+ this.weight = 0;
819
+ this.name = descriptor.name;
820
+ this.vertices = descriptor.vertices;
821
+ this.normals = descriptor.normals ?? null;
822
+ this.tangents = descriptor.tangents ?? null;
823
+ }
824
+ }
825
+ // ============================================================
826
+ // Factory Functions
827
+ // ============================================================
828
+ function createAnimatorController(bones) {
829
+ return new AnimatorController(bones);
830
+ }
831
+ function createAnimationClip(descriptor) {
832
+ return new AnimationClipImpl(descriptor);
833
+ }
834
+
835
+ export { AnimationStateMachine, AnimatorController, createAnimationClip, createAnimatorController };