perf: viewport wire cache, Arc hit_test_wires, snap world-space pre-rejection (I+J+K)

Option I: add per-viewport Arc wire cache so paper-space viewport rendering
no longer re-tessellates model content every frame.

Option J: hit_test_wires() returns Arc<Vec<WireModel>> — O(1) pointer copy
in model space instead of a full Vec clone on every mouse move during commands.
paper_canvas_hatches/wipeouts() now route through the epoch caches added in F.

Option K: add world_snap_r + wire_in_range() pre-check to Snapper::snap().
All Endpoint/Midpoint/Nearest/Perpendicular/Intersection loops skip wires
whose chord sphere is outside the snap circle using O(1) scalar comparisons,
eliminating O(entities × vertices) matrix multiply cost when zoomed in.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-26 11:55:08 +03:00
1 parent 81db3c37d0
commit 2da9301a2b
6 files changed
+149 -32

No files matched your search

+28 -12
View File
@@ -88,6 +88,9 @@ pub struct Scene {
image_cache: RefCell<Option<(u64, Arc<Vec<ImageModel>>)>>,
/// Cached mesh models, keyed by geometry_epoch.
mesh_cache: RefCell<Option<(u64, Arc<Vec<MeshModel>>)>>,
/// Per-viewport wire cache for paper-space rendering.
/// Maps vp_handle → (geometry_epoch, Arc<Vec<WireModel>>).
viewport_wire_cache: RefCell<HashMap<Handle, (u64, Arc<Vec<WireModel>>)>>,
/// Active layout name — "Model" or a paper space layout name.
pub current_layout: String,
/// GPU render data for hatch fills, keyed by the DXF entity Handle.
@@ -124,6 +127,7 @@ impl Scene {
wipeout_cache: RefCell::new(None),
image_cache: RefCell::new(None),
mesh_cache: RefCell::new(None),
viewport_wire_cache: RefCell::new(HashMap::new()),
current_layout: "Model".to_string(),
hatches: HashMap::new(),
meshes: HashMap::new(),
@@ -472,19 +476,15 @@ impl Scene {
/// viewport content is NOT interactive.
/// - MSPACE (active viewport set): model-space content of the active viewport
/// only — paper-space entities are NOT interactive.
pub fn hit_test_wires(&self) -> Vec<WireModel> {
pub fn hit_test_wires(&self) -> Arc<Vec<WireModel>> {
if self.current_layout == "Model" {
return self.entity_wires();
return self.entity_wires_arc();
}
let layout_block = self.current_layout_block_handle();
match self.active_viewport {
None => {
// PSPACE: only paper-space entities (viewport borders, title blocks…)
self.wires_for_block(layout_block)
}
None => Arc::new(self.wires_for_block(layout_block)),
Some(vp_handle) => {
// MSPACE: only model content visible through the active viewport
self.viewport_content_wires(layout_block, Some(vp_handle), None)
Arc::new(self.viewport_content_wires(layout_block, Some(vp_handle), None))
}
}
}
@@ -2277,13 +2277,13 @@ impl Scene {
}
/// Hatch fills for the paper-space canvas.
pub fn paper_canvas_hatches(&self) -> Vec<HatchModel> {
self.synced_hatch_models()
pub fn paper_canvas_hatches(&self) -> Arc<Vec<HatchModel>> {
self.hatch_models_arc()
}
/// Wipeout (opaque background fill) models for the paper-space canvas.
pub fn paper_canvas_wipeouts(&self) -> Vec<HatchModel> {
self.wipeout_models()
pub fn paper_canvas_wipeouts(&self) -> Arc<Vec<HatchModel>> {
self.wipeout_models_arc()
}
pub(super) fn paper_sheet_wires(&self) -> Vec<WireModel> {
@@ -2380,6 +2380,22 @@ impl Scene {
.flat_map(|e| self.tessellate_one(e))
.collect()
}
pub(super) fn model_wires_for_viewport_arc(&self, vp_handle: Handle) -> Arc<Vec<WireModel>> {
{
let cache = self.viewport_wire_cache.borrow();
if let Some((cached_epoch, ref arc)) = cache.get(&vp_handle) {
if *cached_epoch == self.geometry_epoch {
return Arc::clone(arc);
}
}
}
let arc = Arc::new(self.model_wires_for_viewport(vp_handle));
self.viewport_wire_cache
.borrow_mut()
.insert(vp_handle, (self.geometry_epoch, Arc::clone(&arc)));
arc
}
}
impl Default for Scene {