use crate::geometry::{Box3D, Overlaps3D};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ClipSpaceZ {
#[default]
ZeroToOne,
NegOneToOne,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Frustum3D {
planes: [[f64; 4]; 6],
}
impl Frustum3D {
#[inline]
pub const fn from_planes(planes: [[f64; 4]; 6]) -> Self {
Self { planes }
}
pub fn from_view_projection(vp: [[f64; 4]; 4], clip: ClipSpaceZ) -> Self {
let row = |i: usize| vp[i];
let add = |a: [f64; 4], b: [f64; 4]| [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
let sub = |a: [f64; 4], b: [f64; 4]| [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]];
let (r0, r1, r2, r3) = (row(0), row(1), row(2), row(3));
let near = match clip {
ClipSpaceZ::ZeroToOne => r2, ClipSpaceZ::NegOneToOne => add(r3, r2), };
Self {
planes: [
add(r3, r0), sub(r3, r0), add(r3, r1), sub(r3, r1), near, sub(r3, r2), ],
}
}
#[inline]
pub fn planes(&self) -> &[[f64; 4]; 6] {
&self.planes
}
#[inline]
pub fn overlaps_box(&self, b: Box3D) -> bool {
for p in &self.planes {
let px = if p[0] >= 0.0 { b.max_x } else { b.min_x };
let py = if p[1] >= 0.0 { b.max_y } else { b.min_y };
let pz = if p[2] >= 0.0 { b.max_z } else { b.min_z };
if p[0] * px + p[1] * py + p[2] * pz + p[3] < 0.0 {
return false;
}
}
true
}
#[inline]
pub fn contains_box(&self, b: Box3D) -> bool {
for p in &self.planes {
let nx = if p[0] >= 0.0 { b.min_x } else { b.max_x };
let ny = if p[1] >= 0.0 { b.min_y } else { b.max_y };
let nz = if p[2] >= 0.0 { b.min_z } else { b.max_z };
if p[0] * nx + p[1] * ny + p[2] * nz + p[3] < 0.0 {
return false;
}
}
true
}
pub fn bounding_box(&self) -> Option<Box3D> {
const EPS: f64 = 1e-9;
let cross = |a: [f64; 3], b: [f64; 3]| {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
};
let dot = |a: [f64; 3], b: [f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
let mut min = [f64::INFINITY; 3];
let mut max = [f64::NEG_INFINITY; 3];
for &i0 in &[0usize, 1] {
for &i1 in &[2usize, 3] {
for &i2 in &[4usize, 5] {
let p0 = self.planes[i0];
let p1 = self.planes[i1];
let p2 = self.planes[i2];
let n0 = [p0[0], p0[1], p0[2]];
let n1 = [p1[0], p1[1], p1[2]];
let n2 = [p2[0], p2[1], p2[2]];
let (d0, d1, d2) = (p0[3], p1[3], p2[3]);
let n1xn2 = cross(n1, n2);
let det = dot(n0, n1xn2);
if det.abs() < EPS {
return None;
}
let n2xn0 = cross(n2, n0);
let n0xn1 = cross(n0, n1);
let corner = [
-(d0 * n1xn2[0] + d1 * n2xn0[0] + d2 * n0xn1[0]) / det,
-(d0 * n1xn2[1] + d1 * n2xn0[1] + d2 * n0xn1[1]) / det,
-(d0 * n1xn2[2] + d1 * n2xn0[2] + d2 * n0xn1[2]) / det,
];
for axis in 0..3 {
min[axis] = min[axis].min(corner[axis]);
max[axis] = max[axis].max(corner[axis]);
}
}
}
}
Some(Box3D::new(min[0], min[1], min[2], max[0], max[1], max[2]))
}
}
impl Overlaps3D for Frustum3D {
#[inline]
fn overlaps_box(&self, bx: Box3D) -> bool {
self.overlaps_box(bx)
}
#[inline]
fn contains_box(&self, bx: Box3D) -> bool {
self.contains_box(bx)
}
}