Skip to main content

molgfx_math/camera/
projection.rs

1//! Projection construction with reversed [0, 1] clip depth.
2//!
3//! Reversed depth (near plane at 1.0, far at 0.0) is applied here, once, at
4//! matrix construction; nothing downstream reasons about the clip convention
5//! again. Near and far are fit to the scene's bounding sphere rather than
6//! fixed, so depth precision follows the structure on screen.
7
8use crate::aabb::BoundingSphere;
9use crate::{Mat4, Vec3};
10use serde::{Deserialize, Serialize};
11
12#[cfg(test)]
13#[path = "projection_tests.rs"]
14mod tests;
15
16/// The closest the fitted near plane may come to the camera, in Ångström.
17/// Keeps the projection finite when the camera sits inside the scene bound.
18const MIN_NEAR: f32 = 0.01;
19
20/// How a camera maps view space to clip space.
21///
22/// Orthographic projection is offered as a first-class choice because it is
23/// often the physically correct one: parallel lines stay parallel and
24/// distances compare across the image.
25#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
26pub enum Projection {
27    /// Perspective projection with a vertical field of view in radians.
28    Perspective {
29        /// Vertical field of view, radians.
30        fov_y: f32,
31        /// Width over height.
32        aspect: f32,
33        /// Near plane distance, Ångström; maps to clip depth 1.0.
34        near: f32,
35        /// Far plane distance, Ångström; maps to clip depth 0.0.
36        far: f32,
37    },
38    /// Orthographic projection with an explicit vertical extent.
39    Orthographic {
40        /// Full visible height, Ångström.
41        height: f32,
42        /// Width over height.
43        aspect: f32,
44        /// Near plane distance, Ångström; maps to clip depth 1.0.
45        near: f32,
46        /// Far plane distance, Ångström; maps to clip depth 0.0.
47        far: f32,
48    },
49}
50
51impl Projection {
52    /// The clip-from-view matrix with reversed [0, 1] depth.
53    #[must_use]
54    pub fn matrix(&self) -> Mat4 {
55        match *self {
56            Self::Perspective {
57                fov_y,
58                aspect,
59                near,
60                far,
61            } => {
62                // The right-handed [0, 1] projection maps near→0, far→1;
63                // swapping the plane arguments reverses the depth range.
64                glam::camera::rh::proj::directx::perspective(fov_y, aspect, far, near).into()
65            }
66            Self::Orthographic {
67                height,
68                aspect,
69                near,
70                far,
71            } => {
72                let half_h = height * 0.5;
73                let half_w = half_h * aspect;
74                glam::camera::rh::proj::directx::orthographic(
75                    -half_w, half_w, -half_h, half_h, far, near,
76                )
77                .into()
78            }
79        }
80    }
81
82    /// Refits the near and far planes to a world-space bounding sphere seen
83    /// from `eye` looking toward the sphere. Called on camera change so the
84    /// depth range always brackets the scene tightly.
85    pub fn fit_near_far(&mut self, eye: Vec3, bound: &BoundingSphere) {
86        let distance = eye.distance(bound.center);
87        let pad = bound.radius.max(1.0) * 0.01;
88        let new_far = distance + bound.radius + pad;
89        let new_near = (distance - bound.radius - pad).max(MIN_NEAR);
90        match self {
91            Self::Perspective { near, far, .. } | Self::Orthographic { near, far, .. } => {
92                *near = new_near;
93                *far = new_far;
94            }
95        }
96    }
97
98    /// Width over height.
99    #[must_use]
100    pub fn aspect(&self) -> f32 {
101        match *self {
102            Self::Perspective { aspect, .. } | Self::Orthographic { aspect, .. } => aspect,
103        }
104    }
105
106    /// Updates the aspect ratio, preserving everything else.
107    pub fn set_aspect(&mut self, new_aspect: f32) {
108        match self {
109            Self::Perspective { aspect, .. } | Self::Orthographic { aspect, .. } => {
110                *aspect = new_aspect;
111            }
112        }
113    }
114}