@forgeax/engine-scene 0.1.20 → 0.1.23
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 +37 -9
- package/dist/__tests__/flat-propagation.contract.test.d.ts +2 -0
- package/dist/__tests__/flat-propagation.contract.test.d.ts.map +1 -0
- package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts +2 -0
- package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/flat-propagation.perf.test.d.ts +2 -0
- package/dist/__tests__/flat-propagation.perf.test.d.ts.map +1 -0
- package/dist/__tests__/scene-binding.integration.test.d.ts +2 -0
- package/dist/__tests__/scene-binding.integration.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-order.contract.test.d.ts +2 -0
- package/dist/__tests__/schedule-order.contract.test.d.ts.map +1 -0
- package/dist/__tests__/structural.test.d.ts +2 -0
- package/dist/__tests__/structural.test.d.ts.map +1 -0
- package/dist/__tests__/transform-carrier-consumers.test-d.d.ts +2 -0
- package/dist/__tests__/transform-carrier-consumers.test-d.d.ts.map +1 -0
- package/dist/__tests__/transform-carrier-prototype.bench.d.ts +39 -0
- package/dist/__tests__/transform-carrier-prototype.bench.d.ts.map +1 -0
- package/dist/__tests__/transform-carrier-prototype.test-d.d.ts +2 -0
- package/dist/__tests__/transform-carrier-prototype.test-d.d.ts.map +1 -0
- package/dist/__tests__/transform-carrier-prototype.test.d.ts +2 -0
- package/dist/__tests__/transform-carrier-prototype.test.d.ts.map +1 -0
- package/dist/__tests__/transform-component-version.unit.test.d.ts +2 -0
- package/dist/__tests__/transform-component-version.unit.test.d.ts.map +1 -0
- package/dist/assets/scene-decoder.d.ts.map +1 -1
- package/dist/components/child-of.d.ts +15 -8
- package/dist/components/child-of.d.ts.map +1 -1
- package/dist/components/children.d.ts.map +1 -1
- package/dist/components/transform.d.ts +7 -43
- package/dist/components/transform.d.ts.map +1 -1
- package/dist/errors.d.ts +23 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +616 -372
- package/dist/index.mjs.map +1 -1
- package/dist/instances/binding.d.ts +29 -0
- package/dist/instances/binding.d.ts.map +1 -0
- package/dist/instances/externalization.d.ts.map +1 -1
- package/dist/instances/scene-instances.d.ts +5 -17
- package/dist/instances/scene-instances.d.ts.map +1 -1
- package/dist/instances/state.d.ts +28 -0
- package/dist/instances/state.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/systems/hierarchy-projection.d.ts.map +1 -1
- package/dist/systems/index.d.ts +1 -1
- package/dist/systems/index.d.ts.map +1 -1
- package/dist/systems/propagate-transforms.d.ts +1 -2
- package/dist/systems/propagate-transforms.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/components.unit.test.ts +34 -2
- package/src/__tests__/flat-propagation.contract.test.ts +128 -0
- package/src/__tests__/flat-propagation.derived-writer.contract.test.ts +39 -0
- package/src/__tests__/flat-propagation.perf.test.ts +336 -0
- package/src/__tests__/plugin.integration.test.ts +2 -2
- package/src/__tests__/propagation.unit.test.ts +207 -91
- package/src/__tests__/scene-binding.integration.test.ts +124 -0
- package/src/__tests__/schedule-order.contract.test.ts +43 -0
- package/src/__tests__/structural.test.ts +23 -0
- package/src/__tests__/transform-carrier-consumers.test-d.ts +156 -0
- package/src/__tests__/transform-carrier-prototype.bench.ts +168 -0
- package/src/__tests__/transform-carrier-prototype.test-d.ts +71 -0
- package/src/__tests__/transform-carrier-prototype.test.ts +170 -0
- package/src/__tests__/{transform-change-journal.unit.test.ts → transform-component-version.unit.test.ts} +32 -39
- package/src/assets/scene-decoder.ts +6 -1
- package/src/components/child-of.ts +15 -8
- package/src/components/children.ts +6 -0
- package/src/components/transform.ts +23 -81
- package/src/errors.ts +26 -2
- package/src/index.ts +10 -1
- package/src/instances/binding.ts +77 -0
- package/src/instances/externalization.ts +7 -1
- package/src/instances/scene-instances.ts +47 -76
- package/src/instances/state.ts +62 -0
- package/src/plugin.ts +9 -2
- package/src/systems/hierarchy-projection.ts +25 -8
- package/src/systems/index.ts +3 -0
- package/src/systems/propagate-transforms.ts +376 -245
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/transform-change-journal.unit.test.d.ts +0 -2
- package/dist/__tests__/transform-change-journal.unit.test.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -64,7 +64,11 @@ function resolveSceneWireRefs(payload, refs) {
|
|
|
64
64
|
for (const [componentName, rawFields] of Object.entries(entity.components)) {
|
|
65
65
|
components[componentName] = { ...rawFields };
|
|
66
66
|
}
|
|
67
|
-
entities.push({
|
|
67
|
+
entities.push({
|
|
68
|
+
localId: entity.localId,
|
|
69
|
+
...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
|
|
70
|
+
components
|
|
71
|
+
});
|
|
68
72
|
}
|
|
69
73
|
const mounts = resolveMounts(payload.mounts, refs);
|
|
70
74
|
if (!mounts.ok) return mounts;
|
|
@@ -77,6 +81,7 @@ function resolveSceneWireRefs(payload, refs) {
|
|
|
77
81
|
ok: true,
|
|
78
82
|
value: {
|
|
79
83
|
kind: "scene",
|
|
84
|
+
...payload.sourceKey === void 0 ? {} : { sourceKey: payload.sourceKey },
|
|
80
85
|
entities,
|
|
81
86
|
...mounts.value === void 0 ? {} : { mounts: mounts.value },
|
|
82
87
|
...skinGuids.value === void 0 ? {} : { skinGuids: skinGuids.value }
|
|
@@ -103,11 +108,39 @@ var sceneAssetContribution = {
|
|
|
103
108
|
|
|
104
109
|
// src/components/children.ts
|
|
105
110
|
import { defineRelationship } from "@forgeax/engine-ecs";
|
|
111
|
+
|
|
112
|
+
// src/components/transform.ts
|
|
113
|
+
import { defineComponent } from "@forgeax/engine-ecs";
|
|
114
|
+
var IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
115
|
+
var GlobalTransform = defineComponent(
|
|
116
|
+
"GlobalTransform",
|
|
117
|
+
{
|
|
118
|
+
world: { type: "array<f32, 16>", default: IDENTITY_MAT4 }
|
|
119
|
+
},
|
|
120
|
+
{ transient: true }
|
|
121
|
+
);
|
|
122
|
+
var Transform = defineComponent(
|
|
123
|
+
"Transform",
|
|
124
|
+
{
|
|
125
|
+
pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
|
|
126
|
+
// Component order [x, y, z, w] is shared with glTF.
|
|
127
|
+
quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
|
|
128
|
+
scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) }
|
|
129
|
+
},
|
|
130
|
+
{ requires: [GlobalTransform] }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// src/components/children.ts
|
|
106
134
|
var { source: ChildOf, target: Children } = defineRelationship({
|
|
107
135
|
sourceName: "ChildOf",
|
|
108
136
|
sourceField: "parent",
|
|
109
137
|
targetName: "Children",
|
|
110
138
|
targetField: "entities",
|
|
139
|
+
// Every scene hierarchy node is spatial. Adding ChildOf therefore
|
|
140
|
+
// materializes the local/derived transform pair at the same structural
|
|
141
|
+
// boundary, so render- and scene-authored children cannot enter a frame
|
|
142
|
+
// with an incomplete hierarchy node.
|
|
143
|
+
sourceRequires: [Transform],
|
|
111
144
|
exclusive: true,
|
|
112
145
|
linkedSpawn: true
|
|
113
146
|
});
|
|
@@ -134,29 +167,14 @@ function collectSubtree(world, spawnRoot, visited) {
|
|
|
134
167
|
}
|
|
135
168
|
|
|
136
169
|
// src/components/morph-weights.ts
|
|
137
|
-
import { defineComponent } from "@forgeax/engine-ecs";
|
|
138
|
-
var MorphWeights =
|
|
170
|
+
import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
|
|
171
|
+
var MorphWeights = defineComponent2("MorphWeights", {
|
|
139
172
|
weights: { type: "array<f32>" }
|
|
140
173
|
});
|
|
141
174
|
|
|
142
175
|
// src/components/name.ts
|
|
143
|
-
import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
|
|
144
|
-
var Name = defineComponent2("Name", { value: { type: "string" } });
|
|
145
|
-
|
|
146
|
-
// src/components/transform.ts
|
|
147
176
|
import { defineComponent as defineComponent3 } from "@forgeax/engine-ecs";
|
|
148
|
-
var
|
|
149
|
-
var Transform = defineComponent3("Transform", {
|
|
150
|
-
pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
|
|
151
|
-
// Component order [x, y, z, w] end to end (glTF-aligned; E6).
|
|
152
|
-
quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
|
|
153
|
-
scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) },
|
|
154
|
-
// `world` is field-level transient (D-5): scene collect skips it. The resolved
|
|
155
|
-
// world mat4 is derived by the propagate kernel from the persisted local TRS
|
|
156
|
-
// each frame, so serializing it would store reconstructable data (SSOT: local
|
|
157
|
-
// TRS). Round-trip re-derives an equivalent world on the first propagate pass.
|
|
158
|
-
world: { type: "array<f32, 16>", default: IDENTITY_MAT4, transient: true }
|
|
159
|
-
});
|
|
177
|
+
var Name = defineComponent3("Name", { value: { type: "string" } });
|
|
160
178
|
|
|
161
179
|
// src/errors.ts
|
|
162
180
|
import { ComponentNotDefinedError } from "@forgeax/engine-ecs/projection";
|
|
@@ -175,6 +193,55 @@ var SceneError = class extends Error {
|
|
|
175
193
|
}
|
|
176
194
|
};
|
|
177
195
|
|
|
196
|
+
// src/instances/binding.ts
|
|
197
|
+
import { err as err2, ok as ok2 } from "@forgeax/engine-types";
|
|
198
|
+
function validateSceneBindings(sceneSourceKey, bindingKeys) {
|
|
199
|
+
if (sceneSourceKey.length === 0) {
|
|
200
|
+
return err2({
|
|
201
|
+
code: "scene-binding-source-missing",
|
|
202
|
+
expected: "a non-empty scene sourceKey",
|
|
203
|
+
hint: "declare the scene sourceKey in the author inventory",
|
|
204
|
+
detail: {}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
208
|
+
for (const bindingKey of bindingKeys) {
|
|
209
|
+
if (bindingKey.length === 0 || seen.has(bindingKey)) {
|
|
210
|
+
return err2({
|
|
211
|
+
code: "scene-binding-duplicate",
|
|
212
|
+
expected: "unique non-empty bindingKey values within one scene",
|
|
213
|
+
hint: "rename the duplicate bindingKey in the scene producer",
|
|
214
|
+
detail: { sceneSourceKey, bindingKey }
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
seen.add(bindingKey);
|
|
218
|
+
}
|
|
219
|
+
return ok2([...bindingKeys]);
|
|
220
|
+
}
|
|
221
|
+
function sceneEntity(sceneSourceKey, bindingKey) {
|
|
222
|
+
return { sceneSourceKey, bindingKey };
|
|
223
|
+
}
|
|
224
|
+
function resolveSceneEntity(ref, instance) {
|
|
225
|
+
if (ref.sceneSourceKey !== instance.sceneSourceKey) {
|
|
226
|
+
return err2({
|
|
227
|
+
code: "scene-binding-wrong-instance",
|
|
228
|
+
expected: `scene instance ${ref.sceneSourceKey}`,
|
|
229
|
+
hint: "resolve the SceneEntityRef against its owning SceneInstance",
|
|
230
|
+
detail: { sceneSourceKey: ref.sceneSourceKey, bindingKey: ref.bindingKey }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const value = instance.bindings.get(ref.bindingKey);
|
|
234
|
+
if (value === void 0) {
|
|
235
|
+
return err2({
|
|
236
|
+
code: "scene-binding-missing",
|
|
237
|
+
expected: "bindingKey declared by the scene producer",
|
|
238
|
+
hint: "declare the bindingKey in the scene producer before consuming it",
|
|
239
|
+
detail: { sceneSourceKey: ref.sceneSourceKey, bindingKey: ref.bindingKey }
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return ok2(value);
|
|
243
|
+
}
|
|
244
|
+
|
|
178
245
|
// src/instances/collect-profile.ts
|
|
179
246
|
var SCENE_COLLECT_PROFILE = Object.freeze({
|
|
180
247
|
includeComponent: (_componentName, transient) => !transient,
|
|
@@ -182,7 +249,7 @@ var SCENE_COLLECT_PROFILE = Object.freeze({
|
|
|
182
249
|
});
|
|
183
250
|
|
|
184
251
|
// src/instances/externalization.ts
|
|
185
|
-
import { err as
|
|
252
|
+
import { err as err3, ok as ok3 } from "@forgeax/engine-types";
|
|
186
253
|
function sharedKind(type) {
|
|
187
254
|
if (type?.startsWith("shared<")) return "one";
|
|
188
255
|
if (type?.startsWith("array<shared<")) return "many";
|
|
@@ -236,7 +303,11 @@ function externalizeSceneAsset(scene, resolveSchema) {
|
|
|
236
303
|
components[componentName] = fields;
|
|
237
304
|
}
|
|
238
305
|
}
|
|
239
|
-
return {
|
|
306
|
+
return {
|
|
307
|
+
localId: entity.localId,
|
|
308
|
+
...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
|
|
309
|
+
components
|
|
310
|
+
};
|
|
240
311
|
});
|
|
241
312
|
const mounts = scene.mounts?.map((mount) => {
|
|
242
313
|
const source = typeof mount.source === "string" ? addRef(
|
|
@@ -260,11 +331,13 @@ function externalizeSceneAsset(scene, resolveSchema) {
|
|
|
260
331
|
};
|
|
261
332
|
});
|
|
262
333
|
for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {
|
|
263
|
-
if (typeof guid !== "string") return
|
|
334
|
+
if (typeof guid !== "string") return err3({ field: "skinGuids", value: guid });
|
|
264
335
|
addRef(guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
|
|
265
336
|
}
|
|
266
|
-
return
|
|
337
|
+
return ok3({
|
|
267
338
|
payload: {
|
|
339
|
+
kind: "scene",
|
|
340
|
+
...scene.sourceKey === void 0 ? {} : { sourceKey: scene.sourceKey },
|
|
268
341
|
entities,
|
|
269
342
|
...mounts === void 0 || mounts.length === 0 ? {} : { mounts },
|
|
270
343
|
...scene.skinGuids === void 0 ? {} : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid)) }
|
|
@@ -281,14 +354,14 @@ import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/
|
|
|
281
354
|
import { componentSchema } from "@forgeax/engine-ecs/internal";
|
|
282
355
|
import { fillComponentDefaults, StaleEntityError } from "@forgeax/engine-ecs/projection";
|
|
283
356
|
import {
|
|
284
|
-
err as
|
|
285
|
-
ok as
|
|
357
|
+
err as err4,
|
|
358
|
+
ok as ok4,
|
|
286
359
|
PACK_ERROR_HINTS,
|
|
287
360
|
toUnique,
|
|
288
361
|
unwrapHandle
|
|
289
362
|
} from "@forgeax/engine-types";
|
|
290
|
-
|
|
291
|
-
|
|
363
|
+
|
|
364
|
+
// src/instances/state.ts
|
|
292
365
|
var sceneWorldStates = /* @__PURE__ */ new WeakMap();
|
|
293
366
|
function sceneWorldState(world) {
|
|
294
367
|
const current = sceneWorldStates.get(world);
|
|
@@ -297,6 +370,24 @@ function sceneWorldState(world) {
|
|
|
297
370
|
sceneWorldStates.set(world, created);
|
|
298
371
|
return created;
|
|
299
372
|
}
|
|
373
|
+
function mountOverrideStateKey(ov) {
|
|
374
|
+
return ov.field !== void 0 ? `${ov.comp}:${ov.field}` : ov.comp;
|
|
375
|
+
}
|
|
376
|
+
function isPrimitiveScalarFieldType(fieldType) {
|
|
377
|
+
if (fieldType === "f32" || fieldType === "f64" || fieldType === "u32" || fieldType === "i32" || fieldType === "u8" || fieldType === "i8" || fieldType === "u16" || fieldType === "i16" || fieldType === "bool" || fieldType === "string") {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return fieldType.startsWith("enum<");
|
|
381
|
+
}
|
|
382
|
+
function primitiveJsType(fieldType) {
|
|
383
|
+
if (fieldType === "bool") return "boolean";
|
|
384
|
+
if (fieldType === "string") return "string";
|
|
385
|
+
return "number";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/instances/scene-instances.ts
|
|
389
|
+
var entityIndex = (entity) => entity & 16777215;
|
|
390
|
+
var entityGeneration = (entity) => entity >>> 24 & 255;
|
|
300
391
|
function worldSetSceneAssetResolver(world, resolver) {
|
|
301
392
|
sceneWorldState(world).resolver = resolver;
|
|
302
393
|
}
|
|
@@ -308,7 +399,7 @@ function worldInstantiateScene(world, handle, parent) {
|
|
|
308
399
|
const diagnostics = [];
|
|
309
400
|
const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);
|
|
310
401
|
if (!r.ok) return r;
|
|
311
|
-
return
|
|
402
|
+
return ok4({ root: r.value, diagnostics });
|
|
312
403
|
}
|
|
313
404
|
function worldInstantiateScenePayload(world, asset, parent) {
|
|
314
405
|
const handle = world.allocSharedRef("SceneAsset", asset);
|
|
@@ -332,7 +423,7 @@ function worldInstantiateSceneFlat(world, handle) {
|
|
|
332
423
|
stack.delete(handleKey);
|
|
333
424
|
}
|
|
334
425
|
if (!r.ok) return r;
|
|
335
|
-
return
|
|
426
|
+
return ok4({ ...r.value, diagnostics });
|
|
336
427
|
}
|
|
337
428
|
function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
|
|
338
429
|
const handleKey = unwrapHandle(handle);
|
|
@@ -345,7 +436,7 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
|
|
|
345
436
|
kind: "mount-asset",
|
|
346
437
|
cycle: cycleArr
|
|
347
438
|
};
|
|
348
|
-
return
|
|
439
|
+
return err4({
|
|
349
440
|
code: "pack-cyclic-reference",
|
|
350
441
|
expected: "acyclic SceneAsset mount graph",
|
|
351
442
|
hint: PACK_ERROR_HINTS["pack-cyclic-reference"],
|
|
@@ -365,18 +456,27 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
|
|
|
365
456
|
function worldResolveSceneAsset(world, handle) {
|
|
366
457
|
const r = world.sharedRefs.resolve(handle);
|
|
367
458
|
if (!r.ok) {
|
|
368
|
-
return
|
|
459
|
+
return err4(r.error);
|
|
369
460
|
}
|
|
370
|
-
return
|
|
461
|
+
return ok4(r.value);
|
|
371
462
|
}
|
|
372
463
|
function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
|
|
373
464
|
const sceneInstanceToken = world.components.resolve("SceneInstance");
|
|
374
465
|
if (sceneInstanceToken === void 0) {
|
|
375
|
-
return
|
|
466
|
+
return err4(new ComponentNotDefinedError("SceneInstance"));
|
|
376
467
|
}
|
|
377
468
|
const childOfToken = world.components.resolve("ChildOf");
|
|
378
469
|
const ownEntities = asset.entities;
|
|
379
470
|
const ownMounts = asset.mounts ?? [];
|
|
471
|
+
const bindingKeys = ownEntities.flatMap(
|
|
472
|
+
(entity) => entity.bindingKey === void 0 ? [] : [entity.bindingKey]
|
|
473
|
+
);
|
|
474
|
+
if (bindingKeys.length > 0) {
|
|
475
|
+
const bindingCheck = validateSceneBindings(asset.sourceKey ?? "", bindingKeys);
|
|
476
|
+
if (!bindingCheck.ok) {
|
|
477
|
+
return err4(bindingCheck.error);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
380
480
|
const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);
|
|
381
481
|
const countBaseline = ownEntities.length + ownMounts.length + memberSum;
|
|
382
482
|
let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId), -1);
|
|
@@ -417,7 +517,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
|
|
|
417
517
|
}
|
|
418
518
|
if (overlapLids.size > 0) {
|
|
419
519
|
const overlapping = Array.from(overlapLids).sort((a, b) => a - b);
|
|
420
|
-
return
|
|
520
|
+
return err4({
|
|
421
521
|
code: "pack-mount-localid-overlap",
|
|
422
522
|
expected: "each LocalEntityId claimed by exactly one entity or mount slot",
|
|
423
523
|
hint: PACK_ERROR_HINTS["pack-mount-localid-overlap"],
|
|
@@ -457,7 +557,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
|
|
|
457
557
|
const childMapping = childInstRes.value.mapping;
|
|
458
558
|
mountInstances.push({ mount, root: childRes.value, mapping: childMapping });
|
|
459
559
|
if (childMapping.length !== mount.memberCount) {
|
|
460
|
-
return
|
|
560
|
+
return err4({
|
|
461
561
|
code: "pack-mount-count-mismatch",
|
|
462
562
|
expected: "mount.memberCount === child SceneAsset totalSlots",
|
|
463
563
|
hint: PACK_ERROR_HINTS["pack-mount-count-mismatch"],
|
|
@@ -528,7 +628,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
|
|
|
528
628
|
}
|
|
529
629
|
}
|
|
530
630
|
}
|
|
531
|
-
return
|
|
631
|
+
return ok4({
|
|
532
632
|
mapping,
|
|
533
633
|
entityToLocalId,
|
|
534
634
|
rootEntities,
|
|
@@ -541,7 +641,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
|
|
|
541
641
|
function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics) {
|
|
542
642
|
const sceneInstanceToken = world.components.resolve("SceneInstance");
|
|
543
643
|
if (sceneInstanceToken === void 0) {
|
|
544
|
-
return
|
|
644
|
+
return err4(new ComponentNotDefinedError("SceneInstance"));
|
|
545
645
|
}
|
|
546
646
|
const childOfToken = world.components.resolve("ChildOf");
|
|
547
647
|
const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
|
|
@@ -599,8 +699,18 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
|
|
|
599
699
|
}
|
|
600
700
|
}
|
|
601
701
|
const detached = /* @__PURE__ */ new Set();
|
|
702
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const entity of asset.entities) {
|
|
704
|
+
if (entity.bindingKey === void 0) continue;
|
|
705
|
+
const live = mapping[entity.localId];
|
|
706
|
+
if (live !== void 0 && live !== ENTITY_NULL_RAW) {
|
|
707
|
+
bindings.set(entity.bindingKey, live);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
602
710
|
const state = {
|
|
603
711
|
source: handle,
|
|
712
|
+
sceneSourceKey: asset.sourceKey,
|
|
713
|
+
bindings,
|
|
604
714
|
entityToLocalId,
|
|
605
715
|
detachedLocalIds: detached,
|
|
606
716
|
// Convert overrides Map<LocalEntityId, Map<string, MountOverride>>
|
|
@@ -641,7 +751,7 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
|
|
|
641
751
|
if (!r.ok) return r;
|
|
642
752
|
}
|
|
643
753
|
}
|
|
644
|
-
return
|
|
754
|
+
return ok4(rootEntity);
|
|
645
755
|
}
|
|
646
756
|
function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics) {
|
|
647
757
|
const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
|
|
@@ -680,7 +790,7 @@ function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics
|
|
|
680
790
|
}
|
|
681
791
|
}
|
|
682
792
|
}
|
|
683
|
-
return
|
|
793
|
+
return ok4({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
|
|
684
794
|
}
|
|
685
795
|
function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics) {
|
|
686
796
|
const out = [];
|
|
@@ -688,7 +798,7 @@ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics)
|
|
|
688
798
|
for (const compName of Object.keys(node.components)) {
|
|
689
799
|
const token = world.components.resolve(compName);
|
|
690
800
|
if (token === void 0) {
|
|
691
|
-
return
|
|
801
|
+
return err4(new ComponentNotDefinedError(compName));
|
|
692
802
|
}
|
|
693
803
|
const raw = node.components[compName] ?? {};
|
|
694
804
|
const schema = componentSchema(token);
|
|
@@ -715,11 +825,11 @@ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics)
|
|
|
715
825
|
const filled = fillComponentDefaults(token, remappedRaw);
|
|
716
826
|
out.push({ component: token, data: filled });
|
|
717
827
|
}
|
|
718
|
-
return
|
|
828
|
+
return ok4(out);
|
|
719
829
|
}
|
|
720
830
|
function worldApplyMountOverride(world, member, ov) {
|
|
721
831
|
const ovToken = world.components.resolve(ov.comp);
|
|
722
|
-
if (ovToken === void 0) return
|
|
832
|
+
if (ovToken === void 0) return ok4(void 0);
|
|
723
833
|
if (ov.field !== void 0) {
|
|
724
834
|
return world.set(member, ovToken, { [ov.field]: ov.value });
|
|
725
835
|
}
|
|
@@ -733,7 +843,7 @@ function worldApplyMountOverride(world, member, ov) {
|
|
|
733
843
|
}
|
|
734
844
|
function worldValidateMountOverrides(world, mount) {
|
|
735
845
|
const overrides = mount.overrides;
|
|
736
|
-
if (overrides === void 0) return
|
|
846
|
+
if (overrides === void 0) return ok4(void 0);
|
|
737
847
|
const memberFirst = mount.memberFirst;
|
|
738
848
|
const memberCount = mount.memberCount;
|
|
739
849
|
const memberLast = memberFirst + memberCount;
|
|
@@ -741,7 +851,7 @@ function worldValidateMountOverrides(world, mount) {
|
|
|
741
851
|
for (const ov of overrides) {
|
|
742
852
|
const ovLid = ov.localId;
|
|
743
853
|
if (ovLid < memberFirst || ovLid >= memberLast) {
|
|
744
|
-
return
|
|
854
|
+
return err4({
|
|
745
855
|
code: "pack-mount-override-localid-out-of-range",
|
|
746
856
|
expected: `override.localId in [${memberFirst}, ${memberLast})`,
|
|
747
857
|
hint: PACK_ERROR_HINTS["pack-mount-override-localid-out-of-range"],
|
|
@@ -758,7 +868,7 @@ function worldValidateMountOverrides(world, mount) {
|
|
|
758
868
|
if (ovToken !== void 0) {
|
|
759
869
|
const schema = componentSchema(ovToken);
|
|
760
870
|
if (!(ov.field in schema)) {
|
|
761
|
-
return
|
|
871
|
+
return err4({
|
|
762
872
|
code: "pack-mount-override-unknown-field",
|
|
763
873
|
expected: `override.field defined on component '${ov.comp}'`,
|
|
764
874
|
hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
|
|
@@ -773,13 +883,13 @@ function worldValidateMountOverrides(world, mount) {
|
|
|
773
883
|
}
|
|
774
884
|
} else {
|
|
775
885
|
if (ovToken === void 0) {
|
|
776
|
-
return
|
|
886
|
+
return err4(new ComponentNotDefinedError(ov.comp));
|
|
777
887
|
}
|
|
778
888
|
const schema = componentSchema(ovToken);
|
|
779
889
|
const valueMap = ov.value ?? {};
|
|
780
890
|
for (const key of Object.keys(valueMap)) {
|
|
781
891
|
if (!(key in schema)) {
|
|
782
|
-
return
|
|
892
|
+
return err4({
|
|
783
893
|
code: "pack-mount-override-unknown-field",
|
|
784
894
|
expected: `override.value keys defined on component '${ov.comp}'`,
|
|
785
895
|
hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
|
|
@@ -794,7 +904,7 @@ function worldValidateMountOverrides(world, mount) {
|
|
|
794
904
|
}
|
|
795
905
|
}
|
|
796
906
|
}
|
|
797
|
-
return
|
|
907
|
+
return ok4(void 0);
|
|
798
908
|
}
|
|
799
909
|
function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
|
|
800
910
|
const fakeNode = {
|
|
@@ -813,7 +923,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
|
|
|
813
923
|
if (cdRes.value.length === 0) {
|
|
814
924
|
const childOfToken = world.components.resolve("ChildOf");
|
|
815
925
|
if (childOfToken === void 0) {
|
|
816
|
-
return
|
|
926
|
+
return err4(new ComponentNotDefinedError("ChildOf"));
|
|
817
927
|
}
|
|
818
928
|
cdRes.value.push({
|
|
819
929
|
component: childOfToken,
|
|
@@ -825,7 +935,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
|
|
|
825
935
|
function worldResolveMountSource(world, source, parentHandle) {
|
|
826
936
|
const resolver = worldGetSceneAssetResolver(world);
|
|
827
937
|
if (resolver === null) {
|
|
828
|
-
return
|
|
938
|
+
return err4({
|
|
829
939
|
code: "stale-entity",
|
|
830
940
|
expected: "wired SceneAssetResolver (auto-wired by engine.assets.instantiate)",
|
|
831
941
|
hint: "engine.assets.instantiate sugar wires this for you; call worldSetSceneAssetResolver before nested scene expansion.",
|
|
@@ -834,9 +944,9 @@ function worldResolveMountSource(world, source, parentHandle) {
|
|
|
834
944
|
}
|
|
835
945
|
const r = resolver(source, parentHandle);
|
|
836
946
|
if (!r.ok) {
|
|
837
|
-
return
|
|
947
|
+
return err4(r.error);
|
|
838
948
|
}
|
|
839
|
-
return
|
|
949
|
+
return ok4(r.value);
|
|
840
950
|
}
|
|
841
951
|
function worldMountOverridesToStateMap(src) {
|
|
842
952
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -859,7 +969,7 @@ function worldSetUniqueRefPayload(world, handle, payload) {
|
|
|
859
969
|
function worldResolveSceneInstanceStatePayload(world, root) {
|
|
860
970
|
const sceneInstanceToken = world.components.resolve("SceneInstance");
|
|
861
971
|
if (sceneInstanceToken === void 0) {
|
|
862
|
-
return
|
|
972
|
+
return err4(new ComponentNotDefinedError("SceneInstance"));
|
|
863
973
|
}
|
|
864
974
|
const r = world.get(root, sceneInstanceToken);
|
|
865
975
|
if (!r.ok) return r;
|
|
@@ -867,7 +977,7 @@ function worldResolveSceneInstanceStatePayload(world, root) {
|
|
|
867
977
|
const stateRefHandle = toUnique(stateRefRaw);
|
|
868
978
|
const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));
|
|
869
979
|
if (payload === void 0) {
|
|
870
|
-
return
|
|
980
|
+
return err4(
|
|
871
981
|
new StaleEntityError(root, entityIndex(root), entityGeneration(root), {
|
|
872
982
|
operation: "resolveSceneInstanceState",
|
|
873
983
|
component: "SceneInstance",
|
|
@@ -876,17 +986,27 @@ function worldResolveSceneInstanceStatePayload(world, root) {
|
|
|
876
986
|
})
|
|
877
987
|
);
|
|
878
988
|
}
|
|
879
|
-
return
|
|
989
|
+
return ok4(payload);
|
|
880
990
|
}
|
|
881
991
|
function worldGetSceneInstanceState(world, root) {
|
|
882
992
|
return worldResolveSceneInstanceStatePayload(world, root);
|
|
883
993
|
}
|
|
994
|
+
function worldResolveSceneEntity(world, root, ref) {
|
|
995
|
+
const state = worldResolveSceneInstanceStatePayload(world, root);
|
|
996
|
+
if (!state.ok) return state;
|
|
997
|
+
const resolved = resolveSceneEntity(ref, {
|
|
998
|
+
sceneSourceKey: state.value.sceneSourceKey ?? ref.sceneSourceKey,
|
|
999
|
+
bindings: state.value.bindings
|
|
1000
|
+
});
|
|
1001
|
+
if (!resolved.ok) return err4(resolved.error);
|
|
1002
|
+
return ok4(resolved.value);
|
|
1003
|
+
}
|
|
884
1004
|
function worldDespawnScene(world, root, opts) {
|
|
885
1005
|
const dRes = worldDespawnDescendants(world, root, opts);
|
|
886
1006
|
if (!dRes.ok) return dRes;
|
|
887
1007
|
const drop = world.despawn(root);
|
|
888
1008
|
if (!drop.ok) return drop;
|
|
889
|
-
return
|
|
1009
|
+
return ok4(dRes.value + 1);
|
|
890
1010
|
}
|
|
891
1011
|
function worldDespawnDescendants(world, root, opts) {
|
|
892
1012
|
let detached = null;
|
|
@@ -963,7 +1083,7 @@ function worldDespawnDescendants(world, root, opts) {
|
|
|
963
1083
|
}
|
|
964
1084
|
count += 1;
|
|
965
1085
|
}
|
|
966
|
-
return
|
|
1086
|
+
return ok4(count);
|
|
967
1087
|
}
|
|
968
1088
|
function worldSetSceneOverride(world, root, member, component, field, value) {
|
|
969
1089
|
const stateRes = worldResolveSceneInstanceStatePayload(world, root);
|
|
@@ -971,7 +1091,7 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
|
|
|
971
1091
|
const state = stateRes.value;
|
|
972
1092
|
const lid = state.entityToLocalId.get(member);
|
|
973
1093
|
if (lid === void 0) {
|
|
974
|
-
return
|
|
1094
|
+
return err4(
|
|
975
1095
|
new StaleEntityError(
|
|
976
1096
|
member,
|
|
977
1097
|
entityIndex(member),
|
|
@@ -990,7 +1110,7 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
|
|
|
990
1110
|
const expectJsType = primitiveJsType(schemaType);
|
|
991
1111
|
const actualJsType = typeof value;
|
|
992
1112
|
if (expectJsType !== actualJsType) {
|
|
993
|
-
return
|
|
1113
|
+
return err4({
|
|
994
1114
|
code: "scene-override-type-mismatch",
|
|
995
1115
|
expected: `value typeof === ${expectJsType}`,
|
|
996
1116
|
hint: `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, got ${actualJsType}; coerce or pick a different override path.`,
|
|
@@ -1016,14 +1136,14 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
|
|
|
1016
1136
|
field,
|
|
1017
1137
|
value
|
|
1018
1138
|
});
|
|
1019
|
-
return
|
|
1139
|
+
return ok4(void 0);
|
|
1020
1140
|
}
|
|
1021
1141
|
function worldRemoveSceneOverride(world, root, member, component, field) {
|
|
1022
1142
|
const stateRes = worldResolveSceneInstanceStatePayload(world, root);
|
|
1023
1143
|
if (!stateRes.ok) return stateRes;
|
|
1024
1144
|
const state = stateRes.value;
|
|
1025
1145
|
const lid = state.entityToLocalId.get(member);
|
|
1026
|
-
if (lid === void 0) return
|
|
1146
|
+
if (lid === void 0) return ok4(void 0);
|
|
1027
1147
|
const fieldMap = state.overrides.get(lid);
|
|
1028
1148
|
if (fieldMap !== void 0) {
|
|
1029
1149
|
fieldMap.delete(`${component.name}:${field}`);
|
|
@@ -1039,34 +1159,34 @@ function worldRemoveSceneOverride(world, root, member, component, field) {
|
|
|
1039
1159
|
const r = world.set(member, component, { [field]: layer1[field] });
|
|
1040
1160
|
if (!r.ok) return r;
|
|
1041
1161
|
}
|
|
1042
|
-
return
|
|
1162
|
+
return ok4(void 0);
|
|
1043
1163
|
}
|
|
1044
1164
|
function worldDetachSceneMember(world, root, member) {
|
|
1045
1165
|
const sceneInstanceToken = world.components.resolve("SceneInstance");
|
|
1046
1166
|
if (sceneInstanceToken === void 0) {
|
|
1047
|
-
return
|
|
1167
|
+
return err4(new ComponentNotDefinedError("SceneInstance"));
|
|
1048
1168
|
}
|
|
1049
1169
|
const stateRes = worldResolveSceneInstanceStatePayload(world, root);
|
|
1050
1170
|
if (!stateRes.ok) return stateRes;
|
|
1051
1171
|
const state = stateRes.value;
|
|
1052
1172
|
const lid = state.entityToLocalId.get(member);
|
|
1053
|
-
if (lid === void 0) return
|
|
1173
|
+
if (lid === void 0) return ok4(void 0);
|
|
1054
1174
|
state.detachedLocalIds.add(lid);
|
|
1055
|
-
return
|
|
1175
|
+
return ok4(void 0);
|
|
1056
1176
|
}
|
|
1057
1177
|
function worldReattachSceneMember(world, root, member) {
|
|
1058
1178
|
const stateRes = worldResolveSceneInstanceStatePayload(world, root);
|
|
1059
1179
|
if (!stateRes.ok) return stateRes;
|
|
1060
1180
|
const state = stateRes.value;
|
|
1061
1181
|
const lid = state.entityToLocalId.get(member);
|
|
1062
|
-
if (lid === void 0) return
|
|
1182
|
+
if (lid === void 0) return ok4(void 0);
|
|
1063
1183
|
state.detachedLocalIds.delete(lid);
|
|
1064
|
-
return
|
|
1184
|
+
return ok4(void 0);
|
|
1065
1185
|
}
|
|
1066
1186
|
function worldGetSceneAssetForInstance(world, root) {
|
|
1067
1187
|
const stateRes = worldResolveSceneInstanceStatePayload(world, root);
|
|
1068
1188
|
if (!stateRes.ok) return stateRes;
|
|
1069
|
-
return
|
|
1189
|
+
return ok4(stateRes.value.source);
|
|
1070
1190
|
}
|
|
1071
1191
|
function sceneTopoSort(nodes) {
|
|
1072
1192
|
const n = nodes.length;
|
|
@@ -1109,21 +1229,6 @@ function sceneTopoSort(nodes) {
|
|
|
1109
1229
|
}
|
|
1110
1230
|
return order;
|
|
1111
1231
|
}
|
|
1112
|
-
function mountOverrideStateKey(ov) {
|
|
1113
|
-
return ov.field !== void 0 ? `${ov.comp}:${ov.field}` : ov.comp;
|
|
1114
|
-
}
|
|
1115
|
-
function isPrimitiveScalarFieldType(fieldType) {
|
|
1116
|
-
if (fieldType === "f32" || fieldType === "f64" || fieldType === "u32" || fieldType === "i32" || fieldType === "u8" || fieldType === "i8" || fieldType === "u16" || fieldType === "i16" || fieldType === "bool" || fieldType === "string") {
|
|
1117
|
-
return true;
|
|
1118
|
-
}
|
|
1119
|
-
if (fieldType.startsWith("enum<")) return true;
|
|
1120
|
-
return false;
|
|
1121
|
-
}
|
|
1122
|
-
function primitiveJsType(fieldType) {
|
|
1123
|
-
if (fieldType === "bool") return "boolean";
|
|
1124
|
-
if (fieldType === "string") return "string";
|
|
1125
|
-
return "number";
|
|
1126
|
-
}
|
|
1127
1232
|
|
|
1128
1233
|
// src/systems/propagate-transforms.ts
|
|
1129
1234
|
import {
|
|
@@ -1132,316 +1237,338 @@ import {
|
|
|
1132
1237
|
FixedUpdate,
|
|
1133
1238
|
Update
|
|
1134
1239
|
} from "@forgeax/engine-ecs";
|
|
1135
|
-
import {
|
|
1136
|
-
createWorldProjection as createWorldProjection2,
|
|
1137
|
-
setDerivedComponent
|
|
1138
|
-
} from "@forgeax/engine-ecs/projection";
|
|
1139
1240
|
import { mat4 } from "@forgeax/engine-math";
|
|
1140
|
-
import { err as
|
|
1141
|
-
|
|
1142
|
-
// src/systems/hierarchy-projection.ts
|
|
1143
|
-
import { Entity } from "@forgeax/engine-ecs";
|
|
1144
|
-
import { createWorldProjection } from "@forgeax/engine-ecs/projection";
|
|
1145
|
-
var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1146
|
-
function diagnostic(code, entity, parent) {
|
|
1147
|
-
if (code === "hierarchy-cycle") {
|
|
1148
|
-
return {
|
|
1149
|
-
code,
|
|
1150
|
-
expected: "ChildOf parent edges form an acyclic live hierarchy",
|
|
1151
|
-
hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
|
|
1152
|
-
detail: { entity, parent }
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
return {
|
|
1156
|
-
code,
|
|
1157
|
-
expected: "ChildOf.parent references a live entity in the same World",
|
|
1158
|
-
hint: "remove the stale ChildOf component or restore the referenced parent in this World",
|
|
1159
|
-
detail: { entity, parent }
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
function projectHierarchy(world) {
|
|
1163
|
-
const cached = HIERARCHY_PROJECTION_CACHE.get(world);
|
|
1164
|
-
const changes = cached?.changes ?? createWorldProjection(world, { components: [ChildOf] });
|
|
1165
|
-
if (cached !== void 0) {
|
|
1166
|
-
const evidence = changes.poll();
|
|
1167
|
-
if (evidence.status === "delta" && evidence.changes.length === 0) return cached.snapshot;
|
|
1168
|
-
}
|
|
1169
|
-
const liveEntities = /* @__PURE__ */ new Set();
|
|
1170
|
-
const authoredParents = /* @__PURE__ */ new Map();
|
|
1171
|
-
const query = world.query({ read: [Entity], optional: [ChildOf] });
|
|
1172
|
-
if (query.ok) {
|
|
1173
|
-
for (const row of query.value) {
|
|
1174
|
-
liveEntities.add(row.entity);
|
|
1175
|
-
const parent = row.get(ChildOf)?.parent;
|
|
1176
|
-
if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
const parentOf = /* @__PURE__ */ new Map();
|
|
1180
|
-
const diagnostics = [];
|
|
1181
|
-
for (const [entity, parent] of authoredParents) {
|
|
1182
|
-
if (liveEntities.has(parent)) {
|
|
1183
|
-
parentOf.set(entity, parent);
|
|
1184
|
-
} else {
|
|
1185
|
-
diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
const state = /* @__PURE__ */ new Map();
|
|
1189
|
-
const stack = [];
|
|
1190
|
-
const cycleMembers = /* @__PURE__ */ new Set();
|
|
1191
|
-
const visit = (entity) => {
|
|
1192
|
-
const currentState = state.get(entity) ?? 0;
|
|
1193
|
-
if (currentState === 2) return;
|
|
1194
|
-
if (currentState === 1) {
|
|
1195
|
-
const cycleStart = stack.indexOf(entity);
|
|
1196
|
-
for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
|
|
1197
|
-
const member = stack[index];
|
|
1198
|
-
if (member !== void 0) cycleMembers.add(member);
|
|
1199
|
-
}
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
state.set(entity, 1);
|
|
1203
|
-
stack.push(entity);
|
|
1204
|
-
const parent = parentOf.get(entity);
|
|
1205
|
-
if (parent !== void 0) visit(parent);
|
|
1206
|
-
stack.pop();
|
|
1207
|
-
state.set(entity, 2);
|
|
1208
|
-
};
|
|
1209
|
-
for (const entity of liveEntities) visit(entity);
|
|
1210
|
-
for (const entity of cycleMembers) {
|
|
1211
|
-
const parent = authoredParents.get(entity);
|
|
1212
|
-
if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
|
|
1213
|
-
parentOf.delete(entity);
|
|
1214
|
-
}
|
|
1215
|
-
diagnostics.sort((left, right) => {
|
|
1216
|
-
const entityDelta = left.detail.entity - right.detail.entity;
|
|
1217
|
-
if (entityDelta !== 0) return entityDelta;
|
|
1218
|
-
return left.code.localeCompare(right.code);
|
|
1219
|
-
});
|
|
1220
|
-
const stableParentOf = new Map(parentOf);
|
|
1221
|
-
const stableDiagnostics = Object.freeze(diagnostics.slice());
|
|
1222
|
-
const snapshot = {
|
|
1223
|
-
parentOf: stableParentOf,
|
|
1224
|
-
diagnostics: stableDiagnostics,
|
|
1225
|
-
getParent(entity) {
|
|
1226
|
-
return stableParentOf.get(entity);
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
HIERARCHY_PROJECTION_CACHE.set(world, {
|
|
1230
|
-
changes,
|
|
1231
|
-
snapshot
|
|
1232
|
-
});
|
|
1233
|
-
return snapshot;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// src/systems/propagate-transforms.ts
|
|
1241
|
+
import { err as err5, ok as ok5 } from "@forgeax/engine-types";
|
|
1237
1242
|
var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
|
|
1238
1243
|
var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
|
|
1239
1244
|
var TransformSet = defineSystemSet({ name: "transform" });
|
|
1240
1245
|
var TransformFixedSet = defineSystemSet({ name: "transform-fixed" });
|
|
1241
|
-
var
|
|
1246
|
+
var SCRATCH = /* @__PURE__ */ new WeakMap();
|
|
1242
1247
|
var REGISTRATION_LEASES = /* @__PURE__ */ new WeakMap();
|
|
1243
|
-
function
|
|
1244
|
-
return
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1248
|
+
function pairError(entity, expected) {
|
|
1249
|
+
return err5(
|
|
1250
|
+
new SceneError({
|
|
1251
|
+
code: "hierarchy-broken",
|
|
1252
|
+
expected,
|
|
1253
|
+
hint: "attach both Transform and GlobalTransform at scene authoring or import time, then retry propagation",
|
|
1254
|
+
detail: { entity, parent: entity }
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1249
1257
|
}
|
|
1250
|
-
function
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1258
|
+
function ensureQueries(world, scratch) {
|
|
1259
|
+
if (scratch.flatQuery !== void 0 && scratch.outputQuery !== void 0 && scratch.hierarchyQuery !== void 0 && scratch.missingGlobalQuery !== void 0 && scratch.missingTransformQuery !== void 0) {
|
|
1260
|
+
return ok5(void 0);
|
|
1261
|
+
}
|
|
1262
|
+
const flatOutput = world.query({
|
|
1263
|
+
read: [Transform],
|
|
1264
|
+
write: [GlobalTransform],
|
|
1265
|
+
without: [ChildOf],
|
|
1266
|
+
changed: [Transform]
|
|
1267
|
+
});
|
|
1268
|
+
const output = world.query({ write: [GlobalTransform] });
|
|
1269
|
+
const hierarchy = world.query({ read: [Transform, ChildOf] });
|
|
1270
|
+
const missingGlobal = world.query({ with: [Transform], without: [GlobalTransform] });
|
|
1271
|
+
const missingTransform = world.query({ with: [GlobalTransform], without: [Transform] });
|
|
1272
|
+
if (!flatOutput.ok || !output.ok || !hierarchy.ok || !missingGlobal.ok || !missingTransform.ok) {
|
|
1273
|
+
return pairError(0, "valid Transform and GlobalTransform pair queries");
|
|
1274
|
+
}
|
|
1275
|
+
scratch.flatQuery = flatOutput.value;
|
|
1276
|
+
scratch.outputQuery = output.value;
|
|
1277
|
+
scratch.hierarchyQuery = hierarchy.value;
|
|
1278
|
+
scratch.missingGlobalQuery = missingGlobal.value;
|
|
1279
|
+
scratch.missingTransformQuery = missingTransform.value;
|
|
1280
|
+
return ok5(void 0);
|
|
1256
1281
|
}
|
|
1257
|
-
function
|
|
1258
|
-
|
|
1282
|
+
function validateTransformPairs(world, scratch) {
|
|
1283
|
+
const queryResult = ensureQueries(world, scratch);
|
|
1284
|
+
if (!queryResult.ok) return queryResult;
|
|
1285
|
+
const missingGlobal = scratch.missingGlobalQuery;
|
|
1286
|
+
const missingTransform = scratch.missingTransformQuery;
|
|
1287
|
+
if (missingGlobal === void 0 || missingTransform === void 0) {
|
|
1288
|
+
return pairError(0, "valid Transform and GlobalTransform pair queries");
|
|
1289
|
+
}
|
|
1290
|
+
for (const row of missingGlobal) {
|
|
1291
|
+
return pairError(row.entity, "each Transform entity to carry a GlobalTransform pair");
|
|
1292
|
+
}
|
|
1293
|
+
for (const row of missingTransform) {
|
|
1294
|
+
return pairError(row.entity, "each GlobalTransform entity to carry a Transform pair");
|
|
1295
|
+
}
|
|
1296
|
+
return ok5(void 0);
|
|
1259
1297
|
}
|
|
1260
|
-
function
|
|
1261
|
-
|
|
1298
|
+
function scratchFor(world) {
|
|
1299
|
+
const existing = SCRATCH.get(world);
|
|
1300
|
+
if (existing !== void 0) return existing;
|
|
1301
|
+
const created = {
|
|
1302
|
+
position: new Float32Array(3),
|
|
1303
|
+
rotation: new Float32Array(4),
|
|
1304
|
+
scale: new Float32Array(3),
|
|
1305
|
+
local: mat4.create(),
|
|
1306
|
+
hierarchyPath: []
|
|
1307
|
+
};
|
|
1308
|
+
SCRATCH.set(world, created);
|
|
1309
|
+
return created;
|
|
1262
1310
|
}
|
|
1263
|
-
function
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1311
|
+
function composeColumns(position, rotation, scale, out, scratch, positionStart = 0, rotationStart = 0) {
|
|
1312
|
+
scratch.position[0] = position[positionStart] ?? 0;
|
|
1313
|
+
scratch.position[1] = position[positionStart + 1] ?? 0;
|
|
1314
|
+
scratch.position[2] = position[positionStart + 2] ?? 0;
|
|
1315
|
+
scratch.rotation[0] = rotation[rotationStart] ?? 0;
|
|
1316
|
+
scratch.rotation[1] = rotation[rotationStart + 1] ?? 0;
|
|
1317
|
+
scratch.rotation[2] = rotation[rotationStart + 2] ?? 0;
|
|
1318
|
+
scratch.rotation[3] = rotation[rotationStart + 3] ?? 1;
|
|
1319
|
+
scratch.scale[0] = scale[positionStart] ?? 1;
|
|
1320
|
+
scratch.scale[1] = scale[positionStart + 1] ?? 1;
|
|
1321
|
+
scratch.scale[2] = scale[positionStart + 2] ?? 1;
|
|
1322
|
+
mat4.compose(out, scratch.position, scratch.rotation, scratch.scale);
|
|
1271
1323
|
}
|
|
1272
|
-
function
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1324
|
+
function composeFlatColumns(positions, rotations, scales, worlds, count) {
|
|
1325
|
+
for (let row = 0; row < count; row += 1) {
|
|
1326
|
+
const position = row * 3;
|
|
1327
|
+
const rotation = row * 4;
|
|
1328
|
+
const world = row * 16;
|
|
1329
|
+
const x = rotations[rotation] ?? 0;
|
|
1330
|
+
const y = rotations[rotation + 1] ?? 0;
|
|
1331
|
+
const z = rotations[rotation + 2] ?? 0;
|
|
1332
|
+
const w = rotations[rotation + 3] ?? 1;
|
|
1333
|
+
const x2 = x + x;
|
|
1334
|
+
const y2 = y + y;
|
|
1335
|
+
const z2 = z + z;
|
|
1336
|
+
const xx = x * x2;
|
|
1337
|
+
const xy = x * y2;
|
|
1338
|
+
const xz = x * z2;
|
|
1339
|
+
const yy = y * y2;
|
|
1340
|
+
const yz = y * z2;
|
|
1341
|
+
const zz = z * z2;
|
|
1342
|
+
const wx = w * x2;
|
|
1343
|
+
const wy = w * y2;
|
|
1344
|
+
const wz = w * z2;
|
|
1345
|
+
const sx = scales[position] ?? 1;
|
|
1346
|
+
const sy = scales[position + 1] ?? 1;
|
|
1347
|
+
const sz = scales[position + 2] ?? 1;
|
|
1348
|
+
worlds[world] = (1 - (yy + zz)) * sx;
|
|
1349
|
+
worlds[world + 1] = (xy + wz) * sx;
|
|
1350
|
+
worlds[world + 2] = (xz - wy) * sx;
|
|
1351
|
+
worlds[world + 3] = 0;
|
|
1352
|
+
worlds[world + 4] = (xy - wz) * sy;
|
|
1353
|
+
worlds[world + 5] = (1 - (xx + zz)) * sy;
|
|
1354
|
+
worlds[world + 6] = (yz + wx) * sy;
|
|
1355
|
+
worlds[world + 7] = 0;
|
|
1356
|
+
worlds[world + 8] = (xz + wy) * sz;
|
|
1357
|
+
worlds[world + 9] = (yz - wx) * sz;
|
|
1358
|
+
worlds[world + 10] = (1 - (xx + yy)) * sz;
|
|
1359
|
+
worlds[world + 11] = 0;
|
|
1360
|
+
worlds[world + 12] = positions[position] ?? 0;
|
|
1361
|
+
worlds[world + 13] = positions[position + 1] ?? 0;
|
|
1362
|
+
worlds[world + 14] = positions[position + 2] ?? 0;
|
|
1363
|
+
worlds[world + 15] = 1;
|
|
1283
1364
|
}
|
|
1284
|
-
return affected;
|
|
1285
|
-
}
|
|
1286
|
-
function hierarchyError(hierarchy) {
|
|
1287
|
-
const first = hierarchy.diagnostics[0];
|
|
1288
|
-
if (first === void 0) return ok4(void 0);
|
|
1289
|
-
return err4(
|
|
1290
|
-
new SceneError({
|
|
1291
|
-
code: first.code,
|
|
1292
|
-
expected: first.expected,
|
|
1293
|
-
hint: first.hint,
|
|
1294
|
-
detail: first.detail
|
|
1295
|
-
})
|
|
1296
|
-
);
|
|
1297
1365
|
}
|
|
1298
|
-
function
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1366
|
+
function writeWorld(world, output, entity, value) {
|
|
1367
|
+
const row = output.at(entity);
|
|
1368
|
+
if (row === void 0) {
|
|
1369
|
+
return err5(
|
|
1370
|
+
new SceneError({
|
|
1371
|
+
code: "hierarchy-broken",
|
|
1372
|
+
expected: "each Transform entity to carry a GlobalTransform pair",
|
|
1373
|
+
hint: "attach GlobalTransform at scene authoring or import time, then retry propagation",
|
|
1374
|
+
detail: { entity, parent: entity }
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
const current = world.get(entity, GlobalTransform);
|
|
1379
|
+
if (!current.ok)
|
|
1380
|
+
return err5(
|
|
1381
|
+
new SceneError({
|
|
1382
|
+
code: "hierarchy-broken",
|
|
1383
|
+
expected: "each Transform entity to carry a GlobalTransform pair",
|
|
1384
|
+
hint: "attach GlobalTransform at scene authoring or import time, then retry propagation",
|
|
1385
|
+
detail: { entity, parent: entity }
|
|
1386
|
+
})
|
|
1387
|
+
);
|
|
1388
|
+
let changed = false;
|
|
1389
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1390
|
+
if (current.value.world[index] !== value[index]) {
|
|
1391
|
+
changed = true;
|
|
1392
|
+
break;
|
|
1309
1393
|
}
|
|
1310
1394
|
}
|
|
1311
|
-
return
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
parentOf: hierarchy.parentOf,
|
|
1315
|
-
childrenOf: indexChildren(hierarchy),
|
|
1316
|
-
entities,
|
|
1317
|
-
locals,
|
|
1318
|
-
derived: /* @__PURE__ */ new Map(),
|
|
1319
|
-
result: ok4(void 0)
|
|
1320
|
-
};
|
|
1395
|
+
if (!changed) return ok5(void 0);
|
|
1396
|
+
row.mut(GlobalTransform).world.set(value);
|
|
1397
|
+
return ok5(void 0);
|
|
1321
1398
|
}
|
|
1322
|
-
function
|
|
1323
|
-
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
const local = cache.locals.get(entity);
|
|
1327
|
-
if (local === void 0) {
|
|
1328
|
-
visiting.delete(entity);
|
|
1329
|
-
return ok4(void 0);
|
|
1330
|
-
}
|
|
1331
|
-
const parent = cache.parentOf.get(entity);
|
|
1332
|
-
if (parent !== void 0 && cache.entities.has(parent)) {
|
|
1333
|
-
const parentResult = deriveEntity(world, parent, cache, affected, visiting, published);
|
|
1334
|
-
if (!parentResult.ok) return parentResult;
|
|
1335
|
-
}
|
|
1336
|
-
const localWorld = mat4.create();
|
|
1337
|
-
compose(local, localWorld);
|
|
1338
|
-
const parentWorld = parent === void 0 ? void 0 : cache.derived.get(parent);
|
|
1339
|
-
const resolved = mat4.create();
|
|
1340
|
-
if (parentWorld === void 0) resolved.set(localWorld);
|
|
1341
|
-
else mat4.multiply(resolved, parentWorld, localWorld);
|
|
1342
|
-
const write = setDerivedComponent(world, entity, Transform, { world: resolved });
|
|
1343
|
-
if (!write.ok) {
|
|
1344
|
-
visiting.delete(entity);
|
|
1345
|
-
return err4(
|
|
1399
|
+
function propagateFlat(scratch) {
|
|
1400
|
+
const query = scratch.flatQuery;
|
|
1401
|
+
if (query === void 0)
|
|
1402
|
+
return err5(
|
|
1346
1403
|
new SceneError({
|
|
1347
1404
|
code: "hierarchy-broken",
|
|
1348
|
-
expected: "
|
|
1349
|
-
hint: "
|
|
1350
|
-
|
|
1405
|
+
expected: "a valid changed Transform write query",
|
|
1406
|
+
hint: "register the scene components before running TransformPropagation"
|
|
1407
|
+
})
|
|
1408
|
+
);
|
|
1409
|
+
const spans = query.spans();
|
|
1410
|
+
if (!spans.ok) return pairError(0, "dense numeric Transform spans");
|
|
1411
|
+
let bindingIndex = 0;
|
|
1412
|
+
try {
|
|
1413
|
+
for (const span of spans.value) {
|
|
1414
|
+
const local = span.get(Transform);
|
|
1415
|
+
const world = span.mut(GlobalTransform).world;
|
|
1416
|
+
composeFlatColumns(local.pos, local.quat, local.scale, world, span.length);
|
|
1417
|
+
bindingIndex += 1;
|
|
1418
|
+
}
|
|
1419
|
+
} catch (cause) {
|
|
1420
|
+
const error = cause;
|
|
1421
|
+
return err5(
|
|
1422
|
+
new SceneError({
|
|
1423
|
+
code: "hierarchy-broken",
|
|
1424
|
+
expected: "derived GlobalTransform range publication to succeed",
|
|
1425
|
+
hint: error.hint,
|
|
1426
|
+
detail: {
|
|
1427
|
+
kind: "derived-write",
|
|
1428
|
+
entity: 0,
|
|
1429
|
+
parent: 0,
|
|
1430
|
+
bindingIndex,
|
|
1431
|
+
base: 0,
|
|
1432
|
+
start: 0,
|
|
1433
|
+
count: 0,
|
|
1434
|
+
cause: error
|
|
1435
|
+
}
|
|
1351
1436
|
})
|
|
1352
1437
|
);
|
|
1353
1438
|
}
|
|
1354
|
-
|
|
1355
|
-
published.add(entity);
|
|
1356
|
-
visiting.delete(entity);
|
|
1357
|
-
return ok4(void 0);
|
|
1439
|
+
return ok5(void 0);
|
|
1358
1440
|
}
|
|
1359
|
-
function
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
);
|
|
1441
|
+
function findEntry(entries, entity) {
|
|
1442
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1443
|
+
if (entries[index]?.entity === entity) return index;
|
|
1444
|
+
}
|
|
1445
|
+
return -1;
|
|
1365
1446
|
}
|
|
1366
|
-
function
|
|
1367
|
-
|
|
1368
|
-
if (
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1373
|
-
for (
|
|
1374
|
-
const
|
|
1375
|
-
if (
|
|
1376
|
-
}
|
|
1377
|
-
if (!drainPublishedChanges(cache, published2)) {
|
|
1378
|
-
CACHE.delete(world);
|
|
1379
|
-
return propagateTransforms(world);
|
|
1380
|
-
}
|
|
1381
|
-
cache.result = hierarchyError(cache.hierarchy);
|
|
1382
|
-
return cache.result;
|
|
1383
|
-
}
|
|
1384
|
-
const evidence = cache.projection.poll();
|
|
1385
|
-
if (evidence.status === "rebuild") {
|
|
1386
|
-
cache = buildCache(world, cache.projection);
|
|
1387
|
-
CACHE.set(world, cache);
|
|
1388
|
-
const all = new Set(cache.entities);
|
|
1389
|
-
const published2 = /* @__PURE__ */ new Set();
|
|
1390
|
-
for (const entity of all) {
|
|
1391
|
-
const result = deriveEntity(world, entity, cache, all, /* @__PURE__ */ new Set(), published2);
|
|
1392
|
-
if (!result.ok) return result;
|
|
1393
|
-
}
|
|
1394
|
-
if (!drainPublishedChanges(cache, published2)) {
|
|
1395
|
-
CACHE.delete(world);
|
|
1396
|
-
return propagateTransforms(world);
|
|
1447
|
+
function resolveEntry(index, entries, world, output, scratch) {
|
|
1448
|
+
const entry = entries[index];
|
|
1449
|
+
if (entry === void 0 || entry.state === 2) return ok5(void 0);
|
|
1450
|
+
if (entry.state === 1) {
|
|
1451
|
+
const cycleParent = entry.parent ?? entry.entity;
|
|
1452
|
+
const cycleStart = scratch.hierarchyPath.indexOf(index);
|
|
1453
|
+
const start = cycleStart < 0 ? 0 : cycleStart;
|
|
1454
|
+
for (let pathIndex = start; pathIndex < scratch.hierarchyPath.length; pathIndex += 1) {
|
|
1455
|
+
const cycleEntry = entries[scratch.hierarchyPath[pathIndex] ?? -1];
|
|
1456
|
+
if (cycleEntry !== void 0) cycleEntry.parent = void 0;
|
|
1397
1457
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1458
|
+
return err5(
|
|
1459
|
+
new SceneError({
|
|
1460
|
+
code: "hierarchy-cycle",
|
|
1461
|
+
expected: "a parent-before-child Transform hierarchy",
|
|
1462
|
+
hint: "repair the ChildOf cycle and retry TransformPropagation",
|
|
1463
|
+
detail: { entity: entry.entity, parent: cycleParent }
|
|
1464
|
+
})
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
entry.state = 1;
|
|
1468
|
+
scratch.hierarchyPath.push(index);
|
|
1469
|
+
let parentIndex = entry.parent === void 0 ? -1 : findEntry(entries, entry.parent);
|
|
1470
|
+
let failure;
|
|
1471
|
+
if (entry.parent !== void 0 && parentIndex < 0) {
|
|
1472
|
+
const local = world.get(entry.parent, Transform);
|
|
1473
|
+
const global = world.get(entry.parent, GlobalTransform);
|
|
1474
|
+
if (!local.ok || !global.ok) {
|
|
1475
|
+
failure = new SceneError({
|
|
1476
|
+
code: "hierarchy-broken",
|
|
1477
|
+
expected: "each ChildOf parent to carry Transform and GlobalTransform",
|
|
1478
|
+
hint: "repair the missing parent pair before retrying propagation",
|
|
1479
|
+
detail: { entity: entry.entity, parent: entry.parent }
|
|
1480
|
+
});
|
|
1481
|
+
entry.parent = void 0;
|
|
1482
|
+
parentIndex = -1;
|
|
1483
|
+
} else {
|
|
1484
|
+
const relation = world.get(entry.parent, ChildOf);
|
|
1485
|
+
entries.push({
|
|
1486
|
+
entity: entry.parent,
|
|
1487
|
+
parent: relation.ok && relation.value.parent !== null ? relation.value.parent : void 0,
|
|
1488
|
+
position: new Float32Array(local.value.pos),
|
|
1489
|
+
rotation: new Float32Array(local.value.quat),
|
|
1490
|
+
scale: new Float32Array(local.value.scale),
|
|
1491
|
+
world: mat4.create(),
|
|
1492
|
+
state: 0
|
|
1493
|
+
});
|
|
1494
|
+
parentIndex = entries.length - 1;
|
|
1413
1495
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1496
|
+
}
|
|
1497
|
+
if (parentIndex >= 0) {
|
|
1498
|
+
const parentResult = resolveEntry(parentIndex, entries, world, output, scratch);
|
|
1499
|
+
if (!parentResult.ok) failure = parentResult.error;
|
|
1500
|
+
parentIndex = entry.parent === void 0 ? -1 : findEntry(entries, entry.parent);
|
|
1501
|
+
if (parentIndex >= 0 && entries[parentIndex]?.state !== 2) parentIndex = -1;
|
|
1502
|
+
}
|
|
1503
|
+
composeColumns(entry.position, entry.rotation, entry.scale, scratch.local, scratch);
|
|
1504
|
+
if (parentIndex >= 0) {
|
|
1505
|
+
const parent = entries[parentIndex];
|
|
1506
|
+
if (parent !== void 0) mat4.multiply(entry.world, parent.world, scratch.local);
|
|
1507
|
+
} else {
|
|
1508
|
+
entry.world.set(scratch.local);
|
|
1509
|
+
}
|
|
1510
|
+
const write = writeWorld(world, output, entry.entity, entry.world);
|
|
1511
|
+
if (!write.ok) {
|
|
1512
|
+
scratch.hierarchyPath.pop();
|
|
1513
|
+
return write;
|
|
1514
|
+
}
|
|
1515
|
+
entry.state = 2;
|
|
1516
|
+
scratch.hierarchyPath.pop();
|
|
1517
|
+
return failure === void 0 ? ok5(void 0) : err5(failure);
|
|
1518
|
+
}
|
|
1519
|
+
function propagateHierarchy(world, scratch) {
|
|
1520
|
+
const query = scratch.hierarchyQuery;
|
|
1521
|
+
const output = scratch.outputQuery;
|
|
1522
|
+
if (query === void 0 || output === void 0) {
|
|
1523
|
+
return err5(
|
|
1524
|
+
new SceneError({
|
|
1525
|
+
code: "hierarchy-broken",
|
|
1526
|
+
expected: "paired Transform and GlobalTransform query access",
|
|
1527
|
+
hint: "register the scene components before running TransformPropagation"
|
|
1528
|
+
})
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
const entries = [];
|
|
1532
|
+
for (const row of query) {
|
|
1533
|
+
if (findEntry(entries, row.entity) >= 0) continue;
|
|
1534
|
+
const local = row.get(Transform);
|
|
1535
|
+
const parentValue = row.get(ChildOf).parent;
|
|
1536
|
+
const parent = parentValue === null ? void 0 : parentValue;
|
|
1537
|
+
entries.push({
|
|
1538
|
+
entity: row.entity,
|
|
1539
|
+
parent,
|
|
1540
|
+
position: new Float32Array(local.pos),
|
|
1541
|
+
rotation: new Float32Array(local.quat),
|
|
1542
|
+
scale: new Float32Array(local.scale),
|
|
1543
|
+
world: mat4.create(),
|
|
1544
|
+
state: 0
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
scratch.hierarchyPath.length = 0;
|
|
1548
|
+
let firstError;
|
|
1549
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1550
|
+
const result = resolveEntry(index, entries, world, output, scratch);
|
|
1551
|
+
if (!result.ok) {
|
|
1552
|
+
const currentEntity = Number(result.error.detail?.entity ?? Number.MAX_SAFE_INTEGER);
|
|
1553
|
+
if (firstError === void 0) {
|
|
1554
|
+
firstError = result.error;
|
|
1555
|
+
} else {
|
|
1556
|
+
const firstEntity = Number(firstError.detail?.entity ?? Number.MAX_SAFE_INTEGER);
|
|
1557
|
+
if (currentEntity < firstEntity || currentEntity === firstEntity && result.error.code.localeCompare(firstError.code) < 0) {
|
|
1558
|
+
firstError = result.error;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1426
1561
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
if (!result.ok) return result;
|
|
1438
|
-
}
|
|
1439
|
-
if (!drainPublishedChanges(cache, published)) {
|
|
1440
|
-
CACHE.delete(world);
|
|
1441
|
-
return propagateTransforms(world);
|
|
1442
|
-
}
|
|
1443
|
-
cache.result = hierarchyError(cache.hierarchy);
|
|
1444
|
-
return cache.result;
|
|
1562
|
+
}
|
|
1563
|
+
return firstError === void 0 ? ok5(void 0) : err5(firstError);
|
|
1564
|
+
}
|
|
1565
|
+
function propagateTransforms(world) {
|
|
1566
|
+
const scratch = scratchFor(world);
|
|
1567
|
+
const pairs = validateTransformPairs(world, scratch);
|
|
1568
|
+
if (!pairs.ok) return pairs;
|
|
1569
|
+
const flat = propagateFlat(scratch);
|
|
1570
|
+
if (!flat.ok) return flat;
|
|
1571
|
+
return propagateHierarchy(world, scratch);
|
|
1445
1572
|
}
|
|
1446
1573
|
var PropagateTransforms = defineSystem({
|
|
1447
1574
|
name: PROPAGATE_TRANSFORMS_SYSTEM,
|
|
@@ -1469,7 +1596,7 @@ function registerPropagateTransforms(world, options = {}) {
|
|
|
1469
1596
|
world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
|
|
1470
1597
|
world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
|
|
1471
1598
|
REGISTRATION_LEASES.delete(world);
|
|
1472
|
-
|
|
1599
|
+
SCRATCH.delete(world);
|
|
1473
1600
|
}
|
|
1474
1601
|
};
|
|
1475
1602
|
}
|
|
@@ -1486,23 +1613,31 @@ function registerPropagateTransforms(world, options = {}) {
|
|
|
1486
1613
|
]).unwrap();
|
|
1487
1614
|
}
|
|
1488
1615
|
world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();
|
|
1489
|
-
|
|
1490
|
-
REGISTRATION_LEASES.set(world, lease);
|
|
1616
|
+
REGISTRATION_LEASES.set(world, { refs: 1 });
|
|
1491
1617
|
let active = true;
|
|
1492
1618
|
return () => {
|
|
1493
1619
|
if (!active) return;
|
|
1494
1620
|
active = false;
|
|
1621
|
+
const lease = REGISTRATION_LEASES.get(world);
|
|
1622
|
+
if (lease === void 0) return;
|
|
1495
1623
|
lease.refs -= 1;
|
|
1496
1624
|
if (lease.refs !== 0) return;
|
|
1497
1625
|
world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
|
|
1498
1626
|
world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
|
|
1499
1627
|
REGISTRATION_LEASES.delete(world);
|
|
1500
|
-
|
|
1628
|
+
SCRATCH.delete(world);
|
|
1501
1629
|
};
|
|
1502
1630
|
}
|
|
1503
1631
|
|
|
1504
1632
|
// src/plugin.ts
|
|
1505
|
-
var SCENE_COMPONENTS = [
|
|
1633
|
+
var SCENE_COMPONENTS = [
|
|
1634
|
+
ChildOf,
|
|
1635
|
+
Children,
|
|
1636
|
+
MorphWeights,
|
|
1637
|
+
Name,
|
|
1638
|
+
Transform,
|
|
1639
|
+
GlobalTransform
|
|
1640
|
+
];
|
|
1506
1641
|
function registerSceneComponents(world) {
|
|
1507
1642
|
const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());
|
|
1508
1643
|
return () => {
|
|
@@ -1519,10 +1654,115 @@ function scenePlugin() {
|
|
|
1519
1654
|
}
|
|
1520
1655
|
};
|
|
1521
1656
|
}
|
|
1657
|
+
|
|
1658
|
+
// src/systems/hierarchy-projection.ts
|
|
1659
|
+
import { Entity } from "@forgeax/engine-ecs";
|
|
1660
|
+
var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1661
|
+
function createChildOfChangeQuery(world) {
|
|
1662
|
+
const result = world.query({ changed: [ChildOf] });
|
|
1663
|
+
if (!result.ok) throw result.error;
|
|
1664
|
+
return result.value;
|
|
1665
|
+
}
|
|
1666
|
+
function drainChanges(query) {
|
|
1667
|
+
let changed = false;
|
|
1668
|
+
for (const span of query.spans().unwrap()) changed ||= span.length > 0;
|
|
1669
|
+
return changed;
|
|
1670
|
+
}
|
|
1671
|
+
function diagnostic(code, entity, parent) {
|
|
1672
|
+
if (code === "hierarchy-cycle") {
|
|
1673
|
+
return {
|
|
1674
|
+
code,
|
|
1675
|
+
expected: "ChildOf parent edges form an acyclic live hierarchy",
|
|
1676
|
+
hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
|
|
1677
|
+
detail: { entity, parent }
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
return {
|
|
1681
|
+
code,
|
|
1682
|
+
expected: "ChildOf.parent references a live entity in the same World",
|
|
1683
|
+
hint: "remove the stale ChildOf component or restore the referenced parent in this World",
|
|
1684
|
+
detail: { entity, parent }
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function projectHierarchy(world) {
|
|
1688
|
+
const cached = HIERARCHY_PROJECTION_CACHE.get(world);
|
|
1689
|
+
if (cached !== void 0 && cached.structureEpoch === world.getStructureEpoch() && !drainChanges(cached.childOfChanges)) {
|
|
1690
|
+
return cached.snapshot;
|
|
1691
|
+
}
|
|
1692
|
+
const liveEntities = /* @__PURE__ */ new Set();
|
|
1693
|
+
const authoredParents = /* @__PURE__ */ new Map();
|
|
1694
|
+
const query = world.query({ read: [Entity], optional: [ChildOf] });
|
|
1695
|
+
if (query.ok) {
|
|
1696
|
+
for (const row of query.value) {
|
|
1697
|
+
liveEntities.add(row.entity);
|
|
1698
|
+
const parent = row.get(ChildOf)?.parent;
|
|
1699
|
+
if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
1703
|
+
const diagnostics = [];
|
|
1704
|
+
for (const [entity, parent] of authoredParents) {
|
|
1705
|
+
if (liveEntities.has(parent)) {
|
|
1706
|
+
parentOf.set(entity, parent);
|
|
1707
|
+
} else {
|
|
1708
|
+
diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
const state = /* @__PURE__ */ new Map();
|
|
1712
|
+
const stack = [];
|
|
1713
|
+
const cycleMembers = /* @__PURE__ */ new Set();
|
|
1714
|
+
const visit = (entity) => {
|
|
1715
|
+
const currentState = state.get(entity) ?? 0;
|
|
1716
|
+
if (currentState === 2) return;
|
|
1717
|
+
if (currentState === 1) {
|
|
1718
|
+
const cycleStart = stack.indexOf(entity);
|
|
1719
|
+
for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
|
|
1720
|
+
const member = stack[index];
|
|
1721
|
+
if (member !== void 0) cycleMembers.add(member);
|
|
1722
|
+
}
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
state.set(entity, 1);
|
|
1726
|
+
stack.push(entity);
|
|
1727
|
+
const parent = parentOf.get(entity);
|
|
1728
|
+
if (parent !== void 0) visit(parent);
|
|
1729
|
+
stack.pop();
|
|
1730
|
+
state.set(entity, 2);
|
|
1731
|
+
};
|
|
1732
|
+
for (const entity of liveEntities) visit(entity);
|
|
1733
|
+
for (const entity of cycleMembers) {
|
|
1734
|
+
const parent = authoredParents.get(entity);
|
|
1735
|
+
if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
|
|
1736
|
+
parentOf.delete(entity);
|
|
1737
|
+
}
|
|
1738
|
+
diagnostics.sort((left, right) => {
|
|
1739
|
+
const entityDelta = left.detail.entity - right.detail.entity;
|
|
1740
|
+
if (entityDelta !== 0) return entityDelta;
|
|
1741
|
+
return left.code.localeCompare(right.code);
|
|
1742
|
+
});
|
|
1743
|
+
const stableParentOf = new Map(parentOf);
|
|
1744
|
+
const stableDiagnostics = Object.freeze(diagnostics.slice());
|
|
1745
|
+
const snapshot = {
|
|
1746
|
+
parentOf: stableParentOf,
|
|
1747
|
+
diagnostics: stableDiagnostics,
|
|
1748
|
+
getParent(entity) {
|
|
1749
|
+
return stableParentOf.get(entity);
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
const childOfChanges = createChildOfChangeQuery(world);
|
|
1753
|
+
drainChanges(childOfChanges);
|
|
1754
|
+
HIERARCHY_PROJECTION_CACHE.set(world, {
|
|
1755
|
+
structureEpoch: world.getStructureEpoch(),
|
|
1756
|
+
childOfChanges,
|
|
1757
|
+
snapshot
|
|
1758
|
+
});
|
|
1759
|
+
return snapshot;
|
|
1760
|
+
}
|
|
1522
1761
|
export {
|
|
1523
1762
|
ChildOf,
|
|
1524
1763
|
Children,
|
|
1525
1764
|
ComponentNotDefinedError,
|
|
1765
|
+
GlobalTransform,
|
|
1526
1766
|
MorphWeights,
|
|
1527
1767
|
Name,
|
|
1528
1768
|
PROPAGATE_TRANSFORMS_SYSTEM,
|
|
@@ -1535,8 +1775,11 @@ export {
|
|
|
1535
1775
|
projectHierarchy,
|
|
1536
1776
|
propagateTransforms,
|
|
1537
1777
|
registerPropagateTransforms,
|
|
1778
|
+
resolveSceneEntity,
|
|
1538
1779
|
sceneAssetContribution,
|
|
1780
|
+
sceneEntity,
|
|
1539
1781
|
scenePlugin,
|
|
1782
|
+
validateSceneBindings,
|
|
1540
1783
|
worldApplyMountOverride,
|
|
1541
1784
|
worldBuildSceneEntityComponentDatas,
|
|
1542
1785
|
worldDespawnDescendants,
|
|
@@ -1556,6 +1799,7 @@ export {
|
|
|
1556
1799
|
worldRemoveSceneOverride,
|
|
1557
1800
|
worldResolveMountSource,
|
|
1558
1801
|
worldResolveSceneAsset,
|
|
1802
|
+
worldResolveSceneEntity,
|
|
1559
1803
|
worldResolveSceneInstanceStatePayload,
|
|
1560
1804
|
worldSetSceneAssetResolver,
|
|
1561
1805
|
worldSetSceneOverride,
|