Vertex positions (vertexCount * ndim), flattened row-major
Triangle vertex indices (faceCount * 3), indexing into vertices.
Always Uint32Array here regardless of the store's dtype. Externally
produced stores legitimately use int8..int64, and the writer's own
INDEX encoder narrows to the smallest unsigned dtype that fits, so this
is a widening the loader owns. Every value has been range-checked in
[0, vertexCount) against the source-typed values, before coercion —
each side of the cast hides its own wrap-around (spec §3.5 Stage 2).
Per-vertex normals (vertexCount * 3), or null when absent.
Only meaningful in the frame named by MeshMetadata.normal_dims.
Per-vertex colors (vertexCount * colorComponents), or null when absent
OptionalcolorChannels per color entry: 3 (RGB) or 4 (RGBA, the alpha column being
per-vertex opacity). Undefined when colors is null.
OptionalscalarsPer-vertex scalars for colormap lookup (vertexCount), or undefined
OptionaluvsPer-vertex texture coordinates (vertexCount * 2), or undefined.
Deliberately NOT clamped to [0, 1]: values outside it are how a texture
tiles under texture_wrap: 'repeat', so clamping would break the feature it
looks like it protects. Non-finite values ARE refused, on both sides — a NaN
UV pulls its whole triangle into an undefined sample.
OptionaltextureThe decoded texture, or undefined when the node has none
Number of vertices loaded
Number of triangles loaded
Dimensionality for interpreting vertices
OptionalprojectionLoader-owned scratch for the display-space projection, reused across epochs.
The Mesh counterpart of the Points accumulator's reusable target buffers
(points-spatial-index-loader.ts hands positions3D to the projection rather
than letting it allocate). Mesh's home for it is here because updateView
returns this same object for the node's whole life and drops it on dispose, so
the buffer inherits exactly the right lifetime with no separate cache to
invalidate.
Without it, projectMeshTo3D allocated a fresh vertexCount * 3 buffer on every
slice move — so the geometry's array-identity check never matched and every
scrub copied and re-uploaded the whole vertex buffer and recomputed bounds,
defeating the "only the index changes on a pure slice move" design (#1245).
displayDimsKey records which axis triple position currently holds, because
once the buffer is reused its identity can no longer signal a change. Optional
so a caller may still project without one (it then allocates, as before).
A whole decoded mesh, before display-space projection.
Unlike the range-query siblings this is always the complete mesh: the loader is whole-node (spec §7). Slicing does not change what is loaded, only which faces
data/mesh/projection.tswrites into the index buffer.Vertices are still in full nD space here —
extract_3d_positionsprojects them to the displayed triple downstream.