perf: AABB snap pre-rejection via acadrust bounding_box() (Option K upgrade)

Add `aabb: [f32; 4]` field to WireModel (world-space 2-D bounding box).
In tessellate_entity() set each wire's AABB from e.as_entity().bounding_box()
via new entity_aabb() helper; Insert sub-entities each get their own AABB.
Preview/interim wires and any entity returning a zero-extent default box get
UNBOUNDED_AABB so they are never pre-rejected.

Replace the chord-sphere heuristic in Snapper::wire_in_range() with a proper
AABB vs snap-circle overlap test.  The new check is four scalar comparisons and
correctly handles closed curves (circles, arcs) which the chord-sphere approach
had to special-case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-26 12:21:51 +03:00
commit bfd8d0411a
35 changed files with 107 additions and 25 deletions

View file

@ -2552,9 +2552,11 @@ fn tessellate_entity(
} else {
(0.0_f32, [0.0f32; 8])
};
return vec![tessellate::tessellate(
let mut wire = tessellate::tessellate(
document, h, e, sel, color, pattern_length, pattern, 1.5,
)];
);
wire.aabb = entity_aabb(e);
return vec![wire];
}
let (entity_color, pattern_length, pattern, line_weight_px, aci) =
@ -2563,11 +2565,13 @@ fn tessellate_entity(
let lt_name = render::linetype_name_for(document, e);
if let EntityType::Dimension(dim) = e {
let aabb = entity_aabb(e);
let mut wires = tessellate::tessellate_dimension(
document, h, dim, sel, entity_color, line_weight_px,
);
for w in &mut wires {
w.aci = aci;
w.aabb = aabb;
}
return wires;
}
@ -2583,6 +2587,7 @@ fn tessellate_entity(
.flat_map(|sub| {
let (sub_color, sub_pattern_length, sub_pattern, sub_line_weight_px, sub_aci) =
render::render_style_for(document, &sub);
let sub_aabb = entity_aabb(&sub);
let mut wire = tessellate::tessellate(
document,
h,
@ -2595,18 +2600,21 @@ fn tessellate_entity(
);
wire.name = h.value().to_string();
wire.aci = sub_aci;
wire.aabb = sub_aabb;
vec![wire]
})
.collect();
}
let aabb = entity_aabb(e);
let mut base = tessellate::tessellate(
document, h, e, sel, entity_color, pattern_length, pattern, line_weight_px,
);
base.aci = aci;
base.aabb = aabb;
if let Some(clt) = crate::linetypes::complex_lt(lt_name) {
let wires = complex_lt::apply_along(
let mut wires = complex_lt::apply_along(
&base.name,
&base.points,
clt,
@ -2616,6 +2624,7 @@ fn tessellate_entity(
base.line_weight_px,
);
if !wires.is_empty() {
for w in &mut wires { w.aabb = aabb; }
return wires;
}
}
@ -2623,3 +2632,17 @@ fn tessellate_entity(
vec![base]
}
fn entity_aabb(e: &acadrust::EntityType) -> [f32; 4] {
let bbox = e.as_entity().bounding_box();
let min_x = bbox.min.x as f32;
let min_y = bbox.min.y as f32;
let max_x = bbox.max.x as f32;
let max_y = bbox.max.y as f32;
// A degenerate box (min == max == 0) means bounding_box() returned Default —
// use UNBOUNDED so the wire is never wrongly pre-rejected.
if min_x == 0.0 && min_y == 0.0 && max_x == 0.0 && max_y == 0.0 {
return WireModel::UNBOUNDED_AABB;
}
[min_x, min_y, max_x, max_y]
}