The simplest of the four geometry loaders, and deliberately so. It fetches
vertices, faces and the optional per-vertex arrays in full, decodes
them once, and holds them: no spatial index, no progressive refinement, no
chunk-bounds query, no range arithmetic.
Why whole-node is the right shape, not a shortcut
lines-spatial-index-loader.ts runs to ~1400 LOC because line datasets reach
tens of millions of vertices with a meaningful per-slice working set — the
index earns its complexity by letting a slice change fetch a fraction of the
data. A mesh has no such fraction: faces share vertices across any cut, so the
working set after a displayDims change is the whole mesh regardless. An index
would add machinery and skip nothing.
Meshes in this domain (isosurfaces, segmentation boundaries) are typically at
most a few million triangles and fit comfortably. MESH_DECODE_BUDGET_BYTES
is what keeps "comfortably" from being an assumption.
This class still implements the full MeshDataLoader interface, so a
spatial-index implementation can be swapped in behind it later with no caller
change.
The consequence for updateView
Because the mesh is resident in full, updateView returns the same data
every time — it does not re-fetch. What varies with the view is only which
faces get indexed, and that is projection.ts's job, downstream. This is the
one place a reader might expect a re-fetch and find none, so it is stated
plainly rather than left to be inferred from the absence of code.
Two-stage admission
Every fetch is gated. preflight.ts (Stage 1) runs on metadata alone,
before a single chunk is requested; validate.ts (Stage 2) runs on the
materialized values. See those modules for why the split is load-bearing.
Whole-node mesh loader.
The simplest of the four geometry loaders, and deliberately so. It fetches
vertices,facesand the optional per-vertex arrays in full, decodes them once, and holds them: no spatial index, no progressive refinement, no chunk-bounds query, no range arithmetic.Why whole-node is the right shape, not a shortcut
lines-spatial-index-loader.tsruns to ~1400 LOC because line datasets reach tens of millions of vertices with a meaningful per-slice working set — the index earns its complexity by letting a slice change fetch a fraction of the data. A mesh has no such fraction: faces share vertices across any cut, so the working set after adisplayDimschange is the whole mesh regardless. An index would add machinery and skip nothing.Meshes in this domain (isosurfaces, segmentation boundaries) are typically at most a few million triangles and fit comfortably.
MESH_DECODE_BUDGET_BYTESis what keeps "comfortably" from being an assumption.This class still implements the full MeshDataLoader interface, so a spatial-index implementation can be swapped in behind it later with no caller change.
The consequence for
updateViewBecause the mesh is resident in full,
updateViewreturns the same data every time — it does not re-fetch. What varies with the view is only which faces get indexed, and that isprojection.ts's job, downstream. This is the one place a reader might expect a re-fetch and find none, so it is stated plainly rather than left to be inferred from the absence of code.Two-stage admission
Every fetch is gated.
preflight.ts(Stage 1) runs on metadata alone, before a single chunk is requested;validate.ts(Stage 2) runs on the materialized values. See those modules for why the split is load-bearing.