diff --git a/README.md b/README.md index f3f351c..944eedc 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This list may be changed or extended in the future. - [x] Triangle normal generation - [x] Color mapping on triangles - [x] Triangle mesh struct - - [ ] Triangle mesh intersection test + - [x] Triangle mesh intersection test - [ ] Bounding boxes - [ ] Direct lighting - [ ] Point light sources diff --git a/src/main.rs b/src/main.rs index 723e92f..063f03a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,13 +52,10 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> fn main() -> std::io::Result<()> { - let camera = Camera::new(Point3::new(0.0,1.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 720); + 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(Plane::new(Point3::new(0.0, 0.0, -1.0), Vector3::x(), Vector3::z(), - |x, y| Color::gray(y / 30.0) - )), - Object::new(Sphere::new(0.0, 1.0, 4.0, 1.0, |x, y| Color::new(0.0, x, y))) + 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))) ]; render(&camera, &scene, "out.ppm") diff --git a/src/object/plane.rs b/src/object/plane.rs index 47712e3..095c832 100644 --- a/src/object/plane.rs +++ b/src/object/plane.rs @@ -17,6 +17,7 @@ pub struct Plane { // Input coordinates are defined in terms of the axes above. } +#[allow(dead_code)] impl Plane { // Creates a new plane. pub fn new(center: Point3, x_axis: Vector3, y_axis: Vector3, texture: F) -> Self diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 91383d5..9815fb1 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -16,6 +16,7 @@ pub struct Sphere { // Uses spherical coordinates (normalized from 0-1) as input. } +#[allow(dead_code)] impl Sphere { // Creates a new sphere. pub fn new(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self diff --git a/src/object/triangle.rs b/src/object/triangle.rs index 898fc28..96882ff 100644 --- a/src/object/triangle.rs +++ b/src/object/triangle.rs @@ -1,5 +1,7 @@ extern crate nalgebra as na; +use std::cmp::Ordering; + use na::*; use na::geometry::Point3; @@ -11,6 +13,7 @@ pub struct Triangle { pub v2: usize, pub v3: usize, + normal: Unit>, // Precalculated normal vector. area: f32, // Precalculated area for barycentric calculations. texture: Box Color> // Texture map. @@ -18,7 +21,7 @@ pub struct Triangle { } pub struct TriangleMesh { - pub points: Vec>, + pub vertices: Vec>, pub tris: Vec } @@ -47,7 +50,7 @@ impl Triangle { (t, u, v) } - fn intersect(&self, vertices: &Vec>, ray: Ray) -> Option { + fn intersect_(&self, vertices: &Vec>, ray: Ray) -> Option<(f32, f32, f32)> { let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices); let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices); @@ -66,53 +69,51 @@ impl Triangle { if v < 0.0 || (u + v) > 1.0 { return None; } - let t = vect3_1.dot(&q_vect) / det; + let t = 1.0 - u - v; - // Convert from barycentric coordinates - Some(distance(&ray.origin, &self.from_bary(vertices, t, u, v))) + Some((t, u, v)) } - fn normal(&self, vertices: &Vec>, _point: Point3) -> Unit> { - Unit::new_normalize((self.vertex2(vertices) - self.vertex1(vertices)).cross(&(self.vertex3(vertices) - self.vertex1(vertices)))) + fn intersect(&self, vertices: &Vec>, ray: Ray) -> Option { + self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v))) } fn getcolor(&self, vertices: &Vec>, point: Point3) -> Color { - // Converting back and forth between barycentric coordinates - // like this is terrible, but it's necessary for this object to - // match the interface the other objects use. let (t, u, v) = self.to_bary(vertices, point); - (*self.texture)(t, u, v) } } +#[allow(dead_code)] impl TriangleMesh { - pub fn new(points: Vec>, tris: Vec<(usize, usize, usize, Box Color>)>) -> Self { + pub fn new(vertices: Vec>, tris: Vec<(usize, usize, usize, Box Color>)>) -> Self { let triangles = tris.into_iter() .map(|(v1, v2, v3, f)| Triangle { v1: v1, v2: v2, v3: v3, - area: tri_area(&points[v1], &points[v2], &points[v3]), + normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))), + area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), texture: f }).collect(); TriangleMesh { - points: points, + vertices: vertices, tris: triangles } } - pub fn new_solid(points: Vec>, tris: Vec<(usize, usize, usize)>, color: Color) -> Self { + pub fn new_solid(vertices: Vec>, tris: Vec<(usize, usize, usize)>, color: Color) -> Self { let triangles = tris.into_iter() .map(|(v1, v2, v3)| Triangle { v1: v1, v2: v2, v3: v3, - area: tri_area(&points[v1], &points[v2], &points[v3]), + normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))), + area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), texture: Box::new(move |_, _, _| color) }).collect(); TriangleMesh { - points: points, + vertices: vertices, tris: triangles } } @@ -120,3 +121,82 @@ impl TriangleMesh { pub fn singleton(vertex1: Point3, vertex2: Point3, vertex3: Point3, texture: F) -> Self where F: Fn(f32, f32, f32) -> Color { TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) } + + pub fn singleton_solid(vertex1: Point3, vertex2: Point3, vertex3: Point3, color: Color) -> Self + { TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| color) } + + + fn closest_tri(&self, point: Point3) -> &Triangle { + self.tris.iter() + .map(move |tri| { + + let rel_pos = point - tri.vertex1(&self.vertices); + let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos)); + + let (t, u, v) = tri.to_bary(&self.vertices, Point3::from(proj_point3)); + + let t = clamp(t, 0.0, 1.0); + let u = clamp(u, 0.0, 1.0); + let v = clamp(v, 0.0, 1.0); + + let point_new = tri.from_bary(&self.vertices, t, u, v); + + (tri, distance(&point, &point_new)) + }) + .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) + .unwrap().0 + } +} + +impl Surface for TriangleMesh { + fn intersect(&self, ray: Ray) -> Option { + self.tris.iter() + .filter_map(|tri| tri.intersect(&self.vertices, ray)) + .min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)) + } + + fn normal(&self, point: Point3) -> Unit> { + self.closest_tri(point).normal + } + + fn getcolor(&self, point: Point3) -> Color { + self.closest_tri(point).getcolor(&self.vertices, point) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn roundcolor(color: Color) -> Color { + Color::new((color.red * 100.0).round() / 100.0, (color.green * 100.0).round() / 100.0, (color.blue * 100.0).round() / 100.0) + } + + #[test] + fn triangle_intersect() { + let triangle = TriangleMesh::singleton_solid(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), Color::black()); + + let ray = Ray::new(Point3::new(0.5, 5.0, 0.3), Vector3::new(0.0, -1.0, 0.0)); + + let (t, u, v) = triangle.tris[0].intersect_(&triangle.vertices, ray).unwrap(); + + println!("{},{},{}", t, u, v); + + assert!(t >= 0.0 && t <= 1.0); + assert!(u >= 0.0 && u <= 1.0); + assert!(v >= 0.0 && v <= 1.0); + } + + #[test] + fn triangle_getcolor() { + let triangle = TriangleMesh::singleton(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), |t, u, v| Color::new(t, u, v)); + + let t = 0.4; + let u = 0.1; + let v = 1.0 - t - u; + + let point = triangle.tris[0].from_bary(&triangle.vertices, t, u, v); + + assert_eq!(roundcolor(triangle.getcolor(point)), roundcolor(Color::new(t, u, v))); + } +}