forked from 1b-agent/opencadstudio-upstream
fix(osnap): detect polyline self-intersections
This commit is contained in:
1 parent
9c4aa5f896
commit
8efaece5cb
1 file changed
+86
-3
+86
-3
@@ -1755,6 +1755,53 @@ impl Snapper {
|
|||||||
if self.is_on(SnapType::Intersection)
|
if self.is_on(SnapType::Intersection)
|
||||||
&& (local_segments.is_some() || allow_unindexed_pairwise)
|
&& (local_segments.is_some() || allow_unindexed_pairwise)
|
||||||
{
|
{
|
||||||
|
// Unindexed fallback: also test non-adjacent segments belonging to the
|
||||||
|
// same wire. A polyline is one entity but its own segments may cross.
|
||||||
|
if local_segments.is_none() {
|
||||||
|
for wire in in_range_wires.iter() {
|
||||||
|
let src = wire_source(wire);
|
||||||
|
let segment_count = wire.points.len().saturating_sub(1);
|
||||||
|
|
||||||
|
for ai in 0..segment_count {
|
||||||
|
let a0 = wp_f64(wire, ai);
|
||||||
|
let a1 = wp_f64(wire, ai + 1);
|
||||||
|
|
||||||
|
let a_min_x = a0.x.min(a1.x);
|
||||||
|
let a_max_x = a0.x.max(a1.x);
|
||||||
|
let a_min_y = a0.y.min(a1.y);
|
||||||
|
let a_max_y = a0.y.max(a1.y);
|
||||||
|
|
||||||
|
for bi in (ai + 1)..segment_count {
|
||||||
|
let b0 = wp_f64(wire, bi);
|
||||||
|
let b1 = wp_f64(wire, bi + 1);
|
||||||
|
|
||||||
|
// Consecutive polyline segments meet at a normal vertex.
|
||||||
|
// That is already an Endpoint snap, not a self-intersection.
|
||||||
|
if segments_share_endpoint(a0, a1, b0, b1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap AABB rejection before the exact 3-D intersection.
|
||||||
|
if a_max_x < b0.x.min(b1.x)
|
||||||
|
|| a_min_x > b0.x.max(b1.x)
|
||||||
|
|| a_max_y < b0.y.min(b1.y)
|
||||||
|
|| a_min_y > b0.y.max(b1.y)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pt) = seg_intersect_3d(a0, a1, b0, b1) {
|
||||||
|
try_pt(
|
||||||
|
pt,
|
||||||
|
SnapType::Intersection,
|
||||||
|
src,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(segments) = &local_segments {
|
if let Some(segments) = &local_segments {
|
||||||
let local_wires: Vec<_> = wires.iter().filter(|wire| wire_in_range(wire)).collect();
|
let local_wires: Vec<_> = wires.iter().filter(|wire| wire_in_range(wire)).collect();
|
||||||
let mut resolved_pairs = rustc_hash::FxHashSet::default();
|
let mut resolved_pairs = rustc_hash::FxHashSet::default();
|
||||||
@@ -1789,7 +1836,13 @@ impl Snapper {
|
|||||||
if b.min_x() > a.max_x() {
|
if b.min_x() > a.max_x() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if a.wire == b.wire || a.max_y() < b.min_y() || a.min_y() > b.max_y() {
|
let same_wire_neighbours = a.wire == b.wire
|
||||||
|
&& segments_share_endpoint(a.a, a.b, b.a, b.b);
|
||||||
|
|
||||||
|
if same_wire_neighbours
|
||||||
|
|| a.max_y() < b.min_y()
|
||||||
|
|| a.min_y() > b.max_y()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if wires.source_wire(a.wire).zip(wires.source_wire(b.wire))
|
if wires.source_wire(a.wire).zip(wires.source_wire(b.wire))
|
||||||
@@ -1801,11 +1854,23 @@ impl Snapper {
|
|||||||
world_to_screen(pt, view_rot, eye, bounds),
|
world_to_screen(pt, view_rot, eye, bounds),
|
||||||
cursor_screen,
|
cursor_screen,
|
||||||
) <= f32::EPSILON;
|
) <= f32::EPSILON;
|
||||||
|
let primary = wires
|
||||||
|
.source_wire(a.wire)
|
||||||
|
.and_then(wire_source);
|
||||||
|
|
||||||
|
let secondary = if a.wire == b.wire {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
wires
|
||||||
|
.source_wire(b.wire)
|
||||||
|
.and_then(wire_source)
|
||||||
|
};
|
||||||
|
|
||||||
try_pt(
|
try_pt(
|
||||||
pt,
|
pt,
|
||||||
SnapType::Intersection,
|
SnapType::Intersection,
|
||||||
wires.source_wire(a.wire).and_then(wire_source),
|
primary,
|
||||||
wires.source_wire(b.wire).and_then(wire_source),
|
secondary,
|
||||||
);
|
);
|
||||||
if exact_cursor {
|
if exact_cursor {
|
||||||
break 'intersection_sweep;
|
break 'intersection_sweep;
|
||||||
@@ -3099,6 +3164,24 @@ pub(crate) fn exact_curve_intersections(
|
|||||||
Some(points)
|
Some(points)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Two segments of the same polyline that merely meet at a shared endpoint
|
||||||
|
/// are neighbours, not a self-intersection.
|
||||||
|
///
|
||||||
|
/// Non-adjacent segments are allowed to intersect even when they belong to
|
||||||
|
/// the same WireModel.
|
||||||
|
fn segments_share_endpoint(
|
||||||
|
a0: DVec3,
|
||||||
|
a1: DVec3,
|
||||||
|
b0: DVec3,
|
||||||
|
b1: DVec3,
|
||||||
|
) -> bool {
|
||||||
|
const EPS2: f64 = 1.0e-18;
|
||||||
|
|
||||||
|
a0.distance_squared(b0) <= EPS2
|
||||||
|
|| a0.distance_squared(b1) <= EPS2
|
||||||
|
|| a1.distance_squared(b0) <= EPS2
|
||||||
|
|| a1.distance_squared(b1) <= EPS2
|
||||||
|
}
|
||||||
/// XY-plane segment-segment intersection. Returns `None` if parallel or outside.
|
/// XY-plane segment-segment intersection. Returns `None` if parallel or outside.
|
||||||
/// True 3D intersection of two segments: the point where their plan (XY)
|
/// True 3D intersection of two segments: the point where their plan (XY)
|
||||||
/// projections cross **and** both segments are at the same height there. Returns
|
/// projections cross **and** both segments are at the same height there. Returns
|
||||||
|
|||||||
Reference in new issue
Block a user