@galfus/engine 0.0.0-stage → 0.31.0-alpha

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 (80) hide show
  1. package/README.md +190 -2
  2. package/package.json +59 -4
  3. package/src/anchors/cache.ts +101 -0
  4. package/src/anchors/component.ts +123 -0
  5. package/src/anchors/hierarchy.ts +192 -0
  6. package/src/anchors/index.ts +8 -0
  7. package/src/anchors/solver.ts +247 -0
  8. package/src/anchors/strategy.ts +142 -0
  9. package/src/animation/blending.ts +163 -0
  10. package/src/animation/clip.ts +132 -0
  11. package/src/animation/coordinator.ts +568 -0
  12. package/src/animation/index.ts +7 -0
  13. package/src/animation/native-targets.ts +37 -0
  14. package/src/animation/player.ts +287 -0
  15. package/src/audio/index.ts +4 -0
  16. package/src/audio/manager.ts +371 -0
  17. package/src/collision/components.ts +211 -0
  18. package/src/collision/index.ts +16 -0
  19. package/src/collision/query2d.ts +167 -0
  20. package/src/collision/query3d.ts +170 -0
  21. package/src/ecs/component.ts +130 -0
  22. package/src/ecs/entity.ts +75 -0
  23. package/src/ecs/index.ts +9 -0
  24. package/src/ecs/intent.ts +162 -0
  25. package/src/ecs/metadata.ts +44 -0
  26. package/src/ecs/system.ts +224 -0
  27. package/src/ecs/world.ts +291 -0
  28. package/src/index.ts +19 -0
  29. package/src/interaction/focus.ts +327 -0
  30. package/src/interaction/index.ts +9 -0
  31. package/src/interaction/listener.ts +58 -0
  32. package/src/interaction/snapshot2d.ts +210 -0
  33. package/src/interaction/snapshot3d.ts +214 -0
  34. package/src/interaction/system.ts +544 -0
  35. package/src/interaction/types.ts +27 -0
  36. package/src/math/common.ts +67 -0
  37. package/src/math/index.ts +17 -0
  38. package/src/math/mat2d.ts +320 -0
  39. package/src/math/mat4.ts +576 -0
  40. package/src/math/quat.ts +438 -0
  41. package/src/math/vec2.ts +161 -0
  42. package/src/math/vec3.ts +242 -0
  43. package/src/math/vec4.ts +171 -0
  44. package/src/multiverse/coordinator.ts +540 -0
  45. package/src/multiverse/index.ts +13 -0
  46. package/src/multiverse/presentation-graph.ts +306 -0
  47. package/src/multiverse/presentation-links.ts +134 -0
  48. package/src/multiverse/realm.ts +49 -0
  49. package/src/platform/index.ts +2 -0
  50. package/src/platform/platform.ts +149 -0
  51. package/src/platform/window.ts +181 -0
  52. package/src/realm2d/components.ts +254 -0
  53. package/src/realm2d/index.ts +12 -0
  54. package/src/realm2d/presets/index.ts +36 -0
  55. package/src/realm2d/realm.ts +522 -0
  56. package/src/realm2d/sync.ts +284 -0
  57. package/src/realm2d/utils/index.ts +32 -0
  58. package/src/realm3d/components.ts +341 -0
  59. package/src/realm3d/geometry.ts +224 -0
  60. package/src/realm3d/index.ts +12 -0
  61. package/src/realm3d/presets/index.ts +67 -0
  62. package/src/realm3d/realm.ts +588 -0
  63. package/src/realm3d/resource-ids.ts +62 -0
  64. package/src/realm3d/skinning.ts +128 -0
  65. package/src/realm3d/sync.ts +442 -0
  66. package/src/realm3d/utils/index.ts +26 -0
  67. package/src/resources/index.ts +4 -0
  68. package/src/resources/manager.ts +92 -0
  69. package/src/runtime.ts +588 -0
  70. package/src/spatial/dirty_registry.ts +217 -0
  71. package/src/spatial/flags.ts +14 -0
  72. package/src/spatial/index.ts +6 -0
  73. package/src/spatial/metrics.ts +124 -0
  74. package/src/text/editor.ts +224 -0
  75. package/src/text/encoding.ts +38 -0
  76. package/src/text/index.ts +13 -0
  77. package/src/text/input.ts +160 -0
  78. package/src/text/keyboard.ts +50 -0
  79. package/src/text/svg-decorations.ts +147 -0
  80. package/src/types.ts +85 -0
