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

    Module utils/scene-graph-index

    Path → Object3D index for a scene subtree (B9a, tracking #2944).

    The viewer resolves a node by its PATH (its name) on every per-node step of an update pass: the sweep's eligibility probe, each type's process and commit, the lazy-LOD release hooks, the layers panel. Object3D.getObjectByName answers that with a depth-first walk of the whole subtree, so one pass over N nodes costs O(N²) visits — millions per slice move on a 2000-part partition tree. findObjectByName answers it from a map instead.

    Maintained by the scene graph itself, not by the call sites that build it, so no add or remove path can forget it: every member node carries childadded / childremoved listeners (dispatched by three's add / remove / attach / clear / removeFromParent), and a member's name becomes an accessor that re-keys the entry on a rename. A new subtree is indexed as it is added; a removed one (a node disposal, a dataset switch's teardown) is dropped as it is removed.

    Identical answers to getObjectByName, by construction:

    • A UNIQUE name returns the one indexed object after an O(depth) check that it is still attached below the lookup root. That check is what keeps the index safe against graph edits that bypass three's events (e.g. the eager-child loader's slot flattening splices children arrays directly): a stale hit is dropped and the lookup falls back to the walk.
    • A name held by TWO OR MORE live objects is ambiguous, and three's answer is the FIRST in depth-first pre-order — an ordering the map does not keep. Such lookups fall back to getObjectByName itself, so the first-match semantic is preserved exactly rather than approximated. (Loader paths are unique, so this is the exception, not the hot path.)
    • A name the index does not hold falls back to the walk too (a node inserted without an event, or a genuinely absent path), and a hit found that way is indexed so the next lookup is O(1).
    • A walk that MISSES is remembered per lookup root, so a path that is not built yet (a lazy LOD level, a deferred partition part — looked up on every pass) costs one walk, not one per lookup. The memory is invalidated by an EPOCH bumped on every event that could turn a miss into a hit: a childadded anywhere in the tree (a new node, or a member moved into the looked-up root) and a member rename. A removal, and the eager-child loader's event-less slot flattening, can only SHRINK the set of nodes below a root, so they never stale a miss. (A node inserted with no event at all would — nothing in the viewer does that, and the property test in tests/unit/utils/scene-graph-index.test.ts checks every lookup against the walk after each edit, flattening included.)
    • The empty name is never indexed (every unnamed group would collide).

    A lookup rooted at a node of an indexed tree (not only at the indexed root) is served by the same index, with the ancestor check bounded at that node.

    SceneGraphIndex
    ChildEvent
    MissMemo
    Node
    attachSceneGraphIndex
    detachSceneGraphIndex
    sceneGraphIndexOf
    findObjectByName