1extern crate cgmath;
2
3use std::mem;
4
5use cgmath::{BaseFloat, Matrix4, Ortho, Perspective, PerspectiveFov, Vector3, prelude::*};
6
7#[derive(Debug, Copy, Clone, PartialEq)]
8pub struct FrustumCuller<S> {
9 nx_x: S,
10 nx_y: S,
11 nx_z: S,
12 nx_w: S,
13 px_x: S,
14 px_y: S,
15 px_z: S,
16 px_w: S,
17 ny_x: S,
18 ny_y: S,
19 ny_z: S,
20 ny_w: S,
21 py_x: S,
22 py_y: S,
23 py_z: S,
24 py_w: S,
25 nz_x: S,
26 nz_y: S,
27 nz_z: S,
28 nz_w: S,
29 pz_x: S,
30 pz_y: S,
31 pz_z: S,
32 pz_w: S,
33}
34
35#[repr(C)]
36#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
37pub struct BoundingBox<S> {
38 pub min: Vector3<S>,
40 pub max: Vector3<S>,
42}
43
44#[repr(C)]
45#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
46pub struct Sphere<S> {
47 pub center: Vector3<S>,
49 pub radius: S,
51}
52
53impl<S: BaseFloat> Sphere<S> {
54 #[inline]
55 pub fn from_params(center: Vector3<S>, radius: S) -> Self {
56 Self { center, radius }
57 }
58
59 #[inline]
60 pub fn new() -> Self {
61 Self {
62 center: Vector3::zero(),
63 radius: S::zero(),
64 }
65 }
66}
67
68#[derive(Debug, PartialEq, Eq, Copy, Clone)]
69pub enum Intersection {
70 Inside,
72 Partial,
74 Outside,
76}
77
78impl<S: BaseFloat> BoundingBox<S> {
79 #[inline]
80 pub fn from_params(min: Vector3<S>, max: Vector3<S>) -> Self {
81 Self { min, max }
82 }
83
84 #[inline]
85 pub fn new() -> Self {
86 Self::from_params(Vector3::zero(), Vector3::zero())
87 }
88}
89
90impl<S: BaseFloat> FrustumCuller<S> {
91 pub fn new() -> Self {
94 Self::from_matrix(Matrix4::identity())
95 }
96
97 #[inline]
99 pub fn from_perspective(perspective: Perspective<S>) -> Self {
100 Self::from_matrix(perspective.into())
101 }
102
103 #[inline]
105 pub fn from_perspective_fov(perspective: PerspectiveFov<S>) -> Self {
106 Self::from_matrix(perspective.into())
107 }
108
109 #[inline]
110 pub fn from_ortho(ortho: Ortho<S>) -> Self {
111 Self::from_matrix(ortho.into())
112 }
113
114 pub fn from_matrix(m: Matrix4<S>) -> Self {
117 let mut culler: Self = unsafe { mem::zeroed() };
118
119 culler.nx_x = m.x.w + m.x.x;
120 culler.nx_y = m.y.w + m.y.x;
121 culler.nx_z = m.z.w + m.z.x;
122 culler.nx_w = m.w.w + m.w.x;
123 let invl = (culler.nx_x * culler.nx_x + culler.nx_y * culler.nx_y
125 + culler.nx_z * culler.nx_z)
126 .sqrt()
127 .recip();
128 culler.nx_x *= invl;
129 culler.nx_y *= invl;
130 culler.nx_z *= invl;
131 culler.nx_w *= invl;
132 culler.px_x = m.x.w - m.x.x;
134 culler.px_y = m.y.w - m.y.x;
135 culler.px_z = m.z.w - m.z.x;
136 culler.px_w = m.w.w - m.w.x;
137 let invl = (culler.px_x * culler.px_x + culler.px_y * culler.px_y
139 + culler.px_z * culler.px_z)
140 .sqrt()
141 .recip();
142 culler.px_x *= invl;
143 culler.px_y *= invl;
144 culler.px_z *= invl;
145 culler.px_w *= invl;
146 culler.ny_x = m.x.w + m.x.y;
148 culler.ny_y = m.y.w + m.y.y;
149 culler.ny_z = m.z.w + m.z.y;
150 culler.ny_w = m.w.w + m.w.y;
151 let invl = (culler.ny_x * culler.ny_x + culler.ny_y * culler.ny_y
153 + culler.ny_z * culler.ny_z)
154 .sqrt()
155 .recip();
156 culler.ny_x *= invl;
157 culler.ny_y *= invl;
158 culler.ny_z *= invl;
159 culler.ny_w *= invl;
160 culler.py_x = m.x.w - m.x.y;
162 culler.py_y = m.y.w - m.y.y;
163 culler.py_z = m.z.w - m.z.y;
164 culler.py_w = m.w.w - m.w.y;
165 let invl = (culler.py_x * culler.py_x + culler.py_y * culler.py_y
167 + culler.py_z * culler.py_z)
168 .sqrt()
169 .recip();
170 culler.py_x *= invl;
171 culler.py_y *= invl;
172 culler.py_z *= invl;
173 culler.py_w *= invl;
174 culler.nz_x = m.x.w + m.x.z;
176 culler.nz_y = m.y.w + m.y.z;
177 culler.nz_z = m.z.w + m.z.z;
178 culler.nz_w = m.w.w + m.w.z;
179 let invl = (culler.nz_x * culler.nz_x + culler.nz_y * culler.nz_y
181 + culler.nz_z * culler.nz_z)
182 .sqrt()
183 .recip();
184 culler.nz_x *= invl;
185 culler.nz_y *= invl;
186 culler.nz_z *= invl;
187 culler.nz_w *= invl;
188 culler.pz_x = m.x.w - m.x.z;
190 culler.pz_y = m.y.w - m.y.z;
191 culler.pz_z = m.z.w - m.z.z;
192 culler.pz_w = m.w.w - m.w.z;
193 let invl = (culler.pz_x * culler.pz_x + culler.pz_y * culler.pz_y
195 + culler.pz_z * culler.pz_z)
196 .sqrt()
197 .recip();
198 culler.pz_x *= invl;
199 culler.pz_y *= invl;
200 culler.pz_z *= invl;
201 culler.pz_w *= invl;
202 culler
205 }
206
207 pub fn test_point(&self, point: Vector3<S>) -> Intersection {
209 if self.nx_x * point.x + self.nx_y * point.y + self.nx_z * point.z + self.nx_w >= S::zero()
210 && self.px_x * point.x + self.px_y * point.y + self.px_z * point.z + self.px_w
211 >= S::zero()
212 && self.ny_x * point.x + self.ny_y * point.y + self.ny_z * point.z + self.ny_w
213 >= S::zero()
214 && self.py_x * point.x + self.py_y * point.y + self.py_z * point.z + self.py_w
215 >= S::zero()
216 && self.nz_x * point.x + self.nz_y * point.y + self.nz_z * point.z + self.nz_w
217 >= S::zero()
218 && self.pz_x * point.x + self.pz_y * point.y + self.pz_z * point.z + self.pz_w
219 >= S::zero()
220 {
221 Intersection::Inside
222 } else {
223 Intersection::Outside
224 }
225 }
226
227 pub fn test_sphere<T>(&self, sphere: T) -> Intersection
232 where
233 T: Into<Sphere<S>>,
234 {
235 let sphere = sphere.into();
236
237 let mut inside = true;
238 let mut dist;
239 dist = self.nx_x * sphere.center.x + self.nx_y * sphere.center.y
240 + self.nx_z * sphere.center.z + self.nx_w;
241 if dist >= -sphere.radius {
242 inside &= dist >= sphere.radius;
243 dist = self.px_x * sphere.center.x + self.px_y * sphere.center.y
244 + self.px_z * sphere.center.z + self.px_w;
245 if dist >= -sphere.radius {
246 inside &= dist >= sphere.radius;
247 dist = self.ny_x * sphere.center.x + self.ny_y * sphere.center.y
248 + self.ny_z * sphere.center.z + self.ny_w;
249 if dist >= -sphere.radius {
250 inside &= dist >= sphere.radius;
251 dist = self.py_x * sphere.center.x + self.py_y * sphere.center.y
252 + self.py_z * sphere.center.z + self.py_w;
253 if dist >= -sphere.radius {
254 inside &= dist >= sphere.radius;
255 dist = self.nz_x * sphere.center.x + self.nz_y * sphere.center.y
256 + self.nz_z * sphere.center.z
257 + self.nz_w;
258 if dist >= -sphere.radius {
259 inside &= dist >= sphere.radius;
260 dist = self.pz_x * sphere.center.x + self.pz_y * sphere.center.y
261 + self.pz_z * sphere.center.z
262 + self.pz_w;
263 if dist >= -sphere.radius {
264 inside &= dist >= sphere.radius;
265 return if inside {
266 Intersection::Inside
267 } else {
268 Intersection::Partial
269 };
270 }
271 }
272 }
273 }
274 }
275 }
276
277 Intersection::Outside
278 }
279
280 pub fn test_bounding_box<T>(&self, aab: T) -> Intersection
284 where
285 T: Into<BoundingBox<S>>,
286 {
287 let aab = aab.into();
288 let mut inside = true;
289 if self.nx_x * if self.nx_x < S::zero() {
290 aab.min.x
291 } else {
292 aab.max.x
293 } + self.nx_y * if self.nx_y < S::zero() {
294 aab.min.y
295 } else {
296 aab.max.y
297 } + self.nx_z * if self.nx_z < S::zero() {
298 aab.min.z
299 } else {
300 aab.max.z
301 } >= -self.nx_w
302 {
303 inside &= self.nx_x * if self.nx_x < S::zero() {
304 aab.max.x
305 } else {
306 aab.min.x
307 } + self.nx_y * if self.nx_y < S::zero() {
308 aab.max.y
309 } else {
310 aab.min.y
311 } + self.nx_z * if self.nx_z < S::zero() {
312 aab.max.z
313 } else {
314 aab.min.z
315 } >= -self.nx_w;
316 if self.px_x * if self.px_x < S::zero() {
317 aab.min.x
318 } else {
319 aab.max.x
320 } + self.px_y * if self.px_y < S::zero() {
321 aab.min.y
322 } else {
323 aab.max.y
324 } + self.px_z * if self.px_z < S::zero() {
325 aab.min.z
326 } else {
327 aab.max.z
328 } >= -self.px_w
329 {
330 inside &= self.px_x * if self.px_x < S::zero() {
331 aab.max.x
332 } else {
333 aab.min.x
334 } + self.px_y * if self.px_y < S::zero() {
335 aab.max.y
336 } else {
337 aab.min.y
338 } + self.px_z * if self.px_z < S::zero() {
339 aab.max.z
340 } else {
341 aab.min.z
342 } >= -self.px_w;
343 if self.ny_x * if self.ny_x < S::zero() {
344 aab.min.x
345 } else {
346 aab.max.x
347 } + self.ny_y * if self.ny_y < S::zero() {
348 aab.min.y
349 } else {
350 aab.max.y
351 } + self.ny_z * if self.ny_z < S::zero() {
352 aab.min.z
353 } else {
354 aab.max.z
355 } >= -self.ny_w
356 {
357 inside &= self.ny_x * if self.ny_x < S::zero() {
358 aab.max.x
359 } else {
360 aab.min.x
361 } + self.ny_y * if self.ny_y < S::zero() {
362 aab.max.y
363 } else {
364 aab.min.y
365 } + self.ny_z * if self.ny_z < S::zero() {
366 aab.max.z
367 } else {
368 aab.min.z
369 } >= -self.ny_w;
370 if self.py_x * if self.py_x < S::zero() {
371 aab.min.x
372 } else {
373 aab.max.x
374 } + self.py_y * if self.py_y < S::zero() {
375 aab.min.y
376 } else {
377 aab.max.y
378 } + self.py_z * if self.py_z < S::zero() {
379 aab.min.z
380 } else {
381 aab.max.z
382 } >= -self.py_w
383 {
384 inside &= self.py_x * if self.py_x < S::zero() {
385 aab.max.x
386 } else {
387 aab.min.x
388 } + self.py_y * if self.py_y < S::zero() {
389 aab.max.y
390 } else {
391 aab.min.y
392 } + self.py_z * if self.py_z < S::zero() {
393 aab.max.z
394 } else {
395 aab.min.z
396 } >= -self.py_w;
397 if self.nz_x * if self.nz_x < S::zero() {
398 aab.min.x
399 } else {
400 aab.max.x
401 } + self.nz_y * if self.nz_y < S::zero() {
402 aab.min.y
403 } else {
404 aab.max.y
405 } + self.nz_z * if self.nz_z < S::zero() {
406 aab.min.z
407 } else {
408 aab.max.z
409 } >= -self.nz_w
410 {
411 inside &= self.nz_x * if self.nz_x < S::zero() {
412 aab.max.x
413 } else {
414 aab.min.x
415 }
416 + self.nz_y * if self.nz_y < S::zero() {
417 aab.max.y
418 } else {
419 aab.min.y
420 }
421 + self.nz_z * if self.nz_z < S::zero() {
422 aab.max.z
423 } else {
424 aab.min.z
425 } >= -self.nz_w;
426 if self.pz_x * if self.pz_x < S::zero() {
427 aab.min.x
428 } else {
429 aab.max.x
430 }
431 + self.pz_y * if self.pz_y < S::zero() {
432 aab.min.y
433 } else {
434 aab.max.y
435 }
436 + self.pz_z * if self.pz_z < S::zero() {
437 aab.min.z
438 } else {
439 aab.max.z
440 } >= -self.pz_w
441 {
442 inside &= self.pz_x * if self.pz_x < S::zero() {
443 aab.max.x
444 } else {
445 aab.min.x
446 }
447 + self.pz_y * if self.pz_y < S::zero() {
448 aab.max.y
449 } else {
450 aab.min.y
451 }
452 + self.pz_z * if self.pz_z < S::zero() {
453 aab.max.z
454 } else {
455 aab.min.z
456 } >= -self.pz_w;
457 return if inside {
458 Intersection::Inside
459 } else {
460 Intersection::Partial
461 };
462 }
463 }
464 }
465 }
466 }
467 }
468
469 Intersection::Outside
470 }
471}
472
473impl<S> From<(Vector3<S>, Vector3<S>)> for BoundingBox<S> {
474 #[inline]
475 fn from((min, max): (Vector3<S>, Vector3<S>)) -> Self {
476 Self { min, max }
477 }
478}
479
480impl<S> From<(Vector3<S>, S)> for Sphere<S> {
481 #[inline]
482 fn from((center, radius): (Vector3<S>, S)) -> Self {
483 Self { center, radius }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use {BoundingBox, FrustumCuller, Intersection, Sphere};
490
491 use cgmath::{Matrix4, Ortho, PerspectiveFov, Rad, Vector3, prelude::*};
492
493 #[test]
494 fn sphere_in_frustum_ortho() {
495 let frustum_culling = FrustumCuller::from_matrix(
496 Ortho {
497 left: -1.0,
498 right: 1.0,
499 bottom: -1.0,
500 top: 1.0,
501 near: -1.0,
502 far: 1.0,
503 }.into(),
504 );
505
506 assert_eq!(
507 Intersection::Inside,
508 frustum_culling.test_sphere(Sphere::from_params(Vector3::new(0.0, 0.0, 0.0), 0.1))
509 );
510 assert_eq!(
511 Intersection::Partial,
512 frustum_culling.test_sphere(Sphere::from_params(Vector3::new(1.0, 0.0, 0.0), 0.1))
513 );
514 assert_eq!(
515 Intersection::Outside,
516 frustum_culling.test_sphere(Sphere::from_params(Vector3::new(1.2, 0.0, 0.0), 0.1))
517 );
518 }
519
520 #[test]
521 fn sphere_in_frustum_perspective() {
522 let frustum_culling = FrustumCuller::from_matrix(
523 PerspectiveFov {
524 fovy: Rad(3.14159265 / 2.0),
525 aspect: 1.0,
526 near: 0.1,
527 far: 100.0,
528 }.into(),
529 );
530
531 assert_eq!(
532 Intersection::Inside,
533 frustum_culling.test_sphere(Sphere::from_params(Vector3::new(1.0, 0.0, -2.0), 0.1))
534 );
535 assert_eq!(
536 Intersection::Outside,
537 frustum_culling.test_sphere(Sphere::from_params(Vector3::new(4.0, 0.0, -2.0), 0.1))
538 );
539 }
540
541 #[test]
542 fn test_point_in_perspective() {
543 let frustum_culling = FrustumCuller::from_matrix(
544 PerspectiveFov {
545 fovy: Rad(3.14159265 / 2.0),
546 aspect: 1.0,
547 near: 0.1,
548 far: 100.0,
549 }.into(),
550 );
551
552 assert_eq!(
553 Intersection::Inside,
554 frustum_culling.test_point(Vector3::new(0.0, 0.0, -5.0))
555 );
556 assert_eq!(
557 Intersection::Outside,
558 frustum_culling.test_point(Vector3::new(0.0, 6.0, -5.0))
559 );
560 }
561
562 #[test]
563 fn test_aab_in_ortho() {
564 let mut c = FrustumCuller::from_matrix(
565 Ortho {
566 left: -1.0,
567 right: 1.0,
568 bottom: -1.0,
569 top: 1.0,
570 near: -1.0,
571 far: 1.0,
572 }.into(),
573 );
574
575 assert_eq!(
576 Intersection::Partial,
577 c.test_bounding_box(BoundingBox::from_params(
578 Vector3::new(-20.0, -2.0, 0.0),
579 Vector3::new(20.0, 2.0, 0.0)
580 ))
581 );
582 assert_eq!(
583 Intersection::Inside,
584 c.test_bounding_box(BoundingBox::from_params(
585 Vector3::new(-0.5, -0.5, -0.5),
586 Vector3::new(0.5, 0.5, 0.5)
587 ))
588 );
589 assert_eq!(
590 Intersection::Outside,
591 c.test_bounding_box(BoundingBox::from_params(
592 Vector3::new(1.1, 0.0, 0.0),
593 Vector3::new(2.0, 2.0, 2.0)
594 ))
595 );
596
597 c = FrustumCuller::from_ortho(Ortho {
598 left: -1.0,
599 right: 1.0,
600 bottom: -1.0,
601 top: 1.0,
602 near: -1.0,
603 far: 1.0,
604 });
605
606 assert_eq!(
607 Intersection::Partial,
608 c.test_bounding_box(BoundingBox::from_params(
609 Vector3::new(0.0, 0.0, 0.0),
610 Vector3::new(2.0, 2.0, 2.0)
611 ))
612 );
613 assert_eq!(
614 Intersection::Outside,
615 c.test_bounding_box(BoundingBox::from_params(
616 Vector3::new(1.1, 0.0, 0.0),
617 Vector3::new(2.0, 2.0, 2.0)
618 ))
619 );
620
621 c = FrustumCuller::from_matrix(Matrix4::identity());
622
623 assert_eq!(
624 Intersection::Partial,
625 c.test_bounding_box(BoundingBox::from_params(
626 Vector3::new(0.5, 0.5, 0.5),
627 Vector3::new(2.0, 2.0, 2.0)
628 ))
629 );
630 assert_eq!(
631 Intersection::Outside,
632 c.test_bounding_box(BoundingBox::from_params(
633 Vector3::new(1.5, 0.5, 0.5),
634 Vector3::new(2.0, 2.0, 2.0)
635 ))
636 );
637 assert_eq!(
638 Intersection::Outside,
639 c.test_bounding_box(BoundingBox::from_params(
640 Vector3::new(-2.5, 0.5, 0.5),
641 Vector3::new(-1.5, 2.0, 2.0)
642 ))
643 );
644 assert_eq!(
645 Intersection::Outside,
646 c.test_bounding_box(BoundingBox::from_params(
647 Vector3::new(-0.5, -2.5, 0.5),
648 Vector3::new(1.5, -2.0, 2.0)
649 ))
650 );
651 }
652
653 #[test]
654 fn test_aab_in_perspective() {
655 let c = FrustumCuller::from_perspective_fov(PerspectiveFov {
656 fovy: Rad(3.14159265 / 2.0),
657 aspect: 1.0,
658 near: 0.1,
659 far: 100.0,
660 });
661
662 assert_eq!(
663 Intersection::Inside,
664 c.test_bounding_box(BoundingBox::from_params(
665 Vector3::new(0.0, 0.0, -7.0),
666 Vector3::new(1.0, 1.0, -5.0)
667 ))
668 );
669 assert_eq!(
670 Intersection::Outside,
671 c.test_bounding_box(BoundingBox::from_params(
672 Vector3::new(1.1, 0.0, 0.0),
673 Vector3::new(2.0, 2.0, 2.0)
674 ))
675 );
676 assert_eq!(
677 Intersection::Outside,
678 c.test_bounding_box(BoundingBox::from_params(
679 Vector3::new(4.0, 4.0, -3.0),
680 Vector3::new(5.0, 5.0, -5.0)
681 ))
682 );
683 assert_eq!(
684 Intersection::Outside,
685 c.test_bounding_box(BoundingBox::from_params(
686 Vector3::new(-6.0, -6.0, -2.0),
687 Vector3::new(-1.0, -4.0, -4.0)
688 ))
689 );
690 }
691}