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