Display-space positions (vertexCount * 3), from projectMeshTo3D.
Index buffer of visible triangles (visibleFaceCount * 3).
Whether position holds newly extracted values this epoch.
Load-bearing, and not derivable here. The projection REUSES one
loader-owned position buffer across epochs (LoadedMeshData.projection), so
array identity can no longer answer "did the displayed axes change": the
identity is stable while the contents change on a displayDims change, and
unchanged on a pure slice move. Gating on identity therefore either stops
re-uploading after an axis permutation (reused buffer) or re-uploads the whole
vertex buffer on every slice move (freshly allocated buffer) — #1245.
OptionalpositionThe projection's displayDims.join(). Compared against the key the geometry last
uploaded (geometry.userData.meshUploadedKey); a difference forces the position
re-upload even when positionChanged is false, which repairs a commit that was
superseded after its projection advanced the loader's key but before it uploaded.
Optional: callers that omit it (the placeholder factory, unit tests) keep the
pure-positionChanged behavior.
Per-vertex colors in their native dtype, or null for the white default.
OptionalcolorChannels per color entry when colors is present.
OptionalnormalsPer-vertex normals (vertexCount * 3), or null when the node has none.
Whether the normal attribute exists at all is decided ONCE, at creation, from
this being non-null — see createMeshGeometry. The placeholder factory
passes a 1-vertex stub when the node's metadata says has_normals, so a
normal-bearing mesh binds the attribute before any data arrives and the first
commit only replaces its contents.
Always bound when the node HAS normals, even during epochs whose displayDims
make them meaningless (§3.4). That is a deliberate trade: the alternative —
unbinding on a frame change — would mutate a live geometry's attribute SET,
which the WebGPU backend bakes into its render pipeline. The unused buffer costs
V * 12 bytes of VRAM; the shader variant is what actually stops reading it.
OptionalscalarsPer-vertex scalars for the colormap path (vertexCount), or null/absent when
the node has none. Bound as aScalar. Same creation-time set rule as normals.
Already float32 by the time it reaches here (the loader decodes to
Float32Array), which is also the only itemSize-1 vertex format three r184 can
bind on both backends: it has no format for a Uint8Array or a native
Float16Array at itemSize 1 (§6.1.1).
OptionaluvsPer-vertex texture coordinates (vertexCount * 2), or null/absent when the
node has no texture. Bound as uv. Same creation-time set rule as normals.
Named uv on the GPU rather than aUv, unlike aScalar: three.js's vertex
prefix declares position/normal/uv for every program, so the standard
name is already there to be filled and a custom one would need its own
declaration in both shader backends for no gain.
Vertices in the buffers (NOT the visible count — nothing is compacted).
Total faces in the node, which sizes the index buffer's CAPACITY.
Not indices.length / 3: that is the currently-visible count, which changes
every slice move. The buffer is allocated once at the node's full face count and
the visible prefix is drawn via drawRange — see applyMeshIndices.
OptionalboundsProjected AABB over the indexed vertices, or null when nothing is drawn.
Mirrors LineTexelSource.bounds and the gsplat mesh config's bounds: the
projection precomputes the box and computeMeshBounds sets the geometry's
bounding box and sphere from it, instead of a computeBoundingBox() scan that
would also span vertices the cull removed.
Optional so the placeholder factory can build a geometry without a projection;
undefined falls back to the scan, null means nothing is drawn.
OptionalvertexWhether this node's vertex count legitimately GROWS between commits.
True for a reveal-ladder node, whose every level adds vertices, and false for every other mesh — a whole-node mesh is fetched once, so a changed vertex count there means the buffers and the metadata disagree and is worth a warning. The flag exists only to keep that warning meaningful; the rebind itself is identical either way.
OptionalcapacityThe node's LIFETIME vertex/face totals, which every buffer here is sized and dtype-chosen from — as distinct from vertexCount / faceCount, which describe the data being committed NOW.
They differ for exactly one thing: a reveal ladder, whose committed prefix
grows a level at a time while the node's total does not. That distinction is
the whole point. Sizing from the prefix would re-run setAttribute /
setIndex on every level, and three frees a replaced attribute's GL buffer
from nowhere — not on replacement, and not on dispose either (only the
attributes still bound at that moment are freed). So each level would orphan
the previous level's buffers for the session: ~150 MB for a 4-level 2M-vertex
surface with normals and colours, near a gigabyte at 10M (#1521). Sizing from
the total restores the allocate-once invariant applyMeshIndices documents,
and the growing prefix is written INTO the same buffers.
Absent (or equal to the live counts) for every unladdered mesh, which is why this change is a no-op there — including the zero-copy colour path.
Optionalcapacity
Everything createMeshGeometry and updateMeshGeometry need to lay out the buffers.
*Configafter the sibling geometry modules'InstancedLinesMeshConfig/InstancedGSplatsMeshConfig— same role, minus theInstancedqualifier those two carry because a mesh is not an instanced quad.