Builds one pass's candidate table for root's subtree: ids[i] is the
Node.id the texel value i + 1 resolves to, and indexByMatrix maps a
drawable node's own transform.worldMatrixobject back to i (§33;
RFC 0005 §3).
Traversal order is the contract
The walk is the render list's exactly: depth-first in insertion order (§6),
pruning visible === false / enabled === false subtrees. Every
RenderableorisParticleDrawable node met is a candidate —
§46 layer masks and §87 culling do not filter the table, because they
filter per view while the table is per pass; a filtered node simply has no
draw referencing its index, which costs nothing and keeps the table a
function of the scene alone (§33).
Why the correlation key is the world-matrix object
A render item deliberately carries no node reference (§64's compact-item
rule), but buildRenderList documents that a non-interpolated item's
worldMatrixis the node's own transform.worldMatrix — one object per
node, by construction. The id pass builds the plain (never interpolated)
list, so the matrix object is an exact, allocation-free join key between a
sorted item and the traversal-ordered table. A clip node's mask draw and
its content draw share the matrix and therefore the index, which is
correct: they are one candidate.
Particle systems are collected when they satisfy
isParticleDrawable: §36's batched item has one node and no
per-particle geometry, so the table stores one id for the whole
system, joined through the emitter's transform.worldMatrix exactly as
for a Renderable. The id pass draws that index through the instanced
billboard (@fourjs/render-webgl's ParticleIdProgram; WebGPU's private
particle id pipeline — one colour per emitter on both). It is not a
per-particle id. Trails are not drawn. WebGL draws skinned items through
SkinnedIdProgram; WebGPU draws them through a private skinned id
pipeline (skinMatrix(), never bind pose).
Both containers are cleared first, so a caller can reuse them per pass —
which is also what "rebuilt per pass" means: no index survives into the
next table.
Builds one pass's candidate table for
root's subtree:ids[i]is theNode.idthe texel valuei + 1resolves to, andindexByMatrixmaps a drawable node's owntransform.worldMatrixobject back toi(§33; RFC 0005 §3).Traversal order is the contract
The walk is the render list's exactly: depth-first in insertion order (§6), pruning
visible === false/enabled === falsesubtrees. EveryRenderableor isParticleDrawable node met is a candidate — §46 layer masks and §87 culling do not filter the table, because they filter per view while the table is per pass; a filtered node simply has no draw referencing its index, which costs nothing and keeps the table a function of the scene alone (§33).Why the correlation key is the world-matrix object
A render item deliberately carries no node reference (§64's compact-item rule), but
buildRenderListdocuments that a non-interpolated item'sworldMatrixis the node's owntransform.worldMatrix— one object per node, by construction. The id pass builds the plain (never interpolated) list, so the matrix object is an exact, allocation-free join key between a sorted item and the traversal-ordered table. A clip node's mask draw and its content draw share the matrix and therefore the index, which is correct: they are one candidate.Particle systems are collected when they satisfy isParticleDrawable: §36's batched item has one node and no per-particle geometry, so the table stores one id for the whole system, joined through the emitter's
transform.worldMatrixexactly as for aRenderable. The id pass draws that index through the instanced billboard (@fourjs/render-webgl'sParticleIdProgram; WebGPU's private particle id pipeline — one colour per emitter on both). It is not a per-particle id. Trails are not drawn. WebGL draws skinned items throughSkinnedIdProgram; WebGPU draws them through a private skinned id pipeline (skinMatrix(), never bind pose).Both containers are cleared first, so a caller can reuse them per pass — which is also what "rebuilt per pass" means: no index survives into the next table.