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 {
|
||||
crate::{
|
||||
types::{
|
||||
material::{Dielectric, Lambertian, Metal},
|
||||
MovingSphere, Ray, Sphere, Vec3,
|
||||
},
|
||||
Camera, Hitable, HitableList, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||
Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||
},
|
||||
rand::{rngs::SmallRng, Rng, SeedableRng},
|
||||
rayon::prelude::*,
|
||||
|
@ -49,9 +51,7 @@ impl Demo {
|
|||
}
|
||||
|
||||
fn world(&self) -> impl Hitable {
|
||||
let mut world = HitableList {
|
||||
list: Vec::with_capacity(500),
|
||||
};
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(500);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
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),
|
||||
)));
|
||||
|
||||
world
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
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> {
|
||||
/// Rays are represented by A + t * B
|
||||
|
@ -22,7 +27,16 @@ pub struct HitRecord<'a> {
|
|||
}
|
||||
|
||||
pub trait Hitable {
|
||||
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord> {
|
||||
None
|
||||
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>;
|
||||
|
||||
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 crate::{demo::ParallelHit, types::Ray, HitRecord, Hitable};
|
||||
use crate::{demo::ParallelHit, types::Ray, Aabb, HitRecord, Hitable};
|
||||
|
||||
pub struct HitableList {
|
||||
pub list: Vec<Arc<dyn ParallelHit>>,
|
||||
|
@ -18,6 +18,28 @@ impl Hitable for HitableList {
|
|||
}
|
||||
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 {
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -1,5 +1,7 @@
|
|||
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||
|
||||
mod aabb;
|
||||
mod bvh;
|
||||
mod camera;
|
||||
mod demo;
|
||||
mod hitable;
|
||||
|
@ -8,13 +10,15 @@ mod types;
|
|||
|
||||
pub use camera::Camera;
|
||||
|
||||
pub use aabb::Aabb;
|
||||
pub use bvh::BvhNode;
|
||||
pub use hitable::{HitRecord, Hitable};
|
||||
pub use hitable_list::HitableList;
|
||||
use std::time::Instant;
|
||||
|
||||
const NUM_SAMPLES: u8 = 5;
|
||||
const VERTICAL_PARTITION: usize = 8;
|
||||
const HORIZONTAL_PARTITION: usize = 8;
|
||||
const NUM_SAMPLES: u8 = 25;
|
||||
const VERTICAL_PARTITION: usize = 12;
|
||||
const HORIZONTAL_PARTITION: usize = 12;
|
||||
const WIDTH: usize = 1920;
|
||||
const HEIGHT: usize = 1080;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
types::{Material, Ray, Vec3},
|
||||
HitRecord, Hitable,
|
||||
Aabb, HitRecord, Hitable,
|
||||
};
|
||||
|
||||
pub struct MovingSphere<T: Material + Sized> {
|
||||
|
@ -72,4 +72,12 @@ impl<T: Material + Sized> Hitable for MovingSphere<T> {
|
|||
}
|
||||
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::{
|
||||
types::{Material, Ray, Vec3},
|
||||
HitRecord, Hitable,
|
||||
Aabb, HitRecord, Hitable,
|
||||
};
|
||||
|
||||
pub struct Sphere<T: Material + Sized> {
|
||||
|
@ -61,4 +61,9 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
|
|||
}
|
||||
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},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Vec3([f64; 3]);
|
||||
|
||||
impl Vec3 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user