threecrate_visualization/
camera.rs

1//! Camera utilities for 3D visualization
2
3use nalgebra::{Point3, Vector3, Matrix4, Perspective3};
4use std::f32::consts::PI;
5
6/// A 3D camera for viewing point clouds and meshes
7#[derive(Debug, Clone)]
8pub struct Camera {
9    pub position: Point3<f32>,
10    pub target: Point3<f32>,
11    pub up: Vector3<f32>,
12    pub fov: f32,
13    pub fovy_degrees: f32,  // FOV in degrees for UI display
14    pub aspect_ratio: f32,
15    pub near: f32,
16    pub far: f32,
17    
18    // Spherical coordinates for orbit control
19    pub radius: f32,
20    pub theta: f32,  // Azimuth angle
21    pub phi: f32,    // Polar angle
22    
23    // Default position for reset
24    default_position: Point3<f32>,
25    default_target: Point3<f32>,
26    default_up: Vector3<f32>,
27}
28
29impl Camera {
30    /// Create a new camera
31    pub fn new(
32        position: Point3<f32>,
33        target: Point3<f32>,
34        up: Vector3<f32>,
35        fovy_degrees: f32,
36        aspect_ratio: f32,
37        near: f32,
38        far: f32,
39    ) -> Self {
40        let direction = position - target;
41        let radius = direction.magnitude();
42        let theta = direction.z.atan2(direction.x);
43        let phi = (direction.y / radius).asin();
44        let fov = fovy_degrees.to_radians();
45        
46        Self {
47            position,
48            target,
49            up,
50            fov,
51            fovy_degrees,
52            aspect_ratio,
53            near,
54            far,
55            radius,
56            theta,
57            phi,
58            default_position: position,
59            default_target: target,
60            default_up: up,
61        }
62    }
63
64    /// Get the view matrix
65    pub fn view_matrix(&self) -> Matrix4<f32> {
66        Matrix4::look_at_rh(&self.position, &self.target, &self.up)
67    }
68
69    /// Get the projection matrix
70    pub fn projection_matrix(&self) -> Matrix4<f32> {
71        let perspective = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
72        perspective.into_inner()
73    }
74
75    /// Move the camera forward/backward
76    pub fn move_forward(&mut self, distance: f32) {
77        self.radius = (self.radius - distance).max(0.1);
78        self.update_position_from_spherical();
79    }
80
81    /// Rotate the camera around the target (orbit control)
82    pub fn orbit(&mut self, delta_theta: f32, delta_phi: f32) {
83        self.theta += delta_theta;
84        self.phi = (self.phi + delta_phi).clamp(-PI/2.0 + 0.1, PI/2.0 - 0.1);
85        self.update_position_from_spherical();
86    }
87
88    /// Pan the camera (translate both position and target)
89    pub fn pan(&mut self, delta_x: f32, delta_y: f32) {
90        let forward = (self.target - self.position).normalize();
91        let right = forward.cross(&self.up).normalize();
92        let up = right.cross(&forward).normalize();
93        
94        let pan_vector = right * delta_x + up * delta_y;
95        self.position += pan_vector;
96        self.target += pan_vector;
97    }
98
99    /// Zoom by changing the field of view
100    pub fn zoom_fov(&mut self, delta: f32) {
101        self.fov = (self.fov + delta).clamp(0.1, PI - 0.1);
102        self.fovy_degrees = self.fov.to_degrees();
103    }
104
105    /// Zoom by moving closer/farther from target
106    pub fn zoom(&mut self, delta: f32) {
107        self.radius = (self.radius - delta).max(0.1);
108        self.update_position_from_spherical();
109    }
110
111    /// Reset camera to default position
112    pub fn reset(&mut self) {
113        self.position = self.default_position;
114        self.target = self.default_target;
115        self.up = self.default_up;
116        
117        let direction = self.position - self.target;
118        self.radius = direction.magnitude();
119        self.theta = direction.z.atan2(direction.x);
120        self.phi = (direction.y / self.radius).asin();
121    }
122
123    /// Update position based on spherical coordinates
124    fn update_position_from_spherical(&mut self) {
125        let x = self.radius * self.phi.cos() * self.theta.cos();
126        let y = self.radius * self.phi.sin();
127        let z = self.radius * self.phi.cos() * self.theta.sin();
128        
129        self.position = self.target + Vector3::new(x, y, z);
130    }
131
132    /// Reset camera to default position looking at target
133    pub fn reset_to_target(&mut self, target: Point3<f32>, radius: f32) {
134        self.target = target;
135        self.radius = radius;
136        self.theta = 0.0;
137        self.phi = 0.0;
138        self.update_position_from_spherical();
139    }
140
141    /// Get the forward direction vector
142    pub fn forward(&self) -> Vector3<f32> {
143        (self.target - self.position).normalize()
144    }
145
146    /// Get the right direction vector
147    pub fn right(&self) -> Vector3<f32> {
148        self.forward().cross(&self.up).normalize()
149    }
150
151    /// Get the up direction vector (camera local up)
152    pub fn camera_up(&self) -> Vector3<f32> {
153        self.right().cross(&self.forward()).normalize()
154    }
155
156    /// Set aspect ratio
157    pub fn set_aspect_ratio(&mut self, aspect_ratio: f32) {
158        self.aspect_ratio = aspect_ratio;
159    }
160
161    /// Get camera distance from target
162    pub fn distance_to_target(&self) -> f32 {
163        (self.position - self.target).magnitude()
164    }
165
166    /// Set field of view in degrees
167    pub fn set_fov_degrees(&mut self, fovy_degrees: f32) {
168        self.fovy_degrees = fovy_degrees.clamp(1.0, 179.0);
169        self.fov = self.fovy_degrees.to_radians();
170    }
171
172    /// Get field of view in degrees
173    pub fn fov_degrees(&self) -> f32 {
174        self.fovy_degrees
175    }
176}
177
178impl Default for Camera {
179    fn default() -> Self {
180        Self::new(
181            Point3::new(0.0, 0.0, 5.0),
182            Point3::new(0.0, 0.0, 0.0),
183            Vector3::new(0.0, 1.0, 0.0),
184            45.0,  // 45 degrees FOV
185            16.0 / 9.0,
186            0.1,
187            100.0,
188        )
189    }
190}