Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...
    • Build the pick-result callback. Dependencies are captured at call time — fine because initPicking reassigns the loader / overlay references before constructing the PickingSystem, and each dataset reload reruns initPicking end-to-end (disposing the old picking system and its captured closure with it).

      Behavior contract:

      • Two node paths, deliberately split (#1415). The reported path — the selection's nodeName and the overlay's title — is the outermost kind=partition wrapper when the hit sits under one, mirroring how the layers panel treats a partition wrapper as the user-facing layer. The queried path — what the label / key / image loaders are handed — is the hit leaf scene node, result.mainNode.name, which is the CSR owner for a flat node and for a part_<i> of a partition. Using the wrapper for the lookup fails twice over: it is a bare group with no label/key CSR arrays (they are written per part_<i>), and result.elementId is an index in the leaf's own element space, meaningless against a whole-node array. Before the split, every hover on a partitioned layer resolved to an empty tooltip, silently — LabelLoader demotes the missing array to an info log and caches that the node lacks the channel. Of the three lookups only the label/key getLabel calls are reachable under a partition today (all four adders refuse image_labels alongside partition=, so no part_<i> ever owns an image CSR); getImageUrl moves with them for consistency, not because it is broken today.

        The selection event carries both paths so the split is resolvable from outside: nodeName is reportPath (display) and hitNodeName is lookupPath (the node elementIndex is local to, and what an embedder should index against).

        Two known limits survive this fix, both outside the handler: (i) an additive ladder carries ONE union CSR per present labels/keys channel on its parent node (#1422), spanning the levels in additive_<i> order — the same node lookupPath names, and the same space the progressive loader produces when it concatenates the committed levels, so the lookup is correct — for POINTS also under slicing, since the loader now composes each level's slot → on-disk map into that union space, offsetting level i by the preceding levels' on-disk n_points (#1439). Not for LINES: its raw slot is a per-segment one while the union CSR is per-vertex (#1424), so a laddered lines node with a string channel is wrong at the granularity, not merely at an offset, whatever the slicing — and nothing composes a lines ladder's LEVELS either; gsplat ladders carry no labels/keys at all; and (ii) result.elementId is only sometimes the on-disk CSR index. It arrives already resolved wherever the node can resolve one — Points, GSplats and Lines all do, for a node declaring has_labels / has_image_labels / has_keys, through a published slot → on-disk map or trivially where the identity already holds and no map is published, and Mesh needs none because its gl_VertexID already IS the on-disk vertex ordinal (see rendering/picking/picking-system/element-id-map.ts, which does the translation at the single PickResult construction site). For a LINES node the resolved value is the picked segment's start vertex row in the on-disk (spatially sorted) VERTEX ordering, not a segment row: line string channels are per-vertex, and a segment carries a single flat pick id, so exactly one of its two endpoints can be reported and by convention it is the start (#1424). That resolution is published for a FLAT lines node only, so any lines node without it — one with no string channel, and equally a LADDERED one with a string channel, whose per-level maps a lines ladder's concat still drops (limit (i), where only Points composes them) — still reports the raw visible-segment slot, which is neither an on-disk row nor even the right granularity for a per-vertex CSR. A Points or GSplats node with no per-element string channel likewise keeps the raw storage slot — no CSR to miss, but an embedder reading SelectionPayload.elementIndex there is reading a slot.

      • null result → clear hover (updateHoverContent(null)); no loader calls.

      • Non-null result → fetch label + key + image URL in parallel; emit a payload only when at least one is truthy. An empty string counts as no content (matches LabelLoader.getLabel which already returns null for empty labels, but the falsy gate here is the second line of defense).

      • Any fetch rejects → log a warning and clear hover. Errors must not kill the hover loop.

      • Ordering: the string/image fetch is async, so a slow fetch from an older invocation could resolve AFTER a newer one (e.g. an uncached image while the cursor moves on) and re-show a stale tooltip over fresher state. A monotonic latest token, captured per call, gates the post-await emit: a superseded invocation drops its result. A null (hide) call applies immediately and bumps the token, so it also cancels any in-flight content fetch.

      Parameters

      Returns (result: PickResult | null) => Promise<void>