1use crate::math::prelude::{Aabb3, Plane, PlaneBound, PlaneRelation};
4
5use cgmath::num_traits::cast;
6use cgmath::prelude::*;
7use cgmath::{BaseFloat, Matrix, Matrix4, Point3, Rad};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum Projection<S: BaseFloat> {
12 Ortho {
14 width: S,
16 height: S,
18 near: S,
20 far: S,
22 },
23
24 Perspective {
26 fovy: Rad<S>,
28 aspect: S,
30 near: S,
32 far: S,
34 },
35}
36
37impl<S: BaseFloat> Projection<S> {
38 pub fn ortho(width: S, height: S, near: S, far: S) -> Self {
39 Projection::Ortho {
40 width,
41 height,
42 near,
43 far,
44 }
45 }
46
47 pub fn perspective(fovy: Rad<S>, aspect: S, near: S, far: S) -> Self {
48 Projection::Perspective {
49 fovy,
50 aspect,
51 near,
52 far,
53 }
54 }
55
56 pub fn to_matrix(&self) -> Matrix4<S> {
57 Self::matrix(*self)
58 }
59
60 pub fn validate(&self) {
61 match *self {
62 Projection::Perspective {
63 fovy,
64 aspect,
65 near,
66 far,
67 } => {
68 assert!(
69 fovy > Rad::zero(),
70 "The vertical field of view cannot be below zero, found: {:?}",
71 fovy
72 );
73
74 assert!(
75 fovy < Rad::turn_div_2(),
76 "The vertical field of view cannot be greater than a half turn, found: {:?}",
77 fovy
78 );
79
80 assert!(
81 aspect > S::zero(),
82 "The aspect ratio cannot be below zero, found: {:?}",
83 aspect
84 );
85
86 assert!(
87 near > S::zero(),
88 "The near plane distance cannot be below zero, found: {:?}",
89 near
90 );
91
92 assert!(
93 far > S::zero(),
94 "The far plane distance cannot be below zero, found: {:?}",
95 far
96 );
97
98 assert!(
99 far > near,
100 "The far plane cannot be closer than the near plane, found: far: {:?}, near: {:?}",
101 far,
102 near
103 );
104 }
105 Projection::Ortho {
106 width,
107 height,
108 near,
109 far,
110 } => {
111 assert!(
112 width > S::zero(),
113 "The width cannot be below zero, found: {:?}",
114 width
115 );
116
117 assert!(
118 height > S::zero(),
119 "The height cannot be below zero, found: {:?}",
120 height
121 );
122
123 assert!(
124 far > near,
125 "The far plane cannot be closer than the near plane, found: far: {:?}, near: {:?}",
126 far,
127 near
128 );
129 }
130 }
131 }
132
133 pub fn matrix(projection: Projection<S>) -> Matrix4<S> {
135 match projection {
136 Projection::Ortho {
137 width,
138 height,
139 near,
140 far,
141 } => Self::ortho_matrix(width, height, near, far),
142 Projection::Perspective {
143 fovy,
144 aspect,
145 near,
146 far,
147 } => Self::perspective_matrix(fovy, aspect, near, far),
148 }
149 }
150
151 pub fn ortho_matrix(w: S, h: S, n: S, f: S) -> Matrix4<S> {
153 let half: S = cast(0.5).unwrap();
154 let two: S = cast(2.0).unwrap();
155 let zero = S::zero();
156 let one = S::one();
157
158 let (hw, hh) = (w * half, h * half);
159
160 let (l0, r0) = (-hw, hw);
161 let (b0, t0) = (-hh, hh);
162
163 let c0 = [two / (r0 - l0), zero, zero, zero];
164 let c1 = [zero, two / (t0 - b0), zero, zero];
165 let c2 = [zero, zero, two / (f - n), zero];
166 let c3 = [
167 (r0 + l0) / (l0 - r0),
168 (t0 + b0) / (b0 - t0),
169 (f + n) / (n - f),
170 one,
171 ];
172 Matrix4::from_cols(c0.into(), c1.into(), c2.into(), c3.into())
173 }
174
175 pub fn perspective_matrix(fovy: Rad<S>, aspect: S, n: S, f: S) -> Matrix4<S> {
177 let half: S = cast(0.5).unwrap();
178 let two: S = cast(2.0).unwrap();
179 let zero = S::zero();
180 let one = S::one();
181
182 let fc = Rad::cot(fovy * half);
183 let c0 = [fc / aspect, zero, zero, zero];
184 let c1 = [zero, fc, zero, zero];
185 let c2 = [zero, zero, (f + n) / (f - n), one];
186 let c3 = [zero, zero, (two * f * n) / (n - f), zero];
187 Matrix4::from_cols(c0.into(), c1.into(), c2.into(), c3.into())
188 }
189}
190
191#[derive(Copy, Clone, Debug, PartialEq)]
193pub struct Frustum<S: BaseFloat> {
194 projection: Projection<S>,
195
196 pub left: Plane<S>,
198 pub right: Plane<S>,
200 pub bottom: Plane<S>,
202 pub top: Plane<S>,
204 pub near: Plane<S>,
206 pub far: Plane<S>,
208}
209
210impl<S: BaseFloat> Frustum<S> {
211 pub fn new(projection: Projection<S>) -> Frustum<S> {
213 projection.validate();
214 let mat = Projection::matrix(projection);
215
216 Frustum {
217 projection,
218
219 left: Plane::from_vector4_alt(mat.row(3) + mat.row(0))
220 .normalize()
221 .unwrap(),
222
223 right: Plane::from_vector4_alt(mat.row(3) - mat.row(0))
224 .normalize()
225 .unwrap(),
226
227 bottom: Plane::from_vector4_alt(mat.row(3) + mat.row(1))
228 .normalize()
229 .unwrap(),
230
231 top: Plane::from_vector4_alt(mat.row(3) - mat.row(1))
232 .normalize()
233 .unwrap(),
234
235 near: Plane::from_vector4_alt(mat.row(3) + mat.row(2))
236 .normalize()
237 .unwrap(),
238
239 far: Plane::from_vector4_alt(mat.row(3) - mat.row(2))
240 .normalize()
241 .unwrap(),
242 }
243 }
244
245 pub fn projection(&self) -> Projection<S> {
246 self.projection
247 }
248
249 pub fn contains<B: PlaneBound<S>>(&self, bound: &B) -> PlaneRelation {
251 [
252 self.left,
253 self.right,
254 self.top,
255 self.bottom,
256 self.near,
257 self.far,
258 ]
259 .iter()
260 .fold(PlaneRelation::In, |cur, p| {
261 use std::cmp::max;
262 let r = bound.relate(*p);
263 max(cur, r)
267 })
268 }
269
270 pub fn to_matrix(&self) -> Matrix4<S> {
271 self.projection.to_matrix()
272 }
273}
274
275#[derive(Copy, Clone, PartialEq, Debug)]
277pub struct FrustumPoints<S: BaseFloat> {
278 pub near_top_left: Point3<S>,
280 pub near_top_right: Point3<S>,
282 pub near_bottom_left: Point3<S>,
284 pub near_bottom_right: Point3<S>,
286 pub far_top_left: Point3<S>,
288 pub far_top_right: Point3<S>,
290 pub far_bottom_left: Point3<S>,
292 pub far_bottom_right: Point3<S>,
294}
295
296impl<S: BaseFloat> FrustumPoints<S> {
297 #[inline]
300 pub fn transform<T>(&self, transform: &T) -> Self
301 where
302 T: Transform<Point3<S>>,
303 {
304 FrustumPoints {
305 near_top_left: transform.transform_point(self.near_top_left),
306 near_top_right: transform.transform_point(self.near_top_right),
307 near_bottom_left: transform.transform_point(self.near_bottom_left),
308 near_bottom_right: transform.transform_point(self.near_bottom_right),
309 far_top_left: transform.transform_point(self.far_top_left),
310 far_top_right: transform.transform_point(self.far_top_right),
311 far_bottom_left: transform.transform_point(self.far_bottom_left),
312 far_bottom_right: transform.transform_point(self.far_bottom_right),
313 }
314 }
315
316 #[inline]
318 pub fn to_corners(&self) -> [Point3<S>; 8] {
319 [
320 self.near_top_left,
321 self.near_top_right,
322 self.near_bottom_left,
323 self.near_bottom_right,
324 self.far_top_left,
325 self.far_top_right,
326 self.far_bottom_left,
327 self.far_bottom_right,
328 ]
329 }
330
331 #[inline]
333 pub fn aabb(&self) -> Aabb3<S> {
334 let aabb = Aabb3::zero();
335 self.to_corners()[..]
336 .iter()
337 .fold(aabb, |u, &corner| u.grow(corner))
338 }
339}
340
341impl<S: BaseFloat> Into<FrustumPoints<S>> for Frustum<S> {
342 fn into(self) -> FrustumPoints<S> {
343 match self.projection {
344 Projection::Ortho {
345 width,
346 height,
347 near,
348 far,
349 } => {
350 let half: S = cast(0.5).unwrap();
351 let (hw, hh) = (width * half, height * half);
352 let (l, r) = (-hw, hw);
353 let (b, t) = (-hh, hh);
354
355 FrustumPoints {
356 near_top_left: Point3::new(near, t, l),
357 near_top_right: Point3::new(near, t, r),
358 near_bottom_left: Point3::new(near, b, l),
359 near_bottom_right: Point3::new(near, b, r),
360 far_top_left: Point3::new(far, t, l),
361 far_top_right: Point3::new(far, t, r),
362 far_bottom_left: Point3::new(far, b, l),
363 far_bottom_right: Point3::new(far, b, r),
364 }
365 }
366 Projection::Perspective {
367 fovy,
368 aspect,
369 near,
370 far,
371 } => {
372 let m = Projection::perspective_matrix(fovy, aspect, near, far);
373 let im = m.invert().unwrap();
374 let one = S::one();
375
376 let points = FrustumPoints {
377 near_top_left: Point3::new(-one, one, -one),
378 near_top_right: Point3::new(-one, one, one),
379 near_bottom_left: Point3::new(-one, -one, -one),
380 near_bottom_right: Point3::new(-one, -one, one),
381 far_top_left: Point3::new(one, one, -one),
382 far_top_right: Point3::new(one, one, one),
383 far_bottom_left: Point3::new(one, -one, -one),
384 far_bottom_right: Point3::new(one, -one, one),
385 };
386
387 points.transform(&im)
388 }
389 }
390 }
391}
392
393impl<S: BaseFloat> Into<Matrix4<S>> for Frustum<S> {
394 fn into(self) -> Matrix4<S> {
395 self.projection.to_matrix()
396 }
397}