diff --git a/src/main.rs b/src/main.rs index 236851d..5bfcef6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,26 @@ - mod primitives; -mod scene; mod renderer; +mod scene; -use crate::primitives::{ - Vec2i, - Vec3, -}; -use crate::scene::{ - Camera, - Scene -}; +use crate::primitives::{Vec2i, Vec3}; +use crate::scene::{Camera, Scene}; -use crate::renderer::{ - Tile, - RenderProperties, -}; +use crate::renderer::{RenderProperties, Tile}; use rand::SeedableRng; use rand::rngs::SmallRng; - fn main() { // image let aspect_ratio = 3.0 / 2.0; let image = Vec2i { x: 400, - y: (400.0 / aspect_ratio) as i32 + y: (400.0 / aspect_ratio) as i32, }; let render_config = RenderProperties { samples: 10, - bounces: 50 + bounces: 50, }; // random generator @@ -41,20 +30,20 @@ fn main() { let scene = Scene { camera: Camera::new( Vec3::new(13.0, 2.0, 3.0), // lookfrom - Vec3::zero(), // lookat - Vec3::new(0.0, 1.0, 0.0), // vup + Vec3::zero(), // lookat + Vec3::new(0.0, 1.0, 0.0), // vup 20.0, - aspect_ratio, - 0.1, // aperture + aspect_ratio, + 0.1, // aperture 10.0, // dist_to_focus ), - world: Scene::random_world(&mut small_rng) + world: Scene::random_world(&mut small_rng), }; - + // render // The render loop should now be a job submission mechanism // Iterate lines, submitting them as tasks to the thread. - println!("P3\n{} {}\n255", image.x, image.y); + println!("P3\n{} {}\n255", image.x, image.y); // TILE BASED RENDERER // let tile = Tile::render_tile( // Rect { x: 0, y: 0, w: image.x, h: image.y }, diff --git a/src/primitives.rs b/src/primitives.rs index 0f6c774..ccf7861 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -1,106 +1,110 @@ - -use std::ops::{ - Add, - AddAssign, - Sub, - SubAssign, - Mul, - MulAssign, - Div, - DivAssign, - Neg, -}; use std::fmt; use std::fmt::Display; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use rand::Rng; -use rand::rngs::SmallRng; use rand::distr::Uniform; +use rand::rngs::SmallRng; pub type Vec2i = Vec2; pub type Vec2f = Vec2; -#[derive (Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct Vec2{ +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct Vec2 { pub x: T, pub y: T, } - impl Vec2 { pub fn zero() -> Vec2 { - Vec2{ x: 0.0, y: 0.0 } + Vec2 { x: 0.0, y: 0.0 } } pub fn ones() -> Vec2 { - Vec2{ x: 1.0, y: 1.0 } + Vec2 { x: 1.0, y: 1.0 } } pub fn rand(srng: &mut SmallRng, distrib: Uniform) -> Vec2 { - Vec2 { x: srng.sample(distrib), y: srng.sample(distrib) } + Vec2 { + x: srng.sample(distrib), + y: srng.sample(distrib), + } } - } -impl Vec2 -where T: std::ops::Mul{ +impl Vec2 +where + T: std::ops::Mul, +{ pub fn new(x: T, y: T) -> Vec2 { - Vec2{x, y} + Vec2 { x, y } } } -impl Add for Vec2 -where T: std::ops::Add{ +impl Add for Vec2 +where + T: std::ops::Add, +{ type Output = Vec2; fn add(self, other: Vec2) -> Vec2 { - Vec2 { x: self.x + other.x, y: self.y + other.y } + Vec2 { + x: self.x + other.x, + y: self.y + other.y, + } } } -impl Mul for Vec2 -where T: std::ops::Mul{ +impl Mul for Vec2 +where + T: std::ops::Mul, +{ type Output = Vec2; fn mul(self, other: Vec2) -> Vec2 { Vec2 { x: self.x * other.x, - y: self.y * other.y + y: self.y * other.y, } } } -impl Div for Vec2{ +impl Div for Vec2 { type Output = Vec2; fn div(self, other: f32) -> Vec2 { Vec2 { - x: 1.0/other * self.x, - y: 1.0/other * self.y + x: 1.0 / other * self.x, + y: 1.0 / other * self.y, } } } -impl Div for Vec2{ +impl Div for Vec2 { type Output = Vec2; fn div(self, other: i32) -> Vec2 { Vec2 { x: self.x / other, - y: self.y / other + y: self.y / other, } } } -impl Div> for Vec2 -where T: std::ops::Div{ +impl Div> for Vec2 +where + T: std::ops::Div, +{ type Output = Vec2; fn div(self, other: Vec2) -> Vec2 { Vec2 { x: self.x / other.x, - y: self.y / other.y + y: self.y / other.y, } } } -impl Display for Vec2 -where T: Display { // nested type still needs to have Display +impl Display for Vec2 +where + T: Display, +{ + // nested type still needs to have Display fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let str = format!("{} {}", self.x, self.y); fmt.write_str(&str) @@ -108,35 +112,35 @@ where T: Display { // nested type still needs to have Display } #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub struct Vec3{ - pub x: f32, - pub y: f32, - pub z: f32, +pub struct Vec3 { + pub x: f32, + pub y: f32, + pub z: f32, } -impl Vec3{ - pub fn new(x: f32, y: f32, z: f32) -> Vec3{ - Vec3{x, y, z} - } +impl Vec3 { + pub fn new(x: f32, y: f32, z: f32) -> Vec3 { + Vec3 { x, y, z } + } - pub fn zero() -> Vec3{ - Vec3{ + pub fn zero() -> Vec3 { + Vec3 { x: 0.0, y: 0.0, z: 0.0, } } - pub fn ones() -> Vec3{ + pub fn ones() -> Vec3 { Vec3 { x: 1.0, y: 1.0, - z: 1.0 + z: 1.0, } } pub fn rand(srng: &mut SmallRng, distrib: Uniform) -> Vec3 { - Vec3{ + Vec3 { x: srng.sample(distrib), y: srng.sample(distrib), z: srng.sample(distrib), @@ -147,8 +151,11 @@ impl Vec3{ let distrib = Uniform::new(-1.0, 1.0).unwrap(); loop { let p = Vec3::rand(srng, distrib); - if p.length_squared() >= 1.0 { continue; } - else { return p; } + if p.length_squared() >= 1.0 { + continue; + } else { + return p; + } } } @@ -160,8 +167,11 @@ impl Vec3{ y: srng.sample(distrib.unwrap()), z: 0.0, }; - if p.length_squared() >= 1.0 { continue; } - else { return p; } + if p.length_squared() >= 1.0 { + continue; + } else { + return p; + } } } @@ -169,42 +179,38 @@ impl Vec3{ return Vec3::as_unit(Vec3::rand_in_unit_sphere(srng)); } - pub fn length(&self) -> f32 { - self.length_squared().sqrt() - } + pub fn length(&self) -> f32 { + self.length_squared().sqrt() + } + + pub fn length_squared(&self) -> f32 { + (self.x * self.x) + (self.y * self.y) + (self.z * self.z) + } - pub fn length_squared(&self) -> f32 { - (self.x * self.x) + (self.y * self.y) + (self.z * self.z) - } - // roughly equivalent to the `void write_color(...)` in the book pub fn print_ppm(&self, samples_per_pixel: u32) -> String { - let scale = 1.0 / samples_per_pixel as f32; - + // now with gamma correction let r = (self.x * scale).sqrt(); let g = (self.y * scale).sqrt(); let b = (self.z * scale).sqrt(); - - let ir = (r.clamp( 0.0, 0.999) * 256.0) as i32; - let ig = (g.clamp( 0.0, 0.999) * 256.0) as i32; - let ib = (b.clamp( 0.0, 0.999) * 256.0) as i32; + + let ir = (r.clamp(0.0, 0.999) * 256.0) as i32; + let ig = (g.clamp(0.0, 0.999) * 256.0) as i32; + let ib = (b.clamp(0.0, 0.999) * 256.0) as i32; format!("{} {} {}", ir, ig, ib) } - + pub fn near_zero(&self) -> bool { let epsilon: f32 = 1e-4; - return - self.x.abs() < epsilon && - self.y.abs() < epsilon && - self.z.abs() < epsilon + return self.x.abs() < epsilon && self.y.abs() < epsilon && self.z.abs() < epsilon; } - + pub fn reflect(v: Vec3, n: Vec3) -> Vec3 { return v - n * Vec3::dot(v, n) * 2.0; } - + pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) -> Vec3 { let cos_theta = Vec3::dot(-uv, n).min(1.0); let r_out_perp = (uv + n * cos_theta) * etai_over_etat; @@ -212,156 +218,153 @@ impl Vec3{ r_out_perp + r_out_parallel } - pub fn dot(left: Vec3, right: Vec3) -> f32{ - left.x * right.x + - left.y * right.y + - left.z * right.z + pub fn dot(left: Vec3, right: Vec3) -> f32 { + left.x * right.x + left.y * right.y + left.z * right.z } - - pub fn cross(u: Vec3, v: Vec3) -> Vec3{ - Vec3{ + + pub fn cross(u: Vec3, v: Vec3) -> Vec3 { + Vec3 { x: u.y * v.z - u.z * v.y, y: u.z * v.x - u.x * v.z, - z: u.x * v.y - u.y * v.x + z: u.x * v.y - u.y * v.x, } } - + pub fn as_unit(v: Vec3) -> Vec3 { let len = v.length(); v / len } - } impl Add for Vec3 { - type Output = Vec3; - fn add(self, other: Vec3) -> Vec3 { - Vec3{ - x: self.x + other.x, - y: self.y + other.y, - z: self.z + other.z, - } - } + type Output = Vec3; + fn add(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } } impl AddAssign for Vec3 { - fn add_assign(&mut self, other: Vec3){ - *self = Self { - x: self.x + other.x, - y: self.y + other.y, - z: self.z + other.z - }; - } + fn add_assign(&mut self, other: Vec3) { + *self = Self { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + }; + } } impl Sub for Vec3 { - type Output = Vec3; - fn sub(self, other: Vec3) -> Vec3 { - Vec3 { - x: self.x - other.x, - y: self.y - other.y, - z: self.z - other.z, - } - } + type Output = Vec3; + fn sub(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + } + } } impl SubAssign for Vec3 { - fn sub_assign(&mut self, other: Vec3){ - *self = Self { - x: self.x - other.x, - y: self.y - other.y, - z: self.z - other.z - }; - } + fn sub_assign(&mut self, other: Vec3) { + *self = Self { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + }; + } } impl Mul for Vec3 { - type Output = Vec3; - fn mul(self, other: Vec3) -> Vec3 { - Vec3 { - x: self.x * other.x, - y: self.y * other.y, - z: self.z * other.z, - } - } + type Output = Vec3; + fn mul(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x * other.x, + y: self.y * other.y, + z: self.z * other.z, + } + } } -impl Mul for Vec3{ - type Output = Vec3; - fn mul(self, other: f32) -> Vec3 { - Vec3 { - x: self.x * other, - y: self.y * other, - z: self.z * other, - } - } +impl Mul for Vec3 { + type Output = Vec3; + fn mul(self, other: f32) -> Vec3 { + Vec3 { + x: self.x * other, + y: self.y * other, + z: self.z * other, + } + } } impl MulAssign for Vec3 { - fn mul_assign(&mut self, other: Vec3){ - *self = Self { - x: self.x * other.x, - y: self.y * other.y, - z: self.z * other.z - }; - } + fn mul_assign(&mut self, other: Vec3) { + *self = Self { + x: self.x * other.x, + y: self.y * other.y, + z: self.z * other.z, + }; + } } -impl MulAssign for Vec3{ - fn mul_assign(&mut self, other: f32){ - *self = Self { - x: self.x * other, - y: self.y * other, - z: self.z * other - }; - } +impl MulAssign for Vec3 { + fn mul_assign(&mut self, other: f32) { + *self = Self { + x: self.x * other, + y: self.y * other, + z: self.z * other, + }; + } } impl Div for Vec3 { - type Output = Vec3; - fn div(self, other: Vec3) -> Vec3 { - Vec3 { - x: self.x / other.x, - y: self.y / other.y, - z: self.z / other.z, - } - } + type Output = Vec3; + fn div(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x / other.x, + y: self.y / other.y, + z: self.z / other.z, + } + } } impl Div for Vec3 { - type Output = Vec3; - fn div(self, other: f32) -> Vec3 { - Vec3 { - x: 1.0/other * self.x, - y: 1.0/other * self.y, - z: 1.0/other * self.z, - } - } + type Output = Vec3; + fn div(self, other: f32) -> Vec3 { + Vec3 { + x: 1.0 / other * self.x, + y: 1.0 / other * self.y, + z: 1.0 / other * self.z, + } + } } impl DivAssign for Vec3 { - fn div_assign(&mut self, other: Vec3){ - *self = Self { - x: self.x / other.x, - y: self.y / other.y, - z: self.z / other.z - }; - } + fn div_assign(&mut self, other: Vec3) { + *self = Self { + x: self.x / other.x, + y: self.y / other.y, + z: self.z / other.z, + }; + } } impl DivAssign for Vec3 { - fn div_assign(&mut self, other: f32){ - *self = Self { - x: self.x / other, - y: self.y / other, - z: self.z / other - }; - } + fn div_assign(&mut self, other: f32) { + *self = Self { + x: self.x / other, + y: self.y / other, + z: self.z / other, + }; + } } -impl Neg for Vec3{ +impl Neg for Vec3 { type Output = Self; fn neg(self) -> Self::Output { - Vec3{ + Vec3 { x: -self.x, y: -self.y, z: -self.z, @@ -370,27 +373,26 @@ impl Neg for Vec3{ } impl Display for Vec3 { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let str = format!("{} {} {}", self.x, self.y, self.z); - fmt.write_str(&str)?; - Ok(()) - - } + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let str = format!("{} {} {}", self.x, self.y, self.z); + fmt.write_str(&str)?; + Ok(()) + } } #[derive(Copy, Clone)] -pub struct Ray{ +pub struct Ray { pub orig: Vec3, pub dir: Vec3, } -impl Ray{ +impl Ray { pub fn at(&self, t: f32) -> Vec3 { - self.orig + self.dir*t + self.orig + self.dir * t } } -#[derive (Copy, Clone)] +#[derive(Copy, Clone)] pub struct Rect { pub x: i32, pub y: i32, @@ -398,11 +400,11 @@ pub struct Rect { pub h: i32, } -impl Rect{ +impl Rect { pub fn pos(&self) -> Vec2i { Vec2i { x: self.x, - y: self.y, + y: self.y, } } @@ -415,188 +417,188 @@ impl Rect{ } #[cfg(test)] -mod test{ +mod test { use super::*; #[test] - fn test_add(){ + fn test_add() { let v1 = Vec3::new(1.0, 1.0, 0.0); let v2 = Vec3::new(0.0, 0.0, 1.0); let expected = Vec3::new(1.0, 1.0, 1.0); - assert_eq!( v1+v2, expected ); + assert_eq!(v1 + v2, expected); } #[test] - fn test_add_assign(){ + fn test_add_assign() { let mut v1 = Vec3::new(0.0, 1.0, 1.0); let v2 = Vec3::new(1.0, 0.0, 0.0); let expected = Vec3::new(1.0, 1.0, 1.0); - v1+=v2; - assert_eq!( v1, expected ); + v1 += v2; + assert_eq!(v1, expected); } #[test] - fn test_sub(){ + fn test_sub() { let v1 = Vec3::new(1.0, 1.0, 0.0); let v2 = Vec3::new(0.0, 0.0, 1.0); let expected = Vec3::new(1.0, 1.0, -1.0); - assert_eq!( v1-v2, expected ); + assert_eq!(v1 - v2, expected); } #[test] - fn test_sub_assign(){ + fn test_sub_assign() { let mut v1 = Vec3::new(0.0, 1.0, 1.0); let v2 = Vec3::new(1.0, 0.0, 0.0); let expected = Vec3::new(-1.0, 1.0, 1.0); - v1-=v2; - assert_eq!( v1, expected ); + v1 -= v2; + assert_eq!(v1, expected); } #[test] - fn test_mul_vec(){ + fn test_mul_vec() { let v1 = Vec3::new(0.1, 0.5, 0.7); let v2 = Vec3::new(1.0, 2.0, 1.0); let expected = Vec3::new(0.1, 1.0, 0.7); - assert_eq!( v1*v2, expected ); + assert_eq!(v1 * v2, expected); } #[test] - fn test_mul_float(){ + fn test_mul_float() { let v1 = Vec3::new(0.1, 0.5, 0.7); let f1 = 0.5; let expected = Vec3::new(0.05, 0.25, 0.35); - assert_eq!( v1*f1, expected ); + assert_eq!(v1 * f1, expected); } #[test] - fn test_mul_vec_assign(){ + fn test_mul_vec_assign() { let mut v1 = Vec3::new(0.1, 0.5, 0.7); let v2 = Vec3::new(1.0, 2.0, 1.0); let expected = Vec3::new(0.1, 1.0, 0.7); - v1*=v2; - assert_eq!( v1, expected ); + v1 *= v2; + assert_eq!(v1, expected); } #[test] - fn test_mul_float_assign(){ + fn test_mul_float_assign() { let mut v1 = Vec3::new(0.1, 0.5, 0.7); let f1 = 0.5; let expected = Vec3::new(0.05, 0.25, 0.35); - - v1*=f1; - assert_eq!( v1, expected ); + + v1 *= f1; + assert_eq!(v1, expected); } #[test] - fn test_div_vec(){ + fn test_div_vec() { let v1 = Vec3::new(0.1, 0.5, 0.7); let v2 = Vec3::new(0.5, 2.0, 1.0); let expected = Vec3::new(0.2, 0.25, 0.7); - assert_eq!( v1/v2, expected ); + assert_eq!(v1 / v2, expected); } #[test] - fn test_div_float(){ + fn test_div_float() { let v1 = Vec3::new(0.1, 0.5, 0.7); let f1 = 0.5; let expected = Vec3::new(0.2, 1.0, 1.4); - assert_eq!( v1/f1, expected ); + assert_eq!(v1 / f1, expected); } #[test] - fn test_div_vec_assign(){ + fn test_div_vec_assign() { let mut v1 = Vec3::new(0.1, 0.5, 0.7); let v2 = Vec3::new(1.0, 2.0, 1.0); let expected = Vec3::new(0.1, 0.25, 0.7); - v1/=v2; - assert_eq!( v1, expected ); + v1 /= v2; + assert_eq!(v1, expected); } #[test] - fn test_div_float_assign(){ + fn test_div_float_assign() { let mut v1 = Vec3::new(0.1, 0.5, 0.7); let f1 = 0.5; let expected = Vec3::new(0.2, 1., 1.4); - - v1/=f1; - assert_eq!( v1, expected ); + + v1 /= f1; + assert_eq!(v1, expected); } - + #[test] - fn test_length_squared(){ + fn test_length_squared() { let v = Vec3::new(2.0, 0.0, 2.0); let len = v.length_squared(); assert_eq!(len, 8.0); } - + #[test] - fn test_length(){ + fn test_length() { let v = Vec3::new(3.0, 4.0, 0.0); let len = v.length(); assert_eq!(len, 5.0) } #[test] - fn test_dot_perpendicular(){ + fn test_dot_perpendicular() { let v1 = Vec3::new(1.0, 0.0, 0.0); let v2 = Vec3::new(0.0, 1.0, 0.0); assert_eq!(Vec3::dot(v1, v2), 0.0); } - + #[test] - fn test_dot_parallel(){ + fn test_dot_parallel() { let v1 = Vec3::new(1.0, 0.0, 0.0); let v2 = Vec3::new(1.0, 0.0, 0.0); assert_eq!(Vec3::dot(v1, v2), 1.0); } #[test] - fn test_dot_acute(){ + fn test_dot_acute() { let v1 = Vec3::new(1.0, 1.0, 0.0); let v2 = Vec3::new(0.5, 1.0, 0.0); assert_eq!(Vec3::dot(v1, v2), 1.5); } #[test] - fn test_dot_obtuse(){ + fn test_dot_obtuse() { let v1 = Vec3::new(1.0, 1.0, 0.0); let v2 = Vec3::new(0.5, -1.0, 0.0); assert_eq!(Vec3::dot(v1, v2), -0.5); } - + #[test] - fn test_cross_perpendicular(){ + fn test_cross_perpendicular() { let v1 = Vec3::new(1.0, 0.0, 0.0); let v2 = Vec3::new(0.0, 1.0, 0.0); let expected = Vec3::new(0.0, 0.0, 1.0); assert_eq!(Vec3::cross(v1, v2), expected); } - + #[test] - fn test_cross_parallel(){ + fn test_cross_parallel() { let v1 = Vec3::new(1.0, 0.0, 0.0); let v2 = Vec3::new(1.0, 0.0, 0.0); @@ -606,7 +608,7 @@ mod test{ } #[test] - fn test_cross_111(){ + fn test_cross_111() { let v1 = Vec3::new(1.0, 1.0, 1.0); let v2 = Vec3::new(0.0, 1.0, 0.0); @@ -616,32 +618,32 @@ mod test{ } #[test] - fn test_unit_shorten(){ + fn test_unit_shorten() { let v = Vec3::new(2.0, 0.0, 0.0); let expected = Vec3::new(1.0, 0.0, 0.0); - + assert_eq!(Vec3::as_unit(v), expected); } #[test] - fn test_unit_lengthen(){ + fn test_unit_lengthen() { let v = Vec3::new(0.5, 0.0, 0.0); let expected = Vec3::new(1.0, 0.0, 0.0); - + assert_eq!(Vec3::as_unit(v), expected); } #[test] - fn test_unit_111(){ + fn test_unit_111() { let v = Vec3::new(1.0, 1.0, 1.0); - let expected = Vec3::new(0.577350269,0.577350269,0.577350269); + let expected = Vec3::new(0.577350269, 0.577350269, 0.577350269); assert!(Vec3::as_unit(v) <= expected * 1.001); // within very small under-estimate assert!(Vec3::as_unit(v) >= expected * 0.999); // within very small over-estimate } #[test] - fn test_reflect_flat(){ + fn test_reflect_flat() { let ray = Vec3::new(1.0, 0.0, 0.0); let normal = Vec3::new(-1.0, 0.0, 0.0); @@ -649,24 +651,22 @@ mod test{ let expected = Vec3::new(-1.0, 0.0, 0.0); assert!(refl == expected); } - + #[test] - fn test_reflect_flat_back(){ + fn test_reflect_flat_back() { let ray = Vec3::new(1.0, 0.0, 0.0); let normal = Vec3::new(1.0, 0.0, 0.0); let refl = Vec3::reflect(ray, normal); let expected = Vec3::new(-1.0, 0.0, 0.0); assert!(refl == expected); - } #[test] - fn test_reflect_45(){ + fn test_reflect_45() { let ray = Vec3::new(1.0, 0.0, 0.0); let normal = Vec3::as_unit(Vec3::new(-1.0, 1.0, 0.0)); - let refl = Vec3::reflect(ray, normal); let expected = Vec3::new(0.0, 1.0, 0.0); let diff = refl - expected; @@ -675,15 +675,12 @@ mod test{ } #[test] - fn check_lerp(){ - let ray = Ray{ + fn check_lerp() { + let ray = Ray { orig: Vec3::new(0.0, 0.0, 0.0), - dir: Vec3::new(1.0, 1.0, 0.0) + dir: Vec3::new(1.0, 1.0, 0.0), }; let half = ray.at(0.5); - assert_eq!( - half, - Vec3::new(0.5, 0.5, 0.0) - ); + assert_eq!(half, Vec3::new(0.5, 0.5, 0.0)); } } diff --git a/src/renderer.rs b/src/renderer.rs index c1bdbb5..1b09b33 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,21 +1,15 @@ - -use crate::primitives::{ - Vec2i, - Vec2f, - Vec3, - Ray, - Rect, -}; -use crate::scene::{ - Hittable, - Scene, -}; +use crate::primitives::{Ray, Rect, Vec2f, Vec2i, Vec3}; +use crate::scene::{Hittable, Scene}; use rand::rngs::SmallRng; use itertools::{self, Itertools}; -const SKY_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.7, z: 1.0}; +const SKY_COLOR: Vec3 = Vec3 { + x: 0.5, + y: 0.7, + z: 1.0, +}; pub struct RenderProperties { pub samples: u32, // samples are averaged results over a pixel @@ -28,65 +22,54 @@ fn to_uv(coord: Vec2i, img_size: Vec2i) -> Vec2f { Vec2f::new(u, v) } -fn ray_color( - r: Ray, surface: &Hittable, depth: u32, - rng: &mut SmallRng, -) -> Vec3 { +fn ray_color(r: Ray, surface: &Hittable, depth: u32, rng: &mut SmallRng) -> Vec3 { // recursion guard if depth == 0 { return Vec3::zero(); } - + // cast a ray, interrogate hit record - if let Some(record) = surface.hit(r, 0.001, f32::INFINITY){ + if let Some(record) = surface.hit(r, 0.001, f32::INFINITY) { let mut scattered = Ray { orig: Vec3::zero(), dir: Vec3::zero(), }; let mut attenuation = Vec3::zero(); - if record.material.scatter( - r, - &record, - &mut attenuation, - &mut scattered, - rng - ) { - return attenuation * ray_color( - scattered, surface, depth-1, rng - ); + if record + .material + .scatter(r, &record, &mut attenuation, &mut scattered, rng) + { + return attenuation * ray_color(scattered, surface, depth - 1, rng); } } // TODO: explicit else block // Rust gets angry about the inner if{} block because it evaluates to () // when the else path is taken. This is a problem for a function // that returns Vec3 and not (). - { // when nothing is struck, return sky color + { + // when nothing is struck, return sky color let unitdir = Vec3::as_unit(r.dir); let t = 0.5 * (unitdir.y + 1.0); - return Vec3::ones() * (1.0 - t) + SKY_COLOR * t + return Vec3::ones() * (1.0 - t) + SKY_COLOR * t; } } fn sample_pixel( - coord: Vec2i, // location in image/screen space - scene: &Scene, // scene we're drawing + coord: Vec2i, // location in image/screen space + scene: &Scene, // scene we're drawing render_props: &RenderProperties, img_size: Vec2i, // Supplied by the execution environment (the thread) rng: &mut SmallRng, -) -> Vec3{ - (0..render_props.samples) - .fold( - Vec3::zero(), - |color, _sample| -> Vec3 { - let uv = to_uv(coord, img_size); - let ray = scene.camera.get_ray(uv.x, uv.y, rng); - if ray.dir.x.is_nan() { - panic!("Ray dir.x is NAN"); - } - color + ray_color(ray, &scene.world, render_props.bounces, rng) +) -> Vec3 { + (0..render_props.samples).fold(Vec3::zero(), |color, _sample| -> Vec3 { + let uv = to_uv(coord, img_size); + let ray = scene.camera.get_ray(uv.x, uv.y, rng); + if ray.dir.x.is_nan() { + panic!("Ray dir.x is NAN"); } - ) + color + ray_color(ray, &scene.world, render_props.bounces, rng) + }) } pub struct Tile { @@ -96,28 +79,31 @@ pub struct Tile { impl Tile { pub fn render_tile( - bounds: Rect, // bounds of the region to render - img_size: Vec2i, // final image resolution (needed for proper UV mapping) + bounds: Rect, // bounds of the region to render + img_size: Vec2i, // final image resolution (needed for proper UV mapping) scene: &Scene, properties: &RenderProperties, // TODO: Place image size in render properties? rng: &mut SmallRng, ) -> Self { - let pixel_iter = (bounds.y..(bounds.y + bounds.h)) - .cartesian_product( bounds.x..(bounds.x + bounds.w)); - let pixels = pixel_iter.map( - |coord| -> Vec3 { + let pixel_iter = + (bounds.y..(bounds.y + bounds.h)).cartesian_product(bounds.x..(bounds.x + bounds.w)); + let pixels = pixel_iter + .map(|coord| -> Vec3 { sample_pixel( - Vec2i{x: coord.1, y: coord.0}, + Vec2i { + x: coord.1, + y: coord.0, + }, scene, properties, img_size, rng, ) - } - ).collect(); + }) + .collect(); Self { _bounds: bounds, - pixels + pixels, } } pub fn render_line( @@ -128,11 +114,16 @@ impl Tile { rng: &mut SmallRng, // rng utils ) -> Self { Tile::render_tile( - Rect{ x: 0, y, w: img_size.x, h: 1 }, + Rect { + x: 0, + y, + w: img_size.x, + h: 1, + }, img_size, scene, properties, - rng + rng, ) } } diff --git a/src/scene.rs b/src/scene.rs index af36213..a19dc78 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,11 +1,10 @@ - -use crate::primitives::{Vec3, Ray}; +use crate::primitives::{Ray, Vec3}; use rand::Rng; -use rand::rngs::SmallRng; use rand::distr::Uniform; +use rand::rngs::SmallRng; -pub struct HitRecord{ +pub struct HitRecord { pub p: Vec3, pub normal: Vec3, pub material: Material, @@ -13,40 +12,53 @@ pub struct HitRecord{ pub front_face: bool, } -impl HitRecord{ - pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) -> (){ +impl HitRecord { + pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) -> () { self.front_face = Vec3::dot(r.dir, outward_normal) < 0.0; - self.normal = if self.front_face { outward_normal } else { -outward_normal }; + self.normal = if self.front_face { + outward_normal + } else { + -outward_normal + }; } } -#[derive (Clone)] +#[derive(Clone)] pub enum Hittable { - Sphere { center: Vec3, radius: f32, material: Material }, - HittableList { hittables: Vec } + Sphere { + center: Vec3, + radius: f32, + material: Material, + }, + HittableList { + hittables: Vec, + }, } impl Hittable { pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { match self { - Hittable::HittableList { hittables } => { - hittables.iter() - .map( |obj| -> Option { - obj.hit(r, t_min, t_max) - }).filter(|obj| obj.is_some()) + Hittable::HittableList { hittables } => hittables + .iter() + .map(|obj| -> Option { obj.hit(r, t_min, t_max) }) + .filter(|obj| obj.is_some()) .min_by(|lhs, rhs| { let lhs = lhs.as_ref().unwrap(); let rhs = rhs.as_ref().unwrap(); lhs.t.partial_cmp(&rhs.t).expect("Couldn't compare??") - }).unwrap_or(None) - } + }) + .unwrap_or(None), - Hittable::Sphere { center, radius, material } => { + Hittable::Sphere { + center, + radius, + material, + } => { let oc = r.orig - *center; let a = r.dir.length_squared(); let half_b = Vec3::dot(oc, r.dir); let c = oc.length_squared() - radius * radius; - let discriminant = half_b*half_b - a*c; + let discriminant = half_b * half_b - a * c; if discriminant < 0.0 { return None; @@ -61,7 +73,7 @@ impl Hittable { return None; } } - let mut record = HitRecord{ + let mut record = HitRecord { p: r.at(root), normal: (r.at(root) - *center) / *radius, material: *material, @@ -81,11 +93,10 @@ impl Hittable { } } - #[derive(Copy, Clone, Debug)] -pub enum Material{ +pub enum Material { Lambertian { albedo: Vec3 }, - Metal { albedo:Vec3, fuzz: f32 }, + Metal { albedo: Vec3, fuzz: f32 }, Dielectric { index_refraction: f32 }, } @@ -103,55 +114,60 @@ impl Material { let scatter_dir = rec.normal + Vec3::rand_unit_vector(srng); // The compiler might be smart enough to compute this ^^^ just once. In which case, // I don't need to do this weird dance. Oh well. It'll work. - let scatter_dir = if scatter_dir.near_zero() { // if near zero, - rec.normal // replace with normal + let scatter_dir = if scatter_dir.near_zero() { + // if near zero, + rec.normal // replace with normal } else { - scatter_dir // else preserve current + scatter_dir // else preserve current }; //TODO: Revisit this out-parameter pattern // It's a side effect of C++'s obtuse move semantics (and the RTIOW author not // using them at all) - *scattered = Ray{ + *scattered = Ray { orig: rec.p, - dir: scatter_dir + dir: scatter_dir, }; *attenuation = *albedo; // deref on both sides? Wacky return true; - }, + } Material::Metal { albedo, fuzz } => { - let reflected = Vec3::reflect( - Vec3::as_unit(ray_in.dir), - rec.normal - ); - *scattered = Ray{ + let reflected = Vec3::reflect(Vec3::as_unit(ray_in.dir), rec.normal); + *scattered = Ray { orig: rec.p, dir: reflected + Vec3::rand_in_unit_sphere(srng) * *fuzz, }; *attenuation = *albedo; return Vec3::dot(scattered.dir, rec.normal) > 0.0; - }, + } Material::Dielectric { index_refraction } => { *attenuation = Vec3::ones(); - let refraction_ratio = if rec.front_face { 1.0 / index_refraction } else { *index_refraction }; - + let refraction_ratio = if rec.front_face { + 1.0 / index_refraction + } else { + *index_refraction + }; + let unit_direction = Vec3::as_unit(ray_in.dir); let cos_theta = Vec3::dot(-unit_direction, rec.normal).min(1.0); let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); let cannot_refract = refraction_ratio * sin_theta > 1.0; let distrib_zero_one = Uniform::new(0.0, 1.0).unwrap(); - let direction = if cannot_refract || Material::reflectance(cos_theta, refraction_ratio) > srng.sample(distrib_zero_one) { + let direction = if cannot_refract + || Material::reflectance(cos_theta, refraction_ratio) + > srng.sample(distrib_zero_one) + { Vec3::reflect(unit_direction, rec.normal) } else { Vec3::refract(unit_direction, rec.normal, refraction_ratio) }; *scattered = Ray { orig: rec.p, - dir: direction + dir: direction, }; return true; - }, + } } } @@ -174,7 +190,8 @@ pub struct Camera { lower_left_corner: Vec3, horizontal: Vec3, vertical: Vec3, - u: Vec3, v: Vec3, /*w: Vec3,*/ + u: Vec3, + v: Vec3, /*w: Vec3,*/ lens_radius: f32, } @@ -186,7 +203,7 @@ impl Camera { vfov: f32, aspect_ratio: f32, aperture: f32, - focus_dist: f32 + focus_dist: f32, ) -> Camera { let theta = degrees_to_radians(vfov); let h = (theta / 2.0).tan(); @@ -202,12 +219,13 @@ impl Camera { let verti = v * vp_height * focus_dist; let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist; - Camera{ + Camera { origin: orig, lower_left_corner, horizontal: horiz, vertical: verti, - u, v, /* w,*/ + u, + v, /* w,*/ lens_radius: aperture / 2.0, } } @@ -216,18 +234,15 @@ impl Camera { let rd = Vec3::rand_in_unit_disk(srng) * self.lens_radius; let offset = self.u * rd.x + self.v * rd.y; - let dir = self.lower_left_corner - + self.horizontal * s - + self.vertical * t - - self.origin - offset; - Ray{ + let dir = + self.lower_left_corner + self.horizontal * s + self.vertical * t - self.origin - offset; + Ray { orig: self.origin + offset, dir, } } } - pub struct Scene { pub camera: Camera, pub world: Hittable, @@ -235,11 +250,19 @@ pub struct Scene { impl Scene { pub fn random_world(srng: &mut SmallRng) -> Hittable { - let mat_ground = Material::Lambertian { albedo: Vec3::new(0.5, 0.5, 0.5) }; - let mut world = Hittable::HittableList { hittables : Vec::::new() }; - - world.push( Hittable::Sphere { center: Vec3::new(0.0, -1000.0, 0.0), radius: 1000.0, material: mat_ground }); - + let mat_ground = Material::Lambertian { + albedo: Vec3::new(0.5, 0.5, 0.5), + }; + let mut world = Hittable::HittableList { + hittables: Vec::::new(), + }; + + world.push(Hittable::Sphere { + center: Vec3::new(0.0, -1000.0, 0.0), + radius: 1000.0, + material: mat_ground, + }); + let distrib_zero_one = Uniform::new(0.0, 1.0).unwrap(); for a in -11..11 { for b in -11..11 { @@ -250,18 +273,16 @@ impl Scene { z: b as f32 + 0.9 * srng.sample(distrib_zero_one), }; if (center - Vec3::new(4.0, 0.2, 0.0)).length() > 0.9 { - if choose_mat < 0.8 { // diffuse - let albedo = Vec3::rand(srng, distrib_zero_one) * Vec3::rand(srng, distrib_zero_one); + let albedo = + Vec3::rand(srng, distrib_zero_one) * Vec3::rand(srng, distrib_zero_one); let sphere_material = Material::Lambertian { albedo }; - world.push( - Hittable::Sphere { - center, - radius: 0.2, - material: sphere_material, - } - ); + world.push(Hittable::Sphere { + center, + radius: 0.2, + material: sphere_material, + }); } else if choose_mat < 0.95 { // metal let distr_albedo = Uniform::new(0.5, 1.0).unwrap(); @@ -270,49 +291,53 @@ impl Scene { let albedo = Vec3::rand(srng, distr_albedo); let fuzz = srng.sample(distr_fuzz); let material = Material::Metal { albedo, fuzz }; - world.push( - Hittable::Sphere { - center, - radius: 0.2, - material: material, - } - ); + world.push(Hittable::Sphere { + center, + radius: 0.2, + material: material, + }); } else { // glass - let material = Material::Dielectric { index_refraction: 1.5 }; - world.push( - Hittable::Sphere{ - center, - radius: 0.2, - material: material, - } - ); - + let material = Material::Dielectric { + index_refraction: 1.5, + }; + world.push(Hittable::Sphere { + center, + radius: 0.2, + material: material, + }); }; } } } - let material1 = Material::Dielectric { index_refraction: 1.5 }; - world.push( Hittable::Sphere{ + let material1 = Material::Dielectric { + index_refraction: 1.5, + }; + world.push(Hittable::Sphere { center: Vec3::new(0.0, 1.0, 0.0), radius: 1.0, - material: material1 + material: material1, }); - let material2 = Material::Lambertian { albedo: Vec3::new(0.4, 0.2, 0.1) }; - world.push( Hittable::Sphere { + let material2 = Material::Lambertian { + albedo: Vec3::new(0.4, 0.2, 0.1), + }; + world.push(Hittable::Sphere { center: Vec3::new(-4.0, 1.0, 0.0), radius: 1.0, - material: material2 + material: material2, }); - let material3 = Material::Metal { albedo: Vec3::new(0.7, 0.6, 0.5), fuzz: 0.0 }; - world.push( Hittable::Sphere { + let material3 = Material::Metal { + albedo: Vec3::new(0.7, 0.6, 0.5), + fuzz: 0.0, + }; + world.push(Hittable::Sphere { center: Vec3::new(4.0, 1.0, 0.0), radius: 1.0, - material: material3 + material: material3, }); world } -} \ No newline at end of file +}