ECS ↔ Three.js Interop
Updated: Sep 4, 2026
IWSDK links ECS entities to Three.js objects through Transform. The object’s transform properties are synchronized views over ECS storage; the runtime does not copy one representation over the other on every frame.
import { Object3D, Transform, World } from '@iwsdk/core';
const world = await World.create(container);
const object = new Object3D();
const entity = world.createTransformEntity(object);
console.log(entity.object3D === object); // true
console.log(entity.hasComponent(Transform)); // true
When the entity qualifies for TransformSystem, IWSDK associates its object with the entity index and installs synchronized position, quaternion, rotation, and scale objects.
Direct Three.js updates and ECS vector-view updates address the same storage.
entity.object3D!.position.x += 1;
const position = entity.getVectorView(Transform, 'position');
position[1] += 1;
entity.getVectorView(Transform, 'scale').set([2, 2, 2]);
setValue() does not accept vector fields. Vector-view and Object3D
transform changes also do not notify value predicates. When a query must react
to derived transform state, store that state in a scalar application-component
field and update the scalar with setValue().
Use Three.js for rendering data
Create geometry, materials, and non-entity child objects with Three.js.
const root = world.createTransformEntity();
const body = new Mesh(bodyGeometry, bodyMaterial);
const leftWheel = new Mesh(wheelGeometry, wheelMaterial);
root.object3D!.add(body, leftWheel);
leftWheel.position.set(-1, -0.5, 1.2);
The child mesh does not become an ECS entity merely because it is added to an entity’s Object3D hierarchy. Create another transform entity when the child needs its own components and queries.
Pass the parent entity when creating the child.
const parent = world.createTransformEntity();
const child = world.createTransformEntity(undefined, { parent });
console.log(child.getValue(Transform, 'parent') === parent); // true
console.log(child.object3D!.parent === parent.object3D); // true
Transform.parent is an entity reference. Its packed representation uses an index internally, but getValue() returns the entity.
Choose level or persistent lifetime
const levelObject = world.createTransformEntity();
const persistentObject = world.createTransformEntity(undefined, {
persistent: true,
});
Level objects attach below the active level. Persistent objects attach below the scene root and remain when a new level loads.
world.camera.position is local to world.player. Ask Three.js for the world-space viewer position when comparing it with a world-space object.
const viewerPosition = world.camera.getWorldPosition(new Vector3());
const objectPosition = entity.object3D!.getWorldPosition(new Vector3());
const distance = viewerPosition.distanceTo(objectPosition);
Application systems update in numeric priority order. The transform system maintains synchronized accessors and parenting. IWSDK then invokes XR-frame callbacks when applicable and renders the scene.
Do not create a second render loop for transform synchronization.