refactor: finish OST f64 integration; drop vestigial _f64 suffixes

- Object-snap-tracking points now project through the relative-to-eye path
  (view_proj_rte + eye) instead of the full view-projection, so they stay
  precise at UTM-scale coordinates — the last consumer of the legacy full
  view_proj outside the CPU frustum-cull / scissor path.
- Rename Camera::project_f64 → project and unproject_on_plane_f64 →
  unproject_on_plane: the f32 twins are gone, so the disambiguating suffix
  is vestigial. eye_f64 keeps its name — the f32 eye() still builds the
  view matrices.
- Remove stale #[allow(dead_code)] from eye_f64 / project / unproject_on_plane
  (all live now).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-24 18:41:25 +03:00
commit 5b1a5a71a0
3 changed files with 15 additions and 13 deletions

View file

@ -221,14 +221,19 @@ impl OpenCADStudio {
None None
}; };
// OST tracking points → screen positions. // OST tracking points → screen positions, projected relative-to-eye
// so they stay precise at UTM-scale coordinates (the full
// view-projection cancels catastrophically in f32).
let ost_points: Vec<overlay::OstTrackPoint> = if self.snapper.otrack_enabled { let ost_points: Vec<overlay::OstTrackPoint> = if self.snapper.otrack_enabled {
let vp_mat = tab.scene.camera.borrow().view_proj(vp_bounds); let (view_rot, eye) = {
let cam = tab.scene.camera.borrow();
(cam.view_proj_rte(vp_bounds), cam.eye_f64())
};
self.snapper self.snapper
.tracking_points .tracking_points
.iter() .iter()
.map(|&wp| { .map(|&wp| {
let ndc = vp_mat.project_point3(wp); let ndc = view_rot.project_point3((wp.as_dvec3() - eye).as_vec3());
overlay::OstTrackPoint { overlay::OstTrackPoint {
screen: iced::Point::new( screen: iced::Point::new(
(ndc.x + 1.0) * 0.5 * vp_bounds.width, (ndc.x + 1.0) * 0.5 * vp_bounds.width,

View file

@ -43,7 +43,7 @@ pub fn grips_to_screen(
// Project from the f64 grip position via the relative-to-eye path so // 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 // 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). // `as_vec3` cast first would quantize it ~0.5 m off at high zoom).
let screen = match camera.project_f64(g.world, bounds) { let screen = match camera.project(g.world, bounds) {
Some(p) => Point::new(bounds.x + p.x, bounds.y + p.y), Some(p) => Point::new(bounds.x + p.x, bounds.y + p.y),
None => Point::new(f32::NAN, f32::NAN), None => Point::new(f32::NAN, f32::NAN),
}; };
@ -115,7 +115,7 @@ pub fn find_hit_grip(
let mut best: Option<(usize, bool, DVec3)> = None; let mut best: Option<(usize, bool, DVec3)> = None;
for g in grips { for g in grips {
let Some(screen) = camera.project_f64(g.world, bounds) else { let Some(screen) = camera.project(g.world, bounds) else {
continue; continue;
}; };
let screen = Point::new(screen.x, screen.y); let screen = Point::new(screen.x, screen.y);
@ -131,7 +131,7 @@ pub fn find_hit_grip(
} }
/// Project an f64 world point with an explicit relative-to-eye `(view_rot, /// Project an f64 world point with an explicit relative-to-eye `(view_rot,
/// eye)` pair — the camera-less form of `Camera::project_f64`. Used by the /// eye)` pair — the camera-less form of `Camera::project`. Used by the
/// in-viewport editing path, which supplies a *composed* model→screen view /// in-viewport editing path, which supplies a *composed* model→screen view
/// (see `Scene::composed_viewport_view`) instead of a real camera. /// (see `Scene::composed_viewport_view`) instead of a real camera.
fn project_rte(world: DVec3, view_rot: Mat4, eye: DVec3, bounds: Rectangle) -> Option<Vec2> { fn project_rte(world: DVec3, view_rot: Mat4, eye: DVec3, bounds: Rectangle) -> Option<Vec2> {

View file

@ -79,7 +79,6 @@ impl Camera {
/// Eye position in full f64 precision (offset-relative world space). Used /// Eye position in full f64 precision (offset-relative world space). Used
/// by the relative-to-eye render path; the f32 [`eye`] stays for ray/pick /// by the relative-to-eye render path; the f32 [`eye`] stays for ray/pick
/// math that operates at human scale. /// math that operates at human scale.
#[allow(dead_code)] // consumed by the relative-to-eye uniform (next phase)
pub fn eye_f64(&self) -> DVec3 { pub fn eye_f64(&self) -> DVec3 {
let eye_dir = (self.rotation * Vec3::Z).as_dvec3(); let eye_dir = (self.rotation * Vec3::Z).as_dvec3();
self.target + eye_dir * self.distance as f64 self.target + eye_dir * self.distance as f64
@ -160,8 +159,7 @@ impl Camera {
/// the rotation-only projection, so it stays exact at large absolute /// the rotation-only projection, so it stays exact at large absolute
/// coordinates — the CPU equivalent of the GPU's relative-to-eye path. /// coordinates — the CPU equivalent of the GPU's relative-to-eye path.
/// Returns `None` for points at/behind the eye plane (w ≈ 0). /// Returns `None` for points at/behind the eye plane (w ≈ 0).
#[allow(dead_code)] // consumed by the world_offset-removal CPU migration pub fn project(&self, p: glam::DVec3, bounds: Rectangle) -> Option<glam::Vec2> {
pub fn project_f64(&self, p: glam::DVec3, bounds: Rectangle) -> Option<glam::Vec2> {
let rel = (p - self.eye_f64()).as_vec3(); let rel = (p - self.eye_f64()).as_vec3();
let clip = self.view_proj_rte(bounds) * rel.extend(1.0); let clip = self.view_proj_rte(bounds) * rel.extend(1.0);
if clip.w.abs() < 1e-9 { if clip.w.abs() < 1e-9 {
@ -178,8 +176,7 @@ impl Camera {
/// is built in eye-relative space (precise), intersected with the plane /// is built in eye-relative space (precise), intersected with the plane
/// expressed relative to the eye, then shifted back by the f64 eye — so the /// expressed relative to the eye, then shifted back by the f64 eye — so the
/// returned world point keeps full precision at large absolute coordinates. /// returned world point keeps full precision at large absolute coordinates.
#[allow(dead_code)] // consumed by the world_offset-removal CPU migration pub fn unproject_on_plane(
pub fn unproject_on_plane_f64(
&self, &self,
screen: Point, screen: Point,
bounds: Rectangle, bounds: Rectangle,
@ -242,13 +239,13 @@ impl Camera {
plane_normal: Vec3, plane_normal: Vec3,
plane_point: glam::DVec3, plane_point: glam::DVec3,
) -> glam::DVec3 { ) -> glam::DVec3 {
self.unproject_on_plane_f64(screen, bounds, plane_normal, plane_point) self.unproject_on_plane(screen, bounds, plane_normal, plane_point)
} }
/// Project a screen point onto the plane through the orbit target. /// Project a screen point onto the plane through the orbit target.
pub fn pick_on_target_plane(&self, screen: Point, bounds: Rectangle) -> glam::DVec3 { pub fn pick_on_target_plane(&self, screen: Point, bounds: Rectangle) -> glam::DVec3 {
let forward = (self.target.as_vec3() - self.eye()).normalize_or(Vec3::NEG_Z); let forward = (self.target.as_vec3() - self.eye()).normalize_or(Vec3::NEG_Z);
self.unproject_on_plane_f64(screen, bounds, forward, self.target) self.unproject_on_plane(screen, bounds, forward, self.target)
} }