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

    Module scene/lod-group-registry

    Per-frame LOD-group selector.

    Tracks every lod_group scene-graph node currently loaded. For each one, every frame:

    1. Fold each child's raw nD positionBounds into a cached per-entry local-space :type:BoundingBox, using the current displayDims to map nD axes onto X/Y/Z, then transform it to world space for the frustum gate. Eviction uses the same full-geometry box.
    2. When any child publishes optional robust lodBounds, fold them the same way (falling back per child to positionBounds) for metric sizing only, so excluded outliers remain visible and resident.
    3. Transform the metric box into world space via :func:transformBoundingBox and the lod_group's matrixWorld.
    4. Project the 8 corners through the camera and reduce them to the dimensionless coverage metric, on whichever scale the entry's selector names — the two branches of evaluateEntry:
      • 'screen-area' (what every derived ladder stamps): the fraction of the viewport the projected AABB covers by AREA, via projectBoxAreaFraction. Aspect-free, tops out at 1.0.
      • 'coverage' (legacy): back to pixel coordinates, then the diagonal of the screen-space AABB divided by FILL_FACTOR × fittedAxisPx (fittedAxisPx is min(viewport.width, viewport.height) — the extent calculateCameraDistance actually fits; see the FILL_FACTOR doc), so 1.0 == the object's projected diagonal has reached FILL_FACTOR of the fitted axis.
    5. Pick the finest child whose coverage_fraction threshold is satisfied by that coverage metric, with 10% asymmetric hysteresis on the downgrade direction to suppress threshold-edge flicker.
    6. If the desired child differs from the current active one, swap visibility atomically — gated by the never-downgrade display gate: a fresh aspiration whose additive ladder is still streaming is not shown while the previously-displayed level looks strictly better (see shouldHoldPreviousDisplay in lod-display-gate.ts).

    The atomic-swap invariant on initial load is realized by loadLodGroupNode (sequential awaits + visible=false after attach + a single register() call at the end) — no per-child ready gate is needed in the registry.

    Partition frustum gating (registerPartition / evaluatePartitionEntry): a kind=partition group's parts are tested every frame against a frustum padded by PARTITION_FRUSTUM_MARGIN; a part outside it is hidden and stamped userData.partitionFrustumVisible = false, which the scene loader's sweep and refinement read to skip its loaders. Because a culled part misses slice updates, its RE-ENTRY requests a resync of exactly that part's loaders (deps.requestReprocess(partPaths), coalesced per wrapper across frames and gated on isUpdateInProgress). The resync re-sweeps under the UNCHANGED view version — bumping it here would read every lazy fine level scene-wide as stale and drop all groups to coarse on camera motion.

    The bbox infrastructure is shared with the scene-bounds cache and camera framing — projectBoundsToDisplayDims / transformBoundingBox live in scene-manager/clipping/bounds-math.ts and are reused here.

    Wiring: the SceneLoader instantiates one registry per scene; the pipeline hooks evaluatePerFrame into AnimationController alongside the dynamic-clipping callback. Manual override (setSelectorMode(path, { lockLevel: i })) bypasses the auto selector — driven by the layers-panel dropdown.

    LODGroupRegistry
    LODGroupChild
    LODGroupEntry
    PartitionGroupChild
    PartitionGroupEntry
    LODGroupRegistryDeps
    FILL_FACTOR
    SCREEN_FILL_DIAGONAL_RATIO
    pickChildWithHysteresis → pickChildWithHysteresis
    projectBoxAreaFraction → projectBoxAreaFraction
    projectBoxDiagonalPx → projectBoxDiagonalPx