fix(snap): correct active intersection snapping
This commit is contained in:
parent
6a39b82a68
commit
f57c258ada
3 changed files with 118 additions and 232 deletions
104
src/snap.rs
104
src/snap.rs
|
|
@ -865,7 +865,7 @@ impl Snapper {
|
|||
// rotated or isometric grid the user sees.
|
||||
grid_origin: Vec3,
|
||||
grid_axes: (Vec3, Vec3, Vec3),
|
||||
construction_cursor: Option<glam::DVec3>,
|
||||
construction_ray: Option<(glam::DVec3, glam::DVec3)>,
|
||||
) -> Option<SnapResult> {
|
||||
// Object-snap selection is priority-then-distance, NOT nearest-wins.
|
||||
// "Continuous" snaps (Nearest, Perpendicular, …) sit on the geometry
|
||||
|
|
@ -1217,42 +1217,45 @@ impl Snapper {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Intersection with active construction segment (#704) ──────────
|
||||
//
|
||||
// The existing Intersection pass below only compares document
|
||||
// entities with each other. Also compare the rubber-band currently
|
||||
// being drawn against existing geometry.
|
||||
if self.is_on(SnapType::Intersection) {
|
||||
if let (Some(from), Some(to)) = (self.from_point, construction_cursor) {
|
||||
let from = from.as_dvec3();
|
||||
if (to - from).length_squared() > 1e-18 {
|
||||
// Active/deferred Intersection uses the actual nearby wires directly.
|
||||
// The interaction candidate set may contain wires while its optional
|
||||
// segment index is empty.
|
||||
for wire in wires.iter() {
|
||||
if !wire_in_range(wire) {
|
||||
continue;
|
||||
let mut try_ray_intersections = |origin: glam::DVec3, through: glam::DVec3| {
|
||||
if (through - origin).length_squared() <= 1e-18 {
|
||||
return;
|
||||
}
|
||||
if let Some(segments) = &local_segments {
|
||||
for segment in segments {
|
||||
if let Some(point) =
|
||||
ray_segment_intersect_3d(origin, through, segment.a, segment.b)
|
||||
{
|
||||
if (point - origin).length_squared() > 1e-18 {
|
||||
try_pt(point, SnapType::Intersection);
|
||||
}
|
||||
|
||||
for index in 0..wire.points.len().saturating_sub(1) {
|
||||
let b0 = wp_f64(wire, index);
|
||||
let b1 = wp_f64(wire, index + 1);
|
||||
|
||||
if let Some(pt) = ray_segment_intersect_3d(from, to, b0, b1) {
|
||||
if (pt - from).length_squared() > 1e-18 {
|
||||
try_pt(pt, SnapType::Intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for wire in wires.iter().filter(|wire| wire_in_range(wire)) {
|
||||
for index in 0..wire.points.len().saturating_sub(1) {
|
||||
if let Some(point) = ray_segment_intersect_3d(
|
||||
origin,
|
||||
through,
|
||||
wp_f64(wire, index),
|
||||
wp_f64(wire, index + 1),
|
||||
) {
|
||||
if (point - origin).length_squared() > 1e-18 {
|
||||
try_pt(point, SnapType::Intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.is_on(SnapType::Intersection) {
|
||||
if let Some((origin, through)) = construction_ray {
|
||||
try_ray_intersections(origin, through);
|
||||
}
|
||||
}
|
||||
// ── Intersection with active perpendicular OTRACK ray (#704) ──────
|
||||
//
|
||||
// A perpendicular tracking ray is not part of the command's ordinary
|
||||
// ORTHO/POLAR construction cursor. When the cursor is actually tracking
|
||||
// one of those rays, allow Intersection to stop it on nearby geometry.
|
||||
|
||||
// Check engaged perpendicular tracking rays against nearby geometry.
|
||||
if self.is_on(SnapType::Intersection)
|
||||
&& self.otrack_enabled
|
||||
&& self.is_on(SnapType::Perpendicular)
|
||||
|
|
@ -1263,50 +1266,23 @@ impl Snapper {
|
|||
};
|
||||
|
||||
for &dir in perp_dirs {
|
||||
// Project the cursor onto this perpendicular tracking line.
|
||||
let rel = cursor_world - origin;
|
||||
let t = rel.x * dir.x + rel.y * dir.y;
|
||||
|
||||
let aligned = glam::DVec3::new(
|
||||
origin.x + dir.x * t,
|
||||
origin.y + dir.y * t,
|
||||
origin.z,
|
||||
);
|
||||
|
||||
// Only consider the ray when the cursor is genuinely tracking it.
|
||||
// This prevents Intersection from behaving like Nearest.
|
||||
let aligned_screen = world_to_screen(aligned, view_rot, eye, bounds);
|
||||
if dist2(aligned_screen, cursor_screen) > radius2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perpendicular tracking is an infinite line, but once the cursor
|
||||
// chooses a side we only want intersections forward on that side.
|
||||
let dir_to_cursor = if t >= 0.0 { dir } else { -dir };
|
||||
let through = origin + dir_to_cursor;
|
||||
|
||||
for wire in wires.iter() {
|
||||
if !wire_in_range(wire) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for index in 0..wire.points.len().saturating_sub(1) {
|
||||
let b0 = wp_f64(wire, index);
|
||||
let b1 = wp_f64(wire, index + 1);
|
||||
|
||||
if let Some(pt) =
|
||||
ray_segment_intersect_3d(origin, through, b0, b1)
|
||||
{
|
||||
// Do not re-offer the tracking origin itself.
|
||||
if (pt - origin).length_squared() > 1e-18 {
|
||||
try_pt(pt, SnapType::Intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try_ray_intersections(origin, origin + dir_to_cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(try_ray_intersections);
|
||||
|
||||
// ── Intersection — segment-segment intersections (pairwise, gated) ──
|
||||
if self.is_on(SnapType::Intersection)
|
||||
|
|
@ -2103,13 +2079,7 @@ fn perp_foot(query: glam::DVec3, p0: glam::DVec3, p1: glam::DVec3) -> Option<gla
|
|||
))
|
||||
}
|
||||
|
||||
/// Intersection between a forward construction ray and an existing segment.
|
||||
///
|
||||
/// The ray starts at `ray_origin` and points toward `ray_through`, but extends
|
||||
/// indefinitely forward. The existing geometry remains a finite segment.
|
||||
///
|
||||
/// Like `seg_intersect_3d`, this is a true 3D intersection: the XY projections
|
||||
/// must cross and both geometries must have the same Z at the crossing.
|
||||
/// True 3D intersection of a forward ray and a finite segment.
|
||||
fn ray_segment_intersect_3d(
|
||||
ray_origin: glam::DVec3,
|
||||
ray_through: glam::DVec3,
|
||||
|
|
@ -2132,8 +2102,6 @@ fn ray_segment_intersect_3d(
|
|||
let t = (ex * d2y - ey * d2x) / cross;
|
||||
let s = (ex * d1y - ey * d1x) / cross;
|
||||
|
||||
// `t >= 0`: only forward along the construction ray.
|
||||
// `s` must remain inside the existing finite segment.
|
||||
if t < 0.0 || s < 0.0 || s > 1.0 {
|
||||
return None;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue