1use crate::{aabb::AxisAlignedBoundingBox, plane::Plane};
22use nalgebra::Point3;
23use nalgebra::{Matrix4, Vector3};
24
25#[derive(Copy, Clone, Debug, PartialEq)]
26pub struct Frustum {
27 pub planes: [Plane; 6],
29 pub corners: [Vector3<f32>; 8],
30}
31
32impl Default for Frustum {
33 #[inline]
34 fn default() -> Self {
35 Self::from_view_projection_matrix(Matrix4::new_perspective(
36 1.0,
37 std::f32::consts::FRAC_PI_2,
38 0.01,
39 1024.0,
40 ))
41 .unwrap()
42 }
43}
44
45impl Frustum {
46 pub const LEFT: usize = 0;
47 pub const RIGHT: usize = 1;
48 pub const TOP: usize = 2;
49 pub const BOTTOM: usize = 3;
50 pub const FAR: usize = 4;
51 pub const NEAR: usize = 5;
52
53 #[inline]
54 pub fn from_view_projection_matrix(m: Matrix4<f32>) -> Option<Self> {
55 let planes = [
56 Plane::from_abcd(m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12])?,
58 Plane::from_abcd(m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12])?,
60 Plane::from_abcd(m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13])?,
62 Plane::from_abcd(m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13])?,
64 Plane::from_abcd(m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14])?,
66 Plane::from_abcd(m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14])?,
68 ];
69
70 let corners = [
71 planes[Self::LEFT].intersection_point(&planes[Self::TOP], &planes[Self::FAR]),
72 planes[Self::LEFT].intersection_point(&planes[Self::BOTTOM], &planes[Self::FAR]),
73 planes[Self::RIGHT].intersection_point(&planes[Self::BOTTOM], &planes[Self::FAR]),
74 planes[Self::RIGHT].intersection_point(&planes[Self::TOP], &planes[Self::FAR]),
75 planes[Self::LEFT].intersection_point(&planes[Self::TOP], &planes[Self::NEAR]),
76 planes[Self::LEFT].intersection_point(&planes[Self::BOTTOM], &planes[Self::NEAR]),
77 planes[Self::RIGHT].intersection_point(&planes[Self::BOTTOM], &planes[Self::NEAR]),
78 planes[Self::RIGHT].intersection_point(&planes[Self::TOP], &planes[Self::NEAR]),
79 ];
80
81 Some(Self { planes, corners })
82 }
83
84 #[inline]
85 pub fn left(&self) -> &Plane {
86 self.planes.first().unwrap()
87 }
88
89 #[inline]
90 pub fn right(&self) -> &Plane {
91 self.planes.get(1).unwrap()
92 }
93
94 #[inline]
95 pub fn top(&self) -> &Plane {
96 self.planes.get(2).unwrap()
97 }
98
99 #[inline]
100 pub fn bottom(&self) -> &Plane {
101 self.planes.get(3).unwrap()
102 }
103
104 #[inline]
105 pub fn far(&self) -> &Plane {
106 self.planes.get(4).unwrap()
107 }
108
109 #[inline]
110 pub fn near(&self) -> &Plane {
111 self.planes.get(5).unwrap()
112 }
113
114 #[inline]
115 pub fn planes(&self) -> &[Plane] {
116 &self.planes
117 }
118
119 #[inline]
120 pub fn left_top_front_corner(&self) -> Vector3<f32> {
121 self.corners[0]
122 }
123
124 #[inline]
125 pub fn left_bottom_front_corner(&self) -> Vector3<f32> {
126 self.corners[1]
127 }
128
129 #[inline]
130 pub fn right_bottom_front_corner(&self) -> Vector3<f32> {
131 self.corners[2]
132 }
133
134 #[inline]
135 pub fn right_top_front_corner(&self) -> Vector3<f32> {
136 self.corners[3]
137 }
138
139 #[inline]
140 pub fn left_top_back_corner(&self) -> Vector3<f32> {
141 self.corners[4]
142 }
143
144 #[inline]
145 pub fn left_bottom_back_corner(&self) -> Vector3<f32> {
146 self.corners[5]
147 }
148
149 #[inline]
150 pub fn right_bottom_back_corner(&self) -> Vector3<f32> {
151 self.corners[6]
152 }
153
154 #[inline]
155 pub fn right_top_back_corner(&self) -> Vector3<f32> {
156 self.corners[7]
157 }
158
159 #[inline]
160 pub fn corners(&self) -> [Vector3<f32>; 8] {
161 [
162 self.left_top_front_corner(),
163 self.left_bottom_front_corner(),
164 self.right_bottom_front_corner(),
165 self.right_top_front_corner(),
166 self.left_top_back_corner(),
167 self.left_bottom_back_corner(),
168 self.right_bottom_back_corner(),
169 self.right_top_back_corner(),
170 ]
171 }
172
173 #[inline]
174 pub fn near_plane_center(&self) -> Vector3<f32> {
175 (self.left_top_front_corner()
176 + self.left_bottom_front_corner()
177 + self.right_bottom_front_corner()
178 + self.right_top_front_corner())
179 .scale(1.0 / 4.0)
180 }
181
182 #[inline]
183 pub fn far_plane_center(&self) -> Vector3<f32> {
184 (self.left_top_back_corner()
185 + self.left_bottom_back_corner()
186 + self.right_bottom_back_corner()
187 + self.right_top_back_corner())
188 .scale(1.0 / 4.0)
189 }
190
191 #[inline]
192 pub fn view_direction(&self) -> Vector3<f32> {
193 self.far_plane_center() - self.near_plane_center()
194 }
195
196 #[inline]
197 pub fn center(&self) -> Vector3<f32> {
198 self.corners()
199 .iter()
200 .fold(Vector3::default(), |acc, corner| acc + *corner)
201 .scale(1.0 / 8.0)
202 }
203
204 #[inline]
205 pub fn is_intersects_point_cloud(&self, points: &[Vector3<f32>]) -> bool {
206 for plane in self.planes.iter() {
207 let mut back_points = 0;
208 for point in points {
209 if plane.dot(point) <= 0.0 {
210 back_points += 1;
211 if back_points >= points.len() {
212 return false;
214 }
215 }
216 }
217 }
218 true
219 }
220
221 #[inline]
222 pub fn is_intersects_aabb(&self, aabb: &AxisAlignedBoundingBox) -> bool {
223 let corners = [
224 Vector3::new(aabb.min.x, aabb.min.y, aabb.min.z),
225 Vector3::new(aabb.min.x, aabb.min.y, aabb.max.z),
226 Vector3::new(aabb.max.x, aabb.min.y, aabb.max.z),
227 Vector3::new(aabb.max.x, aabb.min.y, aabb.min.z),
228 Vector3::new(aabb.min.x, aabb.max.y, aabb.min.z),
229 Vector3::new(aabb.min.x, aabb.max.y, aabb.max.z),
230 Vector3::new(aabb.max.x, aabb.max.y, aabb.max.z),
231 Vector3::new(aabb.max.x, aabb.max.y, aabb.min.z),
232 ];
233
234 if self.is_intersects_point_cloud(&corners) {
235 return true;
236 }
237
238 for corner in self.corners.iter() {
239 if aabb.is_contains_point(*corner) {
240 return true;
241 }
242 }
243
244 false
245 }
246
247 #[inline]
248 pub fn is_intersects_aabb_offset(
249 &self,
250 aabb: &AxisAlignedBoundingBox,
251 offset: Vector3<f32>,
252 ) -> bool {
253 let corners = [
254 Vector3::new(aabb.min.x, aabb.min.y, aabb.min.z) + offset,
255 Vector3::new(aabb.min.x, aabb.min.y, aabb.max.z) + offset,
256 Vector3::new(aabb.max.x, aabb.min.y, aabb.max.z) + offset,
257 Vector3::new(aabb.max.x, aabb.min.y, aabb.min.z) + offset,
258 Vector3::new(aabb.min.x, aabb.max.y, aabb.min.z) + offset,
259 Vector3::new(aabb.min.x, aabb.max.y, aabb.max.z) + offset,
260 Vector3::new(aabb.max.x, aabb.max.y, aabb.max.z) + offset,
261 Vector3::new(aabb.max.x, aabb.max.y, aabb.min.z) + offset,
262 ];
263
264 if self.is_intersects_point_cloud(&corners) {
265 return true;
266 }
267
268 for corner in self.corners.iter() {
269 if aabb.is_contains_point(*corner) {
270 return true;
271 }
272 }
273
274 false
275 }
276
277 #[deprecated(
278 since = "0.29.0",
279 note = "this method does not handle all cases and could give weird results"
280 )]
281 #[inline]
282 pub fn is_intersects_aabb_transform(
283 &self,
284 aabb: &AxisAlignedBoundingBox,
285 transform: &Matrix4<f32>,
286 ) -> bool {
287 if self.is_contains_point(
288 transform
289 .transform_point(&Point3::from(aabb.center()))
290 .coords,
291 ) {
292 return true;
293 }
294
295 let corners = [
296 transform
297 .transform_point(&Point3::new(aabb.min.x, aabb.min.y, aabb.min.z))
298 .coords,
299 transform
300 .transform_point(&Point3::new(aabb.min.x, aabb.min.y, aabb.max.z))
301 .coords,
302 transform
303 .transform_point(&Point3::new(aabb.max.x, aabb.min.y, aabb.max.z))
304 .coords,
305 transform
306 .transform_point(&Point3::new(aabb.max.x, aabb.min.y, aabb.min.z))
307 .coords,
308 transform
309 .transform_point(&Point3::new(aabb.min.x, aabb.max.y, aabb.min.z))
310 .coords,
311 transform
312 .transform_point(&Point3::new(aabb.min.x, aabb.max.y, aabb.max.z))
313 .coords,
314 transform
315 .transform_point(&Point3::new(aabb.max.x, aabb.max.y, aabb.max.z))
316 .coords,
317 transform
318 .transform_point(&Point3::new(aabb.max.x, aabb.max.y, aabb.min.z))
319 .coords,
320 ];
321
322 self.is_intersects_point_cloud(&corners)
323 }
324
325 #[inline]
326 pub fn is_contains_point(&self, pt: Vector3<f32>) -> bool {
327 for plane in self.planes.iter() {
328 if plane.dot(&pt) <= 0.0 {
329 return false;
330 }
331 }
332 true
333 }
334
335 #[inline]
336 pub fn is_intersects_sphere(&self, p: Vector3<f32>, r: f32) -> bool {
337 for plane in self.planes.iter() {
338 let d = plane.dot(&p);
339 if d < -r {
340 return false;
341 }
342 if d.abs() < r {
343 return true;
344 }
345 }
346 true
347 }
348}
349
350#[cfg(test)]
351mod test {
352 use crate::aabb::AxisAlignedBoundingBox;
353 use crate::{frustum::Frustum, plane::Plane};
354 use nalgebra::{Matrix4, Vector3};
355
356 #[test]
357 fn test_default_for_frustum() {
358 assert_eq!(
359 Frustum::default(),
360 Frustum::from_view_projection_matrix(Matrix4::new_perspective(
361 1.0,
362 std::f32::consts::FRAC_PI_2,
363 0.01,
364 1024.0
365 ))
366 .unwrap()
367 );
368 }
369
370 #[test]
371 fn test_frustum_from_view_projection_matrix() {
372 assert_eq!(
373 Frustum::from_view_projection_matrix(Matrix4::new(
374 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0
378 )),
379 Some(Frustum {
380 planes: [
381 Plane::from_abcd(1.0, 0.0, 0.0, 1.0).unwrap(),
382 Plane::from_abcd(-1.0, 0.0, 0.0, 1.0).unwrap(),
383 Plane::from_abcd(0.0, -1.0, 0.0, 1.0).unwrap(),
384 Plane::from_abcd(0.0, 1.0, 0.0, 1.0).unwrap(),
385 Plane::from_abcd(0.0, 0.0, -1.0, 1.0).unwrap(),
386 Plane::from_abcd(0.0, 0.0, 1.0, 1.0).unwrap(),
387 ],
388 corners: [
389 Vector3::new(-1.0, 1.0, 1.0),
390 Vector3::new(-1.0, -1.0, 1.0),
391 Vector3::new(1.0, -1.0, 1.0),
392 Vector3::new(1.0, 1.0, 1.0),
393 Vector3::new(-1.0, 1.0, -1.0),
394 Vector3::new(-1.0, -1.0, -1.0),
395 Vector3::new(1.0, -1.0, -1.0),
396 Vector3::new(1.0, 1.0, -1.0),
397 ],
398 })
399 );
400 }
401
402 #[test]
403 fn test_frustum_planes_and_corners() {
404 let f = Frustum::from_view_projection_matrix(Matrix4::new(
405 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
409 ))
410 .unwrap();
411
412 assert_eq!(f.left(), &Plane::from_abcd(1.0, 0.0, 0.0, 1.0).unwrap());
413 assert_eq!(f.right(), &Plane::from_abcd(-1.0, 0.0, 0.0, 1.0).unwrap());
414 assert_eq!(f.top(), &Plane::from_abcd(0.0, -1.0, 0.0, 1.0).unwrap());
415 assert_eq!(f.bottom(), &Plane::from_abcd(0.0, 1.0, 0.0, 1.0).unwrap());
416 assert_eq!(f.far(), &Plane::from_abcd(0.0, 0.0, -1.0, 1.0).unwrap());
417 assert_eq!(f.near(), &Plane::from_abcd(0.0, 0.0, 1.0, 1.0).unwrap());
418
419 assert_eq!(
420 f.planes(),
421 [
422 Plane::from_abcd(1.0, 0.0, 0.0, 1.0).unwrap(),
423 Plane::from_abcd(-1.0, 0.0, 0.0, 1.0).unwrap(),
424 Plane::from_abcd(0.0, -1.0, 0.0, 1.0).unwrap(),
425 Plane::from_abcd(0.0, 1.0, 0.0, 1.0).unwrap(),
426 Plane::from_abcd(0.0, 0.0, -1.0, 1.0).unwrap(),
427 Plane::from_abcd(0.0, 0.0, 1.0, 1.0).unwrap(),
428 ]
429 );
430
431 assert_eq!(f.left_top_front_corner(), Vector3::new(-1.0, 1.0, 1.0));
432 assert_eq!(f.left_bottom_front_corner(), Vector3::new(-1.0, -1.0, 1.0));
433 assert_eq!(f.right_bottom_front_corner(), Vector3::new(1.0, -1.0, 1.0));
434 assert_eq!(f.right_top_front_corner(), Vector3::new(1.0, 1.0, 1.0));
435 assert_eq!(f.left_top_back_corner(), Vector3::new(-1.0, 1.0, -1.0));
436 assert_eq!(f.left_bottom_back_corner(), Vector3::new(-1.0, -1.0, -1.0));
437 assert_eq!(f.right_bottom_back_corner(), Vector3::new(1.0, -1.0, -1.0));
438 assert_eq!(f.right_top_back_corner(), Vector3::new(1.0, 1.0, -1.0));
439
440 assert_eq!(
441 f.corners(),
442 [
443 Vector3::new(-1.0, 1.0, 1.0),
444 Vector3::new(-1.0, -1.0, 1.0),
445 Vector3::new(1.0, -1.0, 1.0),
446 Vector3::new(1.0, 1.0, 1.0),
447 Vector3::new(-1.0, 1.0, -1.0),
448 Vector3::new(-1.0, -1.0, -1.0),
449 Vector3::new(1.0, -1.0, -1.0),
450 Vector3::new(1.0, 1.0, -1.0),
451 ]
452 );
453 }
454
455 #[test]
456 fn test_frustum_plane_centers() {
457 let f = Frustum::from_view_projection_matrix(Matrix4::new(
458 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
462 ))
463 .unwrap();
464
465 assert_eq!(f.near_plane_center(), Vector3::new(0.0, 0.0, 1.0));
466 assert_eq!(f.far_plane_center(), Vector3::new(0.0, 0.0, -1.0));
467 assert_eq!(f.view_direction(), Vector3::new(0.0, 0.0, -2.0));
468 assert_eq!(f.center(), Vector3::new(0.0, 0.0, 0.0));
469 }
470
471 #[test]
472 fn test_frustum_is_intersects_point_cloud() {
473 let f = Frustum::from_view_projection_matrix(Matrix4::new(
474 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
478 ))
479 .unwrap();
480
481 assert!(f.is_intersects_point_cloud(&[
482 Vector3::new(0.0, 0.0, 0.0),
483 Vector3::new(1.0, 1.0, 1.0),
484 ]));
485 assert!(!f.is_intersects_point_cloud(&[Vector3::new(-1.0, -2.0, 1.0)]));
486 }
487
488 #[test]
489 fn test_frustum_is_intersects_aabb() {
490 let f = Frustum::from_view_projection_matrix(Matrix4::new(
491 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
495 ))
496 .unwrap();
497
498 assert!(f.is_intersects_aabb(&AxisAlignedBoundingBox::unit()));
499 assert!(!f.is_intersects_aabb(&AxisAlignedBoundingBox::from_min_max(
500 Vector3::new(5.0, 5.0, 5.0),
501 Vector3::new(15.0, 15.0, 15.0)
502 )));
503 }
504
505 #[test]
506 fn test_frustum_is_intersects_aabb_offset() {
507 let f = Frustum::from_view_projection_matrix(Matrix4::new(
508 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
512 ))
513 .unwrap();
514
515 assert!(f.is_intersects_aabb_offset(
516 &AxisAlignedBoundingBox::unit(),
517 Vector3::new(1.0, 1.0, 1.0)
518 ));
519 assert!(!f.is_intersects_aabb_offset(
520 &AxisAlignedBoundingBox::unit(),
521 Vector3::new(10.0, 10.0, 10.0)
522 ));
523 }
524
525 #[test]
526 fn test_frustum_is_contains_point() {
527 let f = Frustum::from_view_projection_matrix(Matrix4::new(
528 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
532 ))
533 .unwrap();
534
535 assert!(f.is_contains_point(Vector3::new(0.0, 0.0, 0.0)));
536 assert!(!f.is_contains_point(Vector3::new(10.0, 10.0, 10.0)));
537 }
538
539 #[test]
540 fn test_frustum_is_intersects_sphere() {
541 let f = Frustum::from_view_projection_matrix(Matrix4::new(
542 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
546 ))
547 .unwrap();
548
549 assert!(f.is_intersects_sphere(Vector3::new(0.0, 0.0, 0.0), 1.0));
550 assert!(f.is_intersects_sphere(Vector3::new(0.0, 0.0, 0.0), 2.0));
551 assert!(!f.is_intersects_sphere(Vector3::new(10.0, 10.0, 10.0), 1.0));
552 }
553}