1use glam::{DMat4, DVec3, dvec2};
4
5use crate::{
6 Extract,
7 animation::{AnimationCell, Eval},
8 core_item::CoreItem,
9 prelude::{Alignable, Interpolatable},
10};
11
12#[derive(Clone, Debug, PartialEq)]
17pub struct CameraFrame {
18 pub pos: DVec3,
20 pub up: DVec3,
22 pub facing: DVec3,
24
25 pub near: f64,
28 pub far: f64,
30 pub perspective_blend: f64,
32
33 pub frame_height: f64,
35 pub scale: f64,
37
38 pub fovy: f64,
40}
41
42impl Extract for CameraFrame {
43 type Target = CoreItem;
44 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
45 buf.push(CoreItem::CameraFrame(self.clone()));
46 }
47}
48
49impl Interpolatable for CameraFrame {
50 fn lerp(&self, target: &Self, t: f64) -> Self {
51 Self {
52 pos: self.pos.lerp(target.pos, t),
53 up: self.up.lerp(target.up, t),
54 facing: self.facing.lerp(target.facing, t),
55 scale: self.scale.lerp(&target.scale, t),
56 fovy: self.fovy.lerp(&target.fovy, t),
57 near: self.near.lerp(&target.near, t),
58 far: self.far.lerp(&target.far, t),
59 frame_height: self.frame_height.lerp(&target.frame_height, t),
60 perspective_blend: self
61 .perspective_blend
62 .lerp(&target.perspective_blend, t)
63 .clamp(0.0, 1.0),
64 }
65 }
66}
67
68impl Alignable for CameraFrame {
69 fn is_aligned(&self, _other: &Self) -> bool {
70 true
71 }
72 fn align_with(&mut self, _other: &mut Self) {}
73}
74
75impl Default for CameraFrame {
76 fn default() -> Self {
77 Self {
78 pos: DVec3::ZERO,
79 up: DVec3::Y,
80 facing: DVec3::NEG_Z,
81
82 near: -1000.0,
83 far: 1000.0,
84 perspective_blend: 0.0,
85
86 scale: 1.0,
87 frame_height: 8.0,
88
89 fovy: std::f64::consts::PI / 2.0,
90 }
91 }
92}
93
94impl CameraFrame {
95 pub fn new() -> Self {
97 Self::default()
98 }
99}
100
101impl CameraFrame {
102 pub fn set_view_matrix(&mut self, view_matrix: DMat4) {
104 let inv = view_matrix.inverse();
105 self.pos = inv.transform_point3(DVec3::ZERO);
106 self.up = inv.transform_vector3(DVec3::Y).normalize();
107 self.facing = inv.transform_vector3(DVec3::NEG_Z).normalize();
108 }
109
110 pub fn with_view_matrix(mut self, view_matrix: DMat4) -> Self {
112 self.set_view_matrix(view_matrix);
113 self
114 }
115
116 pub fn view_matrix(&self) -> DMat4 {
118 DMat4::look_to_rh(self.pos, self.facing, self.up)
119 }
120
121 pub fn orthographic_mat(&self, aspect_ratio: f64) -> DMat4 {
123 let frame_size = dvec2(self.frame_height * aspect_ratio, self.frame_height);
124 let frame_size = frame_size * self.scale;
125 DMat4::orthographic_rh(
126 -frame_size.x / 2.0,
127 frame_size.x / 2.0,
128 -frame_size.y / 2.0,
129 frame_size.y / 2.0,
130 self.near,
131 self.far,
132 )
133 }
134
135 pub fn perspective_mat(&self, aspect_ratio: f64) -> DMat4 {
137 let near = self.near.max(0.1);
138 let far = self.far.max(near);
139 DMat4::perspective_rh(self.fovy, aspect_ratio, near, far)
140 }
141
142 pub fn projection_matrix(&self, aspect_ratio: f64) -> DMat4 {
144 self.orthographic_mat(aspect_ratio)
145 .lerp(&self.perspective_mat(aspect_ratio), self.perspective_blend)
146 }
147
148 pub fn view_projection_matrix(&self, aspect_ratio: f64) -> DMat4 {
150 self.projection_matrix(aspect_ratio) * self.view_matrix()
151 }
152}
153
154impl CameraFrame {
155 pub fn from_spherical(phi: f64, theta: f64, distance: f64) -> Self {
161 let mut cam = Self {
162 perspective_blend: 1.0,
163 up: DVec3::Z,
164 ..Self::default()
165 };
166 cam.set_spherical(phi, theta, distance, DVec3::ZERO);
167 cam
168 }
169
170 pub fn set_spherical(
177 &mut self,
178 phi: f64,
179 theta: f64,
180 distance: f64,
181 target: DVec3,
182 ) -> &mut Self {
183 self.pos = target
184 + DVec3::new(
185 distance * phi.sin() * theta.cos(),
186 distance * phi.sin() * theta.sin(),
187 distance * phi.cos(),
188 );
189 self.facing = (target - self.pos).normalize();
190 self.up = DVec3::Z;
191 self
192 }
193
194 pub fn look_at(&mut self, target: DVec3) -> &mut Self {
196 self.facing = (target - self.pos).normalize();
197 self
198 }
199
200 pub fn orbit(&mut self, target: DVec3, total_angle: f64) -> AnimationCell<Self> {
219 let offset = self.pos - target;
220 let distance = offset.length();
221 let phi = if distance > 0.0 {
222 (offset.z / distance).acos()
223 } else {
224 0.0
225 };
226 let theta0 = offset.y.atan2(offset.x);
227 let src = self.clone();
228
229 struct Orbit {
230 src: CameraFrame,
231 target: DVec3,
232 distance: f64,
233 phi: f64,
234 theta0: f64,
235 total_angle: f64,
236 }
237
238 impl Eval<CameraFrame> for Orbit {
239 fn eval_alpha(&self, alpha: f64) -> CameraFrame {
240 let theta = self.theta0 + self.total_angle * alpha;
241 let mut result = self.src.clone();
242 result.set_spherical(self.phi, theta, self.distance, self.target);
243 result
244 }
245 }
246
247 Orbit {
248 src,
249 target,
250 distance,
251 phi,
252 theta0,
253 total_angle,
254 }
255 .into_animation_cell()
256 .apply_to(self)
257 }
258}
259
260impl CameraFrame {
261 pub fn center_canvas_in_frame(
263 &mut self,
264 center: DVec3,
265 width: f64,
266 height: f64,
267 up: DVec3,
268 normal: DVec3,
269 aspect_ratio: f64,
270 ) -> &mut Self {
271 let canvas_ratio = height / width;
272 let up = up.normalize();
273 let normal = normal.normalize();
274
275 let height = if aspect_ratio > canvas_ratio {
276 height
277 } else {
278 width / aspect_ratio
279 };
280
281 let distance = height * 0.5 / (0.5 * self.fovy).tan();
282
283 self.up = up;
284 self.pos = center + normal * distance;
285 self.facing = -normal;
286 self
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use glam::dvec3;
294
295 #[test]
296 fn test_set_view_matrix_default() {
297 let camera = CameraFrame::new();
298 let view_matrix = camera.view_matrix();
299
300 let mut new_camera = CameraFrame::new();
301 new_camera.set_view_matrix(view_matrix);
302
303 assert!(new_camera.pos.distance(camera.pos) < 1e-10);
304 assert!(new_camera.up.angle_between(camera.up) < 1e-10);
305 assert!(new_camera.facing.angle_between(camera.facing) < 1e-10);
306 }
307
308 #[test]
309 fn test_set_view_matrix_translated() {
310 let mut camera = CameraFrame::new();
311 camera.pos = dvec3(5.0, 3.0, -2.0);
312 let view_matrix = camera.view_matrix();
313
314 let mut new_camera = CameraFrame::new();
315 new_camera.set_view_matrix(view_matrix);
316
317 assert!(new_camera.pos.distance(camera.pos) < 1e-10);
318 assert!(new_camera.up.angle_between(camera.up) < 1e-10);
319 assert!(new_camera.facing.angle_between(camera.facing) < 1e-10);
320 }
321
322 #[test]
323 fn test_set_view_matrix_rotated() {
324 let mut camera = CameraFrame::new();
325 camera.facing = dvec3(1.0, 0.0, 0.0);
326 camera.up = dvec3(0.0, 1.0, 0.0);
327 let view_matrix = camera.view_matrix();
328
329 let mut new_camera = CameraFrame::new();
330 new_camera.set_view_matrix(view_matrix);
331
332 assert!(new_camera.pos.distance(camera.pos) < 1e-10);
333 assert!(new_camera.up.angle_between(camera.up) < 1e-10);
334 assert!(new_camera.facing.angle_between(camera.facing) < 1e-10);
335 }
336
337 #[test]
338 fn test_set_view_matrix_complex() {
339 let mut camera = CameraFrame::new();
340 camera.pos = dvec3(10.0, 5.0, 3.0);
341 camera.facing = dvec3(1.0, 0.0, 1.0).normalize();
342 camera.up = dvec3(0.0, 1.0, 0.0);
343 let view_matrix = camera.view_matrix();
344
345 let mut new_camera = CameraFrame::new();
346 new_camera.set_view_matrix(view_matrix);
347
348 assert!(new_camera.pos.distance(camera.pos) < 1e-10);
349 assert!(new_camera.up.angle_between(camera.up) < 1e-10);
350 assert!(new_camera.facing.angle_between(camera.facing) < 1e-10);
351 }
352}