diff --git a/src/camera.rs b/src/camera.rs index c269cc0..88eae1c 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,87 +1,123 @@ extern crate nalgebra as na; -use na::*; use na::geometry::{Point2, Point3}; +use na::*; use crate::types::Ray; #[derive(Debug)] pub struct Camera { - matrix: Isometry3, // The transformation that stores the - // position and orientation of the camera. (Not actually a matrix, but w/e) + matrix: Isometry3, // The transformation that stores the + // position and orientation of the camera. (Not actually a matrix, but w/e) + focal_length: f64, // The distance from the camera origin to the canvas. + canvas_size: Vector2, // The size of the canvas within the world space. - focal_length: f32, // The distance from the camera origin to the canvas. - canvas_size: Vector2, // The size of the canvas within the world space. - - pub image_size: Vector2 // The size of the final image in pixels. + pub image_size: Vector2, // The size of the final image in pixels. } impl Camera { - // Constructs a new camera from a position and viewing direction. - pub fn new_(pos: Point3, dir: Vector3, up: Vector3, - focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self { + pub fn new_( + pos: Point3, + dir: Vector3, + up: Vector3, + focal_length: f64, + aspect_ratio: f64, + canvas_y: f64, + image_y: u64, + ) -> Self { let iso = Isometry3::face_towards(&pos, &(pos + dir), &up); Camera { matrix: iso, focal_length: focal_length, - canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y), - image_size: Vector2::new((image_y as f32 * aspect_ratio) as u32, image_y) + canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y), + image_size: Vector2::new((image_y as f64 * aspect_ratio) as u64, image_y), } } // Constructs a new camera from a position and viewing direction // (assuming the camera is oriented upright). - pub fn new(pos: Point3, dir: Vector3, - focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self - { Camera::new_(pos, dir, Vector3::y(), focal_length, aspect_ratio, canvas_y, image_y) } + pub fn new( + pos: Point3, + dir: Vector3, + focal_length: f64, + aspect_ratio: f64, + canvas_y: f64, + image_y: u64, + ) -> Self { + Camera::new_( + pos, + dir, + Vector3::y(), + focal_length, + aspect_ratio, + canvas_y, + image_y, + ) + } - pub fn pos(&self) -> Point3 { Point3::from(self.matrix.translation.vector) } + pub fn pos(&self) -> Point3 { + Point3::from(self.matrix.translation.vector) + } // Takes a 2D point in the image space and // maps it to the 3D point on the canvas. - fn project(&self, x: u32, y: u32) -> Point3 { + fn project(&self, x: u64, y: u64) -> Point3 { // convert point from raster coordinates to center-based coordinates - let pixelndc = Point2::new(x as f32 + 0.5 - self.image_size.x as f32 * 0.5, -(y as f32 + 0.5) + self.image_size.y as f32 * 0.5); + let pixelndc = Point2::new( + x as f64 + 0.5 - self.image_size.x as f64 * 0.5, + -(y as f64 + 0.5) + self.image_size.y as f64 * 0.5, + ); - let point: Point3 = Point::from(pixelndc.coords.component_div(&self.image_size.map(|x| x as f32)) - .component_mul(&self.canvas_size) - .fixed_resize(self.focal_length)); + let point: Point3 = Point::from( + pixelndc + .coords + .component_div(&self.image_size.map(|x| x as f64)) + .component_mul(&self.canvas_size) + .fixed_resize(self.focal_length), + ); self.matrix * point } // Takes a 2D point in the image space and // returns a ray in the world space, for use in raytracing. - pub fn raycast(&self, x: u32, y: u32) -> Ray { + pub fn raycast(&self, x: u64, y: u64) -> Ray { Ray::from_points(self.pos(), self.project(x, y)) } } - #[cfg(test)] mod tests { use super::*; - fn round(point: Point3) -> Point3 { + fn round(point: Point3) -> Point3 { Point::from(point.coords.map(|x| x.round())) } #[test] fn camera_pos() { - let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, 1.0, - 2.0, 800); + let camera: Camera = Camera::new( + Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, + 1.0, + 2.0, + 800, + ); assert_eq!(camera.pos(), Point3::new(-5.0, 0.0, 0.0)); } #[test] fn camera_matrix1() { - let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, 1.0, - 2.0, 800); + let camera: Camera = Camera::new( + Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, + 1.0, + 2.0, + 800, + ); let point = Point3::new(0.0, 0.0, 4.0); let point = camera.matrix * point; @@ -91,10 +127,14 @@ mod tests { #[test] fn camera_matrix2() { - let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, 1.0, - 2.0, 800); + let camera: Camera = Camera::new( + Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, + 1.0, + 2.0, + 800, + ); let point = Point3::new(4.0, 0.0, 0.0); let point = camera.matrix * point; @@ -104,10 +144,14 @@ mod tests { #[test] fn camera_project1() { - let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, 1.0, - 2.0, 800); + let camera: Camera = Camera::new( + Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, + 1.0, + 2.0, + 800, + ); let point = camera.project(400, 400); let point = round(point); // round to avoid errors @@ -116,10 +160,14 @@ mod tests { #[test] fn camera_project2() { - let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, 1.0, - 2.0, 800); + let camera: Camera = Camera::new( + Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, + 1.0, + 2.0, + 800, + ); let point = camera.project(0, 0); let point = round(point); // round to avoid errors diff --git a/src/object.rs b/src/object.rs index d7d4d8b..9d7b108 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,19 +1,22 @@ - -mod sphere; pub use sphere::*; -mod plane; pub use plane::*; -mod triangle; pub use triangle::*; -mod bound; pub use bound::*; -mod point_light; pub use point_light::*; +mod sphere; +pub use sphere::*; +mod plane; +pub use plane::*; +mod triangle; +pub use triangle::*; +mod bound; +pub use bound::*; +mod point_light; +pub use point_light::*; use crate::types::*; // A trait for types that can be in Objects. pub trait Surface { - // Takes in a ray and performs an intersection test // on itself. If the ray intersects the object, // returns the distance to the intersection point. - fn intersect(&self, ray: Ray) -> Option; + fn intersect(&self, ray: Ray) -> Option; // Takes in a point (assumed to be on the object's surface) // and returns the normal vector off of that point. @@ -29,7 +32,7 @@ pub trait Surface { pub struct Object { pub surface: Box, - bound: Bound + bound: Bound, } impl Object { @@ -38,18 +41,23 @@ impl Object { let bound = surface.bound(); Object { surface: Box::new(surface), - bound + bound, } } - - pub fn intersect(&self, ray: Ray) -> Option { + pub fn intersect(&self, ray: Ray) -> Option { if self.bound.is_intersected(ray) { self.surface.intersect(ray) - } else { None } + } else { + None + } + } + pub fn normal(&self, point: Point3f) -> Unit3f { + self.surface.normal(point) + } + pub fn get_texture(&self, point: Point3f) -> Texture { + self.surface.get_texture(point) } - pub fn normal(&self, point: Point3f) -> Unit3f { self.surface.normal(point) } - pub fn get_texture(&self, point: Point3f) -> Texture { self.surface.get_texture(point) } } pub trait Light { @@ -60,7 +68,7 @@ pub trait Light { fn get_color(&self, point: Point3f) -> Color; // Compute intensity on a point. - fn intensity(&self, point: Point3f) -> f32; + fn intensity(&self, point: Point3f) -> f64; // Return the direction from the point to the light source. fn direction(&self, point: Point3f) -> Unit3f; @@ -69,5 +77,5 @@ pub trait Light { pub struct Scene { pub objects: Vec, pub lights: Vec>, - pub background: Color + pub background: Color, } diff --git a/src/object/bound.rs b/src/object/bound.rs index 49c0246..e295003 100644 --- a/src/object/bound.rs +++ b/src/object/bound.rs @@ -10,15 +10,17 @@ use crate::types::*; #[derive(Debug)] pub struct Bound { pub center: Point3f, - pub radius: f32, + pub radius: f64, // If true, then the bounding sphere is disabled. - pub bypass: bool + pub bypass: bool, } impl Bound { pub fn is_intersected(&self, ray: Ray) -> bool { - if self.bypass { return true; } + if self.bypass { + return true; + } let l = ray.origin - self.center; l.norm_squared() >= self.radius * self.radius @@ -26,5 +28,11 @@ impl Bound { // pub fn contains(&self, point: &Point3f) -> bool { distance(&self.center, point) < self.radius } - pub fn bypass() -> Self { Bound { center: Point3::origin(), radius: 0.0, bypass: true } } + 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 4d2fc83..e02ac95 100644 --- a/src/object/plane.rs +++ b/src/object/plane.rs @@ -1,81 +1,107 @@ extern crate nalgebra as na; -use na::*; use na::geometry::Point3; +use na::*; +use super::{bound::*, Surface}; use crate::types::*; -use super::{Surface, bound::*}; pub struct Plane { - pub center: Point3f, // Plane origin (used for texture mapping). - pub normal: Unit3f, // Precomputed plane normal. + pub center: Point3f, // Plane origin (used for texture mapping). + pub normal: Unit3f, // Precomputed plane normal. x_axis: Vector3f, // Plane x-axis (The 3D direction that corresponds to the x-direction on the plane). y_axis: Vector3f, // Plane y-axis (The 3D direction that corresponds to the y-direction on the plane). - texture: Box Texture> // Texture map. - // Input coordinates are defined in terms of the axes above. + texture: Box Texture>, // Texture map. + // Input coordinates are defined in terms of the axes above. } #[allow(dead_code)] impl Plane { // Creates a new plane. pub fn new(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self - where F: Fn(f32, f32) -> Texture + where + F: Fn(f64, f64) -> Texture, { Plane { center, normal: Unit::new_normalize(x_axis.cross(&y_axis)), x_axis: x_axis, y_axis: y_axis, - texture: Box::new(texture) + texture: Box::new(texture), } } // Creates a new plane with the normal flipped. - pub fn new_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self - where F: Fn(f32, f32) -> Texture + pub fn new_flip( + center: Point3f, + x_axis: Vector3f, + y_axis: Vector3f, + texture: F, + ) -> Self + where + F: Fn(f64, f64) -> Texture, { Plane { center: center, normal: Unit::new_normalize(y_axis.cross(&x_axis)), x_axis: x_axis, y_axis: y_axis, - texture: Box::new(texture) + texture: Box::new(texture), } } // Creates a new plane of a solid color. - pub fn new_solid(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self - { Plane::new(center, x_axis, y_axis, move |_, _| texture) } + pub fn new_solid( + center: Point3f, + x_axis: Vector3f, + y_axis: Vector3f, + texture: Texture, + ) -> Self { + Plane::new(center, x_axis, y_axis, move |_, _| texture) + } // Creates a new flipped plane of a solid color. - pub fn new_solid_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self - { Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) } - + pub fn new_solid_flip( + center: Point3f, + x_axis: Vector3f, + y_axis: Vector3f, + texture: Texture, + ) -> Self { + Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) + } // Creates a new XY-plane with the given texture map. - pub fn xy(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self - { Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) } + pub fn xy(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { + Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) + } // Creates a new XZ-plane with the given texture map. - pub fn xz(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self - { Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) } + pub fn xz(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { + Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) + } } impl Surface for Plane { - fn intersect(&self, ray: Ray) -> Option { - + fn intersect(&self, ray: Ray) -> Option { let d = self.normal.dot(&ray.direction); - if d > -1e-3 { return None; } + if d > -1e-3 { + return None; + } let t = (self.center - ray.origin).dot(&*self.normal) / d; - if t >= 0.0 { Some(t) } - else { None } + if t >= 0.0 { + Some(t) + } else { + None + } } - fn normal(&self, _point: Point3f) -> Unit3f { self.normal } + fn normal(&self, _point: Point3f) -> Unit3f { + self.normal + } fn get_texture(&self, point: Point3f) -> Texture { let rel_pos = point - self.center; @@ -89,5 +115,7 @@ impl Surface for Plane { // Planes are infinite, so no finite // bounding sphere could possibly contain one. - fn bound(&self) -> Bound { Bound::bypass() } + fn bound(&self) -> Bound { + Bound::bypass() + } } diff --git a/src/object/point_light.rs b/src/object/point_light.rs index 3276356..cc6ad91 100644 --- a/src/object/point_light.rs +++ b/src/object/point_light.rs @@ -2,33 +2,42 @@ extern crate nalgebra as na; use na::*; -use crate::types::*; use super::*; +use crate::types::*; pub struct PointLight { pub pos: Point3f, pub color: Color, - pub intensity: f32 + pub intensity: f64, } #[allow(dead_code)] impl PointLight { - pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight { - PointLight { pos, color, intensity } + pub fn new(pos: Point3f, color: Color, intensity: f64) -> PointLight { + PointLight { + pos, + color, + intensity, + } } } impl Light for PointLight { fn check_shadow(&self, point: Point3f, objects: &Vec) -> bool { let max_d = distance(&self.pos, &point); - objects.iter() - .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) - .all(|d| d - max_d > -1e-3 ) + objects + .iter() + .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) + .all(|d| d - max_d > -1e-3) } - fn get_color(&self, _point: Point3f) -> Color { self.color } + fn get_color(&self, _point: Point3f) -> Color { + self.color + } - fn intensity(&self, _point: Point3f) -> f32 { self.intensity } + fn intensity(&self, _point: Point3f) -> f64 { + self.intensity + } fn direction(&self, point: Point3f) -> Unit3f { Unit::new_normalize(self.pos - point) @@ -42,7 +51,13 @@ mod tests { #[test] fn point_light_check_shadow() { let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0); - let block = Object::new(Sphere::new_solid(0.0, 0.5, 0.0, 0.1, Texture::new(0.0, 0.0, 0.0, 0.0))); + let block = Object::new(Sphere::new_solid( + 0.0, + 0.5, + 0.0, + 0.1, + Texture::new(0.0, 0.0, 0.0, 0.0), + )); assert!(light.check_shadow(Point3::origin(), &Vec::new())); assert!(!light.check_shadow(Point3::origin(), &vec![block])); diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 232637f..5c5f60f 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -1,49 +1,57 @@ extern crate nalgebra as na; -use std::f32::consts::PI; +use std::f64::consts::PI; -use na::*; use na::geometry::Point3; +use na::*; +use super::{bound::*, Surface}; use crate::types::*; -use super::{Surface, bound::*}; pub struct Sphere { pub center: Point3f, // Center point of the sphere. - pub radius: f32, // Radius of the sphere. + pub radius: f64, // Radius of the sphere. - texture: Box Texture> // Texture map. - // Uses spherical coordinates (normalized from 0-1) as input. + texture: Box Texture>, // Texture map. + // 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 - where F: Fn(f32, f32) -> Texture + pub fn new(x: f64, y: f64, z: f64, radius: f64, texture: F) -> Self + where + F: Fn(f64, f64) -> Texture, { Sphere { - center: Point3::new(x, y, z), radius, - texture: Box::new(texture) + center: Point3::new(x, y, z), + radius, + texture: Box::new(texture), } } // Creates a new sphere of a solid color. - pub fn new_solid(x: f32, y: f32, z: f32, radius: f32, texture: Texture) -> Self - { Sphere::new(x, y, z, radius, move |_, _| texture) } + pub fn new_solid(x: f64, y: f64, z: f64, radius: f64, texture: Texture) -> Self { + Sphere::new(x, y, z, radius, move |_, _| texture) + } } impl Surface for Sphere { - fn intersect(&self, ray: Ray) -> Option { - fn solve_quadratic(b: f32, c: f32) -> Option<(f32, f32)> { + fn intersect(&self, ray: Ray) -> Option { + fn solve_quadratic(b: f64, c: f64) -> Option<(f64, f64)> { let discr = b * b - 4.0 * c; - if discr < 0.0 { None } - else if discr == 0.0 { + if discr < 0.0 { + None + } else if discr == 0.0 { 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()) }; + let q = if b > 0.0 { + -0.5 * (b + discr.sqrt()) + } else { + -0.5 * (b - discr.sqrt()) + }; Some((q, c / q)) } } @@ -54,11 +62,17 @@ impl Surface for Sphere { let (mut t0, mut t1) = solve_quadratic(b, c)?; - if t0 > t1 { std::mem::swap(&mut t0, &mut t1); } + if t0 > t1 { + std::mem::swap(&mut t0, &mut t1); + } - if t0 >= 0.0 { Some(t0) } - else if t1 >= 0.0 { Some(t1) } - else { None } + if t0 >= 0.0 { + Some(t0) + } else if t1 >= 0.0 { + Some(t1) + } else { + None + } } fn normal(&self, point: Point3f) -> Unit3f { @@ -77,5 +91,11 @@ impl Surface for Sphere { (*self.texture)(x, y) } - fn bound(&self) -> Bound { Bound { center: self.center, radius: self.radius, bypass: false } } + fn bound(&self) -> Bound { + Bound { + center: self.center, + radius: self.radius, + bypass: false, + } + } } diff --git a/src/object/triangle.rs b/src/object/triangle.rs index c09624f..6186e6f 100644 --- a/src/object/triangle.rs +++ b/src/object/triangle.rs @@ -2,11 +2,11 @@ extern crate nalgebra as na; use std::cmp::Ordering; -use na::*; use na::geometry::Point3; +use na::*; +use super::{bound::*, Surface}; use crate::types::*; -use super::{Surface, bound::*}; pub struct Triangle { pub v1: usize, // Handles to 3 vertices. @@ -14,35 +14,45 @@ pub struct Triangle { pub v3: usize, normal: Unit3f, // Precalculated normal vector. - area: f32, // Precalculated area for barycentric calculations. + area: f64, // Precalculated area for barycentric calculations. - texture: Box Texture> // Texture map. - // Uses barycentric coordinates as input. + texture: Box Texture>, // Texture map. + // Uses barycentric coordinates as input. } pub struct TriangleMesh { pub vertices: Vec, - pub triangles: Vec + pub triangles: Vec, } -fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f32 { - let prlg_area: f32 = (b - a).cross(&(c - a)).norm(); +fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f64 { + let prlg_area: f64 = (b - a).cross(&(c - a)).norm(); prlg_area / 2.0 } impl Triangle { - fn vertex1<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v1] } - fn vertex2<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v2] } - fn vertex3<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v3] } + fn vertex1<'a>(&self, vertices: &'a Vec) -> &'a Point3f { + &vertices[self.v1] + } + fn vertex2<'a>(&self, vertices: &'a Vec) -> &'a Point3f { + &vertices[self.v2] + } + fn vertex3<'a>(&self, vertices: &'a Vec) -> &'a Point3f { + &vertices[self.v3] + } // Conversion of barycentric coordinates to // a point on the triangle. - fn from_bary(&self, vertices: &Vec, t: f32, u: f32, v: f32) -> Point3f { - Point::from(t * self.vertex1(vertices).coords + u * self.vertex2(vertices).coords + v * self.vertex3(vertices).coords) + fn from_bary(&self, vertices: &Vec, t: f64, u: f64, v: f64) -> Point3f { + Point::from( + t * self.vertex1(vertices).coords + + u * self.vertex2(vertices).coords + + v * self.vertex3(vertices).coords, + ) } // Conversion of a point to barycentric coordinates. - fn to_bary(&self, vertices: &Vec, point: Point3f) -> (f32, f32, f32) { + fn to_bary(&self, vertices: &Vec, point: Point3f) -> (f64, f64, f64) { let t = tri_area(self.vertex2(vertices), self.vertex3(vertices), &point) / self.area; let u = tri_area(self.vertex1(vertices), self.vertex3(vertices), &point) / self.area; let v = tri_area(self.vertex1(vertices), self.vertex2(vertices), &point) / self.area; @@ -50,32 +60,39 @@ impl Triangle { (t, u, v) } - fn intersect_(&self, vertices: &Vec, ray: Ray) -> Option<(f32, f32, f32)> { + fn intersect_(&self, vertices: &Vec, ray: Ray) -> Option<(f64, f64, f64)> { let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices); let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices); let p_vect = ray.direction.cross(&vect3_1); let det = p_vect.dot(&vect2_1); - if det.abs() < 1e-3 { return None; } + if det.abs() < 1e-3 { + return None; + } let t_vect = ray.origin - self.vertex1(vertices); let u = t_vect.dot(&p_vect) / det; - if u < 0.0 || u > 1.0 { return None; } + if u < 0.0 || u > 1.0 { + return None; + } let q_vect = t_vect.cross(&vect2_1); let v = ray.direction.dot(&q_vect) / det; - if v < 0.0 || (u + v) > 1.0 { return None; } + if v < 0.0 || (u + v) > 1.0 { + return None; + } let t = 1.0 - u - v; Some((t, u, v)) } - 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 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 get_texture(&self, vertices: &Vec, point: Point3f) -> Texture { @@ -86,45 +103,81 @@ impl Triangle { #[allow(dead_code)] impl TriangleMesh { - pub fn new(vertices: Vec, tris: Vec<(usize, usize, usize, Box Texture>)>) -> Self { - let triangles = tris.into_iter() - .map(|(v1, v2, v3, f)| Triangle { - v1, v2, 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 { - vertices, triangles - } - } - - pub fn new_solid(vertices: Vec, tris: Vec<(usize, usize, usize)>, texture: Texture) -> Self { - let triangles = tris.into_iter() - .map(|(v1, v2, v3)| Triangle { - v1, v2, 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 |_, _, _| texture) - }).collect(); + pub fn new( + vertices: Vec, + tris: Vec<(usize, usize, usize, Box Texture>)>, + ) -> Self { + let triangles = tris + .into_iter() + .map(|(v1, v2, v3, f)| Triangle { + v1, + v2, + 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 { vertices, - triangles + triangles, } } - pub fn singleton(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: F) -> Self - where F: Fn(f32, f32, f32) -> Texture - { TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) } + pub fn new_solid( + vertices: Vec, + tris: Vec<(usize, usize, usize)>, + texture: Texture, + ) -> Self { + let triangles = tris + .into_iter() + .map(|(v1, v2, v3)| Triangle { + v1, + v2, + 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 |_, _, _| texture), + }) + .collect(); + TriangleMesh { + vertices, + triangles, + } + } - pub fn singleton_solid(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: Texture) -> Self - { TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) } + pub fn singleton( + vertex1: Point3f, + vertex2: Point3f, + vertex3: Point3f, + texture: F, + ) -> Self + where + F: Fn(f64, f64, f64) -> Texture, + { + TriangleMesh::new( + vec![vertex1, vertex2, vertex3], + vec![(0, 1, 2, Box::new(texture))], + ) + } + pub fn singleton_solid( + vertex1: Point3f, + vertex2: Point3f, + vertex3: Point3f, + texture: Texture, + ) -> Self { + TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) + } fn closest_tri(&self, point: Point3f) -> &Triangle { - self.triangles.iter() + self.triangles + .iter() .map(move |tri| { - let rel_pos = point - tri.vertex1(&self.vertices); let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos)); @@ -139,15 +192,17 @@ impl TriangleMesh { (tri, distance(&point, &point_new)) }) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) - .unwrap().0 + .unwrap() + .0 } } impl Surface for TriangleMesh { - fn intersect(&self, ray: Ray) -> Option { - self.triangles.iter() - .filter_map(|tri| tri.intersect(&self.vertices, ray)) - .min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)) + fn intersect(&self, ray: Ray) -> Option { + self.triangles + .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: Point3f) -> Unit3f { @@ -160,22 +215,26 @@ impl Surface for TriangleMesh { // Uses Welzl's algorithm to solve the bounding sphere problem fn bound(&self) -> Bound { - fn smallest_sphere_plane(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) { + fn smallest_sphere_plane(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f64) { if points.len() == 0 || boundary.len() == 3 { 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()) }, + 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]), - _ => unreachable!() + _ => 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; } + if distance(&bound.0, removed) < bound.1 { + return bound; + } let mut boundary = boundary.clone(); boundary.push(removed); @@ -184,32 +243,44 @@ impl Surface for TriangleMesh { } } - fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f32) { + fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f64) { 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()); + / (2.0 * crs.norm_squared()); let radius = to_center.norm(); (point1 + to_center, radius) } - fn tetrahedron_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f, point4: &Point3f) -> (Point3f, f32) { - let matrix = Matrix4::from_rows(&[point1.to_homogeneous().transpose(), - point2.to_homogeneous().transpose(), - point3.to_homogeneous().transpose(), - point4.to_homogeneous().transpose()]); + fn tetrahedron_sphere( + point1: &Point3f, + point2: &Point3f, + point3: &Point3f, + point4: &Point3f, + ) -> (Point3f, f64) { + 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; if (a != 0.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()); + 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(); @@ -231,23 +302,27 @@ impl Surface for TriangleMesh { } } - fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) { + fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f64) { 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()) }, + 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!() + _ => 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; } + if distance(&bound.0, removed) < bound.1 { + return bound; + } let mut boundary = boundary.clone(); boundary.push(removed); @@ -257,14 +332,18 @@ impl Surface for TriangleMesh { } extern crate rand; - use rand::thread_rng; use rand::seq::SliceRandom; + use rand::thread_rng; let mut points: Vec<&Point3f> = self.vertices.iter().collect(); points.shuffle(&mut thread_rng()); let (center, radius) = smallest_sphere(points, Vec::new()); - Bound { center, radius: radius + 1e-3, bypass: false } + Bound { + center, + radius: radius + 1e-3, + bypass: false, + } } } diff --git a/src/render.rs b/src/render.rs index 8a6b174..f7ff970 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,18 +1,18 @@ extern crate nalgebra as na; use std::cmp::Ordering; -use std::f32::consts::PI; +use std::f64::consts::PI; -use na::*; use na::geometry::Point3; +use na::*; use crate::object::*; use crate::types::*; -fn trace(ray: Ray, objects: &Vec) -> Option<(&Object, f32)> { - objects.iter() - .filter_map(|obj| obj.intersect(ray) - .map(|x| (obj, x))) +fn trace(ray: Ray, objects: &Vec) -> Option<(&Object, f64)> { + objects + .iter() + .filter_map(|obj| obj.intersect(ray).map(|x| (obj, x))) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) } @@ -20,7 +20,10 @@ fn light_point(objects: &Vec, obj: &Object, point: Point3f, light: &dyn if light.check_shadow(point, objects) { let texture = obj.get_texture(point); - light.get_color(point) * (texture.albedo / PI) * light.intensity(point) * obj.normal(point).dot(&*light.direction(point)) + light.get_color(point) + * (texture.albedo / PI) + * light.intensity(point) + * obj.normal(point).dot(&*light.direction(point)) } else { // Point is in shadow Color::black() @@ -32,8 +35,13 @@ pub fn cast_ray(ray: Ray, scene: &Scene) -> Color { let point = ray.project(dist); let surface_color = obj.get_texture(point).color; - scene.lights.iter() + scene + .lights + .iter() .map(|light| light_point(&scene.objects, obj, point, &**light)) - .fold(Color::black(), |acc, c| acc + c) * surface_color - } else { scene.background } + .fold(Color::black(), |acc, c| acc + c) + * surface_color + } else { + scene.background + } } diff --git a/src/types.rs b/src/types.rs index 6328873..08e43b3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,71 +2,83 @@ extern crate nalgebra as na; use std::ops::{Add, Mul}; -use na::*; use na::geometry::Point3; +use na::*; -pub type Point3f = Point3; -pub type Vector3f = Vector3; -pub type Unit3f = Unit>; +pub type Point3f = Point3; +pub type Vector3f = Vector3; +pub type Unit3f = Unit>; #[derive(Clone, Copy, Debug)] pub struct Ray { pub origin: Point3f, - pub direction: Unit3f + pub direction: Unit3f, } impl Ray { pub fn from_parts(origin: Point3f, direction: Unit3f) -> Self { Ray { origin, direction } } - pub fn new(origin: Point3f, direction: Vector3f) -> Self { Ray::from_parts(origin, Unit::new_normalize(direction)) } - pub fn from_points(origin: Point3f, points_to: Point3f) -> Self { Ray::new(origin, points_to - origin) } + pub fn new(origin: Point3f, direction: Vector3f) -> Self { + Ray::from_parts(origin, Unit::new_normalize(direction)) + } + pub fn from_points(origin: Point3f, points_to: Point3f) -> Self { + Ray::new(origin, points_to - origin) + } - pub fn project(&self, t: f32) -> Point3f { self.origin + t * self.direction.into_inner() } + pub fn project(&self, t: f64) -> Point3f { + self.origin + t * self.direction.into_inner() + } } #[derive(Clone, Copy, Debug, PartialEq)] pub struct Color { - pub red: f32, - pub green: f32, - pub blue: f32, + pub red: f64, + pub green: f64, + pub blue: f64, - _private: () // Private field prevents direct construction + _private: (), // Private field prevents direct construction } #[allow(dead_code)] impl Color { - pub fn new(red: f32, green: f32, blue: f32) -> Self { + pub fn new(red: f64, green: f64, blue: f64) -> Self { Color { - red: if red < 0.0 { 0.0 } else { red }, + red: if red < 0.0 { 0.0 } else { red }, green: if green < 0.0 { 0.0 } else { green }, - blue: if blue < 0.0 { 0.0 } else { blue }, + blue: if blue < 0.0 { 0.0 } else { blue }, - _private: () + _private: (), } } pub fn to_byte_array(&self) -> [u8; 3] { - let red = (255.0 * self.red) as u8; + let red = (255.0 * self.red) as u8; let green = (255.0 * self.green) as u8; - let blue = (255.0 * self.blue) as u8; + let blue = (255.0 * self.blue) as u8; [red, green, blue] } - pub fn gray(brightness: f32) -> Self { Color::new(brightness, brightness, brightness) } + pub fn gray(brightness: f64) -> Self { + Color::new(brightness, brightness, brightness) + } - pub fn black() -> Self { Color::gray(0.0) } - pub fn white() -> Self { Color::gray(1.0) } + pub fn black() -> Self { + Color::gray(0.0) + } + pub fn white() -> Self { + Color::gray(1.0) + } } impl Add for Color { type Output = Color; fn add(self, rhs: Color) -> Color { Color { - red: self.red + rhs.red, + red: self.red + rhs.red, green: self.green + rhs.green, - blue: self.blue + rhs.blue, - _private: () + blue: self.blue + rhs.blue, + _private: (), } } } @@ -75,22 +87,22 @@ impl Mul for Color { type Output = Color; fn mul(self, rhs: Color) -> Color { Color { - red: self.red * rhs.red, + red: self.red * rhs.red, green: self.green * rhs.green, - blue: self.blue * rhs.blue, - _private: () + blue: self.blue * rhs.blue, + _private: (), } } } -impl Mul for Color { +impl Mul for Color { type Output = Color; - fn mul(self, rhs: f32) -> Color { + fn mul(self, rhs: f64) -> Color { Color { - red: self.red * rhs, + red: self.red * rhs, green: self.green * rhs, - blue: self.blue * rhs, - _private: () + blue: self.blue * rhs, + _private: (), } } } @@ -98,15 +110,15 @@ impl Mul for Color { #[derive(Clone, Copy, Debug, PartialEq)] pub struct Texture { pub color: Color, - pub albedo: f32 + pub albedo: f64, } #[allow(dead_code)] impl Texture { - pub fn new(red: f32, green: f32, blue: f32, albedo: f32) -> Self { + pub fn new(red: f64, green: f64, blue: f64, albedo: f64) -> Self { Texture { color: Color::new(red, green, blue), - albedo + albedo, } } }