Node
The base 3D scene node — an empty transform. Mesh, Model,
Light and Camera all extend it, so everything here works on them. The native
engine owns the authoritative transform; the SDK reads it back lazily (physics can rewrite it
every frame), and every transform getter returns a fresh copy — see
value semantics.
At a glance
const pivot = new Node()
pivot.position = [0, 1, -2]
pivot.eulerAngles = [0, 45, 0] // degrees
pivot.add(Mesh.box({ material: Material.lit({ color: '#38f' }) }))
scene.add(pivot)
setLoop(dt => { pivot.eulerAngles = pivot.eulerAngles.add([0, 90 * dt, 0]) })Local transform
node.position = [x, y, z] // Vec3Like; world units ≈ meters
node.x = 3 // scalar accessors: x / y / z
node.quaternion = Quat.fromEuler(0, 90, 0)
node.eulerAngles = [0, 90, 0] // DEGREES
node.scale = 2 // uniform, or [sx, sy, sz]
node.matrix = Mat4.compose([0, 1, 0], Quat.identity, 1) // full local TRSGetters return fresh Vec3 / Quat / Mat4 copies — mutating one never touches the node:
node.position.x = 3 // ✗ silent no-op (mutates a discarded copy)
node.x = 3 // ✓ single-axis setter
const p = node.position; p.y += 1; node.position = p // ✓ mutate a local, assign backWorld space
node.worldMatrix // get/set: full world transform (setting composes through the parent)
node.worldPosition // Vec3 (read-only)
node.worldQuaternion // Quat (read-only)
node.worldEulerAngles // Vec3, degrees (read-only)
node.worldScale // Vec3 (read-only)
node.forward // Vec3: the node's forward (−Z) axis in world space (read-only)Name & visibility
node.name = 'door' // stored natively; Model child lookups match on it
node.visible = false // hides the node (and what it draws)Hierarchy
node.add(...children: Node[]): this // parent children under this node
node.setParent(parent: Node | null, worldPositionStays = false): this
node.parent // Node | null
node.children // Node[] (snapshot array)
node.childCount
node.getChild(index: number): Node | null
node.traverse(callback: (node: Node) => void): void // this node, then all descendantsWith worldPositionStays: true, setParent keeps the node where it is in the world (its local
transform is recomputed); with the default false the local transform is kept as-is.
parenting is separate from scene membership — scene.add(node) puts a node in the
draw set, node.add(child) builds the transform hierarchy. See Scene.
lookAt
node.lookAt(point: Vec3Like, mode = '-z', ortho = [0, 1, 0]): this
// mode: 'z' | '-z' | 'x' | '-x' | 'y' | '-y' — which local axis points at the targetOrients the node so the chosen local axis (default '-z', the forward axis) points at a
world-space point. ortho is the up-hint used to resolve roll. Scale is preserved.
Aspects
Capabilities attach as aspects and chain (see aspects):
const ball = Mesh.sphere({ material: Material.lit() })
.aspect(Shape, { sphere: 0.5 }) // pick/collision shape → hittable, raycastable
.aspect(Physics, { motion: 'dynamic' }) // rigid body → node.physicsSee 3D physics & shapes for Shape, Physics, Trigger, CharacterController.
Events
node.addEventListener(channel, callback): void
node.removeEventListener(channel, callback): void| Channel | Fires | Needs |
|---|---|---|
click |
pointer-up over the node | a Shape aspect (pick body) |
touchstart |
pointer-down over the node; ev.track() starts a drag |
a Shape aspect |
enter / exit |
physics contact / trigger overlap began / ended ((other: Node)) |
Shape + Physics/Trigger |
loopReached |
a looping GLB clip wrapped around ((clip: number)) |
ModelAnimation |
completed |
a non-looping GLB clip finished ((clip: number)) |
ModelAnimation |
track / untrack |
AR anchor gained / lost tracking (also node.isTracked) |
an ARScene anchor |
Without a Shape aspect a node is invisible to taps — only scene-level listeners will see the
touch (with ev.target === null). Event objects and ev.track() drags:
Pointer events & gestures.
Lifecycle
node.destroy(): void // frees the native entity; the node is unusable afterwardsdestroy() is the only teardown — there is no dispose() on nodes. Using a destroyed node is
undefined behavior.
Pitfalls
// ✗ mutating a transform getter's return value
node.position.x += speed * dt // no-op: getters return copies
// ✓
node.x += speed * dt
// ✗ writing the transform of a dynamic physics body per frame
setLoop(() => { node.position = target }) // fights the physics step
// ✓ drive the body: node.physics.velocity / applyImpulse (see physics.md)See also
- Conventions — value semantics, degrees vs radians, units
- Mesh / Model — renderable node kinds
- 3D physics & shapes —
Shapepick bodies,enter/exit, rigid bodies - Pointer events & gestures —
click/touchstartpayloads, drags - ARScene — anchor nodes,
track/untrack