fix(osnap): snap to arc/circle centre when hovering the curve (#152)

The Center object snap was a pre-baked point gated purely on the
cursor's screen distance to the centre point. A circle's centre sits
inside it and an arc's centre is usually off in empty space well away
from the curve, so hovering the curve — the natural gesture — never
landed within the snap radius and no centre marker appeared.

Add a curve-proximity pass: when Center is enabled and the cursor is
near a circle/arc/ellipse curve, offer that curve's centre, ranked by
the cursor's distance to the curve. It runs after the `try_pt` closure's
borrow ends so it can update the candidate state directly, mirroring the
Tangent pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-25 17:01:23 +03:00
1 parent 6fb5c89a7b
commit ce2365e8f9
1 file changed
+49
+49
View File
@@ -892,6 +892,55 @@ impl Snapper {
}
}
// ── Center via curve proximity ─────────────────────────────────────
// A circle/arc/ellipse's centre is offset from its curve — for an arc
// it usually sits in empty space well off the geometry — so gating the
// Center snap purely on the cursor's distance to the centre *point*
// (the pre-baked pass above) means hovering the curve, the natural
// gesture, never offers it. Mirror running-osnap behaviour: when the
// cursor is near such a curve, offer its centre, ranked by how close
// the cursor is to the curve. Runs here, after `try_pt`'s borrow ends,
// so it can update the candidate state directly. (#152)
if self.is_on(SnapType::Center) {
for wire in wires {
if !wire_in_range(wire) {
continue;
}
// Only tessellated curves carry a pre-baked Center hint; reuse
// it as the snap target. Lines / polylines have none → skip.
let Some(center) = wire
.snap_pts
.iter()
.find(|(_, h)| matches!(h, SnapHint::Center))
.map(|&(c, _)| c)
else {
continue;
};
// Nearest screen distance from the cursor to the curve itself.
let mut curve_d2 = f32::INFINITY;
for i in 0..wire.points.len().saturating_sub(1) {
let p = nearest_on_segment(cursor_world, wp_f64(wire, i), wp_f64(wire, i + 1));
let sp = world_to_screen(p, view_rot, eye, bounds);
curve_d2 = curve_d2.min(dist2(sp, cursor_screen));
}
let screen = world_to_screen(center, view_rot, eye, bounds);
let rank = snap_priority(SnapType::Center);
if curve_d2 < radius2
&& in_bounds(screen)
&& (rank < best_rank || (rank == best_rank && curve_d2 < best_d2))
{
best_rank = rank;
best_d2 = curve_d2;
best = Some(SnapResult {
world: center,
screen,
snap_type: SnapType::Center,
tangent_obj: None,
});
}
}
}
best
}
}