Snap: a computed tangent point outranks the rim-hover Center (#1768)

With TAN and CEN on, hovering a circle offered its centre everywhere on
the rim, so the tangent point from the base was never reachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-14 12:27:58 +03:00
1 parent d5de798f8a
commit 93d92b2a51
1 file changed
+68 -5
+68 -5
View File
@@ -2205,7 +2205,9 @@ impl Snapper {
let mut eval_tangent = |wire: &WireModel| {
let mut line_cursor = 0usize;
for tg in &wire.tangent_geoms {
let (world_pt, d2) = match tg {
// A tangent point computed from the rubber-band base.
let mut exact = false;
let (world_pt, mut d2) = match tg {
TangentGeom::Line { p1, p2 } => {
let (lp1, lp2) = tangent_line_endpoints(wire, *p1, *p2, &mut line_cursor);
let sp0 = world_to_screen(lp1, view_rot, eye, bounds);
@@ -2233,6 +2235,7 @@ impl Snapper {
{
let s0 = world_to_screen(t0.as_dvec3(), view_rot, eye, bounds);
let s1 = world_to_screen(t1.as_dvec3(), view_rot, eye, bounds);
exact = true;
if dist2(s0, cursor_screen) <= dist2(s1, cursor_screen) {
t0
} else {
@@ -2299,6 +2302,7 @@ impl Snapper {
})
.unwrap_or_else(|| DVec3::from_array(*center))
};
exact = candidate.is_some();
let world = candidate.unwrap_or_else(fallback);
let edge_d2 = wire
.points
@@ -2348,6 +2352,7 @@ impl Snapper {
})
.unwrap_or(cv)
};
exact = candidate.is_some();
let world = candidate.unwrap_or_else(fallback);
let edge_d2 = wire
.points
@@ -2427,6 +2432,7 @@ impl Snapper {
}
};
exact = candidate.is_some();
let world = candidate.unwrap_or(fallback);
let edge_d2 = if wire.points.len() >= 2 {
wire.points
@@ -2454,11 +2460,17 @@ impl Snapper {
}
}
};
let (tier, sub) = (
snap_tier(SnapType::Tangent),
snap_priority(SnapType::Tangent),
);
let screen_pt = world_to_screen(world_pt, view_rot, eye, bounds);
// Near the computed tangent point it is a point snap like
// the others, so the rim-hover Center no longer masks it.
let point_d2 = dist2(screen_pt, cursor_screen);
let tier = if exact && point_d2 < radius2 {
d2 = point_d2;
0
} else {
snap_tier(SnapType::Tangent)
};
let sub = snap_priority(SnapType::Tangent);
if d2 < radius2
&& in_bounds(screen_pt)
&& snap_better(tier, d2, sub, (best_rank, best_d2, best_sub))
@@ -5492,6 +5504,57 @@ mod ext_tests {
assert!(empty_pts.is_empty());
}
#[test]
fn tangent_point_beats_rim_center() {
// TAN and CEN on, the cursor on the circle at the tangent point seen
// from the base: Tangent wins, not the rim-hover Center (#1768).
let mut snapper = Snapper::default();
snapper.snap_enabled = true;
snapper.enabled = [SnapType::Tangent, SnapType::Center].into_iter().collect();
snapper.from_point = Some(DVec3::new(100.0, 0.0, 0.0));
let points = (0..=64)
.map(|i| {
let a = i as f32 / 64.0 * std::f32::consts::TAU;
[50.0 * a.cos(), 50.0 * a.sin(), 0.0]
})
.collect();
let circle = WireModel {
points,
aabb: [-50.0, -50.0, 50.0, 50.0],
snap_pts: vec![(DVec3::ZERO, SnapHint::Center)],
tangent_geoms: vec![TangentGeom::Circle {
center: [0.0, 0.0, 0.0],
radius: 50.0,
}],
..Default::default()
};
let wires = vec![circle];
let view_rot = Mat4::from_scale(Vec3::new(0.005, 0.005, 1.0));
let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: 1000.0,
height: 1000.0,
};
let tangent = DVec3::new(25.0, 50.0 * 0.75f64.sqrt(), 0.0);
let cursor = world_to_screen(tangent, view_rot, DVec3::ZERO, bounds);
let snap = snapper
.snap(
tangent,
cursor,
wires.as_slice(),
view_rot,
DVec3::ZERO,
bounds,
Vec3::ZERO,
(Vec3::X, Vec3::Y, Vec3::Z),
None,
)
.expect("a snap on the rim");
assert_eq!(snap.snap_type, SnapType::Tangent);
assert!((snap.world - tangent).length() < 1e-3);
}
#[test]
fn test_ellipse_arc_tangent_snap_ignores_break() {
use std::f64::consts::PI;