LeCodesdocs

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

TypeScript
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

TypeScript
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 TRS

Getters return fresh Vec3 / Quat / Mat4 copies — mutating one never touches the node:

TypeScript
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 back

World space

TypeScript
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

TypeScript
node.name = 'door'         // stored natively; Model child lookups match on it
node.visible = false       // hides the node (and what it draws)

Hierarchy

TypeScript
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 descendants

With 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.

Note

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

TypeScript
node.lookAt(point: Vec3Like, mode = '-z', ortho = [0, 1, 0]): this
// mode: 'z' | '-z' | 'x' | '-x' | 'y' | '-y' — which local axis points at the target

Orients 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):

TypeScript
const ball = Mesh.sphere({ material: Material.lit() })
  .aspect(Shape, { sphere: 0.5 })                 // pick/collision shape → hittable, raycastable
  .aspect(Physics, { motion: 'dynamic' })         // rigid body → node.physics

See 3D physics & shapes for Shape, Physics, Trigger, CharacterController.

Events

TypeScript
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

TypeScript
node.destroy(): void      // frees the native entity; the node is unusable afterwards

destroy() is the only teardown — there is no dispose() on nodes. Using a destroyed node is undefined behavior.

Pitfalls

TypeScript
// ✗ 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