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

    Interface LODGroupRegistryDeps

    Injected view-state accessors. Lets the registry stay test-friendly (mock the camera, the viewport, the slice) without coupling to the SceneManager singleton.

    interface LODGroupRegistryDeps {
        getCamera(): Camera;
        getViewportSize(): { width: number; height: number };
        getDisplayDims(): readonly number[];
        hasArchiveFault?: () => boolean;
        hasNetworkFailureUnder?: (path: string) => boolean;
        getResidentByteBudget?: () => number;
        getResidentBytes?: () => number;
        getViewVersion?: () => number;
        now?: () => number;
        requestRender?: () => void;
        requestReprocess?: (paths: readonly string[]) => void;
        isUpdateInProgress?: () => boolean;
        getCrossFadeEnabled?: () => boolean;
        getEnergyCompEnabled?: () => boolean;
        getForceFinestLOD?: () => boolean;
        getLodBias?: () => number | undefined;
        registerMaterial?: (material: Material) => void;
    }
    Index
    hasArchiveFault?: () => boolean

    Whether the owning loader has latched an archive fault.

    hasNetworkFailureUnder?: (path: string) => boolean

    Whether a loader under this LOD group has a recorded network failure.

    getResidentByteBudget?: () => number

    Resident-byte budget (the ceiling). The single, adaptive VRAM budget shared with the GPU buffer pool — one authority, not a competing one. Omitted ⇒ no eviction (pure retention), the default for unit tests that construct the registry directly.

    getResidentBytes?: () => number

    Measured total resident VRAM bytes (active + pooled) from the GPU buffer pool — the single accounting truth. The registry compares this to the budget to decide when to demote cold levels; it no longer keeps its own per-level byte estimate. Omitted ⇒ no eviction (pure retention), the default for unit tests.

    getViewVersion?: () => number

    Current view-update version (SceneLoader.currentViewVersion). Used for the slice-aware fallback: a gsplats level whose committed geometry was stamped with an older version still shows a previous slice/displayDims, so the registry treats it as stale and displays the coarsest level that IS fresh until the re-slice commits. Omitted ⇒ no freshness tracking (every ready level is treated as fresh — identical to the pre-feature behaviour; the default for unit tests that don't exercise scrubbing).

    now?: () => number

    Monotonic wall clock in milliseconds, for the stale-hold budget (see STALE_HOLD_MS). Injectable so tests can advance it deterministically; omitted ⇒ performance.now().

    requestRender?: () => void

    Keep the render loop alive (the viewer is on-demand and idles after ~2s). Called each frame while a lazy level is loading so a deferred fine reload — which commits OUTSIDE the per-slice sweep and can take longer than the idle timeout — still triggers the per-frame swap-up to the fresh level when it lands, instead of waiting for the next user interaction. Wired to AnimationController.startAnimation (resets the idle timeout). Omitted ⇒ no-op (unit tests don't run a loop).

    requestReprocess?: (paths: readonly string[]) => void

    Re-run the current view update for the partition parts that just re-entered the frustum. Culled children skip event-driven slice updates, so the rising edge must resync any geometry that missed the latest view state. paths are the re-entering PART node paths (child.path), or the partition wrapper path when a part has none (= resync the whole partition); the loader prefix-matches its sweep loaders under them, so a nested kind=lod part's eager level is covered. The view state is unchanged, so the loader MUST NOT bump the view version for this pass — otherwise every lazy fine level scene-wide reads stale and drops to coarse.

    isUpdateInProgress?: () => boolean

    Whether the owning loader has a view PASS in flight or queued. Rising edges are held (and coalesced) while this is true so a resync never lands on top of a pass. A refinement hold deliberately does NOT count: the loader parks a resync that arrives during one and cancels into its own pass, so re-entering parts do not sit on a stale slice until the ladders finish.

    getCrossFadeEnabled?: () => boolean

    Whether the LOD cross-fade is enabled (ON by default; ?noLodFade disables). When true and a blendable (additive/luminous/volumetric — see BLENDABLE_MODES in scene/lod-fade.ts) group is zooming across a LOD boundary, the registry blends the two straddling levels' opacity — the finer at smoothstep(coverage metric across a ±band around the boundary), the coarser at the complement — instead of a hard visibility swap. Distance- driven (a function of the coverage metric), independent of additive streaming. Omitted / false ⇒ the pre-cross-fade hard swap, byte-identical. Read live so the flag applies without a reload. The default for unit tests (off).

    getEnergyCompEnabled?: () => boolean

    Whether streaming brightness compensation is enabled: as a blendable (additive/luminous/volumetric) leaf's ladder streams in, scale its opacity by 1/e(k) so the partial prefix renders at the full-level energy (no brightening pop). Distinct axis from the cross-fade (time, not distance) and independently gated; either flag on enables the registry's per-frame opacity management. Omitted / false ⇒ byte-identical (no material writes). Read live so the flag applies without a reload. The default for unit tests (off).

    getForceFinestLOD?: () => boolean

    Force the finest LOD level regardless of projected screen coverage (and never coarsen off-screen) — for high-quality still/video capture (the gallery harness), where a coarse level looks blurry even when the subject is small in frame. Wired from LuxarAppOptions.lodFinest (the ?lodFinest URL flag, threaded through the standalone bootstrap); omitted / false ⇒ normal coverage-driven selection. Read live, like the sibling flags above.

    getLodBias?: () => number | undefined

    Replacement-LOD bias in screen-area units. 2 advances one level on an occupancy-halved ladder. Legacy diagonal coverage receives sqrt(bias) so both selectors shift by the same area factor. Since finite screen-area coverage is at most 1, bias below 1 makes a partition-anchored finest level unreachable and bias below 0.5 does the same for a whole-object finest level. Invalid values are neutral.

    registerMaterial?: (material: Material) => void

    Register a clone-on-first-fade material with the material manager so it keeps receiving per-frame camera-uniform updates (the fade clones the shared cached material to fade one level independently; an unregistered gsplat clone would project with stale camera params). Wired to materialManager.register; omitted in unit tests (no camera loop).

    • Viewport size in CSS pixels (matches the renderer canvas).

      Returns { width: number; height: number }