forked from 1b-agent/opencadstudio-upstream
refactor(camera): store orbit target in f64
Foundation for relative-to-eye rendering: the pivot the camera orbits must stay precise at large offset-relative coordinates so the derived eye (which the RTE path subtracts per vertex) is exact. `Camera::target` is now DVec3 and `eye_f64()` exposes the full-precision eye; `view_proj` still casts to f32 at the matrix, so behaviour is unchanged for now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
4100044c01
commit
091b8e1805
4 files changed
+44
-34
No files matched your search
+4
-4
@@ -3265,8 +3265,8 @@ impl OpenCADStudio {
|
|||||||
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
||||||
let half_h = cam.ortho_size();
|
let half_h = cam.ortho_size();
|
||||||
let half_w = half_h * aspect;
|
let half_w = half_h * aspect;
|
||||||
let tx = cam.target.x;
|
let tx = cam.target.x as f32;
|
||||||
let ty = cam.target.y;
|
let ty = cam.target.y as f32;
|
||||||
drop(cam);
|
drop(cam);
|
||||||
find_hit_grip_paper(
|
find_hit_grip_paper(
|
||||||
p,
|
p,
|
||||||
@@ -8259,8 +8259,8 @@ impl OpenCADStudio {
|
|||||||
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
||||||
let half_h = cam.ortho_size();
|
let half_h = cam.ortho_size();
|
||||||
let half_w = half_h * aspect;
|
let half_w = half_h * aspect;
|
||||||
let tx = cam.target.x;
|
let tx = cam.target.x as f32;
|
||||||
let ty = cam.target.y;
|
let ty = cam.target.y as f32;
|
||||||
drop(cam);
|
drop(cam);
|
||||||
find_hit_grip_paper(
|
find_hit_grip_paper(
|
||||||
p,
|
p,
|
||||||
|
|||||||
+2
-2
@@ -93,8 +93,8 @@ impl OpenCADStudio {
|
|||||||
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
let aspect = if vh > 0.0 { vw / vh } else { 1.0 };
|
||||||
let half_h = cam.ortho_size();
|
let half_h = cam.ortho_size();
|
||||||
let half_w = half_h * aspect;
|
let half_w = half_h * aspect;
|
||||||
let tx = cam.target.x;
|
let tx = cam.target.x as f32;
|
||||||
let ty = cam.target.y;
|
let ty = cam.target.y as f32;
|
||||||
drop(cam);
|
drop(cam);
|
||||||
grips_to_screen_paper(&tab.selected_grips, tx, ty, half_w, half_h, bounds)
|
grips_to_screen_paper(&tab.selected_grips, tx, ty, half_w, half_h, bounds)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-11
@@ -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 w = h * aspect.max(0.01);
|
||||||
let right = cam.rotation * glam::Vec3::X;
|
let right = cam.rotation * glam::Vec3::X;
|
||||||
let up = cam.rotation * glam::Vec3::Y;
|
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 min_x, mut min_y) = (f32::INFINITY, f32::INFINITY);
|
||||||
let (mut max_x, mut max_y) = (f32::NEG_INFINITY, f32::NEG_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)] {
|
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
|
// `cam.target` is in the same local f32 space as emitted wire points
|
||||||
// (fit_to_bounds populates it from local wire coords). No further
|
// (fit_to_bounds populates it from local wire coords). No further
|
||||||
// `world_offset` subtraction is needed.
|
// `world_offset` subtraction is needed.
|
||||||
let cx = cam.target.x;
|
let cx = cam.target.x as f32;
|
||||||
let cy = cam.target.y;
|
let cy = cam.target.y as f32;
|
||||||
Some([
|
Some([
|
||||||
cx - w * margin,
|
cx - w * margin,
|
||||||
cy - h * margin,
|
cy - h * margin,
|
||||||
@@ -5948,11 +5948,7 @@ impl Scene {
|
|||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
let cam = &mut *self.camera.borrow_mut();
|
let cam = &mut *self.camera.borrow_mut();
|
||||||
// view.target is the look-at point; view.direction is eye→target direction.
|
// view.target is the look-at point; view.direction is eye→target direction.
|
||||||
cam.target = Vec3::new(
|
cam.target = glam::DVec3::new(view.target.x, view.target.y, view.target.z);
|
||||||
view.target.x as f32,
|
|
||||||
view.target.y as f32,
|
|
||||||
view.target.z as f32,
|
|
||||||
);
|
|
||||||
// direction in acadrust = from-target-to-eye (same as AutoCAD convention).
|
// direction in acadrust = from-target-to-eye (same as AutoCAD convention).
|
||||||
let eye_dir = Vec3::new(
|
let eye_dir = Vec3::new(
|
||||||
view.direction.x as f32,
|
view.direction.x as f32,
|
||||||
@@ -6162,7 +6158,7 @@ impl Scene {
|
|||||||
let fov_y = 45.0_f32.to_radians();
|
let fov_y = 45.0_f32.to_radians();
|
||||||
let distance = ((view_height as f32 / 2.0) / (fov_y * 0.5).tan()).max(0.001);
|
let distance = ((view_height as f32 / 2.0) / (fov_y * 0.5).tan()).max(0.001);
|
||||||
Some(Camera {
|
Some(Camera {
|
||||||
target,
|
target: target.as_dvec3(),
|
||||||
rotation,
|
rotation,
|
||||||
distance,
|
distance,
|
||||||
fov_y,
|
fov_y,
|
||||||
@@ -7358,8 +7354,8 @@ impl Scene {
|
|||||||
let aspect = canvas_w / canvas_h;
|
let aspect = canvas_w / canvas_h;
|
||||||
let half_h = cam.ortho_size();
|
let half_h = cam.ortho_size();
|
||||||
let half_w = half_h * aspect;
|
let half_w = half_h * aspect;
|
||||||
let tx = cam.target.x;
|
let tx = cam.target.x as f32;
|
||||||
let ty = cam.target.y;
|
let ty = cam.target.y as f32;
|
||||||
drop(cam);
|
drop(cam);
|
||||||
|
|
||||||
// Top-down ortho mapping matching the GPU sheet viewport's camera.
|
// Top-down ortho mapping matching the GPU sheet viewport's camera.
|
||||||
|
|||||||
+31
-17
@@ -11,7 +11,7 @@
|
|||||||
//
|
//
|
||||||
// Coordinate convention: Z-up world space (same as the rest of OpenCADStudio).
|
// 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};
|
use iced::{Point, Rectangle};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
@@ -22,8 +22,11 @@ pub enum Projection {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
/// World-space pivot point the camera orbits around.
|
/// World-space pivot point the camera orbits around. Kept in f64 so a far
|
||||||
pub target: Vec3,
|
/// 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.
|
/// Arcball rotation: maps canonical pose to current orientation.
|
||||||
pub rotation: Quat,
|
pub rotation: Quat,
|
||||||
/// Distance from eye to target.
|
/// Distance from eye to target.
|
||||||
@@ -45,7 +48,7 @@ impl Default for Camera {
|
|||||||
let yaw = 0.0_f32;
|
let yaw = 0.0_f32;
|
||||||
let pitch = std::f32::consts::FRAC_PI_2;
|
let pitch = std::f32::consts::FRAC_PI_2;
|
||||||
Self {
|
Self {
|
||||||
target: Vec3::ZERO,
|
target: DVec3::ZERO,
|
||||||
rotation: yaw_pitch_to_quat(yaw, pitch, 0.0),
|
rotation: yaw_pitch_to_quat(yaw, pitch, 0.0),
|
||||||
distance: 60.36,
|
distance: 60.36,
|
||||||
fov_y: 45.0_f32.to_radians(),
|
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 canonical eye direction is +Z (looking at origin from above).
|
||||||
// The rotation maps that canonical pose to the current orientation.
|
// The rotation maps that canonical pose to the current orientation.
|
||||||
let eye_dir = self.rotation * Vec3::Z;
|
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.
|
/// Half-height of the orthographic frustum in world units.
|
||||||
@@ -89,7 +101,7 @@ impl Camera {
|
|||||||
// in the current camera frame.
|
// in the current camera frame.
|
||||||
let up_dir = self.rotation * Vec3::Y;
|
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 {
|
let proj = match self.projection {
|
||||||
Projection::Perspective => Mat4::perspective_rh(self.fov_y, aspect, near, far),
|
Projection::Perspective => Mat4::perspective_rh(self.fov_y, aspect, near, far),
|
||||||
Projection::Orthographic => {
|
Projection::Orthographic => {
|
||||||
@@ -126,7 +138,7 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
Projection::Orthographic => {
|
Projection::Orthographic => {
|
||||||
let origin = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
|
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)
|
(origin, forward)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -149,24 +161,26 @@ impl Camera {
|
|||||||
|
|
||||||
match self.projection {
|
match self.projection {
|
||||||
Projection::Perspective => {
|
Projection::Perspective => {
|
||||||
|
let target = self.target.as_vec3();
|
||||||
let near_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
|
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 far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0));
|
||||||
let dir = (far_pt - near_pt).normalize();
|
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);
|
let denom = dir.dot(forward);
|
||||||
if denom.abs() < 1e-6 {
|
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 {
|
if t < 0.0 {
|
||||||
return self.target;
|
return target;
|
||||||
}
|
}
|
||||||
near_pt + dir * t
|
near_pt + dir * t
|
||||||
}
|
}
|
||||||
Projection::Orthographic => {
|
Projection::Orthographic => {
|
||||||
|
let target = self.target.as_vec3();
|
||||||
let ray_origin = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
|
let ray_origin = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
|
||||||
let forward = (self.target - self.eye()).normalize();
|
let forward = (target - self.eye()).normalize();
|
||||||
let t = (self.target - ray_origin).dot(forward) / forward.dot(forward);
|
let t = (target - ray_origin).dot(forward) / forward.dot(forward);
|
||||||
ray_origin + forward * t
|
ray_origin + forward * t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +257,7 @@ impl Camera {
|
|||||||
let before = self.pick_on_target_plane(screen, bounds);
|
let before = self.pick_on_target_plane(screen, bounds);
|
||||||
self.zoom(delta);
|
self.zoom(delta);
|
||||||
let after = self.pick_on_target_plane(screen, bounds);
|
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
|
/// 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_right = self.rotation * Vec3::X;
|
||||||
let cam_up = self.rotation * Vec3::Y;
|
let cam_up = self.rotation * Vec3::Y;
|
||||||
self.target -= cam_right * delta_x * wpp;
|
self.target -= (cam_right * delta_x * wpp).as_dvec3();
|
||||||
self.target += cam_up * delta_y * wpp;
|
self.target += (cam_up * delta_y * wpp).as_dvec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fit_to_bounds(&mut self, min: Vec3, max: Vec3) {
|
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();
|
let size = (max - min).length();
|
||||||
self.distance = size * 1.5;
|
self.distance = size * 1.5;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user