From ed29933d87428fa99685691ca54eda1569ebd43e Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 10 Jun 2026 23:41:51 +0300 Subject: [PATCH] =?UTF-8?q?fix(hover):=20drop=20the=20geometry-only=20pick?= =?UTF-8?q?=20cache=20=E2=80=94=20it=20served=20stale=20culled=20wires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hit_test_wires got a cache keyed on geometry_epoch alone (462d9b4), but the underlying wire set IS view-culled (wires_for_block culls to view_world_aabb). After zoom-in + edit, the cache pinned the set culled to that close-up view; zooming back out, blocks that had been off-screen were missing from the pick set, so hover/selection stopped working on them until the next geometry change. Revert to the camera-keyed entity_wires_arc for picking — it re-culls when the view changes. The actual 0.5.1 hover regression fix (hatch rebuild/re-upload on rollover) stays. Co-Authored-By: Claude Opus 4.8 --- src/scene/mod.rs | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 816bcac81..11ebb64a6 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -633,13 +633,6 @@ pub struct Scene { /// invalidates the cull-dependent wire list as well as a geometry change. /// Uses `Arc` so `build_primitive()` avoids a full Vec clone during navigation. wire_cache: RefCell>)>>, - /// Camera-invariant full wire set for **picking** (Model layout), keyed on - /// `geometry_epoch` alone. The wires are world-space, so a pan/zoom must not - /// re-tessellate the whole un-culled model just to hit-test the next mouse - /// move (seconds on a 100k-entity drawing). Built once per geometry change - /// and reused across every camera move; a slightly stale curve tol is - /// harmless for an 8 px pick threshold. - hit_wire_cache: RefCell>)>>, /// Per-Model-tile cached tessellation. Each tile has its own camera /// (live for the active tile, stored snapshot for the others), so /// LOD / frustum culling has to run independently — the shared @@ -827,7 +820,6 @@ impl Scene { block_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed), selection_generation: 0, wire_cache: RefCell::new(None), - hit_wire_cache: RefCell::new(None), model_tile_wire_cache: RefCell::new(HashMap::default()), sort_cache: RefCell::new(None), draw_depth_cache: RefCell::new(None), @@ -2091,23 +2083,10 @@ impl Scene { /// only — paper-space entities are NOT interactive. pub fn hit_test_wires(&self) -> Arc> { if self.current_layout == "Model" { - // Camera-invariant pick cache: reuse the full wire set across pan / - // zoom (world-space wires don't change), rebuilding only when the - // geometry changes. `entity_wires_arc` itself keys on the camera, - // so calling it on every mouse move after a navigate would - // re-tessellate the whole un-culled model — the large-file hover - // stutter. Gate on `geometry_epoch` here instead. - { - let c = self.hit_wire_cache.borrow(); - if let Some((g, ref arc)) = *c { - if g == self.geometry_epoch { - return Arc::clone(arc); - } - } - } - let arc = self.entity_wires_arc(); - *self.hit_wire_cache.borrow_mut() = Some((self.geometry_epoch, Arc::clone(&arc))); - return arc; + // entity_wires_arc is culled to the current view and keyed on the + // camera, so it re-culls when the view changes — picking must reach + // entities that scroll into view after a pan/zoom. + return self.entity_wires_arc(); } let layout_block = self.current_layout_block_handle(); match self.active_viewport {