pbrt/src/core/geometry/bounds.rs
2025-12-11 17:28:06 +00:00

344 lines
9.1 KiB
Rust

use super::{Float, NumFloat};
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::core::geometry::traits::{Sqrt, VectorLike};
use crate::core::geometry::{max, min};
use crate::utils::interval::Interval;
use crate::utils::math::lerp;
use num_traits::{Bounded, Num};
use std::mem;
use std::ops::{Add, Div, DivAssign, Mul, Sub};
// AABB BOUNDING BOXES
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Bounds<T, const N: usize> {
pub p_min: Point<T, N>,
pub p_max: Point<T, N>,
}
impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
type Item = &'a Point<T, N>;
type IntoIter = std::array::IntoIter<&'a Point<T, N>, 2>;
fn into_iter(self) -> Self::IntoIter {
[&self.p_min, &self.p_max].into_iter()
}
}
impl<T, const N: usize> Bounds<T, N>
where
T: Num + PartialOrd + Copy,
{
pub fn from_point(p: Point<T, N>) -> Self {
Self { p_min: p, p_max: p }
}
pub fn from_points(p1: Point<T, N>, p2: Point<T, N>) -> Self {
let mut p_min_arr = [T::zero(); N];
let mut p_max_arr = [T::zero(); N];
for i in 0..N {
if p1[i] < p2[i] {
p_min_arr[i] = p1[i];
p_max_arr[i] = p2[i];
} else {
p_min_arr[i] = p2[i];
p_max_arr[i] = p1[i];
}
}
Self {
p_min: Point(p_min_arr),
p_max: Point(p_max_arr),
}
}
pub fn union_point(self, p: Point<T, N>) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
for i in 0..N {
p_min[i] = min(p_min[i], p[i]);
p_max[i] = max(p_max[i], p[i]);
}
Self { p_min, p_max }
}
pub fn union(self, b2: Self) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
for i in 0..N {
p_min[i] = min(p_min[i], b2.p_min[i]);
p_max[i] = max(p_max[i], b2.p_max[i]);
}
Self { p_min, p_max }
}
pub fn diagonal(&self) -> Vector<T, N> {
self.p_max - self.p_min
}
pub fn centroid(&self) -> Point<T, N> {
let two = T::one() + T::one();
self.p_min + (self.diagonal() / two)
}
pub fn volume(&self) -> T {
let d = self.diagonal();
d.0.iter().fold(T::one(), |acc, &val| acc * val)
}
pub fn expand(&self, delta: T) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
p_min = p_min - Vector::fill(delta);
p_max = p_max + Vector::fill(delta);
Self { p_min, p_max }
}
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
let mut results_arr = [T::zero(); N];
for i in 0..N {
results_arr[i] = lerp(t[i], self.p_min[i], self.p_max[i])
}
Point(results_arr)
}
pub fn max_dimension(&self) -> usize
where
Point<T, N>: Sub<Output = Vector<T, N>>,
{
let d = self.diagonal();
let mut max_dim = 0;
let mut max_span = d[0];
for i in 1..N {
if d[i] > max_span {
max_span = d[i];
max_dim = i;
}
}
max_dim
}
pub fn offset(&self, p: &Point<T, N>) -> Vector<T, N>
where
Point<T, N>: Sub<Output = Vector<T, N>>,
Vector<T, N>: DivAssign<T>,
{
let mut o = *p - self.p_min;
let d = self.diagonal();
for i in 0..N {
if d[i] > T::zero() {
o[i] = o[i] / d[i];
}
}
o
}
pub fn corner(&self, corner_index: usize) -> Point<T, N> {
Point(std::array::from_fn(|i| {
if (corner_index >> i) & 1 == 1 {
self.p_max[i]
} else {
self.p_min[i]
}
}))
}
pub fn overlaps(&self, rhs: &Self) -> bool {
for i in 0..N {
if self.p_max[i] < rhs.p_min[i] || self.p_min[i] > rhs.p_max[i] {
return false;
}
}
true
}
pub fn contains(&self, p: Point<T, N>) -> bool {
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
}
pub fn contains_exclusive(&self, p: Point<T, N>) -> bool {
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] < self.p_max[i])
}
pub fn is_empty(&self) -> bool {
(0..N).any(|i| self.p_min[i] >= self.p_max[i])
}
pub fn is_degenerate(&self) -> bool {
(0..N).any(|i| self.p_min[i] > self.p_max[i])
}
}
impl<T, const N: usize> Default for Bounds<T, N>
where
T: Bounded + Copy,
{
fn default() -> Self {
Self {
p_min: Point([T::max_value(); N]),
p_max: Point([T::min_value(); N]),
}
}
}
pub type Bounds2<T> = Bounds<T, 2>;
pub type Bounds2f = Bounds2<Float>;
pub type Bounds2i = Bounds2<i32>;
pub type Bounds2fi = Bounds2<Interval>;
pub type Bounds3<T> = Bounds<T, 3>;
pub type Bounds3i = Bounds3<i32>;
pub type Bounds3f = Bounds3<Float>;
pub type Bounds3fi = Bounds3<Interval>;
impl<T> Bounds3<T>
where
T: Num + PartialOrd + Copy + Default,
{
pub fn surface_area(&self) -> T {
let d = self.diagonal();
let two = T::one() + T::one();
two * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z())
}
}
impl<T> Bounds3<T>
where
T: NumFloat + PartialOrd + Copy + Default + Sqrt,
{
pub fn bounding_sphere(&self) -> (Point3<T>, T) {
let two = T::one() + T::one();
let center = self.p_min + self.diagonal() / two;
let radius = if self.contains(center) {
center.distance(self.p_max)
} else {
T::zero()
};
(center, radius)
}
pub fn insersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> {
let mut t0 = T::zero();
let mut t1 = t_max;
for i in 0..3 {
let inv_ray_dir = T::one() / d[i];
let mut t_near = (self.p_min[i] - o[i]) * inv_ray_dir;
let mut t_far = (self.p_max[i] - o[i]) * inv_ray_dir;
if t_near > t_far {
mem::swap(&mut t_near, &mut t_far);
}
t0 = if t_near > t0 { t_near } else { t0 };
t1 = if t_far < t1 { t_far } else { t1 };
if t0 > t1 {
return None;
}
}
Some((t0, t1))
}
}
impl<T> Bounds2<T>
where
T: Num + Copy + Default,
{
pub fn area(&self) -> T {
let d: Vector2<T> = self.p_max - self.p_min;
d.x() * d.y()
}
}
impl Bounds3f {
#[inline(always)]
pub fn intersect_p(
&self,
o: Point3f,
ray_t_max: Float,
inv_dir: Vector3f,
dir_is_neg: &[usize; 3],
) -> Option<(Float, Float)> {
let bounds = [&self.p_min, &self.p_max];
// Check X
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
// Check Y
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
if t_min > ty_max || ty_min > t_max {
return None;
}
if ty_min > t_min {
t_min = ty_min;
}
if ty_max < t_max {
t_max = ty_max;
}
// Check Z
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
if t_min > tz_max || tz_min > t_max {
return None;
}
if tz_min > t_min {
t_min = tz_min;
}
if tz_max < t_max {
t_max = tz_max;
}
if (t_min < ray_t_max) && (t_max > 0.0) {
Some((t_min, t_max))
} else {
None
}
}
pub fn intersect_with_inverse(&self, o: Point3f, d: Vector3f, ray_t_max: Float) -> bool {
let inv_dir = Vector3::new(1.0 / d.x(), 1.0 / d.y(), 1.0 / d.z());
let dir_is_neg: [usize; 3] = [
(d.x() < 0.0) as usize,
(d.y() < 0.0) as usize,
(d.z() < 0.0) as usize,
];
let bounds = [&self.p_min, &self.p_max];
// Check for ray intersection against x and y slabs
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
if t_min > ty_max || ty_min > t_max {
return false;
}
if ty_min > t_min {
t_min = ty_min;
}
if ty_max < t_max {
t_max = ty_max;
}
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
if t_min > tz_max || tz_min > t_max {
return false;
}
if tz_min > t_min {
t_min = tz_min;
}
if tz_max < t_max {
t_max = tz_max;
}
(t_min < ray_t_max) && (t_max > 0.0)
}
}