ifcviewer: fix pick-pass cull corruption and cached-model ID collisions

Two stability bugs:

1. Clicking an object left the scene with wrong shading until the camera
   moved.  The pick pass re-culls every model with its own parameters
   (min_pixel_radius=0, no HiZ) and overwrites each model's visible_ssbo
   and indirect buffer.  The next render() saw an unchanged camera,
   skipped the cull via the have_cached_cull_ shortcut, and drew the
   stale pick-pass buffers.  Fix: invalidate have_cached_cull_ at the
   end of pickObjectAt().

2. Loading two sidecar-cached models made the second model's picked
   properties resolve to the first model's elements.  Sidecars store raw
   object_id / model_id values from the session that wrote them, and
   both files start at object_id=1, so element_map_ entries collided.
   Fix: on load, rebase every PackedElementInfo and InstanceCpu by
   (next_object_id_ - min_id_in_sidecar) and overwrite model_id with
   the freshly-assigned handle before the elements hit element_map_.

Also document both in the README — the pick-pass note under 3A
contribution culling, the sidecar rebase under the sidecar format
section.
This commit is contained in:
Dion Moult
2026-04-15 18:30:03 +10:00
parent 99f409280a
commit 03662d2016
3 changed files with 44 additions and 5 deletions
+15
View File
@@ -307,6 +307,14 @@ uint32_t + char[] string table
Staleness check: `source_file_size` vs actual file size. Mismatched →
reject and rebuild. Endianness marker rejects cross-arch caches.
Sidecars store the raw `object_id` / `model_id` values from the session
that wrote them. On load they are rebased onto the current session's ID
space (`object_id += next_object_id_ - min_id_in_sidecar`, `model_id`
overwritten with the freshly-assigned handle) before the elements hit
`element_map_` or the viewport. Without this, two cached models loaded
back-to-back collide — both start at `object_id=1` and the second model's
property lookups return the first model's data.
### GPU Instancing pipeline (the central pillar)
Everything above plugs into a single data-flow, worth documenting on its
@@ -451,6 +459,13 @@ and per-instance level. Short-circuits when the camera is inside the
AABB so nothing-you're-standing-next-to is ever lost. Pick pass uses
threshold 0 so sub-pixel objects remain clickable.
Because the pick pass re-runs the cull with its own parameters (no
contribution cull, no HiZ) and writes into each model's shared
`visible_ssbo` / indirect buffer, `pickObjectAt()` must invalidate
`have_cached_cull_` on exit. Otherwise the next `render()` sees an
unchanged camera, skips the cull, and draws the pick-pass buffers —
the user sees obviously-wrong shading until they nudge the camera.
Sphere-based (centre = AABB midpoint, radius = half-diagonal,
r_px = focal_px · radius / distance). Loses a little precision on
very elongated bounds vs. 8-corner projection, but costs ~5× less per