307 lines
9.9 KiB
Rust
307 lines
9.9 KiB
Rust
use std::fmt;
|
|
|
|
use crate::{ray::Ray, vec3::Vec3};
|
|
|
|
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
|
pub struct AABB {
|
|
bounds: [Vec3; 2],
|
|
}
|
|
|
|
impl fmt::Display for AABB {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "({})-({})", self.bounds[0], self.bounds[1])
|
|
}
|
|
}
|
|
|
|
fn min(x: f32, y: f32) -> f32 {
|
|
if x < y {
|
|
x
|
|
} else {
|
|
y
|
|
}
|
|
}
|
|
|
|
fn max(x: f32, y: f32) -> f32 {
|
|
if x > y {
|
|
x
|
|
} else {
|
|
y
|
|
}
|
|
}
|
|
|
|
impl AABB {
|
|
pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB {
|
|
let min: Vec3 = min.into();
|
|
let max: Vec3 = max.into();
|
|
assert!(min.x < max.x);
|
|
assert!(min.y < max.y);
|
|
assert!(min.z < max.z);
|
|
AABB { bounds: [min, max] }
|
|
}
|
|
|
|
pub fn volume(&self) -> f32 {
|
|
(self.min().x - self.max().x).abs()
|
|
* (self.min().y - self.max().y).abs()
|
|
* (self.min().z - self.max().z).abs()
|
|
}
|
|
|
|
pub fn longest_axis(&self) -> usize {
|
|
let xd = (self.min().x - self.max().x).abs();
|
|
let yd = (self.min().y - self.max().y).abs();
|
|
let zd = (self.min().z - self.max().z).abs();
|
|
|
|
if xd >= yd && xd >= zd {
|
|
return 0;
|
|
}
|
|
if yd >= xd && yd >= zd {
|
|
return 1;
|
|
}
|
|
2
|
|
}
|
|
|
|
pub fn mid_point(&self) -> Vec3 {
|
|
self.bounds[0] + (self.bounds[1] - self.bounds[0]) / 2.
|
|
}
|
|
|
|
pub fn min(&self) -> Vec3 {
|
|
self.bounds[0]
|
|
}
|
|
|
|
pub fn max(&self) -> Vec3 {
|
|
self.bounds[1]
|
|
}
|
|
|
|
// TODO(wathiede): implement branchless https://tavianator.com/cgit/dimension.git/tree/libdimension/bvh/bvh.c#n194
|
|
|
|
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
|
self.hit_simd(r, t_min, t_max)
|
|
}
|
|
|
|
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
|
let mut t_min = t_min;
|
|
let mut t_max = t_max;
|
|
for axis in 0..3 {
|
|
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
|
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
|
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
|
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
|
t_min = t0.max(t_min);
|
|
t_max = t1.min(t_max);
|
|
if t_max <= t_min {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
|
let mut t_min = t_min;
|
|
let mut t_max = t_max;
|
|
for axis in 0..3 {
|
|
let d_inv = 1.0 / r.direction[axis];
|
|
let t0 = min(
|
|
(self.min()[axis] - r.origin[axis]) * d_inv,
|
|
(self.max()[axis] - r.origin[axis]) * d_inv,
|
|
);
|
|
let t1 = max(
|
|
(self.min()[axis] - r.origin[axis]) * d_inv,
|
|
(self.max()[axis] - r.origin[axis]) * d_inv,
|
|
);
|
|
t_min = max(t0, t_min);
|
|
t_max = min(t1, t_max);
|
|
if t_max <= t_min {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> bool {
|
|
// TODO(wathiede): this has bugs.
|
|
let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
|
let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
|
let t_y_min = (self.bounds[r.sign[0]].y - r.origin.y) * r.inv_direction.y;
|
|
let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y;
|
|
|
|
if t_min > t_y_max || t_y_min > t_max {
|
|
return false;
|
|
}
|
|
|
|
if t_y_min > t_min {
|
|
t_min = t_y_min;
|
|
}
|
|
if t_y_max < t_max {
|
|
t_max = t_y_max;
|
|
}
|
|
|
|
let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
|
let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
|
if t_min > t_z_max || t_z_min > t_max {
|
|
return false;
|
|
}
|
|
if t_z_min > t_min {
|
|
t_min = t_z_min;
|
|
}
|
|
if t_z_max < t_max {
|
|
t_max = t_z_max;
|
|
}
|
|
t_min < t1 && t_max > t0
|
|
}
|
|
|
|
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
|
#[cfg(target_arch = "x86_64")]
|
|
unsafe {
|
|
use std::arch::x86_64::*;
|
|
let o4 = _mm_set_ps(0., r.origin.z, r.origin.y, r.origin.x);
|
|
let d4 = _mm_set_ps(0., r.inv_direction.z, r.inv_direction.y, r.inv_direction.x);
|
|
let bmin4 = _mm_set_ps(0., self.min().z, self.min().y, self.min().x);
|
|
let bmax4 = _mm_set_ps(0., self.max().z, self.max().y, self.max().x);
|
|
let mask4 = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_set_ps(1., 0., 0., 0.));
|
|
let t1 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmin4, mask4), o4), d4);
|
|
let t2 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmax4, mask4), o4), d4);
|
|
let vmax4 = _mm_max_ps(t1, t2);
|
|
let vmin4 = _mm_min_ps(t1, t2);
|
|
let vmax4: (f32, f32, f32, f32) = std::mem::transmute(vmax4);
|
|
let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4);
|
|
let tmax = min(min(vmax4.0, min(vmax4.1, vmax4.2)), t_max);
|
|
let tmin = max(max(vmin4.0, max(vmin4.1, vmin4.2)), t_min);
|
|
tmin <= tmax
|
|
}
|
|
}
|
|
|
|
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool {
|
|
let b = self;
|
|
let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0];
|
|
let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0];
|
|
|
|
let mut tmin = t1.min(t2);
|
|
let mut tmax = t1.max(t2);
|
|
|
|
for i in 1..3 {
|
|
t1 = (b.min()[i] - r.origin[i]) * 1. / r.direction[i];
|
|
t2 = (b.max()[i] - r.origin[i]) * 1. / r.direction[i];
|
|
|
|
tmin = tmin.max(t1.min(t2));
|
|
tmax = tmax.min(t1.max(t2));
|
|
}
|
|
|
|
tmax > tmin.max(0.0)
|
|
}
|
|
}
|
|
|
|
pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
|
|
let min = Vec3::new(
|
|
box0.min().x.min(box1.min().x),
|
|
box0.min().y.min(box1.min().y),
|
|
box0.min().z.min(box1.min().z),
|
|
);
|
|
let max = Vec3::new(
|
|
box0.max().x.max(box1.max().x),
|
|
box0.max().y.max(box1.max().y),
|
|
box0.max().z.max(box1.max().z),
|
|
);
|
|
AABB::new(min, max)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
macro_rules! hit_test {
|
|
($($name:ident,)*) => {
|
|
$(
|
|
mod $name {
|
|
use super::*;
|
|
const T_MIN: f32 = 0.001;
|
|
const T_MAX: f32 = f32::MAX;
|
|
|
|
fn test_bb() -> AABB {
|
|
AABB::new([-1., -1., -1.], [1., 1., 1.])
|
|
}
|
|
|
|
#[test]
|
|
fn hit_front() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([-2., 0., 0.], [1., 0., 0.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn hit_back() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([2., 0., 0.], [-1., 0., 0.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn hit_top() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 2., 0.], [0., -1., 0.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn hit_bottom() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., -2., 0.], [0., 1., 0.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn hit_left() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 0., -2.], [0., 0., 1.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn hit_right() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 0., 2.], [0., 0., -1.], 0.5);
|
|
assert!(bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
|
|
#[test]
|
|
fn miss_front() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 1.1, -1.1], [0., -1., 0.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn miss_back() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 1.1, 1.1], [0., -1., 0.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn miss_top() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., 1.1, -1.1], [0., 0., 1.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn miss_bottom() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([0., -1.1, -1.1], [0., 0., 1.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn miss_left() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([-1.1, 0., -1.1], [0., 0., 1.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
#[test]
|
|
fn miss_right() {
|
|
let bb = test_bb();
|
|
let r = Ray::new([1.1, 0., -1.1], [0., 0., 1.], 0.5);
|
|
assert!(!bb.$name(r, T_MIN, T_MAX));
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
hit_test! {
|
|
hit_naive,
|
|
hit2,
|
|
hit_fast,
|
|
hit_simd,
|
|
}
|
|
}
|