Skip to main content

polyscope_structures/camera_view/
camera_parameters.rs

1//! Camera parameters (intrinsics and extrinsics).
2
3use glam::{Mat3, Mat4, Vec3};
4
5/// Camera intrinsics parameters.
6#[derive(Debug, Clone, Copy)]
7pub struct CameraIntrinsics {
8    /// Vertical field of view in degrees.
9    pub fov_vertical_degrees: f32,
10    /// Aspect ratio (width / height).
11    pub aspect_ratio: f32,
12}
13
14impl CameraIntrinsics {
15    /// Creates new camera intrinsics.
16    #[must_use]
17    pub fn new(fov_vertical_degrees: f32, aspect_ratio: f32) -> Self {
18        Self {
19            fov_vertical_degrees,
20            aspect_ratio,
21        }
22    }
23
24    /// Creates intrinsics from horizontal `FoV` and aspect ratio.
25    #[must_use]
26    pub fn from_horizontal_fov(fov_horizontal_degrees: f32, aspect_ratio: f32) -> Self {
27        // Convert horizontal to vertical FoV: tan(v/2) = tan(h/2) / aspect
28        let h_rad = fov_horizontal_degrees.to_radians();
29        let v_rad = 2.0 * ((h_rad / 2.0).tan() / aspect_ratio).atan();
30        Self {
31            fov_vertical_degrees: v_rad.to_degrees(),
32            aspect_ratio,
33        }
34    }
35
36    /// Creates default intrinsics (60° vertical `FoV`, 16:9 aspect).
37    #[must_use]
38    pub fn default_intrinsics() -> Self {
39        Self {
40            fov_vertical_degrees: 60.0,
41            aspect_ratio: 16.0 / 9.0,
42        }
43    }
44}
45
46impl Default for CameraIntrinsics {
47    fn default() -> Self {
48        Self::default_intrinsics()
49    }
50}
51
52/// Camera extrinsics parameters (position and orientation).
53#[derive(Debug, Clone, Copy)]
54pub struct CameraExtrinsics {
55    /// Camera position in world space.
56    pub position: Vec3,
57    /// Look direction (normalized).
58    pub look_dir: Vec3,
59    /// Up direction (normalized).
60    pub up_dir: Vec3,
61}
62
63impl CameraExtrinsics {
64    /// Creates new camera extrinsics from position and directions.
65    #[must_use]
66    pub fn new(position: Vec3, look_dir: Vec3, up_dir: Vec3) -> Self {
67        Self {
68            position,
69            look_dir: look_dir.normalize(),
70            up_dir: up_dir.normalize(),
71        }
72    }
73
74    /// Creates extrinsics with camera at origin looking down -Z.
75    #[must_use]
76    pub fn default_extrinsics() -> Self {
77        Self {
78            position: Vec3::ZERO,
79            look_dir: -Vec3::Z,
80            up_dir: Vec3::Y,
81        }
82    }
83
84    /// Creates extrinsics for a camera looking at a target point.
85    #[must_use]
86    pub fn look_at(position: Vec3, target: Vec3, up: Vec3) -> Self {
87        let look_dir = (target - position).normalize();
88        Self::new(position, look_dir, up)
89    }
90
91    /// Gets the right direction (look cross up).
92    #[must_use]
93    pub fn right_dir(&self) -> Vec3 {
94        self.look_dir.cross(self.up_dir).normalize()
95    }
96
97    /// Gets the camera frame as (look, up, right).
98    #[must_use]
99    pub fn camera_frame(&self) -> (Vec3, Vec3, Vec3) {
100        let right = self.right_dir();
101        // Re-orthogonalize up
102        let up = right.cross(self.look_dir).normalize();
103        (self.look_dir, up, right)
104    }
105
106    /// Returns the view matrix (world to camera space).
107    #[must_use]
108    pub fn view_matrix(&self) -> Mat4 {
109        let (look, up, right) = self.camera_frame();
110        // Camera looks down -Z in eye space
111        let rotation = Mat3::from_cols(right, up, -look);
112        let translation = -rotation * self.position;
113        Mat4::from_cols(
114            rotation.x_axis.extend(0.0),
115            rotation.y_axis.extend(0.0),
116            rotation.z_axis.extend(0.0),
117            translation.extend(1.0),
118        )
119    }
120}
121
122impl Default for CameraExtrinsics {
123    fn default() -> Self {
124        Self::default_extrinsics()
125    }
126}
127
128/// Combined camera parameters (intrinsics + extrinsics).
129#[derive(Debug, Clone, Copy, Default)]
130pub struct CameraParameters {
131    /// Intrinsic parameters (`FoV`, aspect ratio).
132    pub intrinsics: CameraIntrinsics,
133    /// Extrinsic parameters (position, orientation).
134    pub extrinsics: CameraExtrinsics,
135}
136
137impl CameraParameters {
138    /// Creates new camera parameters.
139    #[must_use]
140    pub fn new(intrinsics: CameraIntrinsics, extrinsics: CameraExtrinsics) -> Self {
141        Self {
142            intrinsics,
143            extrinsics,
144        }
145    }
146
147    /// Creates camera parameters from vectors.
148    #[must_use]
149    pub fn from_vectors(
150        position: Vec3,
151        look_dir: Vec3,
152        up_dir: Vec3,
153        fov_vertical_degrees: f32,
154        aspect_ratio: f32,
155    ) -> Self {
156        Self {
157            intrinsics: CameraIntrinsics::new(fov_vertical_degrees, aspect_ratio),
158            extrinsics: CameraExtrinsics::new(position, look_dir, up_dir),
159        }
160    }
161
162    /// Creates camera parameters for looking at a target.
163    #[must_use]
164    pub fn look_at(
165        position: Vec3,
166        target: Vec3,
167        up: Vec3,
168        fov_vertical_degrees: f32,
169        aspect_ratio: f32,
170    ) -> Self {
171        Self {
172            intrinsics: CameraIntrinsics::new(fov_vertical_degrees, aspect_ratio),
173            extrinsics: CameraExtrinsics::look_at(position, target, up),
174        }
175    }
176
177    /// Gets the camera position.
178    #[must_use]
179    pub fn position(&self) -> Vec3 {
180        self.extrinsics.position
181    }
182
183    /// Gets the look direction.
184    #[must_use]
185    pub fn look_dir(&self) -> Vec3 {
186        self.extrinsics.look_dir
187    }
188
189    /// Gets the up direction.
190    #[must_use]
191    pub fn up_dir(&self) -> Vec3 {
192        self.extrinsics.up_dir
193    }
194
195    /// Gets the right direction.
196    #[must_use]
197    pub fn right_dir(&self) -> Vec3 {
198        self.extrinsics.right_dir()
199    }
200
201    /// Gets the camera frame as (look, up, right).
202    #[must_use]
203    pub fn camera_frame(&self) -> (Vec3, Vec3, Vec3) {
204        self.extrinsics.camera_frame()
205    }
206
207    /// Gets the vertical field of view in degrees.
208    #[must_use]
209    pub fn fov_vertical_degrees(&self) -> f32 {
210        self.intrinsics.fov_vertical_degrees
211    }
212
213    /// Gets the aspect ratio (width / height).
214    #[must_use]
215    pub fn aspect_ratio(&self) -> f32 {
216        self.intrinsics.aspect_ratio
217    }
218
219    /// Gets the view matrix.
220    #[must_use]
221    pub fn view_matrix(&self) -> Mat4 {
222        self.extrinsics.view_matrix()
223    }
224
225    /// Gets the projection matrix.
226    #[must_use]
227    pub fn projection_matrix(&self, near: f32, far: f32) -> Mat4 {
228        Mat4::perspective_rh(
229            self.intrinsics.fov_vertical_degrees.to_radians(),
230            self.intrinsics.aspect_ratio,
231            near,
232            far,
233        )
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_camera_frame() {
243        let extrinsics = CameraExtrinsics::new(
244            Vec3::new(0.0, 0.0, 5.0),
245            Vec3::new(0.0, 0.0, -1.0),
246            Vec3::new(0.0, 1.0, 0.0),
247        );
248        let (look, up, right) = extrinsics.camera_frame();
249        assert!((look - Vec3::new(0.0, 0.0, -1.0)).length() < 1e-6);
250        assert!((up - Vec3::new(0.0, 1.0, 0.0)).length() < 1e-6);
251        assert!((right - Vec3::new(1.0, 0.0, 0.0)).length() < 1e-6);
252    }
253
254    #[test]
255    fn test_look_at() {
256        let params =
257            CameraParameters::look_at(Vec3::new(0.0, 0.0, 5.0), Vec3::ZERO, Vec3::Y, 60.0, 1.5);
258        assert!((params.look_dir() - Vec3::new(0.0, 0.0, -1.0)).length() < 1e-6);
259        assert_eq!(params.fov_vertical_degrees(), 60.0);
260        assert_eq!(params.aspect_ratio(), 1.5);
261    }
262}