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}
227
228///
229/// Represents a camera used for viewing 3D assets.
230///
231#[derive(Clone, Debug)]
232#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
233pub struct Camera {
234    viewport: Viewport,
235    projection_type: ProjectionType,
236    z_near: f32,
237    z_far: f32,
238    position: Vec3,
239    target: Vec3,
240    up: Vec3,
241    view: Mat4,
242    projection: Mat4,
243}
244
245impl Camera {
246    ///
247    /// New camera which projects the world with an orthographic projection.
248    ///
249    pub fn new_orthographic(
250        viewport: Viewport,
251        position: Vec3,
252        target: Vec3,
253        up: Vec3,
254        height: f32,
255        z_near: f32,
256        z_far: f32,
257    ) -> Self {
258        let mut camera = Camera::new(viewport);
259        camera.set_view(position, target, up);
260        camera.set_orthographic_projection(height, z_near, z_far);
261        camera
262    }
263
264    ///
265    /// New camera which projects the world with a perspective projection.
266    ///
267    pub fn new_perspective(
268        viewport: Viewport,
269        position: Vec3,
270        target: Vec3,
271        up: Vec3,
272        field_of_view_y: impl Into<Radians>,
273        z_near: f32,
274        z_far: f32,
275    ) -> Self {
276        let mut camera = Camera::new(viewport);
277        camera.set_view(position, target, up);
278        camera.set_perspective_projection(field_of_view_y, z_near, z_far);
279        camera
280    }
281
282    ///
283    /// Specify the camera to use perspective projection with the given field of view in the y-direction and near and far plane.
284    ///
285    pub fn set_perspective_projection(
286        &mut self,
287        field_of_view_y: impl Into<Radians>,
288        z_near: f32,
289        z_far: f32,
290    ) {
291        self.z_near = z_near;
292        self.z_far = z_far;
293        let field_of_view_y = field_of_view_y.into();
294        self.projection_type = ProjectionType::Perspective { field_of_view_y };
295        self.projection =
296            cgmath::perspective(field_of_view_y, self.viewport.aspect(), z_near, z_far);
297    }
298
299    ///
300    /// Specify the camera to use orthographic projection with the given dimensions.
301    /// The view frustum height is `+/- height/2`.
302    /// The view frustum width is calculated as `height * viewport.width / viewport.height`.
303    /// The view frustum depth is `z_near` to `z_far`.
304    /// All of the above values are scaled by the zoom factor which is one over the distance between the camera position and target.
305    ///
306    pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
307        self.projection_type = ProjectionType::Orthographic { height };
308        self.z_near = z_near;
309        self.z_far = z_far;
310        let zoom = self.position.distance(self.target);
311        let height = zoom * height;
312        let width = height * self.viewport.aspect();
313        self.projection = cgmath::ortho(
314            -0.5 * width,
315            0.5 * width,
316            -0.5 * height,
317            0.5 * height,
318            z_near,
319            z_far,
320        );
321    }
322
323    ///
324    /// Set the current viewport.
325    /// Returns whether or not the viewport actually changed.
326    ///
327    pub fn set_viewport(&mut self, viewport: Viewport) -> bool {
328        if self.viewport != viewport {
329            self.viewport = viewport;
330            match self.projection_type {
331                ProjectionType::Orthographic { height } => {
332                    self.set_orthographic_projection(height, self.z_near, self.z_far);
333                }
334                ProjectionType::Perspective { field_of_view_y } => {
335                    self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
336                }
337            }
338            true
339        } else {
340            false
341        }
342    }
343
344    ///
345    /// Change the view of the camera.
346    /// The camera is placed at the given position, looking at the given target and with the given up direction.
347    ///
348    pub fn set_view(&mut self, position: Vec3, target: Vec3, up: Vec3) {
349        self.position = position;
350        self.target = target;
351        self.up = up.normalize();
352        self.view = Mat4::look_at_rh(
353            Point3::from_vec(self.position),
354            Point3::from_vec(self.target),
355            self.up,
356        );
357        if let ProjectionType::Orthographic { height } = self.projection_type {
358            self.set_orthographic_projection(height, self.z_near, self.z_far);
359        }
360    }
361
362    /// Returns the [Frustum] for this camera.
363    pub fn frustum(&self) -> Frustum {
364        Frustum::new(self.projection() * self.view())
365    }
366
367    ///
368    /// Returns the 3D position at the given pixel coordinate.
369    ///
370    pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
371        match self.projection_type() {
372            ProjectionType::Orthographic { .. } => {
373                let coords = self.uv_coordinates_at_pixel(pixel);
374                self.position_at_uv_coordinates(coords)
375            }
376            ProjectionType::Perspective { .. } => self.position,
377        }
378    }
379
380    ///
381    /// Returns the 3D position at the given uv coordinate of the viewport.
382    ///
383    pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
384        match self.projection_type() {
385            ProjectionType::Orthographic { .. } => {
386                let coords = coords.into();
387                let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0.0, 1.);
388                let p = (self.screen2ray() * screen_pos).truncate();
389                p + (self.position - p).project_on(self.view_direction()) // Project onto the image plane
390            }
391            ProjectionType::Perspective { .. } => self.position,
392        }
393    }
394
395    ///
396    /// Returns the 3D view direction at the given pixel coordinate.
397    ///
398    pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
399        match self.projection_type() {
400            ProjectionType::Orthographic { .. } => self.view_direction(),
401            ProjectionType::Perspective { .. } => {
402                let coords = self.uv_coordinates_at_pixel(pixel);
403                self.view_direction_at_uv_coordinates(coords)
404            }
405        }
406    }
407
408    ///
409    /// Returns the 3D view direction at the given uv coordinate of the viewport.
410    ///
411    pub fn view_direction_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
412        match self.projection_type() {
413            ProjectionType::Orthographic { .. } => self.view_direction(),
414            ProjectionType::Perspective { .. } => {
415                let coords = coords.into();
416                let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
417                (self.screen2ray() * screen_pos).truncate().normalize()
418            }
419        }
420    }
421
422    ///
423    /// Returns the uv coordinate for the given pixel coordinate.
424    ///
425    pub fn uv_coordinates_at_pixel(&self, pixel: impl Into<PixelPoint>) -> UvCoordinate {
426        let pixel = pixel.into();
427        (
428            (pixel.x - self.viewport.x as f32) / self.viewport.width as f32,
429            (pixel.y - self.viewport.y as f32) / self.viewport.height as f32,
430        )
431            .into()
432    }
433
434    ///
435    /// Returns the uv coordinate for the given world position.
436    ///
437    pub fn uv_coordinates_at_position(&self, position: Vec3) -> UvCoordinate {
438        let proj = self.projection() * self.view() * position.extend(1.0);
439        (
440            0.5 * (proj.x / proj.w.abs() + 1.0),
441            0.5 * (proj.y / proj.w.abs() + 1.0),
442        )
443            .into()
444    }
445
446    ///
447    /// Returns the pixel coordinate for the given uv coordinate.
448    ///
449    pub fn pixel_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> PixelPoint {
450        let coords = coords.into();
451        (
452            coords.u * self.viewport.width as f32 + self.viewport.x as f32,
453            coords.v * self.viewport.height as f32 + self.viewport.y as f32,
454        )
455            .into()
456    }
457
458    ///
459    /// Returns the pixel coordinate for the given world position.
460    ///
461    pub fn pixel_at_position(&self, position: Vec3) -> PixelPoint {
462        self.pixel_at_uv_coordinates(self.uv_coordinates_at_position(position))
463    }
464
465    ///
466    /// Returns the type of projection (orthographic or perspective) including parameters.
467    ///
468    pub fn projection_type(&self) -> &ProjectionType {
469        &self.projection_type
470    }
471
472    ///
473    /// 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).
474    ///
475    pub fn view(&self) -> Mat4 {
476        self.view
477    }
478
479    ///
480    /// Returns the projection matrix, ie. the matrix that projects objects in view space onto this cameras image plane.
481    ///
482    pub fn projection(&self) -> Mat4 {
483        self.projection
484    }
485
486    ///
487    /// Returns the viewport.
488    ///
489    pub fn viewport(&self) -> Viewport {
490        self.viewport
491    }
492
493    ///
494    /// Returns the distance to the near plane of the camera frustum.
495    ///
496    pub fn z_near(&self) -> f32 {
497        self.z_near
498    }
499
500    ///
501    /// Returns the distance to the far plane of the camera frustum.
502    ///
503    pub fn z_far(&self) -> f32 {
504        self.z_far
505    }
506
507    ///
508    /// Returns the position of this camera.
509    ///
510    pub fn position(&self) -> Vec3 {
511        self.position
512    }
513
514    ///
515    /// Returns the target of this camera, ie the point that this camera looks towards.
516    ///
517    pub fn target(&self) -> Vec3 {
518        self.target
519    }
520
521    ///
522    /// Returns the up direction of this camera.
523    /// This will probably not be orthogonal to the view direction, use [up_orthogonal](Camera::up_orthogonal) instead if that is needed.
524    ///
525    pub fn up(&self) -> Vec3 {
526        self.up
527    }
528
529    ///
530    /// Returns the up direction of this camera that is orthogonal to the view direction.
531    ///
532    pub fn up_orthogonal(&self) -> Vec3 {
533        self.right_direction().cross(self.view_direction())
534    }
535
536    ///
537    /// Returns the view direction of this camera, ie. the direction the camera is looking.
538    ///
539    pub fn view_direction(&self) -> Vec3 {
540        (self.target - self.position).normalize()
541    }
542
543    ///
544    /// Returns the right direction of this camera.
545    ///
546    pub fn right_direction(&self) -> Vec3 {
547        self.view_direction().cross(self.up)
548    }
549
550    fn new(viewport: Viewport) -> Camera {
551        Camera {
552            viewport,
553            projection_type: ProjectionType::Orthographic { height: 1.0 },
554            z_near: 0.0,
555            z_far: 0.0,
556            position: vec3(0.0, 0.0, 5.0),
557            target: vec3(0.0, 0.0, 0.0),
558            up: vec3(0.0, 1.0, 0.0),
559            view: Mat4::identity(),
560            projection: Mat4::identity(),
561        }
562    }
563
564    fn screen2ray(&self) -> Mat4 {
565        let mut v = self.view;
566        if let ProjectionType::Perspective { .. } = self.projection_type {
567            v[3] = vec4(0.0, 0.0, 0.0, 1.0);
568        }
569        (self.projection * v)
570            .invert()
571            .unwrap_or_else(|| Mat4::identity())
572    }
573
574    ///
575    /// Translate the camera by the given change while keeping the same view and up directions.
576    ///
577    pub fn translate(&mut self, change: Vec3) {
578        self.set_view(self.position + change, self.target + change, self.up);
579    }
580
581    ///
582    /// Rotates the camera by the angle delta around the 'right' direction.
583    ///
584    pub fn pitch(&mut self, delta: impl Into<Radians>) {
585        let target = (self.view.invert().unwrap()
586            * Mat4::from_angle_x(delta)
587            * self.view
588            * self.target.extend(1.0))
589        .truncate();
590        if (target - self.position).normalize().dot(self.up).abs() < 0.999 {
591            self.set_view(self.position, target, self.up);
592        }
593    }
594
595    ///
596    /// Rotates the camera by the angle delta around the 'up' direction.
597    ///
598    pub fn yaw(&mut self, delta: impl Into<Radians>) {
599        let target = (self.view.invert().unwrap()
600            * Mat4::from_angle_y(delta)
601            * self.view
602            * self.target.extend(1.0))
603        .truncate();
604        self.set_view(self.position, target, self.up);
605    }
606
607    ///
608    /// Rotates the camera by the angle delta around the 'view' direction.
609    ///
610    pub fn roll(&mut self, delta: impl Into<Radians>) {
611        let up = (self.view.invert().unwrap()
612            * Mat4::from_angle_z(delta)
613            * self.view
614            * (self.up + self.position).extend(1.0))
615        .truncate()
616            - self.position;
617        self.set_view(self.position, self.target, up.normalize());
618    }
619
620    ///
621    /// Rotate the camera around the given point while keeping the same distance to the point.
622    /// The input `x` specifies the amount of rotation in the left direction and `y` specifies the amount of rotation in the up direction.
623    /// 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.
624    ///
625    pub fn rotate_around(&mut self, point: Vec3, x: f32, y: f32) {
626        let dir = (point - self.position()).normalize();
627        let right = dir.cross(self.up);
628        let up = right.cross(dir);
629        let new_dir = (point - self.position() + right * x - up * y).normalize();
630        let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
631        let new_position = (rotation * (self.position() - point).extend(1.0)).truncate() + point;
632        let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
633        self.set_view(new_position, new_target, up);
634    }
635
636    ///
637    /// Rotate the camera around the given point while keeping the same distance to the point and the same up direction.
638    /// The input `x` specifies the amount of rotation in the left direction and `y` specifies the amount of rotation in the up direction.
639    ///
640    pub fn rotate_around_with_fixed_up(&mut self, point: Vec3, x: f32, y: f32) {
641        // Since rotations in linear algebra always describe rotations about the origin, we
642        // subtract the point, do all rotations, and add the point again
643        let position = self.position() - point;
644        let target = self.target() - point;
645        let up = self.up.normalize();
646        // We use Rodrigues' rotation formula to rotate around the fixed `up` vector and around the
647        // horizon which is calculated from the camera's view direction and `up`
648        // https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
649        let k_x = up;
650        let k_y = (target - position).cross(up).normalize();
651        // Prepare cos and sin terms, inverted because the method rotates left and up while
652        // rotations follow the right hand rule
653        let cos_x = (-x).cos();
654        let sin_x = (-x).sin();
655        let cos_y = (-y).cos();
656        let sin_y = (-y).sin();
657        // Do the rotations following the rotation formula
658        let rodrigues =
659            |v, k: Vec3, cos, sin| v * cos + k.cross(v) * sin + k * k.dot(v) * (1.0 - cos);
660        let position_x = rodrigues(position, k_x, cos_x, sin_x);
661        let target_x = rodrigues(target, k_x, cos_x, sin_x);
662        let position_y = rodrigues(position_x, k_y, cos_y, sin_y);
663        let target_y = rodrigues(target_x, k_y, cos_y, sin_y);
664        // Forbid to face the camera exactly up or down, fall back to just rotate in x direction
665        let new_dir = (target_y - position_y).normalize();
666        if new_dir.dot(up).abs() < 0.999 {
667            self.set_view(position_y + point, target_y + point, self.up);
668        } else {
669            self.set_view(position_x + point, target_x + point, self.up);
670        }
671    }
672
673    ///
674    /// Moves the camera towards the camera target by the amount delta while keeping the given minimum and maximum distance to the target.
675    ///
676    pub fn zoom(&mut self, delta: f32, minimum_distance: f32, maximum_distance: f32) {
677        self.zoom_towards(self.target, delta, minimum_distance, maximum_distance);
678    }
679
680    ///
681    /// Moves the camera towards the given point by the amount delta while keeping the given minimum and maximum distance to the camera target.
682    /// Note that the camera target is also updated so that the view direction is the same.
683    ///
684    pub fn zoom_towards(
685        &mut self,
686        point: Vec3,
687        delta: f32,
688        minimum_distance: f32,
689        maximum_distance: f32,
690    ) {
691        let view = self.view_direction();
692        let towards = (point - self.position).normalize();
693        let cos_angle = view.dot(towards);
694        if cos_angle.abs() > std::f32::EPSILON {
695            let distance = self.target.distance(self.position);
696            let minimum_distance = minimum_distance.max(std::f32::EPSILON);
697            let maximum_distance = maximum_distance.max(minimum_distance);
698            let delta_clamped =
699                distance - (distance - delta).clamp(minimum_distance, maximum_distance);
700            let a = view * delta_clamped;
701            let b = towards * delta_clamped / cos_angle;
702            self.set_view(self.position + b, self.target + b - a, self.up);
703        }
704    }
705
706    ///
707    /// Sets the zoom factor of this camera, ie. the distance to the camera will be `1/zoom_factor`.
708    ///
709    pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
710        let zoom_factor = zoom_factor.max(std::f32::EPSILON);
711        let position = self.target - self.view_direction() / zoom_factor;
712        self.set_view(position, self.target, self.up);
713    }
714
715    ///
716    /// The zoom factor for this camera, which is the same as one over the distance between the camera position and target.
717    ///
718    pub fn zoom_factor(&self) -> f32 {
719        let distance = self.target.distance(self.position);
720        if distance > f32::EPSILON {
721            1.0 / distance
722        } else {
723            0.0
724        }
725    }
726}