From 11a7cc6f08c19497f459503e77c163b339d2fca3 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 16 Sep 2018 18:59:27 -0700 Subject: [PATCH] Implement AABB and BVH. --- rtiow/src/aabb.rs | 45 +++++++++++ rtiow/src/bin/tracer.rs | 70 +++++++++++++++-- rtiow/src/bvh.rs | 149 +++++++++++++++++++++++++++++++++++++ rtiow/src/cube.rs | 10 +++ rtiow/src/hitable.rs | 2 + rtiow/src/hitable_list.rs | 20 +++++ rtiow/src/lib.rs | 2 + rtiow/src/moving_sphere.rs | 15 ++++ rtiow/src/renderer.rs | 5 +- rtiow/src/sphere.rs | 8 ++ 10 files changed, 318 insertions(+), 8 deletions(-) create mode 100644 rtiow/src/aabb.rs create mode 100644 rtiow/src/bvh.rs diff --git a/rtiow/src/aabb.rs b/rtiow/src/aabb.rs new file mode 100644 index 0000000..5cdc825 --- /dev/null +++ b/rtiow/src/aabb.rs @@ -0,0 +1,45 @@ +use ray::Ray; +use vec3::Vec3; + +#[derive(Clone)] +pub struct AABB { + pub min: Vec3, + pub max: Vec3, +} + +impl AABB { + pub fn new(min: Vec3, max: Vec3) -> AABB { + AABB { min, max } + } + + pub fn hit(&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.direction[axis]) + .min((self.max[axis] - r.origin[axis]) / r.direction[axis]); + let t1 = ((self.min[axis] - r.origin[axis]) / r.direction[axis]) + .max((self.max[axis] - r.origin[axis]) / r.direction[axis]); + t_min = t0.max(t_min); + t_max = t1.min(t_max); + if t_max <= t_min { + return false; + } + } + true + } +} + +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) +} diff --git a/rtiow/src/bin/tracer.rs b/rtiow/src/bin/tracer.rs index 313382c..a107838 100644 --- a/rtiow/src/bin/tracer.rs +++ b/rtiow/src/bin/tracer.rs @@ -11,6 +11,7 @@ use std::time::Instant; use rand::Rng; use structopt::StructOpt; +use rtiow::bvh::BVH; use rtiow::camera::Camera; use rtiow::cube::Cube; use rtiow::hitable::Hit; @@ -111,7 +112,63 @@ fn build_scene_book(opt: &Opt) -> Scene { time_min, time_max, ); - let world = HitableList::new(random_scene()); + let world: Box = Box::new(HitableList::new(random_scene())); + Scene { + camera, + world, + subsamples: opt.subsamples, + width: opt.width, + height: opt.height, + } +} + +fn build_scene_bvh(opt: &Opt) -> Scene { + let lookfrom = Vec3::new(3., 3., 2.); + let lookat = Vec3::new(0., 0., -1.); + let dist_to_focus = (lookfrom - lookat).length(); + let aperture = 0.1; + let time_min = 0.; + let time_max = 1.; + let camera = Camera::new( + lookfrom, + lookat, + Vec3::new(0., 1., 0.), + 45., + opt.width as f32 / opt.height as f32, + aperture, + dist_to_focus, + time_min, + time_max, + ); + let world: Box = Box::new(BVH::new( + vec![ + Box::new(Sphere::new( + Vec3::new(0., 0., -1.), + 0.5, + Box::new(Lambertian::new(Vec3::new(0.1, 0.2, 0.5))), + )), + Box::new(Sphere::new( + Vec3::new(0., -100.5, -1.), + 100., + Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.8))), + )), + Box::new(Sphere::new( + Vec3::new(1., 0., -1.), + 0.5, + Box::new(Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.2)), + )), + Box::new(MovingSphere::new( + Vec3::new(-1., 0., -1.25), + Vec3::new(-1., 0., -0.75), + 0.5, + time_min, + time_max, + Box::new(Lambertian::new(Vec3::new(0.2, 0.8, 0.2))), + )), + ], + time_min, + time_max, + )); Scene { camera, world, @@ -139,7 +196,7 @@ fn build_scene_tutorial(opt: &Opt) -> Scene { time_min, time_max, ); - let world = HitableList::new(vec![ + let world: Box = Box::new(HitableList::new(vec![ Box::new(Sphere::new( Vec3::new(0., 0., -1.), 0.5, @@ -163,7 +220,7 @@ fn build_scene_tutorial(opt: &Opt) -> Scene { 1., Box::new(Lambertian::new(Vec3::new(0.2, 0.8, 0.2))), )), - ]); + ])); Scene { camera, world, @@ -191,7 +248,7 @@ fn build_scene_cube(opt: &Opt) -> Scene { time_min, time_max, ); - let world = HitableList::new(vec![ + let world: Box = Box::new(HitableList::new(vec![ Box::new(Sphere::new( Vec3::new(0., 0., -1.), 0.5, @@ -207,7 +264,7 @@ fn build_scene_cube(opt: &Opt) -> Scene { 100., Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.8))), )), - ]); + ])); Scene { camera, world, @@ -222,6 +279,7 @@ pub enum Model { Book, Tutorial, Cube, + BVH, } #[derive(Debug)] @@ -240,6 +298,7 @@ impl str::FromStr for Model { "book" => Ok(Model::Book), "tutorial" => Ok(Model::Tutorial), "cube" => Ok(Model::Cube), + "bvh" => Ok(Model::BVH), _ => Err(ModelParseError(s.to_owned())), } } @@ -272,6 +331,7 @@ fn main() -> Result<(), std::io::Error> { Model::Book => build_scene_book(&opt), Model::Cube => build_scene_cube(&opt), Model::Tutorial => build_scene_tutorial(&opt), + Model::BVH => build_scene_bvh(&opt), }; let res = render(scene, &opt.output); let runtime = start.elapsed(); diff --git a/rtiow/src/bvh.rs b/rtiow/src/bvh.rs new file mode 100644 index 0000000..4489ae0 --- /dev/null +++ b/rtiow/src/bvh.rs @@ -0,0 +1,149 @@ +use std; + +use rand; +use rand::Rng; + +use aabb::surrounding_box; +use aabb::AABB; +use hitable::Hit; +use hitable::HitRecord; +use ray::Ray; + +enum BVHNode { + Leaf(Box), + Branch { + left: Box, + right: Box, + bbox: Option, + }, +} + +fn box_x_compare(ah: &Box, bh: &Box) -> std::cmp::Ordering { + match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) { + (Some(box_left), Some(box_right)) => { + eprintln!("box_x_compare {} < {}", box_left.min, box_right.min); + return box_left.min.x.partial_cmp(&box_right.min.x).unwrap(); + } + _ => panic!("hit missing bounding box"), + } +} + +fn box_y_compare(ah: &Box, bh: &Box) -> std::cmp::Ordering { + match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) { + (Some(box_left), Some(box_right)) => { + eprintln!("box_y_compare {} < {}", box_left.min, box_right.min); + return box_left.min.y.partial_cmp(&box_right.min.y).unwrap(); + } + _ => panic!("hit missing bounding box"), + } +} + +fn box_z_compare(ah: &Box, bh: &Box) -> std::cmp::Ordering { + match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) { + (Some(box_left), Some(box_right)) => { + eprintln!("box_z_compare {} < {}", box_left.min, box_right.min); + return box_left.min.z.partial_cmp(&box_right.min.z).unwrap(); + } + _ => panic!("hit missing bounding box"), + } +} + +impl BVHNode { + fn new(l: Vec>, t_min: f32, t_max: f32) -> BVHNode { + if l.len() == 1 { + // Return the first element from the vector, which should be the only element. + // TODO(wathiede): we really want a .first_into() (.first() doesn't work because it + // returns a reference.) + for h in l.into_iter() { + return BVHNode::Leaf(h); + } + panic!("Unreachable"); + } else { + let mut l: Vec> = l.into_iter().collect(); + let mut rng = rand::thread_rng(); + match rng.gen_range::(0, 3) { + 0 => l.sort_by(box_x_compare), + 1 => l.sort_by(box_y_compare), + 2 => l.sort_by(box_z_compare), + val @ _ => panic!("unknown axis {}", val), + } + + let mut left_half = Vec::new(); + let mut right_half = Vec::new(); + let half_idx = l.len() / 2; + l.into_iter().enumerate().for_each(|(i, h)| { + if i < half_idx { + left_half.push(h); + } else { + right_half.push(h); + } + }); + let left = Box::new(BVHNode::new(left_half, t_min, t_max)); + let right = Box::new(BVHNode::new(right_half, t_min, t_max)); + let bbox = BVHNode::surrounding_box(left.as_ref(), right.as_ref(), t_min, t_max); + return BVHNode::Branch { left, right, bbox }; + } + } + + fn surrounding_box(left: &Hit, right: &Hit, t_min: f32, t_max: f32) -> Option { + match ( + left.bounding_box(t_min, t_max), + right.bounding_box(t_min, t_max), + ) { + (Some(left_bbox), Some(right_bbox)) => Some(surrounding_box(&left_bbox, &right_bbox)), + (None, Some(right_bbox)) => Some(right_bbox), + (Some(left_bbox), None) => Some(left_bbox), + (None, None) => None, + } + } +} + +impl Hit for BVHNode { + fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { + match self { + BVHNode::Leaf(ref hit) => hit.hit(r, t_min, t_max), + BVHNode::Branch { + ref left, + ref right, + .. + } => match (left.hit(r, t_min, t_max), right.hit(r, t_min, t_max)) { + (Some(hit_left), Some(hit_right)) => if hit_left.t < hit_right.t { + return Some(hit_left); + } else { + return Some(hit_right); + }, + (Some(hit_left), None) => Some(hit_left), + (None, Some(hit_right)) => Some(hit_right), + (None, None) => None, + }, + } + } + fn bounding_box(&self, t_min: f32, t_max: f32) -> Option { + match self { + BVHNode::Leaf(ref hit) => hit.bounding_box(t_min, t_max), + BVHNode::Branch { ref bbox, .. } => bbox.clone(), + } + } +} + +pub struct BVH { + root: BVHNode, +} + +impl BVH { + pub fn new(l: Vec>, t_min: f32, t_max: f32) -> BVH { + BVH { + root: BVHNode::new(l, t_min, t_max), + } + } +} + +impl Hit for BVH { + fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { + self.root.hit(r, t_min, t_max) + } + + fn bounding_box(&self, t_min: f32, t_max: f32) -> Option { + self.root.bounding_box(t_min, t_max) + } +} diff --git a/rtiow/src/cube.rs b/rtiow/src/cube.rs index 21b8e06..cceae83 100644 --- a/rtiow/src/cube.rs +++ b/rtiow/src/cube.rs @@ -1,3 +1,4 @@ +use aabb::AABB; use hitable::Hit; use hitable::HitRecord; use material::Material; @@ -5,6 +6,7 @@ use ray::Ray; use vec3::dot; use vec3::Vec3; +// Cube is an axis-aligned cube with dimensions length x length x length with center in the middle. pub struct Cube { center: Vec3, length: f32, @@ -23,6 +25,7 @@ impl Cube { impl Hit for Cube { fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { + // See if it hit the positive X wall first. let oc = r.origin - self.center; let a = dot(r.direction, r.direction); let b = dot(oc, r.direction); @@ -52,4 +55,11 @@ impl Hit for Cube { } None } + + fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option { + Some(AABB::new( + self.center - Vec3::new(self.length / 2., self.length / 2., self.length / 2.), + self.center + Vec3::new(self.length / 2., self.length / 2., self.length / 2.), + )) + } } diff --git a/rtiow/src/hitable.rs b/rtiow/src/hitable.rs index 3dbad07..ddb27bd 100644 --- a/rtiow/src/hitable.rs +++ b/rtiow/src/hitable.rs @@ -1,3 +1,4 @@ +use aabb::AABB; use material::Material; use ray::Ray; use vec3::Vec3; @@ -11,4 +12,5 @@ pub struct HitRecord<'m> { pub trait Hit: Send + Sync { fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option; + fn bounding_box(&self, t_min: f32, t_max: f32) -> Option; } diff --git a/rtiow/src/hitable_list.rs b/rtiow/src/hitable_list.rs index c020cbb..309da9c 100644 --- a/rtiow/src/hitable_list.rs +++ b/rtiow/src/hitable_list.rs @@ -1,6 +1,11 @@ +use std; + +use aabb::surrounding_box; +use aabb::AABB; use hitable::Hit; use hitable::HitRecord; use ray::Ray; +use vec3::Vec3; #[derive(Default)] pub struct HitableList { @@ -25,4 +30,19 @@ impl Hit for HitableList { } min_hit } + + fn bounding_box(&self, t_min: f32, t_max: f32) -> Option { + // TODO(wathiede): I don't understand the version from the book, this implementation + // returns a superset of all bounding boxes. + let super_box = AABB::new( + Vec3::new(std::f32::MAX, std::f32::MAX, std::f32::MAX), + Vec3::new(std::f32::MIN, std::f32::MIN, std::f32::MIN), + ); + Some(self.list.iter().fold(super_box, |acc, hitable| { + match hitable.bounding_box(t_min, t_max) { + Some(bbox) => surrounding_box(&acc, &bbox), + None => acc, + } + })) + } } diff --git a/rtiow/src/lib.rs b/rtiow/src/lib.rs index d3a55ac..ee92f5d 100644 --- a/rtiow/src/lib.rs +++ b/rtiow/src/lib.rs @@ -1,3 +1,5 @@ +pub mod aabb; +pub mod bvh; pub mod camera; pub mod cube; pub mod hitable; diff --git a/rtiow/src/moving_sphere.rs b/rtiow/src/moving_sphere.rs index 7b5e45d..b5583ac 100644 --- a/rtiow/src/moving_sphere.rs +++ b/rtiow/src/moving_sphere.rs @@ -1,3 +1,5 @@ +use aabb::surrounding_box; +use aabb::AABB; use hitable::Hit; use hitable::HitRecord; use material::Material; @@ -69,4 +71,17 @@ impl Hit for MovingSphere { } None } + + fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option { + let t_min_bb = AABB::new( + self.center0 - Vec3::new(self.radius, self.radius, self.radius), + self.center0 + Vec3::new(self.radius, self.radius, self.radius), + ); + + let t_max_bb = AABB::new( + self.center1 - Vec3::new(self.radius, self.radius, self.radius), + self.center1 + Vec3::new(self.radius, self.radius, self.radius), + ); + Some(surrounding_box(&t_min_bb, &t_max_bb)) + } } diff --git a/rtiow/src/renderer.rs b/rtiow/src/renderer.rs index bb2fc20..9e0e0c3 100644 --- a/rtiow/src/renderer.rs +++ b/rtiow/src/renderer.rs @@ -12,12 +12,11 @@ use rand::Rng; use camera::Camera; use hitable::Hit; -use hitable_list::HitableList; use ray::Ray; use vec3::Vec3; pub struct Scene { - pub world: HitableList, + pub world: Box, pub camera: Camera, pub subsamples: usize, pub width: usize, @@ -44,7 +43,7 @@ fn trace_pixel(x: usize, y: usize, scene: &Scene) -> Vec3 { let u = (rng.gen_range::(0., 1.) + x as f32) / scene.width as f32; let v = (rng.gen_range::(0., 1.) + y as f32) / scene.height as f32; let ray = scene.camera.get_ray(u, v); - color(ray, &scene.world, 0) + color(ray, scene.world.as_ref(), 0) } fn render_worker( diff --git a/rtiow/src/sphere.rs b/rtiow/src/sphere.rs index fdde748..ca1624d 100644 --- a/rtiow/src/sphere.rs +++ b/rtiow/src/sphere.rs @@ -1,3 +1,4 @@ +use aabb::AABB; use hitable::Hit; use hitable::HitRecord; use material::Material; @@ -52,4 +53,11 @@ impl Hit for Sphere { } None } + + fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option { + Some(AABB::new( + self.center - Vec3::new(self.radius, self.radius, self.radius), + self.center + Vec3::new(self.radius, self.radius, self.radius), + )) + } }