Calculate effective radii for nD points when sliced.
When an nD hypersphere is sliced by a hyperplane, the visible cross-section has a smaller radius. This function computes that effective radius.
Calculate effective radii for nD points when sliced.
Point positions [numPoints * ndim]
Original point radii [numPoints]
Dimensions to display (typically [0,1,2]) [numDisplayDims]
Current slice position [ndim]
Which dims are spatial (1) vs discrete (0).
Callers SHOULD pass an array of length ndim for explicit control.
For dimensions d >= spatialExtendDims.length, this implementation
DEFAULTS to spatial (treats the missing entry as 1). This is the more
permissive fallback (extra dims contribute to the Pythagorean distance
instead of silently dropping the point as a discrete mismatch); production
callers in this codebase enforce length >= ndim at the worker boundary
(see projectPointsTo3D). [length: SHOULD be ndim]
Total number of dimensions
Number of points
Output effective radii [numPoints]
Number of points with non-zero effective radius (visible points)
Sort splats back-to-front by camera-space depth.
Produces the permutation consumed by the aSortedIndex instance
attribute: ordering[j] is the original splat index drawn at instance
slot j (slot 0 = farthest). Degenerate depth ranges (single depth
plane, everything behind the camera) yield the identity ordering.
Input is always projected 3D centers, so no ndim cap applies.
Sort splats back-to-front by camera-space depth.
Projected 3D splat centers [count * 3] (x, y, z triplets)
Column-major 4x4 model-view matrix [16]
(camera.matrixWorldInverse × mesh.matrixWorld, THREE.js layout)
Output permutation [count] — ordering[j] is the
original splat index drawn at instance slot j (slot 0 = farthest)
Number of splats
Number of splats placed via depth keys, or 0 when the identity fallback was taken (degenerate depth range — ordering is still written)
Decode quantized uint8 to float32. Maps [0,255] -> [minVal,maxVal]
Decode quantized uint8 data to float32. Maps uint8 [0, 255] to [minVal, maxVal] linearly.
Decode quantized uint16 to float32. Maps [0,65535] -> [minVal,maxVal]
Decode quantized uint16 data to float32. Maps uint16 [0, 65535] to [minVal, maxVal] linearly.
Decode log-space quantized uint8. Result = expm1(normalized * maxLog)
Decode log-space quantized uint8 data to float32. Decoding: expm1(normalized * maxLog) Callers provide integral codes in [0, 255]; ArrayLike also accepts widened Float32Array codes.
Decode geometric-log uint8 (reserved zero level; min/max-anchored).
Decode geometric-log quantized uint8 data to float32. Reserved zero level: 0 -> exactly 0; levels [1,255] -> exp(minLog + (u-1)/254 * (maxLog - minLog)). Mirrors the Rust kernel 1:1.
Decode geometric-log uint16 (reserved zero level; min/max-anchored).
Decode geometric-log quantized uint16 data to float32.
Decode per-channel LINEAR (fixed-point) uint8 codes to float32.
Per-column [lo, hi] scales (f64, straight from the JSON attrs);
colOffset is the column phase of the first element.
Decode per-channel LINEAR (fixed-point) uint8 codes to float32.
Identity transform, per-column scales — the COORDINATE encoding. No
reserved zero level (mirrors Python's _decode_linear_perchannel).
Decode per-channel LINEAR (fixed-point) uint16 codes to float32.
Decode per-channel LINEAR (fixed-point) uint16 codes to float32.
Decode per-channel LOG uint8 codes to float32 (x = expm1(y)).
zeroLevel: true = reserved zero code 0 + codes 1..255 over the
nonzero-anchored scale; false = legacy all-levels mapping.
Decode per-channel LOG uint8 codes to float32 (x = expm1(y)).
The Cholesky-diagonal encoding; zeroLevel per the current-vs-legacy
layout contract in decode.rs.
Decode per-channel LOG uint16 codes to float32.
Decode per-channel LOG uint16 codes to float32.
Decode per-channel SIGNED-LOG uint8 codes (x = sign(y)·expm1(|y|)).
Decode per-channel SIGNED-LOG uint8 codes to float32
(x = sign(y)·expm1(|y|)). The Cholesky off-diagonal encoding.
Decode per-channel SIGNED-LOG uint16 codes to float32.
Decode per-channel SIGNED-LOG uint16 codes to float32.
Decode per-channel TRUE-log uint8 codes (x = exp(y); HDR colors).
Reserved zero level always on (name contract — no flag).
Decode per-channel TRUE-log uint8 codes to float32 (x = exp(y)).
HDR colors: ln-domain per-column anchors, uniform relative precision.
The reserved zero level is part of the name contract (always on).
Decode per-channel TRUE-log uint16 codes to float32.
Decode per-channel TRUE-log uint16 codes to float32.
Decode log-space quantized uint16. Result = expm1(normalized * maxLog)
Decode log-space quantized uint16 data to float32. Callers provide integral codes in [0, 65535]; ArrayLike also accepts widened Float32Array codes.
Decode LUT indices (uint8) to scalar float values
Decode LUT-encoded uint8 indices to float32 (scalar mode). Each index maps to a single float value from the LUT.
Decode LUT indices (uint16) to scalar float values
Decode LUT-encoded uint16 indices to float32 (scalar mode).
Decode LUT indices (uint8) to k-element vectors
Decode LUT-encoded uint8 indices to float32 (row mode). Each index maps to k consecutive float values from the LUT.
Decode LUT indices (uint16) to k-element vectors
Decode LUT-encoded uint16 indices to float32 (row mode).
Broadcast a value to all points
Broadcast a single value to all points.
Contract: value.length must be either 1 (scalar broadcast to all
elementsPerPoint slots) or exactly elementsPerPoint (per-element
vector replicated across every point). Any other length is rejected
because the previous "mixed broadcast" semantics produced surprising
rows like [v0, v1, v0] for value.length=2, elementsPerPoint=3.
Extract 3D positions from nD positions using display dimension indices.
Extract 3D positions from nD positions using display dimension indices.
Compute Mahalanobis distance for a single point using packed Cholesky factor.
Given L (lower-triangular Cholesky of covariance), Mahalanobis distance = ||L⁻¹ · (x - μ)||
Compute Mahalanobis distance for a single point using packed Cholesky factor.
Given L (lower-triangular Cholesky of covariance), Mahalanobis distance = ||L⁻¹ · (x - μ)||
We use forward substitution to solve L·y = d, then ||y|| is the Mahalanobis distance.
Difference vector (x - μ) for the dimensions [ndim]
Packed Cholesky factor [packedSize]
Dimensionality of the Cholesky
Mahalanobis distance
Fused nD→3D GSplat projection in a single pass: discrete-visibility gate →
continuous attenuation (marginal Cholesky + shifted Gaussian) → visibility
(amplitude * attenuation >= minAmplitude) → COMPACTED outputs. Replaces the
former 6-call pipeline (compute_gsplats_attenuation + extract_3d_positions +
compact_by_mask ×2 + extract_visible_cholesky_3d +
compact_attenuated_amplitudes), eliminating ~5 passes and the repeated
large-array boundary copies. Bit-identical visible set + values.
Colors must be pre-normalized to f32 (white-filled when absent) — the kernel
takes a single Float32Array because wasm-bindgen can't accept a typed-array
union. Outputs are sized for the splatCount worst case; slice each to the
returned visible count.
Fused nD→3D GSplat projection — TypeScript reference mirroring
gsplats_processing.rs::project_gsplats_nd_to_3d.
Single pass over the splats: discrete-visibility gate → continuous
attenuation (marginal Cholesky + shifted Gaussian) → visibility decision
(amplitude * attenuation >= minAmplitude) → write COMPACTED outputs
(visible centers3D, cholesky3D[6], attenuated amplitudes, colors). Bit-for-bit
equivalent to the legacy 6-call pipeline (reuses the same
computeMarginalCholesky / mahalanobisDistanceInternal helpers in the same
order). Replaces ~5 full passes and the repeated large-array copies.
Colors are pre-normalized to f32 by the caller (white-filled when absent);
colorComponents is 3 (RGB) or 4 (RGBA — alpha is per-splat opacity and
compacts with its splat). Outputs are sized for the splatCount worst case;
the caller slices each to the returned visible count.
outSourceIndices records, per emitted splat, the SOURCE index it came from
(issue #1423) — compaction destroys that mapping, and picking needs it to
translate a storage slot back into an on-disk element index. Pass an EMPTY
array to opt out; the recording is then skipped entirely.
Number of visible splats written.
Clip a single segment to the nD slice and return interpolation parameters.
Clip a single segment to the nD slice and return interpolation parameters.
Returns [visible, t1, t2] where:
MED-20: pass workspace (length >= ndim) to avoid per-call Uint8Array
allocation in hot loops; the buffer is zeroed by this function before use.
Optionalworkspace: Uint8Array<ArrayBufferLike>Batch clip all segments and output visibility mask and interpolation parameters.
Batch clip all segments and output visibility mask and interpolation parameters.
Vertex positions [numVertices * ndim]
Segment indices [numSegments * 2]
Current slice position [ndim]
Per-dimension tolerance [ndim]
Which dimensions to display [numDisplayDims]
Number of dimensions
Number of segments
Output visibility mask [numSegments]
Output t1 parameters [numSegments]
Output t2 parameters [numSegments]
Number of visible segments
Interpolate clipped positions to 3D display space.
Interpolate clipped positions to 3D display space.
Vertex positions [numVertices * ndim]
Segment indices [numSegments * 2]
Visibility mask [numSegments]
Start interpolation parameters [numSegments]
End interpolation parameters [numSegments]
Which dimensions to display [3]
Number of dimensions
Total number of segments
Output start positions [visibleCount * 3]
Output end positions [visibleCount * 3]
Number of visible segments written
Batch interpolate scalar attributes for visible segments.
Batch interpolate scalar attributes for visible segments.
Per-vertex attribute values [numVertices]
Segment indices [numSegments * 2]
Visibility mask [numSegments]
Start interpolation parameters [numSegments]
End interpolation parameters [numSegments]
Total number of segments
Output interpolated start values [visibleCount]
Output interpolated end values [visibleCount]
Number of visible segments written
Batch interpolate RGB color attributes for visible segments.
Batch interpolate RGB color attributes for visible segments.
Per-vertex RGB colors [numVertices * 3]
Segment indices [numSegments * 2]
Visibility mask [numSegments]
Start interpolation parameters [numSegments]
End interpolation parameters [numSegments]
Total number of segments
Output interpolated start colors [visibleCount * 3]
Output interpolated end colors [visibleCount * 3]
Number of visible segments written
Calculate 3D segment lengths for visible segments.
Calculate 3D segment lengths for visible segments.
Clipped start positions [visibleCount * 3]
Clipped end positions [visibleCount * 3]
Number of visible segments
Output segment lengths [visibleCount]
Per-endpoint joint code: how the line shader should treat this endpoint, and — at an ordinary two-segment joint — which segment it joins.
0 free polyline end (keep the soft cap); -1 slice-clipped (suppress the
cap; no neighbour will arrive); -2 degree->=3 hub (keep the cap);
+(slot + 1) joins visible segment slot at that segment's START;
-(slot + 3) joins it at that segment's END. slot is a line-texture
storage slot, so it survives the depth-sort worker's draw-order
permutation. Purely topological — no positions, no angle: the vertex stage
measures the bend in SCREEN space so it tracks the camera. See
wasm/rust/src/lines_clipping.rs for the full derivation.
Per-endpoint joint code (drives the shader's join geometry and cap).
TypeScript reference for the Rust compute_joint_codes kernel — see the Rust
doc comment in wasm/rust/src/lines_clipping.rs for the full rationale. This
mirror is not just a WASM-missing fallback: it is the production backend for
ndim > 16, which the fixed-size Rust kernels cannot serve, so the two must
agree exactly.
Agreement is trivial here in a way it was not for the scalar this replaced: the output is integer index arithmetic, so there is no f32/f64 accumulation order to reconcile between the backends.
| value | meaning |
|---|---|
0 |
free polyline end — keep the soft cap |
-1 |
slice-clipped — suppress the cap entirely |
-2 |
degree->=3 hub — keep the cap |
+(slot + 1) |
joins visible segment slot, at that segment's START |
-(slot + 3) |
joins visible segment slot, at that segment's END |
slot is a storage slot in the line texture, so it survives the depth-sort
worker's draw-order permutation without adjustment.
Vertex index pairs [numSegments * 2]
Visibility mask [numSegments]
Start interpolation parameters [numSegments]
End interpolation parameters [numSegments]
Total number of segments
Total number of source vertices (bounds the touch tables)
Output start joint codes [visibleCount]
Output end joint codes [visibleCount]
Number of visible segments written
Compute per-vertex nD slab membership for a mesh.
For each non-displayed ("hidden") dimension d, with
sliceMin = slicePosition[d] - tolerance[d] and
sliceMax = slicePosition[d] + tolerance[d], a vertex is in iff
v[d] >= sliceMin && v[d] <= sliceMax for every such d. This is the
p1_in branch of WasmModule.clip_segment_single applied per vertex.
A NaN or ±Inf coordinate on any hidden dimension makes the vertex
invisible (the #806 rule, shared with the lines backends).
Unlike lines, nothing is clipped or interpolated — see
WasmModule.compact_visible_faces for the whole-triangle rule and
docs/specs/MESH_NODE_SPEC.md §5 for why v1 does not clip.
The WASM implementation calls validate_ndim and therefore panics above 16
dimensions; pickBackend(ctx, ndim) routes ndim > 16 to the uncapped
TypeScript backend.
Compute per-vertex nD slab membership.
For each non-displayed ("hidden") dimension d, with
sliceMin = slicePosition[d] - tolerance[d] and
sliceMax = slicePosition[d] + tolerance[d], a vertex is in iff
v[d] >= sliceMin && v[d] <= sliceMax for every such d.
A NaN or ±Inf coordinate on any hidden dimension makes the vertex
invisible (the #806 rule). The explicit Number.isFinite test is
load-bearing rather than decorative: an infinite tolerance makes
sliceMax = +Infinity, and +Infinity <= +Infinity is true, so the bare
range test alone would report an infinite coordinate as visible.
That rule covers the coordinate only, and the slab parameters behave the
OPPOSITE way: a NaN in slicePosition or tolerance makes every finite
vertex visible (both value < NaN and value > NaN are false, so the test
degenerates to "not non-finite" — it fails OPEN), while a negative
tolerance inverts the slab and culls everything. Both are caller bugs —
these are viewer-computed, not store-supplied — and both backends agree
exactly, so neither is guarded. Don't let a NaN tolerance derived from
absent dimension metadata reach here expecting it to be culled.
Vertex positions [numVertices * ndim]
Current slice position [ndim]
Per-dimension tolerance [ndim]
Which dimensions are displayed [numDisplayDims]
Number of dimensions (uncapped here)
Number of vertices
Output visibility mask [numVertices] (1 = in, 0 = out)
Number of visible vertices
Compact faces to those whose three vertices are all visible.
Writes ORIGINAL (un-remapped) vertex indices, so a slice change rebuilds only the index buffer while the vertex attribute buffers stay uploaded in full. The authored per-face index order is preserved, so this is winding-agnostic.
A face index >= vertexMask.length drops the whole face rather than reading
out of bounds — the indices are store-supplied, and the two backends fail
differently without the guard (a Rust out-of-bounds read traps with an
uncatchable RuntimeError: unreachable; the TS read yields undefined).
Compact faces to those whose three vertices are all visible.
Writes original (un-remapped) vertex indices: on a slice change only the
index buffer is rebuilt, while the vertex attribute buffers stay uploaded in
full. drawElements never fetches an unreferenced vertex, so culled vertices
cost nothing to draw.
The authored per-face index order is preserved exactly, so this is winding-agnostic; restoring front-facing winding under a reflected display permutation is a separate caller-owned post-pass (§5.4).
A face index >= vertexMask.length drops the whole face. The values come
from the store — the viewer loads arbitrary, possibly corrupted datasets —
and the two backends fail differently without this guard: Rust would read out
of bounds and, being panic = "abort", trap with an opaque, uncatchable
RuntimeError: unreachable, while here the read would yield undefined and
silently diverge. The loader
range-checks face indices up front and fails the node with a LoaderError
before reaching either backend (§3.5 Stage 2), so on the sanctioned path this
is unreachable; it is defense in depth, not a substitute for that gate.
Triangle vertex indices [numFaces * 3]
Per-vertex visibility from
mesh_vertex_visibility_mask; its length defines the valid vertex
range, so pass a view sized exactly numVertices rather than a larger
reused scratch buffer
Number of triangles
Output indices [numFaces * 3] worst case
Number of visible faces written. Slice output to 3 × this before
use. Everything past that point is left untouched — deliberately, to avoid a
second pass — so a reused buffer still holds the previous frame's indices and a
fresh one holds zeros. Uploading the whole buffer as an index range draws
stale or degenerate triangles rather than nothing, which is the failure this
return value exists to prevent.
Per-vertex visibility from
WasmModule.mesh_vertex_visibility_mask; its LENGTH defines the
valid vertex range, so pass a view sized exactly numVertices
TypeScript fallback class implementing WasmModule interface. Used when WASM is unavailable or fails to load.