Scene hierarchy: parentId with local positions, tree panel, reparenting and Parent node
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
551a50ffc9
commit
26d5edc496
22 files changed
+729
-166
No files matched your search
@@ -13,12 +13,13 @@ export interface Entity {
|
||||
assetId: string;
|
||||
type: AssetType;
|
||||
name: string;
|
||||
/** Position local to `parent` (world position for top-level entities). */
|
||||
x: number;
|
||||
y: number;
|
||||
/** Draw order; higher is drawn on top. */
|
||||
/** Draw order, relative to the parent as well; higher is drawn on top. */
|
||||
z: number;
|
||||
/** For behaviors: the resolved variables (mutable state). For others: resolved properties. */
|
||||
props: PropertyBag;
|
||||
/** Scene object id of the owner (behaviors), empty when none. */
|
||||
ownerObjectId: string;
|
||||
/** Parent entity (mirrors `SceneObject.parentId`); children move and die with it. */
|
||||
parent: Entity | null;
|
||||
}
|
||||
+20
-3
@@ -1,3 +1,4 @@
|
||||
import { worldPositionOf } from '../core/graph';
|
||||
import { findAsset, findScene, type Project } from '../core/model/Project';
|
||||
import { resolveSceneObjectProps } from '../core/properties/resolve';
|
||||
import type { Entity } from './Entity';
|
||||
@@ -27,10 +28,11 @@ export class World {
|
||||
if (!scene) throw new Error(`Scene ${sceneId} not found`);
|
||||
const background = scene.props.backgroundColor;
|
||||
const world = new World(sceneId, typeof background === 'number' ? background : 0x000000);
|
||||
const parentIds = new Map<Entity, string>();
|
||||
for (const object of scene.objects) {
|
||||
const asset = findAsset(project, object.assetId);
|
||||
if (!asset || asset.type === 'scene') continue;
|
||||
world.entities.push({
|
||||
const entity: Entity = {
|
||||
id: `e-${object.id}`,
|
||||
objectId: object.id,
|
||||
assetId: asset.id,
|
||||
@@ -40,12 +42,25 @@ export class World {
|
||||
y: object.position.y,
|
||||
z: object.position.z,
|
||||
props: resolveSceneObjectProps(asset, object),
|
||||
ownerObjectId: object.ownerId,
|
||||
});
|
||||
parent: null,
|
||||
};
|
||||
world.entities.push(entity);
|
||||
if (object.parentId) parentIds.set(entity, object.parentId);
|
||||
}
|
||||
// Link parents once every entity exists (storage order is not guaranteed to be parent-first).
|
||||
for (const [entity, parentId] of parentIds) entity.parent = world.findByObjectId(parentId) ?? null;
|
||||
return world;
|
||||
}
|
||||
|
||||
/** World position of an entity: local position plus those of all ancestors. */
|
||||
worldPosition(entity: Entity): { x: number; y: number; z: number } {
|
||||
return worldPositionOf(entity);
|
||||
}
|
||||
|
||||
childrenOf(entity: Entity): Entity[] {
|
||||
return this.entities.filter((e) => e.parent === entity);
|
||||
}
|
||||
|
||||
addSystem(system: System): this {
|
||||
this.systems.push(system);
|
||||
system.start?.(this);
|
||||
@@ -74,7 +89,9 @@ export class World {
|
||||
return this.entities.find((e) => e.type === type);
|
||||
}
|
||||
|
||||
/** Removes an entity and, recursively, everything parented to it. */
|
||||
remove(entity: Entity): void {
|
||||
for (const child of this.childrenOf(entity)) this.remove(child);
|
||||
const index = this.entities.indexOf(entity);
|
||||
if (index >= 0) this.entities.splice(index, 1);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export class BehaviorSystem implements System {
|
||||
private context(entity: Entity, world: World, dt: number): NodeContext {
|
||||
return {
|
||||
self: entity,
|
||||
owner: entity.ownerObjectId ? world.findByObjectId(entity.ownerObjectId) ?? null : null,
|
||||
owner: entity.parent,
|
||||
variables: entity.props,
|
||||
time: world.time,
|
||||
dt,
|
||||
|
||||
Reference in new issue
Block a user