Progressive Mesh loader for reveal-ladder (multi-additive-LOD) nodes.
Wraps N MeshWholeNodeLoader instances — one per additive_<i>
subgroup — behind the single MeshDataLoader interface, so the scene
loader, the commit path and the Layers panel see one mesh node that gains
triangles over time. The fourth member of the progressive-loader family
(points- / lines- / gsplats-progressive-loader.ts).
What a mesh ladder IS, and why the prefix is presentable
A prefix of an arbitrary index buffer is a surface full of holes, not a
coarser one — which is why mesh has no level-of-detail additive ladder and
never will (docs/specs/MESH_NODE_SPEC.md §9). What it has is a REVEAL: the
writer orders faces by best-first growth through face adjacency keyed on
radius, so every prefix is ONE connected patch. The renderer does nothing
special; each level is just more triangles. That is the whole design —
MESH_ADDITIVE_METHODS on the Python side admits radial and nothing else
for exactly this reason.
Connectivity is the guarantee; "grows outward from the centre" is what it
LOOKS like only where the radius actually varies over the surface — an
isosurface with depth structure, the target case. On a shell at near-constant
radius (a sphere, a membrane) the radius carries almost no information, the
frontier admits a whole radius class at once, and the half-revealed surface
reads as a sieve filling in rather than a cap spreading. Observed on an
icosphere, not inferred. Still one connected patch, still far better than an
arbitrary order — but worth knowing before promising a user a growing cap.
Why this loader is half the size of its three siblings
They are per-slice STREAMING ladders: their sub-loaders answer a spatial range
query, so a slice move genuinely changes what each level holds. That forces
the whole apparatus this file does not have — a view-state equality check, a
reset generation, a departure-store/restore against the SliceCache, and a
per-view concat.
A mesh is whole-node resident (spec §7): every level fetches its faces ONCE
and serves every later updateView from that decode, so the ladder is
view-independent. What is loaded depends only on how many levels have
arrived; what is drawn depends on the slice, and that is decided downstream
in projection.ts exactly as it is for an unladdered mesh.
Two consequences worth stating, because both are load-bearing:
No SliceCache. Each sub-loader's own cached decode is the cache, and
it lasts the node's lifetime. A whole-ladder S-cache entry keyed by slice
would store the same bytes under every key.
The concat memo is keyed on level count alone, so a slice move returns
the SAME object — and with it the same loader-owned projection scratch
(#1245). Had the ladder reset per view like its siblings, every scrub frame
would have reallocated and re-uploaded the whole vertex buffer, which is
precisely the regression that scratch exists to prevent.
Progressive Mesh loader for reveal-ladder (multi-additive-LOD) nodes.
Wraps N MeshWholeNodeLoader instances — one per
additive_<i>subgroup — behind the single MeshDataLoader interface, so the scene loader, the commit path and the Layers panel see one mesh node that gains triangles over time. The fourth member of the progressive-loader family (points-/lines-/gsplats-progressive-loader.ts).What a mesh ladder IS, and why the prefix is presentable
A prefix of an arbitrary index buffer is a surface full of holes, not a coarser one — which is why mesh has no level-of-detail additive ladder and never will (
docs/specs/MESH_NODE_SPEC.md§9). What it has is a REVEAL: the writer orders faces by best-first growth through face adjacency keyed on radius, so every prefix is ONE connected patch. The renderer does nothing special; each level is just more triangles. That is the whole design —MESH_ADDITIVE_METHODSon the Python side admitsradialand nothing else for exactly this reason.Connectivity is the guarantee; "grows outward from the centre" is what it LOOKS like only where the radius actually varies over the surface — an isosurface with depth structure, the target case. On a shell at near-constant radius (a sphere, a membrane) the radius carries almost no information, the frontier admits a whole radius class at once, and the half-revealed surface reads as a sieve filling in rather than a cap spreading. Observed on an icosphere, not inferred. Still one connected patch, still far better than an arbitrary order — but worth knowing before promising a user a growing cap.
Why this loader is half the size of its three siblings
They are per-slice STREAMING ladders: their sub-loaders answer a spatial range query, so a slice move genuinely changes what each level holds. That forces the whole apparatus this file does not have — a view-state equality check, a reset generation, a departure-store/restore against the
SliceCache, and a per-view concat.A mesh is whole-node resident (spec §7): every level fetches its faces ONCE and serves every later
updateViewfrom that decode, so the ladder is view-independent. What is loaded depends only on how many levels have arrived; what is drawn depends on the slice, and that is decided downstream inprojection.tsexactly as it is for an unladdered mesh.Two consequences worth stating, because both are load-bearing:
SliceCache. Each sub-loader's own cached decode is the cache, and it lasts the node's lifetime. A whole-ladder S-cache entry keyed by slice would store the same bytes under every key.