Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Interface MeshGeometryConfig

    Everything createMeshGeometry and updateMeshGeometry need to lay out the buffers.

    *Config after the sibling geometry modules' InstancedLinesMeshConfig / InstancedGSplatsMeshConfig — same role, minus the Instanced qualifier those two carry because a mesh is not an instanced quad.

    interface MeshGeometryConfig {
        position: Float32Array;
        indices: Uint32Array;
        positionChanged: boolean;
        positionKey?: string;
        colors: MeshColorArray | null;
        colorComponents?: 3 | 4;
        normals?: Float32Array<ArrayBufferLike> | null;
        scalars?: Float32Array<ArrayBufferLike> | null;
        uvs?: Float32Array<ArrayBufferLike> | null;
        vertexCount: number;
        faceCount: number;
        bounds?: MeshProjectionBounds | null;
        vertexCountGrows?: boolean;
        capacityVertexCount?: number;
        capacityFaceCount?: number;
    }
    Index
    position: Float32Array

    Display-space positions (vertexCount * 3), from projectMeshTo3D.

    indices: Uint32Array

    Index buffer of visible triangles (visibleFaceCount * 3).

    positionChanged: boolean

    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.

    positionKey?: string

    The 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.

    colors: MeshColorArray | null

    Per-vertex colors in their native dtype, or null for the white default.

    colorComponents?: 3 | 4

    Channels per color entry when colors is present.

    normals?: Float32Array<ArrayBufferLike> | null

    Per-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.

    scalars?: Float32Array<ArrayBufferLike> | null

    Per-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).

    uvs?: Float32Array<ArrayBufferLike> | null

    Per-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.

    vertexCount: number

    Vertices in the buffers (NOT the visible count — nothing is compacted).

    faceCount: number

    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.

    bounds?: MeshProjectionBounds | null

    Projected 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.

    vertexCountGrows?: boolean

    Whether 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.

    capacityVertexCount?: number

    The 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.

    capacityFaceCount?: number