fix(snap): f64-precise computed snaps + UTM-tolerant cull + f64 previews

Computed snaps (midpoint/nearest/perpendicular/intersection/extension/
apparent/grid) reconstructed the snap point from f32 `wire.points`
(absolute high half), so at UTM-scale they landed ~0.5 m off the geometry
while endpoint/center (sourced from f64 key_vertices/snap_pts) stayed
correct. Now they rebuild each vertex from points + points_low (the
double-single low residual) and run the geometry in f64:
- snap() cursor + geometry helpers (nearest_on_segment, perp_foot,
  seg_intersect_xy, extension_snap) are DVec3; wp_f64() reconstructs each
  vertex; midpoint uses f64 key_vertices directly (was downcast to f32).
- snap callers pass the precise DVec3 cursor.

Snap capture at deep zoom: the wire-AABB cull compared a sub-millimetre
snap radius against f32 bounds quantized by ~1 ulp (~0.7 m at 5.7e6),
wrongly excluding wires under the cursor. Pad the cull by the bound's own
f32 quantization so it never rejects an in-range wire.

Rubber-band previews (line/circle/arc/ellipse/rect/polygon) built f32
absolute points with an empty points_low, so the wire pass (relative-to-
eye) drew them ~0.5 m off the cursor at UTM. Add WireModel::solid_f64,
which splits f64 points into the double-single high/low pair; previews now
track the cursor exactly.

Known: snapping at extreme zoom on large drawings spikes CPU (per-move
snap cost) — deferred.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-24 04:26:05 +03:00
1 parent 4f7a98a2ad
commit bca8958025
8 files changed
+166 -158

No files matched your search

+28
View File
@@ -98,6 +98,34 @@ impl WireModel {
f32::INFINITY,
];
/// Double-single split: `high + low ≈ v` to ~f64 precision in two f32s.
/// Matches the renderer's relative-to-eye reconstruction.
#[inline]
pub fn split_ds(v: f64) -> (f32, f32) {
let high = v as f32;
(high, (v - high as f64) as f32)
}
/// Create a solid preview wire from f64 points, filling the double-single
/// `points_low` buffer so the line stays precise at UTM-scale coordinates.
/// Rubber-band previews built straight from f32 absolute points jitter
/// ~0.5 m at UTM because the wire pass is relative-to-eye and expects the
/// low residual; this keeps the preview glued to the cursor.
pub fn solid_f64(name: String, points: Vec<[f64; 3]>, color: [f32; 4], selected: bool) -> Self {
let mut hi = Vec::with_capacity(points.len());
let mut lo = Vec::with_capacity(points.len());
for [x, y, z] in points {
let (hx, lx) = Self::split_ds(x);
let (hy, ly) = Self::split_ds(y);
let (hz, lz) = Self::split_ds(z);
hi.push([hx, hy, hz]);
lo.push([lx, ly, lz]);
}
let mut w = Self::solid(name, hi, color, selected);
w.points_low = lo;
w
}
/// Create a solid wire (no dash pattern, 1px weight).
pub fn solid(name: String, points: Vec<[f32; 3]>, color: [f32; 4], selected: bool) -> Self {
Self {