@galfus/engine 0.0.0-stage → 0.31.1-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.
- package/README.md +190 -2
- package/package.json +59 -4
- package/src/anchors/cache.ts +101 -0
- package/src/anchors/component.ts +123 -0
- package/src/anchors/hierarchy.ts +192 -0
- package/src/anchors/index.ts +8 -0
- package/src/anchors/solver.ts +247 -0
- package/src/anchors/strategy.ts +142 -0
- package/src/animation/blending.ts +163 -0
- package/src/animation/clip.ts +132 -0
- package/src/animation/coordinator.ts +568 -0
- package/src/animation/index.ts +7 -0
- package/src/animation/native-targets.ts +37 -0
- package/src/animation/player.ts +287 -0
- package/src/audio/index.ts +4 -0
- package/src/audio/manager.ts +371 -0
- package/src/collision/components.ts +211 -0
- package/src/collision/index.ts +16 -0
- package/src/collision/query2d.ts +167 -0
- package/src/collision/query3d.ts +170 -0
- package/src/ecs/component.ts +130 -0
- package/src/ecs/entity.ts +75 -0
- package/src/ecs/index.ts +9 -0
- package/src/ecs/intent.ts +162 -0
- package/src/ecs/metadata.ts +44 -0
- package/src/ecs/system.ts +224 -0
- package/src/ecs/world.ts +291 -0
- package/src/index.ts +19 -0
- package/src/interaction/focus.ts +327 -0
- package/src/interaction/index.ts +9 -0
- package/src/interaction/listener.ts +58 -0
- package/src/interaction/snapshot2d.ts +210 -0
- package/src/interaction/snapshot3d.ts +214 -0
- package/src/interaction/system.ts +544 -0
- package/src/interaction/types.ts +27 -0
- package/src/math/common.ts +67 -0
- package/src/math/index.ts +17 -0
- package/src/math/mat2d.ts +320 -0
- package/src/math/mat4.ts +576 -0
- package/src/math/quat.ts +438 -0
- package/src/math/vec2.ts +161 -0
- package/src/math/vec3.ts +242 -0
- package/src/math/vec4.ts +171 -0
- package/src/multiverse/coordinator.ts +540 -0
- package/src/multiverse/index.ts +13 -0
- package/src/multiverse/presentation-graph.ts +306 -0
- package/src/multiverse/presentation-links.ts +134 -0
- package/src/multiverse/realm.ts +49 -0
- package/src/platform/index.ts +2 -0
- package/src/platform/platform.ts +149 -0
- package/src/platform/window.ts +181 -0
- package/src/realm2d/components.ts +254 -0
- package/src/realm2d/index.ts +12 -0
- package/src/realm2d/presets/index.ts +36 -0
- package/src/realm2d/realm.ts +522 -0
- package/src/realm2d/sync.ts +284 -0
- package/src/realm2d/utils/index.ts +32 -0
- package/src/realm3d/components.ts +341 -0
- package/src/realm3d/geometry.ts +224 -0
- package/src/realm3d/index.ts +12 -0
- package/src/realm3d/presets/index.ts +67 -0
- package/src/realm3d/realm.ts +588 -0
- package/src/realm3d/resource-ids.ts +62 -0
- package/src/realm3d/skinning.ts +128 -0
- package/src/realm3d/sync.ts +442 -0
- package/src/realm3d/utils/index.ts +26 -0
- package/src/resources/index.ts +4 -0
- package/src/resources/manager.ts +92 -0
- package/src/runtime.ts +588 -0
- package/src/spatial/dirty_registry.ts +217 -0
- package/src/spatial/flags.ts +14 -0
- package/src/spatial/index.ts +6 -0
- package/src/spatial/metrics.ts +124 -0
- package/src/text/editor.ts +224 -0
- package/src/text/encoding.ts +38 -0
- package/src/text/index.ts +13 -0
- package/src/text/input.ts +160 -0
- package/src/text/keyboard.ts +50 -0
- package/src/text/svg-decorations.ts +147 -0
- 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,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
|
+
}
|