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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-26 12:21:51 +03:00
1 parent 2da9301a2b
commit bfd8d0411a
35 files changed
+107 -25

No files matched your search

+1
View File
@@ -161,6 +161,7 @@ pub fn apply_along(
tangent_geoms: vec![],
aci: 0,
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
})
.collect()
}
+26 -3
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]
}
+10
View File
@@ -96,6 +96,7 @@ pub fn tessellate(
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
};
}
@@ -122,6 +123,7 @@ pub fn tessellate(
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
};
}
_ => {}
@@ -142,6 +144,7 @@ pub fn tessellate(
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
};
}
}
@@ -160,6 +163,7 @@ pub fn tessellate(
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
};
}
}
@@ -177,6 +181,7 @@ pub fn tessellate(
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
};
}
@@ -205,6 +210,7 @@ pub fn tessellate(
snap_pts,
tangent_geoms,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
}
}
@@ -241,6 +247,7 @@ pub fn tessellate_dimension(
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
}];
if let Some(text) = dimension_text_entity(dim) {
@@ -287,6 +294,7 @@ fn tessellate_leader(
tangent_geoms: vec![],
aci: 0,
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
}];
}
@@ -354,6 +362,7 @@ fn tessellate_leader(
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
}]
}
@@ -460,6 +469,7 @@ fn tessellate_multileader(
tangent_geoms: vec![],
aci: 0,
key_vertices: key_verts,
aabb: WireModel::UNBOUNDED_AABB,
}];
// Render text content as MText wire
+28
View File
@@ -55,12 +55,20 @@ pub struct WireModel {
/// Non-empty only for entities with distinct vertex positions (Line, LwPolyline).
/// Empty for tessellated curves (Circle, Arc, Ellipse) which use snap_pts instead.
pub key_vertices: Vec<[f32; 3]>,
/// World-space 2-D bounding box [min_x, min_y, max_x, max_y].
/// Set from acadrust `bounding_box()` in `tessellate_entity()`.
/// Preview / interim wires use `UNBOUNDED_AABB` so they are never pre-rejected
/// by the snap world-space filter.
pub aabb: [f32; 4],
}
impl WireModel {
pub const WHITE: [f32; 4] = [1.00, 1.00, 1.00, 1.0];
pub const CYAN: [f32; 4] = [0.25, 0.85, 1.00, 1.0];
pub const SELECTED: [f32; 4] = [0.15, 0.55, 1.00, 1.0];
/// Sentinel AABB that never rejects any snap query.
pub const UNBOUNDED_AABB: [f32; 4] =
[f32::NEG_INFINITY, f32::NEG_INFINITY, f32::INFINITY, f32::INFINITY];
/// Create a solid wire (no dash pattern, 1px weight).
pub fn solid(name: String, points: Vec<[f32; 3]>, color: [f32; 4], selected: bool) -> Self {
@@ -76,6 +84,7 @@ impl WireModel {
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: Self::UNBOUNDED_AABB,
}
}
@@ -159,3 +168,22 @@ impl WireModel {
.sum()
}
}
impl Default for WireModel {
fn default() -> Self {
Self {
name: String::new(),
points: Vec::new(),
color: Self::WHITE,
selected: false,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
aci: 0,
snap_pts: Vec::new(),
tangent_geoms: Vec::new(),
key_vertices: Vec::new(),
aabb: Self::UNBOUNDED_AABB,
}
}
}