From 14f1ed2a31126c607b8a69fb1d91acf279413766 Mon Sep 17 00:00:00 2001 From: bijan2005 Date: Tue, 8 Dec 2020 11:22:56 -0500 Subject: [PATCH] Implemented bounding spheres for objects --- Cargo.toml | 1 + README.md | 4 +- src/main.rs | 2 +- src/object.rs | 40 ++++++++++-- src/object/bound.rs | 37 +++++++++++ src/object/plane.rs | 6 +- src/object/sphere.rs | 17 ++--- src/object/triangle.rs | 143 ++++++++++++++++++++++++++++++++++++++++- src/types.rs | 1 + 9 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 src/object/bound.rs diff --git a/Cargo.toml b/Cargo.toml index cbfe2b0..ed2abd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2018" [dependencies] nalgebra = "0.18" +rand = "0.7.3" diff --git a/README.md b/README.md index 944eedc..99de8d4 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ This list may be changed or extended in the future. - [x] Plane struct - [x] Plane intersection test - [x] Color mapping on planes -- [ ] Triangle objects +- [x] Triangle objects - [x] Triangle struct - [x] Triangle intersection test - [x] Triangle normal generation - [x] Color mapping on triangles - [x] Triangle mesh struct - [x] Triangle mesh intersection test -- [ ] Bounding boxes +- [x] Bounding spheres - [ ] Direct lighting - [ ] Point light sources - [ ] Point source struct diff --git a/src/main.rs b/src/main.rs index 063f03a..26e6761 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ fn main() -> std::io::Result<()> { let camera = Camera::new(Point3::new(0.0,0.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 480); let scene = vec![ - Object::new(TriangleMesh::singleton(Point3::new(-1.0, -1.0, 3.0), Point3::new(0.0, 1.0, 3.0), Point3::new(1.0, -1.0, 3.0), |t, u, v| Color::new(t, u, v))) + Object::new_boundless(TriangleMesh::singleton(Point3::new(-1.0, -1.0, 2.0), Point3::new(0.0, 1.0, 2.0), Point3::new(1.0, -1.0, 2.0), |t, u, v| Color::new(t, u, v))) ]; render(&camera, &scene, "out.ppm") diff --git a/src/object.rs b/src/object.rs index 7be17e2..50bf2fb 100644 --- a/src/object.rs +++ b/src/object.rs @@ -2,6 +2,7 @@ mod sphere; pub use sphere::*; mod plane; pub use plane::*; mod triangle; pub use triangle::*; +mod bound; pub use bound::*; use na::*; @@ -22,18 +23,49 @@ pub trait Surface { // Takes in a point (assumed to be on the object's surface) // and returns the color information on that point. fn getcolor(&self, point: Point3) -> Color; + + // Creates a bounding sphere around the object. + fn bound(&self) -> Bound; } pub struct Object { - pub surface: Box + pub surface: Box, + bound: Bound } +#[allow(dead_code)] impl Object { - pub fn new(surface: impl 'static + Surface) -> Self { - Object { surface: Box::new(surface) } + // Creates a new object with a custom bounding sphere. + pub fn new_(surface: impl 'static + Surface, center: Point3, radius: f32) -> Self { + Object { + surface: Box::new(surface), + bound: Bound { center: center, radius: radius, bypass: false } + } } - pub fn intersect(&self, ray: Ray) -> Option { self.surface.intersect(ray) } + // Creates a new object with no bounding sphere. + pub fn new_boundless(surface: impl 'static + Surface) -> Self { + Object { + surface: Box::new(surface), + bound: Bound::bypass() + } + } + + // Creates a new object with the default bounding sphere. + pub fn new(surface: impl 'static + Surface) -> Self { + let bound = surface.bound(); + Object { + surface: Box::new(surface), + bound: bound + } + } + + + pub fn intersect(&self, ray: Ray) -> Option { + if self.bound.is_intersected(ray) { + self.surface.intersect(ray) + } else { None } + } pub fn normal(&self, point: Point3) -> Unit> { self.surface.normal(point) } pub fn getcolor(&self, point: Point3) -> Color { self.surface.getcolor(point) } } diff --git a/src/object/bound.rs b/src/object/bound.rs new file mode 100644 index 0000000..b1a4832 --- /dev/null +++ b/src/object/bound.rs @@ -0,0 +1,37 @@ +extern crate nalgebra as na; + +use na::distance; +use na::geometry::Point3; + +use crate::types::Ray; + +// A bounding sphere, used for +// intersection test optimization. +#[derive(Debug)] +pub struct Bound { + pub center: Point3, + pub radius: f32, + + // If true, then the bounding sphere is disabled. + pub bypass: bool +} + +impl Bound { + pub fn is_intersected(&self, ray: Ray) -> bool { + + if self.bypass { return true; } + + let l = ray.origin - self.center; + + let b_2 = ray.direction.dot(&l); + let c = l.norm_squared() - self.radius * self.radius; + + let discr = b_2 * b_2 * c; + + discr >= 0.0 + } + + pub fn contains(&self, point: &Point3) -> bool { distance(&self.center, point) < self.radius } + + pub fn bypass() -> Self { Bound { center: Point3::origin(), radius: 0.0, bypass: true } } +} diff --git a/src/object/plane.rs b/src/object/plane.rs index 095c832..309c353 100644 --- a/src/object/plane.rs +++ b/src/object/plane.rs @@ -4,7 +4,7 @@ use na::*; use na::geometry::Point3; use crate::types::*; -use super::Surface; +use super::{Surface, bound::*}; pub struct Plane { pub center: Point3, // Plane origin (used for texture mapping). @@ -86,6 +86,10 @@ impl Surface for Plane { (*self.texture)(x, y) } + + // Planes are infinite, so no finite + // bounding sphere could possibly contain one. + fn bound(&self) -> Bound { Bound::bypass() } } #[cfg(test)] diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 9815fb1..7f4af73 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -6,7 +6,7 @@ use na::*; use na::geometry::Point3; use crate::types::*; -use super::Surface; +use super::{Surface, bound::*}; pub struct Sphere { pub center: Point3, // Center point of the sphere. @@ -36,25 +36,24 @@ impl Sphere { impl Surface for Sphere { fn intersect(&self, ray: Ray) -> Option { - fn solve_quadratic(a: f32, b: f32, c: f32) -> Option<(f32, f32)> { - let discr = b * b - 4.0 * a * c; + fn solve_quadratic(b: f32, c: f32) -> Option<(f32, f32)> { + let discr = b * b - 4.0 * c; if discr < 0.0 { None } else if discr == 0.0 { - let x = -0.5 * b / a; + let x = -0.5 * b; Some((x, x)) } else { let q = if b > 0.0 { -0.5 * (b + discr.sqrt()) } else { -0.5 * (b - discr.sqrt()) }; - Some((q / a, c / q)) + Some((q, c / q)) } } let l = ray.origin - self.center; - let a = ray.direction.dot(&ray.direction); let b = 2.0 * ray.direction.dot(&l); - let c = l.dot(&l) - self.radius * self.radius; + let c = l.normsquared() - self.radius * self.radius; - let (mut t0, mut t1) = solve_quadratic(a, b, c)?; + let (mut t0, mut t1) = solve_quadratic(b, c)?; if t0 > t1 { std::mem::swap(&mut t0, &mut t1); } @@ -78,4 +77,6 @@ impl Surface for Sphere { (*self.texture)(x, y) } + + fn bound(&self) -> Bound { Bound::bypass() } } diff --git a/src/object/triangle.rs b/src/object/triangle.rs index 96882ff..06376b1 100644 --- a/src/object/triangle.rs +++ b/src/object/triangle.rs @@ -6,7 +6,7 @@ use na::*; use na::geometry::Point3; use crate::types::*; -use super::Surface; +use super::{Surface, bound::*}; pub struct Triangle { pub v1: usize, // Handles to 3 vertices. @@ -162,6 +162,85 @@ impl Surface for TriangleMesh { fn getcolor(&self, point: Point3) -> Color { self.closest_tri(point).getcolor(&self.vertices, point) } + + // Uses Welzl's algorithm to solve the bounding sphere problem + fn bound(&self) -> Bound { + fn triangle_sphere(point1: &Point3, point2: &Point3, point3: &Point3) -> (Point3, f32) { + let a = point3 - point1; + let b = point2 - point1; + + let crs = b.cross(&a); + + let to_center = (crs.cross(&b) * a.norm_squared() + a.cross(&crs) * b.norm_squared()) + / (2.0 * crs.norm_squared()); + + let radius = to_center.norm(); + + (point1 + to_center, radius) + } + + fn tetrahedron_sphere(point1: &Point3, point2: &Point3, point3: &Point3, point4: &Point3) -> (Point3, f32) { + let matrix = Matrix4::from_rows(&[point1.to_homogeneous().transpose(), + point2.to_homogeneous().transpose(), + point3.to_homogeneous().transpose(), + point4.to_homogeneous().transpose()]); + + let a = matrix.determinant() * 2.0; + let mut matrix_mut = matrix.clone(); + + let squares = Vector4::new(point1.coords.norm_squared(), point2.coords.norm_squared(), point3.coords.norm_squared(), point4.coords.norm_squared()); + matrix_mut.set_column(0, &squares); + let center_x = matrix_mut.determinant(); + + matrix_mut.set_column(1, &matrix.index((.., 0))); + let center_y = -matrix_mut.determinant(); + + matrix_mut.set_column(2, &matrix.index((.., 1))); + let center_z = matrix_mut.determinant(); + + let center = Point3::new(center_x / a, center_y / a, center_z / a); + let radius = distance(point1, ¢er); + + (center, radius) + } + + fn smallest_sphere(points: Vec<&Point3>, boundary: Vec<&Point3>) -> (Point3, f32) { + println!("{:?}\n{:?}\n", points, boundary); + if points.len() == 0 || boundary.len() == 4 { + match boundary.len() { + 0 => (Point3::new(0.0, 0.0, 0.0), 0.0), + 1 => (*boundary[0], 0.0), + 2 => { let half_span = 0.5 * (boundary[1] - boundary[0]); + (*boundary[0] + half_span, half_span.norm()) }, + 3 => triangle_sphere(boundary[0], boundary[1], boundary[2]), + 4 => tetrahedron_sphere(boundary[0], boundary[1], boundary[2], boundary[3]), + _ => unreachable!() + } + } else { + let removed = points[0]; + let points = Vec::from(&points[1..]); + + let bound = smallest_sphere(points.clone(), boundary.clone()); + if distance(&bound.0, removed) < bound.1 { return bound; } + + let mut boundary = boundary.clone(); + boundary.push(removed); + + smallest_sphere(points, boundary) + } + } + + extern crate rand; + use rand::thread_rng; + use rand::seq::SliceRandom; + + let mut points: Vec<&Point3> = self.vertices.iter().collect(); + points.shuffle(&mut thread_rng()); + + let (center, radius) = smallest_sphere(points, Vec::new()); + + Bound { center: center, radius: radius + 0.01, bypass: false } + } } #[cfg(test)] @@ -199,4 +278,66 @@ mod tests { assert_eq!(roundcolor(triangle.getcolor(point)), roundcolor(Color::new(t, u, v))); } + + #[test] + fn triangle_bounds() { + let point1 = Point3::new(0.0, 0.0, 0.0); + let point2 = Point3::new(1.0, 0.0, 0.0); + let point3 = Point3::new(0.0, 1.0, 0.0); + + let triangle = TriangleMesh::singleton_solid(point1, point2, point3, Color::black()); + + let bound = triangle.bound(); + + println!("{:?}", bound); + + assert!(bound.contains(&point1)); + assert!(bound.contains(&point2)); + assert!(bound.contains(&point3)); + } + /* + #[test] + fn triangle_tobound() { + let point1 = Point3::new(-3.0, 4.0, -6.0); + let point2 = Point3::new(5.0, -2.0, -7.0); + let point3 = Point3::new(9.0, -7.0, 3.0); + + let (center, radius) = triangle_sphere(&point1, &point2, &point3); + let bound = Bound { center: center, radius: radius + 0.01, bypass: false }; + + println!("{:?}", bound); + + println!("{}\n{}\n{}", distance(&bound.center, &point1), + distance(&bound.center, &point2), + distance(&bound.center, &point3)); + + assert!(bound.contains(&point1)); + assert!(bound.contains(&point2)); + assert!(bound.contains(&point3)); + } + + #[test] + fn triangle_tetrabound() { + let point1 = Point3::new(8.0, -2.0, -5.0); + let point2 = Point3::new(-3.0, 4.0, -6.0); + let point3 = Point3::new(-3.0, -9.0, 3.0); + let point4 = Point3::new(-6.0, 5.0, -9.0); + + let (center, radius) = tetrahedron_sphere(&point1, &point2, &point3, &point4); + + let bound = Bound { center: center, radius: radius + 0.01, bypass: false }; + + println!("{:?}", bound); + + println!("{}\n{}\n{}\n{}", distance(&bound.center, &point1), + distance(&bound.center, &point2), + distance(&bound.center, &point3), + distance(&bound.center, &point4)); + + assert!(bound.contains(&point1)); + assert!(bound.contains(&point2)); + assert!(bound.contains(&point3)); + assert!(bound.contains(&point4)); + } + */ } diff --git a/src/types.rs b/src/types.rs index 5ebc7be..0546647 100644 --- a/src/types.rs +++ b/src/types.rs @@ -30,6 +30,7 @@ pub struct Color { _private: () // Private field prevents direct construction } +#[allow(dead_code)] impl Color { pub fn new(red: f32, green: f32, blue: f32) -> Self { Color {