@@ -0,0 +1,568 @@
1
+ /**
2
+ * AnimationCoordinator orchestrates native property transitions, path followers, and animation events.
3
+ */
4
+
5
+ import type {
6
+ AnimationEvent,
7
+ AnimationTarget,
8
+ AnimValue,
9
+ GalfusEngine,
10
+ PathCommand,
11
+ PathFollowConfig,
12
+ PathFollowState,
13
+ TransitionConfig,
14
+ TransitionState
15
+ } from '@galfus/transport-types';
16
+ import type { EntityId } from '../ecs/entity';
17
+ import type { EcsWorld } from '../ecs/world';
18
+ import { mat4, quat, vec3 } from '../math/index';
19
+ import type { Model2DComponent } from '../realm2d/components';
20
+ import type { Model3DComponent } from '../realm3d/components';
21
+ import { IdAllocator, type PathId, type TransitionId } from '../types';
22
+ import { NativeTargetTracker } from './native-targets';
23
+
24
+ export type AnimationEventListener = (event: AnimationEvent) => void;
25
+ export type EcsAnimProperty = 'position' | 'rotation' | 'scale' | 'transform3d' | 'transform2d';
26
+
27
+ export interface EcsAnimBinding {
28
+ readonly transitionId: TransitionId;
29
+ readonly world: EcsWorld;
30
+ readonly entityId: EntityId;
31
+ readonly property: EcsAnimProperty;
32
+ }
33
+
34
+ export class AnimationCoordinator {
35
+ private _engine: GalfusEngine | null = null;
36
+ private readonly _transitionIdAllocator = new IdAllocator<TransitionId>(1);
37
+ private readonly _pathIdAllocator = new IdAllocator<PathId>(1);
38
+ private readonly _transitions = new Set<TransitionId>();
39
+ private readonly _paths = new Set<PathId>();
40
+ private readonly _eventListeners = new Set<AnimationEventListener>();
41
+ private readonly _ecsBindings = new Map<TransitionId, EcsAnimBinding>();
42
+ private readonly _entityPropertyBindings = new Map<string, TransitionId>();
43
+
44
+ private readonly _nativeTargets = new NativeTargetTracker();
45
+
46
+ /** @internal Observes native updates that affect host presentation data. */
47
+ setNativeTargetObserver(visit: (target: AnimationTarget) => void): void {
48
+ this._nativeTargets.setObserver(visit);
49
+ }
50
+
51
+ bindEngine(engine: GalfusEngine): void {
52
+ this._engine = engine;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Transitions
57
+ // ---------------------------------------------------------------------------
58
+ createTransition(
59
+ initialValue: AnimValue,
60
+ config: TransitionConfig,
61
+ target?: AnimationTarget
62
+ ): TransitionId {
63
+ const id = this._transitionIdAllocator.allocate('TransitionId');
64
+ if (this._engine) {
65
+ this._engine.animationCreateTransition(id, initialValue, config);
66
+ if (target) {
67
+ this._engine.animationBindTarget(id, target);
68
+ }
69
+ }
70
+ this._transitions.add(id);
71
+ this._nativeTargets.transition(id, target);
72
+ return id;
73
+ }
74
+
75
+ bindTarget(transitionId: TransitionId, target: AnimationTarget): void {
76
+ if (this._engine) {
77
+ this._engine.animationBindTarget(transitionId, target);
78
+ }
79
+ this._nativeTargets.transition(transitionId, target);
80
+ }
81
+
82
+ unbindTarget(transitionId: TransitionId): void {
83
+ if (this._engine) {
84
+ this._engine.animationUnbindTarget(transitionId);
85
+ }
86
+ this._nativeTargets.transition(transitionId);
87
+ }
88
+
89
+ transitionTo(transitionId: TransitionId, targetValue: AnimValue, config: TransitionConfig): void {
90
+ if (this._engine) {
91
+ this._engine.animationTransitionTo(transitionId, targetValue, config);
92
+ }
93
+ }
94
+
95
+ createScalarTransition(
96
+ initialScalar: number,
97
+ duration: number,
98
+ delay = 0,
99
+ easingId = 0,
100
+ target?: AnimationTarget
101
+ ): TransitionId {
102
+ const id = this._transitionIdAllocator.allocate('TransitionId');
103
+ if (this._engine) {
104
+ this._engine.animationCreateScalarTransition(id, initialScalar, duration, delay, easingId);
105
+ if (target) {
106
+ this._engine.animationBindTarget(id, target);
107
+ }
108
+ }
109
+ this._transitions.add(id);
110
+ this._nativeTargets.transition(id, target);
111
+ return id;
112
+ }
113
+
114
+ transitionToScalar(
115
+ transitionId: TransitionId,
116
+ targetScalar: number,
117
+ duration: number,
118
+ delay = 0,
119
+ easingId = 0
120
+ ): void {
121
+ if (this._engine) {
122
+ this._engine.animationTransitionToScalar(
123
+ transitionId,
124
+ targetScalar,
125
+ duration,
126
+ delay,
127
+ easingId
128
+ );
129
+ }
130
+ }
131
+
132
+ transitionToTransform3d(
133
+ transitionId: TransitionId,
134
+ floats: Float32Array,
135
+ duration: number,
136
+ delay = 0,
137
+ easingId = 0
138
+ ): void {
139
+ if (this._engine) {
140
+ this._engine.animationTransitionToTransform3d(
141
+ transitionId,
142
+ floats,
143
+ duration,
144
+ delay,
145
+ easingId
146
+ );
147
+ }
148
+ }
149
+
150
+ transitionToTransform2d(
151
+ transitionId: TransitionId,
152
+ floats: Float32Array,
153
+ duration: number,
154
+ delay = 0,
155
+ easingId = 0
156
+ ): void {
157
+ if (this._engine) {
158
+ this._engine.animationTransitionToTransform2d(
159
+ transitionId,
160
+ floats,
161
+ duration,
162
+ delay,
163
+ easingId
164
+ );
165
+ }
166
+ }
167
+
168
+ getState(transitionId: TransitionId): TransitionState | undefined {
169
+ return this._engine?.animationGetState(transitionId);
170
+ }
171
+
172
+ getValue(transitionId: TransitionId): AnimValue | undefined {
173
+ return this._engine?.animationGetValue(transitionId);
174
+ }
175
+
176
+ destroyTransition(transitionId: TransitionId): void {
177
+ this.unbindEcsTarget(transitionId);
178
+ if (!this._transitions.has(transitionId)) return;
179
+ if (this._engine) {
180
+ this._engine.animationDestroyTransition(transitionId);
181
+ }
182
+ this._transitions.delete(transitionId);
183
+ this._nativeTargets.transition(transitionId);
184
+ }
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // ECS Integration
188
+ // ---------------------------------------------------------------------------
189
+ bindEcsTarget(
190
+ transitionId: TransitionId,
191
+ world: EcsWorld,
192
+ entityId: EntityId,
193
+ property: EcsAnimProperty
194
+ ): void {
195
+ const binding: EcsAnimBinding = { transitionId, world, entityId, property };
196
+ this._ecsBindings.set(transitionId, binding);
197
+ const key = `${entityId}:${property}`;
198
+ this._entityPropertyBindings.set(key, transitionId);
199
+ }
200
+
201
+ unbindEcsTarget(transitionId: TransitionId): void {
202
+ const binding = this._ecsBindings.get(transitionId);
203
+ if (binding) {
204
+ const key = `${binding.entityId}:${binding.property}`;
205
+ if (this._entityPropertyBindings.get(key) === transitionId) {
206
+ this._entityPropertyBindings.delete(key);
207
+ }
208
+ this._ecsBindings.delete(transitionId);
209
+ }
210
+ }
211
+
212
+ getEcsBinding(transitionId: TransitionId): EcsAnimBinding | undefined {
213
+ return this._ecsBindings.get(transitionId);
214
+ }
215
+
216
+ animateEcsProperty(
217
+ world: EcsWorld,
218
+ entityId: EntityId,
219
+ property: EcsAnimProperty,
220
+ targetValue: unknown,
221
+ config: TransitionConfig
222
+ ): TransitionId | undefined {
223
+ if (!world.isAlive(entityId)) return undefined;
224
+
225
+ const model3d = world.getComponent<Model3DComponent>(entityId, 'Model3D');
226
+ const model2d = world.getComponent<Model2DComponent>(entityId, 'Model2D');
227
+
228
+ if (!model3d && !model2d) return undefined;
229
+
230
+ let initialValue: AnimValue;
231
+ let normalizedTarget: AnimValue;
232
+
233
+ if (model3d) {
234
+ switch (property) {
235
+ case 'position': {
236
+ initialValue = {
237
+ type: 'vec3',
238
+ value: [model3d.localMatrix[12]!, model3d.localMatrix[13]!, model3d.localMatrix[14]!]
239
+ };
240
+ normalizedTarget = Array.isArray(targetValue)
241
+ ? { type: 'vec3', value: [targetValue[0]!, targetValue[1]!, targetValue[2]!] }
242
+ : (targetValue as AnimValue);
243
+ break;
244
+ }
245
+ case 'rotation': {
246
+ const q = quat.create();
247
+ mat4.getRotation(q, model3d.localMatrix);
248
+ initialValue = {
249
+ type: 'quat',
250
+ value: [q[0], q[1], q[2], q[3]]
251
+ };
252
+ normalizedTarget = Array.isArray(targetValue)
253
+ ? {
254
+ type: 'quat',
255
+ value: [targetValue[0]!, targetValue[1]!, targetValue[2]!, targetValue[3]!]
256
+ }
257
+ : (targetValue as AnimValue);
258
+ break;
259
+ }
260
+ case 'scale': {
261
+ const s = vec3.create();
262
+ mat4.getScaling(s, model3d.localMatrix);
263
+ initialValue = {
264
+ type: 'vec3',
265
+ value: [s[0], s[1], s[2]]
266
+ };
267
+ normalizedTarget = Array.isArray(targetValue)
268
+ ? { type: 'vec3', value: [targetValue[0]!, targetValue[1]!, targetValue[2]!] }
269
+ : (targetValue as AnimValue);
270
+ break;
271
+ }
272
+ case 'transform3d': {
273
+ const t = vec3.create();
274
+ const q = quat.create();
275
+ const s = vec3.create();
276
+ mat4.getTranslation(t, model3d.localMatrix);
277
+ mat4.getRotation(q, model3d.localMatrix);
278
+ mat4.getScaling(s, model3d.localMatrix);
279
+ initialValue = {
280
+ type: 'transform3D',
281
+ value: [t[0], t[1], t[2], q[0], q[1], q[2], q[3], s[0], s[1], s[2]]
282
+ };
283
+ if (targetValue && typeof targetValue === 'object') {
284
+ const raw = targetValue as Record<string, unknown>;
285
+ if (raw.type === 'transform3D' && Array.isArray(raw.value)) {
286
+ normalizedTarget = targetValue as AnimValue;
287
+ } else if (Array.isArray(targetValue)) {
288
+ normalizedTarget = {
289
+ type: 'transform3D',
290
+ value: targetValue as unknown as [
291
+ number,
292
+ number,
293
+ number,
294
+ number,
295
+ number,
296
+ number,
297
+ number,
298
+ number,
299
+ number,
300
+ number
301
+ ]
302
+ };
303
+ } else {
304
+ const pos = (raw.position as number[]) ?? [t[0], t[1], t[2]];
305
+ const rot = (raw.rotation as number[]) ?? [q[0], q[1], q[2], q[3]];
306
+ const sc = (raw.scale as number[]) ?? [s[0], s[1], s[2]];
307
+ normalizedTarget = {
308
+ type: 'transform3D',
309
+ value: [
310
+ pos[0]!,
311
+ pos[1]!,
312
+ pos[2]!,
313
+ rot[0]!,
314
+ rot[1]!,
315
+ rot[2]!,
316
+ rot[3]!,
317
+ sc[0]!,
318
+ sc[1]!,
319
+ sc[2]!
320
+ ]
321
+ };
322
+ }
323
+ } else {
324
+ normalizedTarget = targetValue as AnimValue;
325
+ }
326
+ break;
327
+ }
328
+ default:
329
+ return undefined;
330
+ }
331
+ } else if (model2d) {
332
+ switch (property) {
333
+ case 'position': {
334
+ initialValue = {
335
+ type: 'vec2',
336
+ value: [model2d.localMatrix[4]!, model2d.localMatrix[5]!]
337
+ };
338
+ normalizedTarget = Array.isArray(targetValue)
339
+ ? { type: 'vec2', value: [targetValue[0]!, targetValue[1]!] }
340
+ : (targetValue as AnimValue);
341
+ break;
342
+ }
343
+ case 'rotation': {
344
+ const rot = Math.atan2(model2d.localMatrix[1]!, model2d.localMatrix[0]!);
345
+ initialValue = {
346
+ type: 'scalar',
347
+ value: rot
348
+ };
349
+ normalizedTarget =
350
+ typeof targetValue === 'number'
351
+ ? { type: 'scalar', value: targetValue }
352
+ : (targetValue as AnimValue);
353
+ break;
354
+ }
355
+ case 'scale': {
356
+ const sx = Math.hypot(model2d.localMatrix[0]!, model2d.localMatrix[1]!);
357
+ const sy =
358
+ Math.hypot(model2d.localMatrix[2]!, model2d.localMatrix[3]!) *
359
+ (model2d.localMatrix[0]! * model2d.localMatrix[3]! -
360
+ model2d.localMatrix[1]! * model2d.localMatrix[2]! <
361
+ 0
362
+ ? -1
363
+ : 1);
364
+ initialValue = {
365
+ type: 'vec2',
366
+ value: [sx, sy]
367
+ };
368
+ normalizedTarget = Array.isArray(targetValue)
369
+ ? { type: 'vec2', value: [targetValue[0]!, targetValue[1]!] }
370
+ : (targetValue as AnimValue);
371
+ break;
372
+ }
373
+ case 'transform2d': {
374
+ const px = model2d.localMatrix[4]!;
375
+ const py = model2d.localMatrix[5]!;
376
+ const rot = Math.atan2(model2d.localMatrix[1]!, model2d.localMatrix[0]!);
377
+ const sx = Math.hypot(model2d.localMatrix[0]!, model2d.localMatrix[1]!);
378
+ const sy =
379
+ Math.hypot(model2d.localMatrix[2]!, model2d.localMatrix[3]!) *
380
+ (model2d.localMatrix[0]! * model2d.localMatrix[3]! -
381
+ model2d.localMatrix[1]! * model2d.localMatrix[2]! <
382
+ 0
383
+ ? -1
384
+ : 1);
385
+ initialValue = {
386
+ type: 'transform2D',
387
+ value: [px, py, rot, sx, sy, 0]
388
+ };
389
+ if (targetValue && typeof targetValue === 'object') {
390
+ const raw = targetValue as Record<string, unknown>;
391
+ if (raw.type === 'transform2D' && Array.isArray(raw.value)) {
392
+ normalizedTarget = targetValue as AnimValue;
393
+ } else if (Array.isArray(targetValue)) {
394
+ normalizedTarget = {
395
+ type: 'transform2D',
396
+ value: targetValue as unknown as [number, number, number, number, number, number]
397
+ };
398
+ } else {
399
+ const pos = (raw.position as number[]) ?? [px, py];
400
+ const r = (raw.rotation as number) ?? rot;
401
+ const sc = (raw.scale as number[]) ?? [sx, sy];
402
+ const skew = (raw.skew as number) ?? 0;
403
+ normalizedTarget = {
404
+ type: 'transform2D',
405
+ value: [pos[0]!, pos[1]!, r, sc[0]!, sc[1]!, skew]
406
+ };
407
+ }
408
+ } else {
409
+ normalizedTarget = targetValue as AnimValue;
410
+ }
411
+ break;
412
+ }
413
+ default:
414
+ return undefined;
415
+ }
416
+ } else {
417
+ return undefined;
418
+ }
419
+
420
+ // Cancel existing transition on the same entity and property (deterministic single transition per property)
421
+ const key = `${entityId}:${property}`;
422
+ const existingTransitionId = this._entityPropertyBindings.get(key);
423
+ if (existingTransitionId !== undefined) {
424
+ this.destroyTransition(existingTransitionId);
425
+ }
426
+
427
+ // Create mathematical transition without native core targets
428
+ const transitionId = this.createTransition(initialValue, config);
429
+ this.bindEcsTarget(transitionId, world, entityId, property);
430
+ this.transitionTo(transitionId, normalizedTarget, config);
431
+
432
+ return transitionId;
433
+ }
434
+
435
+ // ---------------------------------------------------------------------------
436
+ // Path Followers
437
+ // ---------------------------------------------------------------------------
438
+ createPathFollow(
439
+ commands: PathCommand[],
440
+ config: PathFollowConfig,
441
+ target?: AnimationTarget
442
+ ): PathId {
443
+ const id = this._pathIdAllocator.allocate('PathId');
444
+ if (this._engine) {
445
+ this._engine.animationCreatePathFollow(id, commands, config);
446
+ if (target) {
447
+ this._engine.animationBindPathFollowTarget(id, target);
448
+ }
449
+ }
450
+ this._paths.add(id);
451
+ this._nativeTargets.path(id, target);
452
+ return id;
453
+ }
454
+
455
+ bindPathFollowTarget(pathId: PathId, target: AnimationTarget): void {
456
+ if (this._engine) {
457
+ this._engine.animationBindPathFollowTarget(pathId, target);
458
+ }
459
+ this._nativeTargets.path(pathId, target);
460
+ }
461
+
462
+ unbindPathFollowTarget(pathId: PathId): void {
463
+ if (this._engine) {
464
+ this._engine.animationUnbindPathFollowTarget(pathId);
465
+ }
466
+ this._nativeTargets.path(pathId);
467
+ }
468
+
469
+ getPathState(pathId: PathId): PathFollowState | undefined {
470
+ return this._engine?.animationGetPathFollowState(pathId);
471
+ }
472
+
473
+ destroyPath(pathId: PathId): void {
474
+ if (!this._paths.has(pathId)) return;
475
+ if (this._engine) {
476
+ this._engine.animationDestroyPathFollow(pathId);
477
+ }
478
+ this._paths.delete(pathId);
479
+ this._nativeTargets.path(pathId);
480
+ }
481
+
482
+ // ---------------------------------------------------------------------------
483
+ // Event Pump & Lifecycle
484
+ // ---------------------------------------------------------------------------
485
+ on(_type: 'animation', listener: AnimationEventListener): () => void {
486
+ this._eventListeners.add(listener);
487
+ return () => {
488
+ this._eventListeners.delete(listener);
489
+ };
490
+ }
491
+
492
+ clearListeners(): void {
493
+ this._eventListeners.clear();
494
+ }
495
+
496
+ step(dt?: number): void {
497
+ if (!this._engine) return;
498
+ if (dt !== undefined && this._engine.animationStep) {
499
+ this._engine.animationStep(dt);
500
+ }
501
+ this._nativeTargets.refresh();
502
+
503
+ // Apply values to ECS targets prior to AnchorSolveSystem
504
+ for (const [transitionId, binding] of this._ecsBindings) {
505
+ if (!binding.world.isAlive(binding.entityId)) {
506
+ this.destroyTransition(transitionId);
507
+ continue;
508
+ }
509
+
510
+ const val = this.getValue(transitionId);
511
+ if (!val) continue;
512
+
513
+ const model3d = binding.world.getComponent<Model3DComponent>(binding.entityId, 'Model3D');
514
+ const model2d = binding.world.getComponent<Model2DComponent>(binding.entityId, 'Model2D');
515
+
516
+ if (model3d) {
517
+ if (val.type === 'vec3' && binding.property === 'position') {
518
+ model3d.setPosition(val.value[0], val.value[1], val.value[2]);
519
+ } else if (val.type === 'quat' && binding.property === 'rotation') {
520
+ model3d.setRotation(val.value[0], val.value[1], val.value[2], val.value[3]);
521
+ } else if (val.type === 'vec3' && binding.property === 'scale') {
522
+ model3d.setScale(val.value[0], val.value[1], val.value[2]);
523
+ } else if (val.type === 'transform3D') {
524
+ const v = val.value;
525
+ model3d.setTRS([v[0], v[1], v[2]], [v[3], v[4], v[5], v[6]], [v[7], v[8], v[9]]);
526
+ }
527
+ } else if (model2d) {
528
+ if (val.type === 'vec2' && binding.property === 'position') {
529
+ model2d.setPosition(val.value[0], val.value[1]);
530
+ } else if (val.type === 'scalar' && binding.property === 'rotation') {
531
+ model2d.setRotation(val.value);
532
+ } else if (val.type === 'vec2' && binding.property === 'scale') {
533
+ model2d.setScale(val.value[0], val.value[1]);
534
+ } else if (val.type === 'transform2D') {
535
+ const v = val.value;
536
+ model2d.setTRS([v[0], v[1]], v[2], [v[3], v[4]]);
537
+ }
538
+ }
539
+ }
540
+
541
+ const events = this._engine.animationPumpEvents ? this._engine.animationPumpEvents() : [];
542
+ if (events && events.length > 0) {
543
+ for (const event of events) {
544
+ for (const listener of this._eventListeners) {
545
+ listener(event);
546
+ }
547
+ }
548
+ }
549
+ }
550
+
551
+ dispose(): void {
552
+ if (this._engine) {
553
+ for (const transitionId of this._transitions) {
554
+ this._engine.animationDestroyTransition(transitionId);
555
+ }
556
+ for (const pathId of this._paths) {
557
+ this._engine.animationDestroyPathFollow(pathId);
558
+ }
559
+ }
560
+ this._transitions.clear();
561
+ this._paths.clear();
562
+ this._nativeTargets.clear();
563
+ this._ecsBindings.clear();
564
+ this._entityPropertyBindings.clear();
565
+ this._eventListeners.clear();
566
+ this._engine = null;
567
+ }
568
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Subpath export for Animation subsystem.
3
+ */
4
+ export * from './blending';
5
+ export * from './clip';
6
+ export * from './coordinator';
7
+ export * from './player';
@@ -0,0 +1,37 @@
1
+ import type { AnimationTarget } from '@galfus/transport-types';
2
+
3
+ /** Tracks native targets whose host-side presentation data requires refreshing. */
4
+ export class NativeTargetTracker {
5
+ private readonly transitions = new Map<number, AnimationTarget>();
6
+ private readonly paths = new Map<number, AnimationTarget>();
7
+ private observer?: (target: AnimationTarget) => void;
8
+ setObserver(observer: (target: AnimationTarget) => void): void {
9
+ this.observer = observer;
10
+ }
11
+ private set(map: Map<number, AnimationTarget>, id: number, target?: AnimationTarget): void {
12
+ if (
13
+ target &&
14
+ (target.kind === 'svgAttributeScalar' ||
15
+ target.kind === 'svgAttributeVec2' ||
16
+ target.kind === 'svgAttributeColor' ||
17
+ target.kind === 'camera3DLookAt')
18
+ )
19
+ map.set(id, { ...target });
20
+ else map.delete(id);
21
+ }
22
+ transition(id: number, target?: AnimationTarget): void {
23
+ this.set(this.transitions, id, target);
24
+ }
25
+ path(id: number, target?: AnimationTarget): void {
26
+ this.set(this.paths, id, target);
27
+ }
28
+ refresh(): void {
29
+ if (!this.observer || (!this.transitions.size && !this.paths.size)) return;
30
+ for (const target of this.transitions.values()) this.observer(target);
31
+ for (const target of this.paths.values()) this.observer(target);
32
+ }
33
+ clear(): void {
34
+ this.transitions.clear();
35
+ this.paths.clear();
36
+ }
37
+ }