Skip to main content

three_d_asset/
camera.rs

1pub use crate::prelude::*;
2
3/// UV coordinates which must be between `(0, 0)` indicating the bottom left corner
4/// and `(1, 1)` indicating the top right corner.
5#[derive(Debug, Copy, Clone, PartialEq)]
6pub struct UvCoordinate {
7    /// Coordinate that is 0 at the left edge to 1 at the right edge.
8    pub u: f32,
9    /// Coordinate that is 0 at the bottom edge to 1 at the top edge.
10    pub v: f32,
11}
12
13impl From<(f32, f32)> for UvCoordinate {
14    fn from(value: (f32, f32)) -> Self {
15        Self {
16            u: value.0,
17            v: value.1,
18        }
19    }
20}
21
22impl From<UvCoordinate> for (f32, f32) {
23    fn from(value: UvCoordinate) -> Self {
24        (value.u, value.v)
25    }
26}
27
28impl From<Vec2> for UvCoordinate {
29    fn from(value: Vec2) -> Self {
30        Self {
31            u: value.x,
32            v: value.y,
33        }
34    }
35}
36
37impl From<UvCoordinate> for Vec2 {
38    fn from(value: UvCoordinate) -> Self {
39        Self {
40            x: value.u,
41            y: value.v,
42        }
43    }
44}
45
46/// A pixel coordinate in physical pixels, where `x` is on the horizontal axis with zero being at the left edge
47/// and `y` is on the vertical axis with zero being at bottom edge.
48#[derive(Debug, Copy, Clone, PartialEq)]
49pub struct PixelPoint {
50    /// The horizontal pixel distance from the left edge.
51    pub x: f32,
52    /// The vertical pixel distance from the bottom edge.
53    pub y: f32,
54}
55
56impl From<(f32, f32)> for PixelPoint {
57    fn from(value: (f32, f32)) -> Self {
58        Self {
59            x: value.0,
60            y: value.1,
61        }
62    }
63}
64
65impl From<PixelPoint> for (f32, f32) {
66    fn from(value: PixelPoint) -> Self {
67        (value.x, value.y)
68    }
69}
70
71impl From<Vec2> for PixelPoint {
72    fn from(value: Vec2) -> Self {
73        Self {
74            x: value.x,
75            y: value.y,
76        }
77    }
78}
79
80impl From<PixelPoint> for Vec2 {
81    fn from(value: PixelPoint) -> Self {
82        Self {
83            x: value.x,
84            y: value.y,
85        }
86    }
87}
88
89///
90/// Defines the part of the screen/render target that the camera is projecting into.
91/// All values should be in physical pixels.
92///
93#[derive(Debug, Copy, Clone, PartialEq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct Viewport {
96    /// The distance in pixels from the left edge of the screen/render target.
97    pub x: i32,
98    /// The distance in pixels from the bottom edge of the screen/render target.
99    pub y: i32,
100    /// The width of the viewport.
101    pub width: u32,
102    /// The height of the viewport.
103    pub height: u32,
104}
105
106impl Viewport {
107    ///
108    /// Creates a new viewport with the bottom left corner at origo `(0, 0)`.
109    ///
110    pub fn new_at_origo(width: u32, height: u32) -> Self {
111        Self {
112            x: 0,
113            y: 0,
114            width,
115            height,
116        }
117    }
118
119    ///
120    /// Returns the aspect ratio of this viewport.
121    ///
122    pub fn aspect(&self) -> f32 {
123        self.width as f32 / self.height as f32
124    }
125
126    ///
127    /// Returns the intersection between this and the other Viewport.
128    ///
129    pub fn intersection(&self, other: impl Into<Self>) -> Self {
130        let other = other.into();
131        let x = self.x.max(other.x);
132        let y = self.y.max(other.y);
133        let width =
134            (self.x + self.width as i32 - x).clamp(0, other.x + other.width as i32 - x) as u32;
135        let height =
136            (self.y + self.height as i32 - y).clamp(0, other.y + other.height as i32 - y) as u32;
137        Self {
138            x,
139            y,
140            width,
141            height,
142        }
143    }
144}
145
146///
147/// The view frustum which can be used for frustum culling.
148///
149pub struct Frustum([Vec4; 6]);
150
151impl Frustum {
152    /// Computes the frustum for the given view-projection matrix.
153    pub fn new(view_projection: Mat4) -> Self {
154        let m = view_projection;
155        Self([
156            vec4(m.x.w + m.x.x, m.y.w + m.y.x, m.z.w + m.z.x, m.w.w + m.w.x),
157            vec4(m.x.w - m.x.x, m.y.w - m.y.x, m.z.w - m.z.x, m.w.w - m.w.x),
158            vec4(m.x.w + m.x.y, m.y.w + m.y.y, m.z.w + m.z.y, m.w.w + m.w.y),
159            vec4(m.x.w - m.x.y, m.y.w - m.y.y, m.z.w - m.z.y, m.w.w - m.w.y),
160            vec4(m.x.w + m.x.z, m.y.w + m.y.z, m.z.w + m.z.z, m.w.w + m.w.z),
161            vec4(m.x.w - m.x.z, m.y.w - m.y.z, m.z.w - m.z.z, m.w.w - m.w.z),
162        ])
163    }
164
165    /// Used for frustum culling. Returns false if the entire bounding box is outside of the frustum.
166    pub fn contains(&self, aabb: AxisAlignedBoundingBox) -> bool {
167        if aabb.is_infinite() {
168            return true;
169        }
170        if aabb.is_empty() {
171            return false;
172        }
173        // check box outside/inside of frustum
174        for i in 0..6 {
175            let mut out = 0;
176            if self.0[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
177                out += 1
178            };
179            if self.0[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
180                out += 1
181            };
182            if self.0[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
183                out += 1
184            };
185            if self.0[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
186                out += 1
187            };
188            if self.0[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
189                out += 1
190            };
191            if self.0[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
192                out += 1
193            };
194            if self.0[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
195                out += 1
196            };
197            if self.0[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
198                out += 1
199            };
200            if out == 8 {
201                return false;
202            }
203        }
204        // TODO: Test the frustum corners against the box planes (http://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm)
205
206        true
207    }
208}
209
210///
211/// The type of projection used by a camera (orthographic or perspective) including parameters.
212///
213#[derive(Clone, Debug)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215pub enum ProjectionType {
216    /// Orthographic projection
217    Orthographic {
218        /// Height of the camera film/sensor.
219        height: f32,
220    },
221    /// Perspective projection
222    Perspective {
223        /// The field of view angle in the vertical direction.
224        field_of_view_y: Radians,
225    },
226    /// General planar projection
227    Planar {
228        /// The field of view angle in the vertical direction.
229        field_of_view_y: Radians,
230    },
231}
232
233///
234/// Represents a camera used for viewing 3D assets.
235///
236#[derive(Clone, Debug)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct Camera {
239    viewport: Viewport,
240    projection_type: ProjectionType,
241    z_near: f32,
242    z_far: f32,
243    position: Vec3,
244    target: Vec3,
245    up: Vec3,
246    view: Mat4,
247    projection: Mat4,
248}
249
250impl Camera {
251    ///
252    /// New camera which projects the world with an orthographic projection.
253    ///
254    pub fn new_orthographic(
255        viewport: Viewport,
256        position: Vec3,
257        target: Vec3,
258        up: Vec3,
259        height: f32,
260        z_near: f32,
261        z_far: f32,
262    ) -> Self {
263        let mut camera = Camera::new(viewport);
264        camera.set_view(position, target, up);
265        camera.set_orthographic_projection(height, z_near, z_far);
266        camera
267    }
268
269    ///
270    /// New camera which projects the world with a perspective projection.
271    ///
272    pub fn new_perspective(
273        viewport: Viewport,
274        position: Vec3,
275        target: Vec3,
276        up: Vec3,
277        field_of_view_y: impl Into<Radians>,
278        z_near: f32,
279        z_far: f32,
280    ) -> Self {
281        let mut camera = Camera::new(viewport);
282        camera.set_view(position, target, up);
283        camera.set_perspective_projection(field_of_view_y, z_near, z_far);
284        camera
285    }
286
287    ///
288    /// New camera which projects the world with a general planar projection.
289    ///
290    pub fn new_planar(
291        viewport: Viewport,
292        position: Vec3,
293        target: Vec3,
294        up: Vec3,
295        field_of_view_y: impl Into<Radians>,
296        z_near: f32,
297        z_far: f32,
298    ) -> Self {
299        let mut camera = Camera::new(viewport);
300        camera.set_view(position, target, up);
301        camera.set_planar_projection(field_of_view_y, z_near, z_far);
302        camera
303    }
304
305    ///
306    /// Specify the camera to use perspective projection with the given field of view in the y-direction and near and far plane.
307    ///
308    pub fn set_perspective_projection(
309        &mut self,
310        field_of_view_y: impl Into<Radians>,
311        z_near: f32,
312        z_far: f32,
313    ) {
314        self.z_near = z_near;
315        self.z_far = z_far;
316        let field_of_view_y = field_of_view_y.into();
317        self.projection_type = ProjectionType::Perspective { field_of_view_y };
318        self.projection =
319            cgmath::perspective(field_of_view_y, self.viewport.aspect(), z_near, z_far);
320    }
321
322    ///
323    /// Specify the camera to use orthographic projection with the given dimensions.
324    /// The view frustum height is `+/- height/2`.
325    /// The view frustum width is calculated as `height * viewport.width / viewport.height`.
326    /// The view frustum depth is `z_near` to `z_far`.
327    /// All of the above values are scaled by the zoom factor which is one over the distance between the camera position and target.
328    ///
329    pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
330        self.projection_type = ProjectionType::Orthographic { height };
331        self.z_near = z_near;
332        self.z_far = z_far;
333        let zoom = self.position.distance(self.target);
334        let height = zoom * height;
335        let width = height * self.viewport.aspect();
336        self.projection = cgmath::ortho(
337            -0.5 * width,
338            0.5 * width,
339            -0.5 * height,
340            0.5 * height,
341            z_near,
342            z_far,
343        );
344    }
345
346    ///
347    /// Specify the camera to use planar projection with the given field of view in the y-direction and near and far plane.
348    /// This can be either a planar or perspective projection depending on the field of view provided, which is permitted to be zero or negative.
349    ///
350    pub fn set_planar_projection(
351        &mut self,
352        field_of_view_y: impl Into<Radians>,
353        mut z_near: f32,
354        mut z_far: f32,
355    ) {
356        self.z_near = z_near;
357        self.z_far = z_far;
358        let field_of_view_y = field_of_view_y.into();
359        self.projection_type = ProjectionType::Planar { field_of_view_y };
360        let depth = self.position.distance(self.target);
361        let height = 2.0 * depth;
362        let focal = -Rad::cot(field_of_view_y / 2.0) * depth;
363        z_near -= depth;
364        z_far -= depth;
365        // Required to ensure near/far plane does not cross focal point when at close zoom levels
366        if focal < 0.0 && z_near < focal {
367            z_near = focal + 0.001;
368        } else if focal > 0.0 && z_far > focal {
369            z_far = focal - 0.001;
370        }
371        self.projection = planar(
372            field_of_view_y,
373            self.viewport.aspect(),
374            height,
375            z_near,
376            z_far,
377        ) * Mat4::from_translation(vec3(0.0, 0.0, depth));
378    }
379
380    ///
381    /// Set the current viewport.
382    /// Returns whether or not the viewport actually changed.
383    ///
384    pub fn set_viewport(&mut self, viewport: Viewport) -> bool {
385        if self.viewport != viewport {
386            self.viewport = viewport;
387            match self.projection_type {
388                ProjectionType::Orthographic { height } => {
389                    self.set_orthographic_projection(height, self.z_near, self.z_far);
390                }
391                ProjectionType::Perspective { field_of_view_y } => {
392                    self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
393                }
394                ProjectionType::Planar { field_of_view_y } => {
395                    self.set_planar_projection(field_of_view_y, self.z_near, self.z_far);
396                }
397            }
398            true
399        } else {
400            false
401        }
402    }
403
404    ///
405    /// Change the view of the camera.
406    /// The camera is placed at the given position, looking at the given target and with the given up direction.
407    ///
408    pub fn set_view(&mut self, position: Vec3, target: Vec3, up: Vec3) {
409        self.position = position;
410        self.target = target;
411        self.up = up.normalize();
412        self.view = Mat4::look_at_rh(
413            Point3::from_vec(self.position),
414            Point3::from_vec(self.target),
415            self.up,
416        );
417        match self.projection_type {
418            ProjectionType::Orthographic { height } => {
419                self.set_orthographic_projection(height, self.z_near, self.z_far)
420            }
421            ProjectionType::Planar { field_of_view_y } => {
422                self.set_planar_projection(field_of_view_y, self.z_near, self.z_far)
423            }
424            _ => {}
425        };
426    }
427
428    /// Returns the [Frustum] for this camera.
429    pub fn frustum(&self) -> Frustum {
430        Frustum::new(self.projection() * self.view())
431    }
432
433    ///
434    /// Returns the 3D position at the given pixel coordinate.
435    ///
436    pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
437        match self.projection_type() {
438            ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
439                let coords = self.uv_coordinates_at_pixel(pixel);
440                self.position_at_uv_coordinates(coords)
441            }
442            ProjectionType::Perspective { .. } => self.position,
443        }
444    }
445
446    ///
447    /// Returns the 3D position at the given uv coordinate of the viewport.
448    ///
449    pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
450        match self.projection_type() {
451            ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
452                let coords = coords.into();
453                let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0.0, 1.);
454                let p = (self.screen2ray() * screen_pos).truncate();
455                p + (self.position - p).project_on(self.view_direction()) // Project onto the image plane
456            }
457            ProjectionType::Perspective { .. } => self.position,
458        }
459    }
460
461    ///
462    /// Returns the 3D view direction at the given pixel coordinate.
463    ///
464    pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
465        match self.projection_type() {
466            ProjectionType::Orthographic { .. } => self.view_direction(),
467            ProjectionType::Perspective { .. } | ProjectionType::Planar { .. } => {
468                let coords = self.uv_coordinates_at_pixel(pixel);
469                self.view_direction_at_uv_coordinates(coords)
470            }
471        }
472    }
473
474    ///
475    /// Returns the 3D view direction at the given uv coordinate of the viewport.
476    ///
477    pub fn view_direction_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
478        match self.projection_type() {
479            ProjectionType::Orthographic { .. } => self.view_direction(),
480            ProjectionType::Perspective { .. } => {
481                let coords = coords.into();
482                let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
483                (self.screen2ray() * screen_pos).truncate().normalize()
484            }
485            ProjectionType::Planar { .. } => {
486                let coords = coords.into();
487                let start_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, -0.5);
488                let end_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, 0.5);
489                (self.screen2ray().transform_point(end_pos)
490                    - self.screen2ray().transform_point(start_pos))
491                .normalize()
492            }
493        }
494    }
495
496    ///
497    /// Returns the uv coordinate for the given pixel coordinate.
498    ///
499    pub fn uv_coordinates_at_pixel(&self, pixel: impl Into<PixelPoint>) -> UvCoordinate {
500        let pixel = pixel.into();
501        (
502            (pixel.x - self.viewport.x as f32) / self.viewport.width as f32,
503            (pixel.y - self.viewport.y as f32) / self.viewport.height as f32,
504        )
505            .into()
506    }
507
508    ///
509    /// Returns the uv coordinate for the given world position.
510    ///
511    pub fn uv_coordinates_at_position(&self, position: Vec3) -> UvCoordinate {
512        let proj = self.projection() * self.view() * position.extend(1.0);
513        (
514            0.5 * (proj.x / proj.w.abs() + 1.0),
515            0.5 * (proj.y / proj.w.abs() + 1.0),
516        )
517            .into()
518    }
519
520    ///
521    /// Returns the pixel coordinate for the given uv coordinate.
522    ///
523    pub fn pixel_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> PixelPoint {
524        let coords = coords.into();
525        (
526            coords.u * self.viewport.width as f32 + self.viewport.x as f32,
527            coords.v * self.viewport.height as f32 + self.viewport.y as f32,
528        )
529            .into()
530    }
531
532    ///
533    /// Returns the pixel coordinate for the given world position.
534    ///
535    pub fn pixel_at_position(&self, position: Vec3) -> PixelPoint {
536        self.pixel_at_uv_coordinates(self.uv_coordinates_at_position(position))
537    }
538
539    ///
540    /// Returns the type of projection (orthographic or perspective) including parameters.
541    ///
542    pub fn projection_type(&self) -> &ProjectionType {
543        &self.projection_type
544    }
545
546    ///
547    /// Returns the view matrix, ie. the matrix that transforms objects from world space (as placed in the world) to view space (as seen from this camera).
548    ///
549    pub fn view(&self) -> Mat4 {
550        self.view
551    }
552
553    ///
554    /// Returns the projection matrix, ie. the matrix that projects objects in view space onto this cameras image plane.
555    ///
556    pub fn projection(&self) -> Mat4 {
557        self.projection
558    }
559
560    ///
561    /// Returns the viewport.
562    ///
563    pub fn viewport(&self) -> Viewport {
564        self.viewport
565    }
566
567    ///
568    /// Returns the distance to the near plane of the camera frustum.
569    ///
570    pub fn z_near(&self) -> f32 {
571        self.z_near
572    }
573
574    ///
575    /// Returns the distance to the far plane of the camera frustum.
576    ///
577    pub fn z_far(&self) -> f32 {
578        self.z_far
579    }
580
581    ///
582    /// Returns the position of this camera.
583    ///
584    pub fn position(&self) -> Vec3 {
585        self.position
586    }
587
588    ///
589    /// Returns the target of this camera, ie the point that this camera looks towards.
590    ///
591    pub fn target(&self) -> Vec3 {
592        self.target
593    }
594
595    ///
596    /// Returns the up direction of this camera.
597    /// This will probably not be orthogonal to the view direction, use [up_orthogonal](Camera::up_orthogonal) instead if that is needed.
598    ///
599    pub fn up(&self) -> Vec3 {
600        self.up
601    }
602
603    ///
604    /// Returns the up direction of this camera that is orthogonal to the view direction.
605    ///
606    pub fn up_orthogonal(&self) -> Vec3 {
607        self.right_direction().cross(self.view_direction())
608    }
609
610    ///
611    /// Returns the view direction of this camera, ie. the direction the camera is looking.
612    ///
613    pub fn view_direction(&self) -> Vec3 {
614        (self.target - self.position).normalize()
615    }
616
617    ///
618    /// Returns the right direction of this camera.
619    ///
620    pub fn right_direction(&self) -> Vec3 {
621        self.view_direction().cross(self.up)
622    }
623
624    fn new(viewport: Viewport) -> Camera {
625        Camera {
626            viewport,
627            projection_type: ProjectionType::Orthographic { height: 1.0 },
628            z_near: 0.0,
629            z_far: 0.0,
630            position: vec3(0.0, 0.0, 5.0),
631            target: vec3(0.0, 0.0, 0.0),
632            up: vec3(0.0, 1.0, 0.0),
633            view: Mat4::identity(),
634            projection: Mat4::identity(),
635        }
636    }
637
638    fn screen2ray(&self) -> Mat4 {
639        let mut v = self.view;
640        if let ProjectionType::Perspective { .. } = self.projection_type {
641            v[3] = vec4(0.0, 0.0, 0.0, 1.0);
642        }
643        (self.projection * v).invert().unwrap_or(Mat4::identity())
644    }
645
646    ///
647    /// Translate the camera by the given change while keeping the same view and up directions.
648    ///
649    pub fn translate(&mut self, change: Vec3) {
650        self.set_view(self.position + change, self.target + change, self.up);
651    }
652
653    ///
654    /// Rotates the camera by the angle delta around the 'right' direction.
655    ///
656    pub fn pitch(&mut self, delta: impl Into<Radians>) {
657        let target = (self.view.invert().unwrap()
658            * Mat4::from_angle_x(delta)
659            * self.view
660            * self.target.extend(1.0))
661        .truncate();
662        if (target - self.position).normalize().dot(self.up).abs() < 0.999 {
663            self.set_view(self.position, target, self.up);
664        }
665    }
666
667    ///
668    /// Rotates the camera by the angle delta around the 'up' direction.
669    ///
670    pub fn yaw(&mut self, delta: impl Into<Radians>) {
671        let target = (self.view.invert().unwrap()
672            * Mat4::from_angle_y(delta)
673            * self.view
674            * self.target.extend(1.0))
675        .truncate();
676        self.set_view(self.position, target, self.up);
677    }
678
679    ///
680    /// Rotates the camera by the angle delta around the 'view' direction.
681    ///
682    pub fn roll(&mut self, delta: impl Into<Radians>) {
683        let up = (self.view.invert().unwrap()
684            * Mat4::from_angle_z(delta)
685            * self.view
686            * (self.up + self.position).extend(1.0))
687        .truncate()
688            - self.position;
689        self.set_view(self.position, self.target, up.normalize());
690    }
691
692    ///
693    /// Rotate the camera around the given point while keeping the same distance to the point.
694    /// The input `x` specifies the amount of rotation in the left direction and `y` specifies the amount of rotation in the up direction.
695    /// If you want the camera up direction to stay fixed, use the [rotate_around_with_fixed_up](Camera::rotate_around_with_fixed_up) function instead.
696    ///
697    pub fn rotate_around(&mut self, point: Vec3, x: f32, y: f32) {
698        let dir = (point - self.position()).normalize();
699        let right = dir.cross(self.up);
700        let up = right.cross(dir);
701        let new_dir = (point - self.position() + right * x - up * y).normalize();
702        let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
703        let new_position = (rotation * (self.position() - point).extend(1.0)).truncate() + point;
704        let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
705        self.set_view(new_position, new_target, up);
706    }
707
708    ///
709    /// Rotate the camera around the given point while keeping the same distance to the point and the same up direction.
710    /// The input `x` specifies the amount of rotation in the left direction and `y` specifies the amount of rotation in the up direction.
711    ///
712    pub fn rotate_around_with_fixed_up(&mut self, point: Vec3, x: f32, y: f32) {
713        // Since rotations in linear algebra always describe rotations about the origin, we
714        // subtract the point, do all rotations, and add the point again
715        let position = self.position() - point;
716        let target = self.target() - point;
717        let up = self.up.normalize();
718        // We use Rodrigues' rotation formula to rotate around the fixed `up` vector and around the
719        // horizon which is calculated from the camera's view direction and `up`
720        // https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
721        let k_x = up;
722        let k_y = (target - position).cross(up).normalize();
723        // Prepare cos and sin terms, inverted because the method rotates left and up while
724        // rotations follow the right hand rule
725        let cos_x = (-x).cos();
726        let sin_x = (-x).sin();
727        let cos_y = (-y).cos();
728        let sin_y = (-y).sin();
729        // Do the rotations following the rotation formula
730        let rodrigues =
731            |v, k: Vec3, cos, sin| v * cos + k.cross(v) * sin + k * k.dot(v) * (1.0 - cos);
732        let position_x = rodrigues(position, k_x, cos_x, sin_x);
733        let target_x = rodrigues(target, k_x, cos_x, sin_x);
734        let position_y = rodrigues(position_x, k_y, cos_y, sin_y);
735        let target_y = rodrigues(target_x, k_y, cos_y, sin_y);
736        // Forbid to face the camera exactly up or down, fall back to just rotate in x direction
737        let new_dir = (target_y - position_y).normalize();
738        if new_dir.dot(up).abs() < 0.999 {
739            self.set_view(position_y + point, target_y + point, self.up);
740        } else {
741            self.set_view(position_x + point, target_x + point, self.up);
742        }
743    }
744
745    ///
746    /// Moves the camera towards the camera target by the amount delta while keeping the given minimum and maximum distance to the target.
747    ///
748    pub fn zoom(&mut self, delta: f32, minimum_distance: f32, maximum_distance: f32) {
749        self.zoom_towards(self.target, delta, minimum_distance, maximum_distance);
750    }
751
752    ///
753    /// Moves the camera towards the given point by the amount delta while keeping the given minimum and maximum distance to the camera target.
754    /// Note that the camera target is also updated so that the view direction is the same.
755    ///
756    pub fn zoom_towards(
757        &mut self,
758        point: Vec3,
759        delta: f32,
760        minimum_distance: f32,
761        maximum_distance: f32,
762    ) {
763        let view = self.view_direction();
764        let towards = (point - self.position).normalize();
765        let cos_angle = view.dot(towards);
766        if cos_angle.abs() > f32::EPSILON {
767            let distance = self.target.distance(self.position);
768            let minimum_distance = minimum_distance.max(f32::EPSILON);
769            let maximum_distance = maximum_distance.max(minimum_distance);
770            let delta_clamped =
771                distance - (distance - delta).clamp(minimum_distance, maximum_distance);
772            let a = view * delta_clamped;
773            let b = towards * delta_clamped / cos_angle;
774            self.set_view(self.position + b, self.target + b - a, self.up);
775        }
776    }
777
778    ///
779    /// Sets the zoom factor of this camera, ie. the distance to the camera will be `1/zoom_factor`.
780    ///
781    pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
782        let zoom_factor = zoom_factor.max(f32::EPSILON);
783        let position = self.target - self.view_direction() / zoom_factor;
784        self.set_view(position, self.target, self.up);
785    }
786
787    ///
788    /// The zoom factor for this camera, which is the same as one over the distance between the camera position and target.
789    ///
790    pub fn zoom_factor(&self) -> f32 {
791        let distance = self.target.distance(self.position);
792        if distance > f32::EPSILON {
793            1.0 / distance
794        } else {
795            0.0
796        }
797    }
798}