use std::ops::{Index, IndexMut};
use crate::math::{Point3, Ray, Vec3, XYZEnum};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Bounds3 {
pub min: Point3,
pub max: Point3,
}
impl Default for Bounds3 {
fn default() -> Self {
Bounds3 {
min: Point3::splat(f64::INFINITY),
max: Point3::splat(f64::NEG_INFINITY),
}
}
}
impl Bounds3 {
pub fn new(point_a: Point3, point_b: Point3) -> Self {
Bounds3 {
min: Point3::new(
point_a.x.min(point_b.x),
point_a.y.min(point_b.y),
point_a.z.min(point_b.z),
),
max: Point3::new(
point_a.x.max(point_b.x),
point_a.y.max(point_b.y),
point_a.z.max(point_b.z),
),
}
}
pub fn include_point(self, point: Point3) -> Self {
Bounds3 {
min: Point3::new(
self.min.x.min(point.x),
self.min.y.min(point.y),
self.min.z.min(point.z),
),
max: Point3::new(
self.max.x.max(point.x),
self.max.y.max(point.y),
self.max.z.max(point.z),
),
}
}
pub fn include_bounds(self, bounds: Bounds3) -> Self {
Bounds3 {
min: Point3::new(
self.min.x.min(bounds.min.x),
self.min.y.min(bounds.min.y),
self.min.z.min(bounds.min.z),
),
max: Point3::new(
self.max.x.max(bounds.max.x),
self.max.y.max(bounds.max.y),
self.max.z.max(bounds.max.z),
),
}
}
pub fn intersection(bounds_a: Bounds3, bounds_b: Bounds3) -> Self {
Bounds3 {
min: Point3::new(
bounds_a.min.x.max(bounds_b.min.x),
bounds_a.min.y.max(bounds_b.min.y),
bounds_a.min.z.max(bounds_b.min.z),
),
max: Point3::new(
bounds_a.max.x.min(bounds_b.max.x),
bounds_a.max.y.min(bounds_b.max.y),
bounds_a.max.z.min(bounds_b.max.z),
),
}
}
pub fn overlaps(&self, bounds: Bounds3) -> bool {
let overlaps_x = (self.max.x >= bounds.min.x) && (self.min.x <= bounds.max.x);
let overlaps_y = (self.max.y >= bounds.min.y) && (self.min.y <= bounds.max.y);
let overlaps_z = (self.max.z >= bounds.min.z) && (self.min.z <= bounds.max.z);
overlaps_x && overlaps_y && overlaps_z
}
pub fn includes_point(&self, point: Point3) -> bool {
point.x >= self.min.x
&& point.x <= self.max.x
&& point.y >= self.min.y
&& point.y <= self.max.y
&& point.z >= self.min.z
&& point.z <= self.max.z
}
pub fn diagonal(&self) -> Vec3 {
self.max - self.min
}
pub fn surface_area(&self) -> f64 {
let diagonal = self.diagonal();
2.0 * (diagonal.x * diagonal.y + diagonal.x * diagonal.z + diagonal.y * diagonal.z)
}
pub fn maximum_extent(&self) -> XYZEnum {
let diagonal = self.diagonal();
if diagonal.x > diagonal.y && diagonal.x > diagonal.z {
XYZEnum::X
} else if diagonal.y > diagonal.z {
XYZEnum::Y
} else {
XYZEnum::Z
}
}
pub fn offset(&self, point: Point3) -> Vec3 {
let mut offset = point - self.min;
if self.max.x > self.min.x {
offset = Vec3::new(offset.x / (self.max.x - self.min.x), offset.y, offset.z);
}
if self.max.y > self.min.y {
offset = Vec3::new(offset.x, offset.y / (self.max.y - self.min.y), offset.z);
}
if self.max.z > self.min.z {
offset = Vec3::new(offset.x, offset.y, offset.z / (self.max.z - self.min.z));
}
offset
}
pub fn intersects(
&self,
ray: Ray,
max_intersection_distance: f64,
inverse_direction: Vec3,
direction_is_negative: [bool; 3],
) -> bool {
let direction_is_negative_0 = direction_is_negative[0] as usize;
let direction_is_not_negative_0 = !direction_is_negative[0] as usize;
let direction_is_negative_1 = direction_is_negative[1] as usize;
let direction_is_not_negative_1 = !direction_is_negative[1] as usize;
let direction_is_negative_2 = direction_is_negative[2] as usize;
let direction_is_not_negative_2 = !direction_is_negative[2] as usize;
let mut t_min = (self[direction_is_negative_0].x - ray.origin.x) * inverse_direction.x;
let mut t_max = (self[direction_is_not_negative_0].x - ray.origin.x) * inverse_direction.x;
let ty_min = (self[direction_is_negative_1].y - ray.origin.y) * inverse_direction.y;
let ty_max = (self[direction_is_not_negative_1].y - ray.origin.y) * inverse_direction.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 = (self[direction_is_negative_2].z - ray.origin.z) * inverse_direction.z;
let tz_max = (self[direction_is_not_negative_2].z - ray.origin.z) * inverse_direction.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 < max_intersection_distance && t_max > 0.0
}
}
impl Index<usize> for Bounds3 {
type Output = Point3;
fn index(&self, index: usize) -> &Self::Output {
match index {
0 => &self.min,
1 => &self.max,
_ => panic!(
"index out of bounds: the len is 2 but the index is {}",
index
),
}
}
}
impl IndexMut<usize> for Bounds3 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match index {
0 => &mut self.min,
1 => &mut self.max,
_ => panic!(
"index out of bounds: the len is 2 but the index is {}",
index
),
}
}
}
#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
use crate::math::Point3;
use crate::util::EPSILON_F64;
use super::Bounds3;
#[test]
fn bounds_include_point() {
let mut bounds = Bounds3::new(Point3::splat(-1.0), Point3::splat(1.0));
assert!(!bounds.includes_point(Point3::new(-1.2, 0.6, 6.3)));
bounds = bounds.include_point(Point3::new(-1.2, 0.6, 6.3));
assert!(bounds.includes_point(Point3::new(-1.2, 0.6, 6.3)));
assert_approx_eq!(bounds.min, Point3::new(-1.2, -1.0, -1.0), EPSILON_F64);
assert_approx_eq!(bounds.max, Point3::new(1.0, 1.0, 6.3), EPSILON_F64);
}
#[test]
fn bounds_include_bounds() {
let mut bounds = Bounds3::default();
assert!(!bounds.includes_point(Point3::new(3.8, -8.42, -0.3)));
assert!(!bounds.includes_point(Point3::new(-6.7, -2.0, 0.4)));
bounds = Bounds3::new(Point3::new(3.8, -8.42, -0.3), Point3::new(-6.7, -2.0, 0.4));
assert_approx_eq!(bounds.min, Point3::new(-6.7, -8.42, -0.3));
assert_approx_eq!(bounds.max, Point3::new(3.8, -2.0, 0.4));
assert!(bounds.includes_point(Point3::new(3.8, -8.42, -0.3)));
assert!(bounds.includes_point(Point3::new(-6.7, -2.0, 0.4)));
}
#[test]
fn bounds_index() {
let mut bounds = Bounds3::new(Point3::new(3.8, -8.42, -0.3), Point3::new(-6.7, -2.0, 0.4));
assert_approx_eq!(bounds.min, bounds[0]);
assert_approx_eq!(bounds.max, bounds[1]);
bounds[0] = Point3::new(-5.987, -8.7, -1.2);
bounds[1] = Point3::new(8.7, 1.2, 5.987);
assert_approx_eq!(bounds.min, Point3::new(-5.987, -8.7, -1.2));
assert_approx_eq!(bounds.max, Point3::new(8.7, 1.2, 5.987));
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn bounds_index_panic() {
let bounds = Bounds3::default();
let _x = bounds[4];
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn bounds_index_mut_panic() {
let mut bounds = Bounds3::default();
bounds[2] = Point3::new(-5.987, -8.7, -1.2);
}
}