Completed AABB BVH implementation
This commit is contained in:
parent
5916155b8e
commit
18bb6a250b
51
src/aabb.rs
Normal file
51
src/aabb.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::types::{Ray, Vec3};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Aabb {
|
||||||
|
pub min: Vec3,
|
||||||
|
pub max: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aabb {
|
||||||
|
pub const fn new(min: Vec3, max: Vec3) -> Self {
|
||||||
|
Self { min, max }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hit(&self, ray: &Ray, mut t_min: f64, mut t_max: f64) -> bool {
|
||||||
|
for i in 0..=2 {
|
||||||
|
let inverse_dir = 1.0 / ray.direction()[i];
|
||||||
|
let mut t0 = (self.min[i] - ray.origin()[i]) * inverse_dir;
|
||||||
|
let mut t1 = (self.max[i] - ray.origin()[i]) * inverse_dir;
|
||||||
|
if inverse_dir < 0.0 {
|
||||||
|
std::mem::swap(&mut t0, &mut t1);
|
||||||
|
}
|
||||||
|
t_min = if t0 > t_min { t0 } else { t_min };
|
||||||
|
t_max = if t1 < t_max { t1 } else { t_max };
|
||||||
|
|
||||||
|
if t_max <= t_min {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn surrounding_box(box0: Aabb, box1: Aabb) -> Self {
|
||||||
|
let smol_box = Vec3::new(
|
||||||
|
box0.min.x().min(box1.min.x()),
|
||||||
|
box0.min.y().min(box1.min.y()),
|
||||||
|
box0.min.z().min(box1.min.z()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let big_box = Vec3::new(
|
||||||
|
box0.max.x().max(box1.max.x()),
|
||||||
|
box0.max.y().max(box1.max.y()),
|
||||||
|
box0.max.z().max(box1.max.z()),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
min: smol_box,
|
||||||
|
max: big_box,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
src/bvh.rs
Normal file
134
src/bvh.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use rand::{prelude::SliceRandom, Rng};
|
||||||
|
|
||||||
|
use crate::{types::Ray, Aabb, HitRecord, Hitable};
|
||||||
|
|
||||||
|
pub struct BvhNode<T: Hitable> {
|
||||||
|
bounding_box: Aabb,
|
||||||
|
left: HitNode<T>,
|
||||||
|
right: HitNode<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Hitable + Clone> BvhNode<T> {
|
||||||
|
pub fn new<R: Rng + ?Sized>(rng: &mut R, objects: &mut [T], t0: f64, t1: f64) -> Self {
|
||||||
|
let comparator = [
|
||||||
|
Self::box_x_compare,
|
||||||
|
Self::box_y_compare,
|
||||||
|
Self::box_z_compare,
|
||||||
|
]
|
||||||
|
.choose(rng)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
objects.sort_by(comparator);
|
||||||
|
|
||||||
|
let (left, right) = match objects.len() {
|
||||||
|
1 => (
|
||||||
|
HitNode::Direct(objects[0].clone()),
|
||||||
|
HitNode::Direct(objects[0].clone()),
|
||||||
|
),
|
||||||
|
2 => match comparator(&objects[0], &objects[1]) {
|
||||||
|
Ordering::Greater => (
|
||||||
|
HitNode::Direct(objects[1].clone()),
|
||||||
|
HitNode::Direct(objects[0].clone()),
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
HitNode::Direct(objects[0].clone()),
|
||||||
|
HitNode::Direct(objects[1].clone()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
n => {
|
||||||
|
let (l, r) = objects.split_at_mut(n / 2);
|
||||||
|
(
|
||||||
|
HitNode::Bvh(Box::new(BvhNode::new(rng, l, t0, t1))),
|
||||||
|
HitNode::Bvh(Box::new(BvhNode::new(rng, r, t0, t1))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_box = left
|
||||||
|
.bounding_box(t0, t1)
|
||||||
|
.expect("missing bounding box for left BVH Node");
|
||||||
|
let right_box = right
|
||||||
|
.bounding_box(t0, t1)
|
||||||
|
.expect("missing bounding box for right BVH Node");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
bounding_box: Aabb::surrounding_box(left_box, right_box),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_x_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||||
|
if let (Some(bbox_a), Some(bbox_b)) =
|
||||||
|
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||||
|
{
|
||||||
|
return bbox_a.min.x().partial_cmp(&bbox_b.min.x()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("No bounding box for this BVH Node!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_y_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||||
|
if let (Some(bbox_a), Some(bbox_b)) =
|
||||||
|
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||||
|
{
|
||||||
|
return bbox_a.min.y().partial_cmp(&bbox_b.min.y()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("No bounding box for this BVH Node!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_z_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||||
|
if let (Some(bbox_a), Some(bbox_b)) =
|
||||||
|
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||||
|
{
|
||||||
|
return bbox_a.min.z().partial_cmp(&bbox_b.min.z()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("No bounding box for this BVH Node!!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Hitable> Hitable for BvhNode<T> {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
self.bounding_box(t_min, t_max)?;
|
||||||
|
|
||||||
|
let hbox_left = self.left.hit(ray, t_min, t_max);
|
||||||
|
|
||||||
|
let hbox_right = if let Some(ref hleft) = hbox_left {
|
||||||
|
self.right.hit(ray, t_min, hleft.t)
|
||||||
|
} else {
|
||||||
|
self.right.hit(ray, t_min, t_max)
|
||||||
|
};
|
||||||
|
|
||||||
|
hbox_right.or(hbox_left)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _t_min: f64, _t_max: f64) -> Option<Aabb> {
|
||||||
|
Some(self.bounding_box)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HitNode<T: Hitable> {
|
||||||
|
Bvh(Box<BvhNode<T>>),
|
||||||
|
Direct(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Hitable> HitNode<T> {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
match self {
|
||||||
|
HitNode::Bvh(node) => node.hit(ray, t_min, t_max),
|
||||||
|
HitNode::Direct(node) => node.hit(ray, t_min, t_max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||||
|
match self {
|
||||||
|
HitNode::Bvh(node) => node.bounding_box(t0, t1),
|
||||||
|
HitNode::Direct(node) => node.bounding_box(t0, t1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/demo.rs
10
src/demo.rs
|
@ -1,10 +1,12 @@
|
||||||
|
use crate::BvhNode;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
types::{
|
types::{
|
||||||
material::{Dielectric, Lambertian, Metal},
|
material::{Dielectric, Lambertian, Metal},
|
||||||
MovingSphere, Ray, Sphere, Vec3,
|
MovingSphere, Ray, Sphere, Vec3,
|
||||||
},
|
},
|
||||||
Camera, Hitable, HitableList, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||||
},
|
},
|
||||||
rand::{rngs::SmallRng, Rng, SeedableRng},
|
rand::{rngs::SmallRng, Rng, SeedableRng},
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
|
@ -49,9 +51,7 @@ impl Demo {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn world(&self) -> impl Hitable {
|
fn world(&self) -> impl Hitable {
|
||||||
let mut world = HitableList {
|
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(500);
|
||||||
list: Vec::with_capacity(500),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||||
|
@ -125,7 +125,7 @@ impl Demo {
|
||||||
Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0),
|
Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
world
|
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
use crate::types::{Material, Ray, Vec3};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
types::{Material, Ray, Vec3},
|
||||||
|
Aabb,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct HitRecord<'a> {
|
pub struct HitRecord<'a> {
|
||||||
/// Rays are represented by A + t * B
|
/// Rays are represented by A + t * B
|
||||||
|
@ -22,7 +27,16 @@ pub struct HitRecord<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Hitable {
|
pub trait Hitable {
|
||||||
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord> {
|
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>;
|
||||||
None
|
|
||||||
|
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Hitable + ?Sized> Hitable for Arc<T> {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
self.as_ref().hit(ray, t_min, t_max)
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||||
|
self.as_ref().bounding_box(t0, t1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{demo::ParallelHit, types::Ray, HitRecord, Hitable};
|
use crate::{demo::ParallelHit, types::Ray, Aabb, HitRecord, Hitable};
|
||||||
|
|
||||||
pub struct HitableList {
|
pub struct HitableList {
|
||||||
pub list: Vec<Arc<dyn ParallelHit>>,
|
pub list: Vec<Arc<dyn ParallelHit>>,
|
||||||
|
@ -18,6 +18,28 @@ impl Hitable for HitableList {
|
||||||
}
|
}
|
||||||
hit_rec
|
hit_rec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||||
|
if self.list.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output_box = None;
|
||||||
|
|
||||||
|
for obj in self.list.iter() {
|
||||||
|
if let Some(bbox) = obj.bounding_box(t0, t1) {
|
||||||
|
if let Some(ref mut opbox) = output_box {
|
||||||
|
*opbox = Aabb::surrounding_box(*opbox, bbox);
|
||||||
|
} else {
|
||||||
|
output_box = Some(bbox);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return output_box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output_box
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HitableList {
|
impl HitableList {
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -1,5 +1,7 @@
|
||||||
#![allow(clippy::suspicious_arithmetic_impl)]
|
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
|
||||||
|
mod aabb;
|
||||||
|
mod bvh;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod demo;
|
mod demo;
|
||||||
mod hitable;
|
mod hitable;
|
||||||
|
@ -8,13 +10,15 @@ mod types;
|
||||||
|
|
||||||
pub use camera::Camera;
|
pub use camera::Camera;
|
||||||
|
|
||||||
|
pub use aabb::Aabb;
|
||||||
|
pub use bvh::BvhNode;
|
||||||
pub use hitable::{HitRecord, Hitable};
|
pub use hitable::{HitRecord, Hitable};
|
||||||
pub use hitable_list::HitableList;
|
pub use hitable_list::HitableList;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
const NUM_SAMPLES: u8 = 5;
|
const NUM_SAMPLES: u8 = 25;
|
||||||
const VERTICAL_PARTITION: usize = 8;
|
const VERTICAL_PARTITION: usize = 12;
|
||||||
const HORIZONTAL_PARTITION: usize = 8;
|
const HORIZONTAL_PARTITION: usize = 12;
|
||||||
const WIDTH: usize = 1920;
|
const WIDTH: usize = 1920;
|
||||||
const HEIGHT: usize = 1080;
|
const HEIGHT: usize = 1080;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Material, Ray, Vec3},
|
types::{Material, Ray, Vec3},
|
||||||
HitRecord, Hitable,
|
Aabb, HitRecord, Hitable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MovingSphere<T: Material + Sized> {
|
pub struct MovingSphere<T: Material + Sized> {
|
||||||
|
@ -72,4 +72,12 @@ impl<T: Material + Sized> Hitable for MovingSphere<T> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||||
|
let radius = Vec3::new(self.radius, self.radius, self.radius);
|
||||||
|
let box_smol = Aabb::new(self.center(t0) - radius, self.center(t0) + radius);
|
||||||
|
let box_big = Aabb::new(self.center(t1) - radius, self.center(t1) + radius);
|
||||||
|
|
||||||
|
Some(Aabb::surrounding_box(box_smol, box_big))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Material, Ray, Vec3},
|
types::{Material, Ray, Vec3},
|
||||||
HitRecord, Hitable,
|
Aabb, HitRecord, Hitable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Sphere<T: Material + Sized> {
|
pub struct Sphere<T: Material + Sized> {
|
||||||
|
@ -61,4 +61,9 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
|
||||||
|
let radius = Vec3::new(self.radius, self.radius, self.radius);
|
||||||
|
Some(Aabb::new(self.center - radius, self.center + radius))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
|
ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Vec3([f64; 3]);
|
pub struct Vec3([f64; 3]);
|
||||||
|
|
||||||
impl Vec3 {
|
impl Vec3 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user