perf(scene): quadtree culling in wire tessellation (Phase 2.1, step 3)
`wires_for_block` now drives candidate selection from the quadtree when a Model-layout view AABB is available — `query_rect` replaces the O(N) `doc.entities().filter(...)` scan. Visibility predicate is hoisted into a closure and reused for the quadtree-candidate path and the unindexable-entity append (Insert/Viewport). Paper space and the pre-fit first-frame settle path keep the original full-scan behaviour (no view AABB → no cull). Per-entity LOD + frustum checks inside `tessellate_entity` stay in place; the quadtree is purely a candidate reducer so culling correctness still funnels through one place. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
3e874602e2
commit
e43498d6c4
1 changed files with 61 additions and 26 deletions
|
|
@ -1235,34 +1235,69 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// Collect visible entities sequentially (filter needs &self).
|
||||
let visible: Vec<&EntityType> = self
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| {
|
||||
let c = e.common();
|
||||
if c.invisible {
|
||||
return false;
|
||||
// Visibility test reused by both paths below.
|
||||
let visibility_ok = |e: &EntityType| -> bool {
|
||||
let c = e.common();
|
||||
if c.invisible {
|
||||
return false;
|
||||
}
|
||||
// Block/BlockEnd are block-defn sentinels, not drawable geometry.
|
||||
// Without this skip they fall through to legacy_geometry's `_`
|
||||
// arm and emit a 1-unit phantom segment at world_offset that
|
||||
// poisons fit_all and shows up in selection.
|
||||
if matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
||||
return false;
|
||||
}
|
||||
if self
|
||||
.document
|
||||
.layers
|
||||
.get(&c.layer)
|
||||
.map(|l| l.flags.off || l.flags.frozen)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.belongs_to_visible_block(e.common().handle, c.owner_handle, block_handle)
|
||||
};
|
||||
|
||||
// Phase 2.1 — quadtree-driven candidate selection. When a view
|
||||
// AABB exists (Model layout with a settled camera), only iterate
|
||||
// entities whose stored WCS bbox intersects the view; unindexable
|
||||
// entities (Insert/Viewport) are appended via a small linear scan.
|
||||
// Paper space and the first-frame "settle" path fall back to the
|
||||
// full doc scan — preserving prior behaviour.
|
||||
let visible: Vec<&EntityType> = if let Some(local_view) = self.view_world_aabb() {
|
||||
let [ox, oy, _] = self.world_offset;
|
||||
let view_wcs: [f64; 4] = [
|
||||
local_view[0] as f64 + ox,
|
||||
local_view[1] as f64 + oy,
|
||||
local_view[2] as f64 + ox,
|
||||
local_view[3] as f64 + oy,
|
||||
];
|
||||
let candidates: Vec<Handle> = {
|
||||
let tree = self.entity_index();
|
||||
tree.query_rect(view_wcs)
|
||||
};
|
||||
let mut out: Vec<&EntityType> = Vec::with_capacity(candidates.len() + 16);
|
||||
for h in candidates {
|
||||
if let Some(e) = self.document.get_entity(h) {
|
||||
if visibility_ok(e) {
|
||||
out.push(e);
|
||||
}
|
||||
}
|
||||
// Block/BlockEnd are block-defn sentinels, not drawable geometry.
|
||||
// Without this skip they fall through to legacy_geometry's `_`
|
||||
// arm and emit a 1-unit phantom segment at world_offset that
|
||||
// poisons fit_all and shows up in selection.
|
||||
if matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
||||
return false;
|
||||
}
|
||||
for e in self.document.entities() {
|
||||
if is_unindexable_entity(e) && visibility_ok(e) {
|
||||
out.push(e);
|
||||
}
|
||||
if self
|
||||
.document
|
||||
.layers
|
||||
.get(&c.layer)
|
||||
.map(|l| l.flags.off || l.flags.frozen)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.belongs_to_visible_block(e.common().handle, c.owner_handle, block_handle)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
out
|
||||
} else {
|
||||
self.document
|
||||
.entities()
|
||||
.filter(|e| visibility_ok(e))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Tessellate in parallel across all available CPU cores.
|
||||
use rayon::prelude::*;
|
||||
|
|
|
|||
Loading…
Reference in a new issue