Scene graph · class

SceneNode

Explained in Hello world, The scene graph.

class SceneNode
import { SceneNode } from '@driftengine/core';

Constructor

new

constructor()

Properties

NameTypeDescription
positionreadonlyFloat32Array<ArrayBuffer>Local translation. Written in place; call markMoved if you write it directly.
rotationreadonlyFloat32Array<ArrayBuffer>Local rotation, as a quaternion.
More

A quaternion and not the yaw this engine uses everywhere else. Every rotation in the tree today is a yaw or a matrix, because nothing has needed to interpolate one — and a node is what animation will hold, where "rigid TRS, then skinning" is the first line of that track. Interpolating Euler angles gives gimbal artefacts and a path through orientations nobody authored, and carrying the right type from the start is cheaper than migrating every node a consumer has built.

scalereadonlyFloat32Array<ArrayBuffer>
localMatrixreadonlyFloat32Array<ArrayBuffer>
worldMatrixreadonlyFloat32Array<ArrayBuffer>
parentSceneNode | null
childrenreadonlySceneNode[]
worldRevisionnumberWhat worldMatrix was last recomputed at. See revisions.
worldBoundsreadonlyBoundsA sphere enclosing this node's geometry and every descendant's.
More

The union is what makes a traversal a partition rather than a loop. A parent that can be tested once and answer for everything beneath it lets a frustum discard a subtree; a parent covering only its own geometry answers for nothing, and the walk has to visit every node to find out — which is the linear scan a hierarchy exists to replace.

A node with no geometry and no children is a point at its own origin, which is the honest answer and is never inside a frustum by accident: a zero radius passes only where the point itself does.

Accessors

NameTypeDescription
hasGeometrygetbooleanWhether this node has geometry of its own, as opposed to only grouping others.

Methods

setPosition

setPosition(x: number, y: number, z: number): void
ParameterTypeDescription
xnumber
ynumber
znumber

setScale

setScale(x: number, y: number, z: number): void
ParameterTypeDescription
xnumber
ynumber
znumber

setBounds

setBounds(bounds: Bounds | null): void

Say what geometry this node holds, in its own space.

ParameterTypeDescription
boundsBounds | null
More

Held by reference rather than copied: a mesh's bounds do not change after upload, and a caller passing the same mesh to a thousand nodes should not pay for a thousand copies.

setRotationAxisAngle

setRotationAxisAngle(x: number, y: number, z: number, radians: number): void

Set the rotation from an axis and an angle in radians. The axis need not be normalised.

ParameterTypeDescription
xnumber
ynumber
znumber
radiansnumber

markMoved

markMoved(): void

Say that this node's transform was written directly.

More

position, rotation and scale are handed out as arrays a caller can write in place, because copying three vectors per node per frame is the allocation-free path an animation system wants. The cost is that this class cannot see the write, so a caller doing it says so.

attachChild

attachChild(child: SceneNode): void

Put child under this node, taking it from wherever it was.

ParameterTypeDescription
childSceneNode
More

A cycle is refused here rather than discovered while walking, because a walk that meets one does not throw: it recurses until the stack is gone, inside a frame loop, with the tab stopped and no useful trace. The check walks up from this node, which is the depth of the tree and not its size.

detachChild

detachChild(child: SceneNode): void
ParameterTypeDescription
childSceneNode

updateWorld

updateWorld(): void

Bring this node and everything under it up to date.

More

Only what moved. A hierarchy that recomputes every world matrix every frame is slower than the callers it replaces, which already keep matrices they touch only when something moves. A clean subtree is skipped entirely, which is what makes the walk proportional to what changed rather than to the size of the world.

Its ancestors are brought up to date first, and that is a correctness fix rather than a courtesy. Without it, updating a branch after moving something above it composes against a stale parent and puts the whole branch in the wrong place — silently, with no error and a plausible-looking matrix. Walking up costs the depth of the tree, not its size, and touches only this node's own ancestors: a sibling subtree is not recomputed for it.