diff --git a/src/app/update.rs b/src/app/update.rs index 8b62ff302..d6759e3a9 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -3265,8 +3265,8 @@ impl OpenCADStudio { let aspect = if vh > 0.0 { vw / vh } else { 1.0 }; let half_h = cam.ortho_size(); let half_w = half_h * aspect; - let tx = cam.target.x; - let ty = cam.target.y; + let tx = cam.target.x as f32; + let ty = cam.target.y as f32; drop(cam); find_hit_grip_paper( p, @@ -8259,8 +8259,8 @@ impl OpenCADStudio { let aspect = if vh > 0.0 { vw / vh } else { 1.0 }; let half_h = cam.ortho_size(); let half_w = half_h * aspect; - let tx = cam.target.x; - let ty = cam.target.y; + let tx = cam.target.x as f32; + let ty = cam.target.y as f32; drop(cam); find_hit_grip_paper( p, diff --git a/src/app/view.rs b/src/app/view.rs index 5f21d3a90..a53cbc00f 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -93,8 +93,8 @@ impl OpenCADStudio { let aspect = if vh > 0.0 { vw / vh } else { 1.0 }; let half_h = cam.ortho_size(); let half_w = half_h * aspect; - let tx = cam.target.x; - let ty = cam.target.y; + let tx = cam.target.x as f32; + let ty = cam.target.y as f32; drop(cam); grips_to_screen_paper(&tab.selected_grips, tx, ty, half_w, half_h, bounds) } else { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 381ea617b..d27743d76 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -676,7 +676,7 @@ fn view_cull_aabb(cam: &Camera, aspect: f32, margin: f32) -> Option<[f32; 4]> { let w = h * aspect.max(0.01); let right = cam.rotation * glam::Vec3::X; let up = cam.rotation * glam::Vec3::Y; - let c = cam.target; + let c = cam.target.as_vec3(); let (mut min_x, mut min_y) = (f32::INFINITY, f32::INFINITY); let (mut max_x, mut max_y) = (f32::NEG_INFINITY, f32::NEG_INFINITY); for (sw, sh) in [(-w, -h), (w, -h), (-w, h), (w, h)] { @@ -1012,8 +1012,8 @@ impl Scene { // `cam.target` is in the same local f32 space as emitted wire points // (fit_to_bounds populates it from local wire coords). No further // `world_offset` subtraction is needed. - let cx = cam.target.x; - let cy = cam.target.y; + let cx = cam.target.x as f32; + let cy = cam.target.y as f32; Some([ cx - w * margin, cy - h * margin, @@ -5948,11 +5948,7 @@ impl Scene { use glam::Vec3; let cam = &mut *self.camera.borrow_mut(); // view.target is the look-at point; view.direction is eye→target direction. - cam.target = Vec3::new( - view.target.x as f32, - view.target.y as f32, - view.target.z as f32, - ); + cam.target = glam::DVec3::new(view.target.x, view.target.y, view.target.z); // direction in acadrust = from-target-to-eye (same as AutoCAD convention). let eye_dir = Vec3::new( view.direction.x as f32, @@ -6162,7 +6158,7 @@ impl Scene { let fov_y = 45.0_f32.to_radians(); let distance = ((view_height as f32 / 2.0) / (fov_y * 0.5).tan()).max(0.001); Some(Camera { - target, + target: target.as_dvec3(), rotation, distance, fov_y, @@ -7358,8 +7354,8 @@ impl Scene { let aspect = canvas_w / canvas_h; let half_h = cam.ortho_size(); let half_w = half_h * aspect; - let tx = cam.target.x; - let ty = cam.target.y; + let tx = cam.target.x as f32; + let ty = cam.target.y as f32; drop(cam); // Top-down ortho mapping matching the GPU sheet viewport's camera. diff --git a/src/scene/view/camera.rs b/src/scene/view/camera.rs index 435c7590c..c9cedae58 100644 --- a/src/scene/view/camera.rs +++ b/src/scene/view/camera.rs @@ -11,7 +11,7 @@ // // Coordinate convention: Z-up world space (same as the rest of OpenCADStudio). -use glam::{vec3, Mat4, Quat, Vec3}; +use glam::{vec3, DVec3, Mat4, Quat, Vec3}; use iced::{Point, Rectangle}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -22,8 +22,11 @@ pub enum Projection { #[derive(Clone)] pub struct Camera { - /// World-space pivot point the camera orbits around. - pub target: Vec3, + /// World-space pivot point the camera orbits around. Kept in f64 so a far + /// pan (large offset-relative coordinate) doesn't lose precision in the + /// pivot itself — the eventual relative-to-eye render path needs an exact + /// eye, which derives from this target. + pub target: DVec3, /// Arcball rotation: maps canonical pose to current orientation. pub rotation: Quat, /// Distance from eye to target. @@ -45,7 +48,7 @@ impl Default for Camera { let yaw = 0.0_f32; let pitch = std::f32::consts::FRAC_PI_2; Self { - target: Vec3::ZERO, + target: DVec3::ZERO, rotation: yaw_pitch_to_quat(yaw, pitch, 0.0), distance: 60.36, fov_y: 45.0_f32.to_radians(), @@ -70,7 +73,16 @@ impl Camera { // The canonical eye direction is +Z (looking at origin from above). // The rotation maps that canonical pose to the current orientation. let eye_dir = self.rotation * Vec3::Z; - self.target + eye_dir * self.distance + self.target.as_vec3() + eye_dir * self.distance + } + + /// 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 + /// math that operates at human scale. + #[allow(dead_code)] // consumed by the relative-to-eye uniform (next phase) + pub fn eye_f64(&self) -> DVec3 { + let eye_dir = (self.rotation * Vec3::Z).as_dvec3(); + self.target + eye_dir * self.distance as f64 } /// Half-height of the orthographic frustum in world units. @@ -89,7 +101,7 @@ impl Camera { // in the current camera frame. let up_dir = self.rotation * Vec3::Y; - let view = Mat4::look_at_rh(self.eye(), self.target, up_dir); + let view = Mat4::look_at_rh(self.eye(), self.target.as_vec3(), up_dir); let proj = match self.projection { Projection::Perspective => Mat4::perspective_rh(self.fov_y, aspect, near, far), Projection::Orthographic => { @@ -126,7 +138,7 @@ impl Camera { } Projection::Orthographic => { let origin = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0)); - let forward = (self.target - self.eye()).normalize(); + let forward = (self.target.as_vec3() - self.eye()).normalize(); (origin, forward) } }; @@ -149,24 +161,26 @@ impl Camera { match self.projection { Projection::Perspective => { + let target = self.target.as_vec3(); let near_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0)); let far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0)); let dir = (far_pt - near_pt).normalize(); - let forward = (self.target - self.eye()).normalize(); + let forward = (target - self.eye()).normalize(); let denom = dir.dot(forward); if denom.abs() < 1e-6 { - return self.target; + return target; } - let t = (self.target - near_pt).dot(forward) / denom; + let t = (target - near_pt).dot(forward) / denom; if t < 0.0 { - return self.target; + return target; } near_pt + dir * t } Projection::Orthographic => { + let target = self.target.as_vec3(); let ray_origin = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0)); - let forward = (self.target - self.eye()).normalize(); - let t = (self.target - ray_origin).dot(forward) / forward.dot(forward); + let forward = (target - self.eye()).normalize(); + let t = (target - ray_origin).dot(forward) / forward.dot(forward); ray_origin + forward * t } } @@ -243,7 +257,7 @@ impl Camera { let before = self.pick_on_target_plane(screen, bounds); self.zoom(delta); let after = self.pick_on_target_plane(screen, bounds); - self.target += before - after; + self.target += (before - after).as_dvec3(); } /// Pan so the world point under the cursor tracks it: screen pixels are @@ -258,12 +272,12 @@ impl Camera { }; let cam_right = self.rotation * Vec3::X; let cam_up = self.rotation * Vec3::Y; - self.target -= cam_right * delta_x * wpp; - self.target += cam_up * delta_y * wpp; + self.target -= (cam_right * delta_x * wpp).as_dvec3(); + self.target += (cam_up * delta_y * wpp).as_dvec3(); } pub fn fit_to_bounds(&mut self, min: Vec3, max: Vec3) { - self.target = (min + max) * 0.5; + self.target = ((min + max) * 0.5).as_dvec3(); let size = (max - min).length(); self.distance = size * 1.5; }