molgfx_math/camera/
camera.rs1use crate::aabb::{Aabb, BoundingSphere};
8use crate::projection::Projection;
9use crate::{Mat4, Vec3, Vec4};
10use serde::{Deserialize, Serialize};
11
12#[cfg(test)]
13#[path = "camera_tests.rs"]
14mod tests;
15
16#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
19pub struct Camera {
20 pub eye: Vec3,
22 pub target: Vec3,
24 pub up: Vec3,
26 pub projection: Projection,
28}
29
30impl Camera {
31 #[must_use]
33 pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Mat4 {
34 glam::camera::rh::view::look_at_mat4(eye.into(), target.into(), up.into()).into()
35 }
36
37 #[must_use]
41 pub fn framing_aabb(bound: &Aabb, aspect: f32) -> Self {
42 if bound.is_empty() {
43 return Self::framing(
44 &BoundingSphere {
45 center: Vec3::ZERO,
46 radius: 1.0,
47 },
48 aspect,
49 );
50 }
51 let fov_y = std::f32::consts::FRAC_PI_4;
52 let half = bound.half_extents() * 1.12;
53 let vertical_tangent = (fov_y * 0.5).tan();
54 let horizontal_tangent = vertical_tangent * aspect.max(1e-3);
55 let extents = [half.x, half.y, half.z];
61 let mut depth_axis = 2usize;
62 for axis in [1usize, 0usize] {
63 if extents[axis] < extents[depth_axis] {
64 depth_axis = axis;
65 }
66 }
67 let first = (depth_axis + 1) % 3;
68 let second = (depth_axis + 2) % 3;
69 let (horizontal_axis, vertical_axis) = if extents[first] >= extents[second] {
72 (first, second)
73 } else {
74 (second, first)
75 };
76 let projected = (extents[vertical_axis] / vertical_tangent)
77 .max(extents[horizontal_axis] / horizontal_tangent)
78 .max(1.0);
79 let center = bound.center();
80 let unit = |axis: usize| match axis {
81 0 => Vec3::X,
82 1 => Vec3::Y,
83 _ => Vec3::Z,
84 };
85 let eye = center + unit(depth_axis) * (extents[depth_axis] + projected);
86 let up = unit(vertical_axis);
87 let sphere = bound.bounding_sphere();
88 let mut projection = Projection::Perspective {
89 fov_y,
90 aspect,
91 near: 0.1,
92 far: eye.distance(center) + sphere.radius * 2.0,
93 };
94 projection.fit_near_far(eye, &sphere);
95 Self {
96 eye,
97 target: center,
98 up,
99 projection,
100 }
101 }
102
103 #[must_use]
106 pub fn framing(bound: &BoundingSphere, aspect: f32) -> Self {
107 let fov_y = std::f32::consts::FRAC_PI_4;
108 let radius = bound.radius.max(1.0);
109 let half_min_fov = if aspect < 1.0 {
111 (fov_y * 0.5).tan() * aspect
112 } else {
113 (fov_y * 0.5).tan()
114 };
115 let distance = radius / half_min_fov.clamp(1e-3, 1.0) * 1.2;
116 let eye = bound.center + Vec3::new(0.0, 0.0, distance);
117 let mut projection = Projection::Perspective {
118 fov_y,
119 aspect,
120 near: 0.1,
121 far: distance + radius * 2.0,
122 };
123 projection.fit_near_far(eye, bound);
124 Self {
125 eye,
126 target: bound.center,
127 up: Vec3::Y,
128 projection,
129 }
130 }
131
132 #[must_use]
134 pub fn view(&self) -> Mat4 {
135 glam::camera::rh::view::look_at_mat4(self.eye.into(), self.target.into(), self.up.into())
136 .into()
137 }
138
139 #[must_use]
142 pub fn view_proj(&self) -> Mat4 {
143 self.projection.matrix() * self.view()
144 }
145
146 #[must_use]
150 pub fn frustum_planes(&self) -> [Vec4; 6] {
151 let m = self.view_proj();
152 let row = |i: usize| m.row(i);
153 let (r0, r1, r2, r3) = (row(0), row(1), row(2), row(3));
154 let normalize = |p: Vec4| {
155 let len = p.truncate().length();
156 if len > 0.0 { p / len } else { p }
157 };
158 [
159 normalize(r3 + r0), normalize(r3 - r0), normalize(r3 + r1), normalize(r3 - r1), normalize(r3 - r2), normalize(r2), ]
168 }
169
170 #[must_use]
172 pub fn focus_distance(&self) -> f32 {
173 self.eye.distance(self.target)
174 }
175}