Merge PR #1567 from gianlucafiore/fix/polyline-self-intersections: polyline self-intersection snapping and trimming

This commit is contained in:
Hakan Seven committed 2026-10-02 10:04:11 +03:00
commit 3bf8906ed3
2 files changed
+322 -46

No files matched your search

+86 -3
View File
@@ -1755,6 +1755,53 @@ impl Snapper {
if self.is_on(SnapType::Intersection)
&& (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 {
let local_wires: Vec<_> = wires.iter().filter(|wire| wire_in_range(wire)).collect();
let mut resolved_pairs = rustc_hash::FxHashSet::default();
@@ -1789,7 +1836,13 @@ impl Snapper {
if b.min_x() > a.max_x() {
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;
}
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),
cursor_screen,
) <= 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(
pt,
SnapType::Intersection,
wires.source_wire(a.wire).and_then(wire_source),
wires.source_wire(b.wire).and_then(wire_source),
primary,
secondary,
);
if exact_cursor {
break 'intersection_sweep;
@@ -3151,6 +3216,24 @@ pub(crate) fn exact_curve_intersections(
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.
/// True 3D intersection of two segments: the point where their plan (XY)
/// projections cross **and** both segments are at the same height there. Returns