feat(overlay): relative-to-eye CPU projection for grips and grid (P5)

Route the on-screen chrome that projects absolute world coordinates
through the f64 relative-to-eye helpers so it stays glued/jitter-free at
UTM scale after the world_offset removal: grips (grips_to_screen) and
grip hit-testing (find_hit_grip) now project from the f64 GripDef.world
via Camera::project_f64; the model grid and its UCS axes carry the camera
eye (DVec3) + rotation-only matrix and a f64 origin, projecting each line
relative to the eye. Snap-marker / OST-point / compute_grid_step still use
the f32 path (single points / spacing — negligible) and can follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-23 22:57:31 +03:00
commit 52882749eb
4 changed files with 64 additions and 57 deletions

View file

@ -1,7 +1,7 @@
//! OpenCADStudio-style grip editing.
use acadrust::Handle;
use glam::{Mat4, Vec3};
use glam::Vec3;
use iced::{Point, Rectangle};
use crate::scene::model::object::{GripDef, GripShape};
@ -34,17 +34,19 @@ pub struct GripEdit {
/// Returns `(grip_id, screen_pos, is_midpoint, shape)` for each grip.
pub fn grips_to_screen(
grips: &[GripDef],
view_proj: Mat4,
camera: &crate::scene::view::camera::Camera,
bounds: Rectangle,
) -> Vec<(usize, Point, bool, GripShape, Option<[f32; 2]>)> {
grips
.iter()
.map(|g| {
let ndc = view_proj.project_point3(g.world.as_vec3());
let screen = Point::new(
bounds.x + (ndc.x + 1.0) * 0.5 * bounds.width,
bounds.y + (1.0 - ndc.y) * 0.5 * bounds.height,
);
// Project from the f64 grip position via the relative-to-eye path so
// the grip stays glued to the wire at UTM-scale coordinates (an
// `as_vec3` cast first would quantize it ~0.5 m off at high zoom).
let screen = match camera.project_f64(g.world, bounds) {
Some(p) => Point::new(bounds.x + p.x, bounds.y + p.y),
None => Point::new(f32::NAN, f32::NAN),
};
(g.id, screen, g.is_midpoint, g.shape, g.dir)
})
.collect()
@ -106,18 +108,17 @@ pub fn find_hit_grip_paper(
pub fn find_hit_grip(
cursor: Point,
grips: &[GripDef],
view_proj: Mat4,
camera: &crate::scene::view::camera::Camera,
bounds: Rectangle,
) -> Option<(usize, bool, Vec3)> {
let mut best_dist = GRIP_THRESHOLD_PX;
let mut best: Option<(usize, bool, Vec3)> = None;
for g in grips {
let ndc = view_proj.project_point3(g.world.as_vec3());
let screen = Point::new(
(ndc.x + 1.0) * 0.5 * bounds.width,
(1.0 - ndc.y) * 0.5 * bounds.height,
);
let Some(screen) = camera.project_f64(g.world, bounds) else {
continue;
};
let screen = Point::new(screen.x, screen.y);
let dx = screen.x - cursor.x;
let dy = screen.y - cursor.y;
let d = (dx * dx + dy * dy).sqrt();