diff --git a/src/hittable.rs b/src/hittable.rs index cdadab6..6bde1c0 100644 --- a/src/hittable.rs +++ b/src/hittable.rs @@ -2,12 +2,11 @@ use crate::vec3::Vec3; use crate::ray::Ray; use crate::material::Material; -use std::rc::Rc; pub struct HitRecord{ pub p: Vec3, pub normal: Vec3, - pub material: Option>, + pub material: Option, pub t: f32, pub front_face: bool, } diff --git a/src/main.rs b/src/main.rs index 52c9856..1331139 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use crate::hittable::{ Hittable, HittableList, }; +use crate::material::Material; use crate::camera::Camera; use rand::{Rng, SeedableRng}; @@ -32,25 +33,52 @@ fn main() { // world + let mat_ground = Material::Lambertian{ albedo: Vec3::new(0.8, 0.8, 0.0) }; + let mat_center = Material::Lambertian{ albedo: Vec3::new(0.7, 0.3, 0.3) }; + let mat_left = Material::Metal{ albedo: Vec3::new(0.8, 0.8, 0.8) }; + let mat_right = Material::Metal{ albedo: Vec3::new(0.8, 0.6, 0.2) }; + let mut world = HittableList::new(); - world.add( - Box::new( - Sphere{ - center: Vec3{ x: 0.0, y: 0.0, z: -1.0}, - radius: 0.5, - material: None, - } - ) - ); world.add( Box::new( Sphere{ center: Vec3{ x: 0.0, y: -100.5, z: -1.0 }, radius: 100.0, - material: None, + material: Some(mat_ground), } ) ); + + world.add( + Box::new( + Sphere{ + center: Vec3{ x: 0.0, y: 0.0, z: -1.0}, + radius: 0.5, + material: Some(mat_center), + } + ) + ); + + world.add( + Box::new( + Sphere{ + center: Vec3::new(-1.0, 0.0, -1.0), + radius: 0.5, + material: Some(mat_left), + } + ) + ); + + world.add( + Box::new( + Sphere{ + center: Vec3::new( 1.0, 0.0, -1.0), + radius: 0.5, + material: Some(mat_right), + } + ) + ); + // camera let cam = Camera::new(); @@ -78,19 +106,26 @@ fn main() { fn ray_color(r: Ray, world: &dyn Hittable, depth: u32, srng: &mut SmallRng, distrib: Uniform ) -> Vec3 { // recursion depth guard if depth == 0 { - return Vec3::new(0.0, 0.0, 0.0); + return Vec3::zero(); } if let Some(rec) = world.hit(r, 0.001, f32::INFINITY){ - let target = rec.p + rec.normal + Vec3::rand_unit_vector(srng, distrib); - return ray_color( - Ray{ - orig: rec.p, - dir: target - rec.p, + let mut scattered = Ray { + orig: Vec3::zero(), + dir: Vec3::zero() + }; + let mut attenuation = Vec3::zero(); + match rec.material { + Some(mat) => { + if mat.scatter(r, rec, &mut attenuation, &mut scattered, srng, distrib) { + return attenuation * ray_color(scattered, world, depth-1, srng, distrib); + }; + }, - world, depth, srng, distrib - ) * 0.5; + None => return Vec3::zero(), + } } + let unitdir = Vec3::as_unit(&r.dir); let t = 0.5 * (unitdir.y + 1.0); return Vec3::ones() * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t diff --git a/src/material.rs b/src/material.rs index d3f0cee..df85e04 100644 --- a/src/material.rs +++ b/src/material.rs @@ -3,9 +3,62 @@ use crate::ray::Ray; use crate::hittable::HitRecord; use crate::Vec3; -pub trait Material { - fn scatter( - &self, ray_in: Ray, rec: HitRecord, attenuation: Vec3, scattered: Ray - ) -> bool; +use rand::Rng; +use rand::rngs::SmallRng; +use rand::distributions::Uniform; + + + +#[derive(Copy, Clone, Debug)] +pub enum Material{ + Lambertian{ albedo: Vec3 }, + Metal{ albedo:Vec3 }, } +impl Material { + pub fn scatter( + &self, + ray_in: Ray, + rec: HitRecord, + attenuation: &mut Vec3, + scattered:&mut Ray, + srng: &mut SmallRng, + distrib: Uniform, + ) -> bool { + match self { + Material::Lambertian { albedo } => { + let scatter_dir = rec.normal + Vec3::rand_unit_vector(srng, distrib); + // 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 + } else { + 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{ + orig: rec.p, + dir: scatter_dir + }; + *attenuation = *albedo; // deref on both sides? Wacky + return true; + }, + Material::Metal { albedo } => { + let reflected = Vec3::reflect( + Vec3::as_unit(&ray_in.dir), + rec.normal + ); + *scattered = Ray{ + orig: rec.p, + dir: reflected, + }; + *attenuation = *albedo; + return Vec3::dot(scattered.dir, rec.normal) > 0.0; + }, + _ => return false, + } + } +} diff --git a/src/sphere.rs b/src/sphere.rs index 89bff19..b1c5977 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -7,12 +7,10 @@ use crate::hittable::{ use crate::material::Material; use crate::ray::Ray; -use std::rc::Rc; - pub struct Sphere{ pub center: Vec3, pub radius: f32, - pub material: Option>, + pub material: Option, } impl Hittable for Sphere { @@ -39,7 +37,7 @@ impl Hittable for Sphere { let mut record = HitRecord{ p: r.at(root), normal: (r.at(root) - self.center) / self.radius, - material: self.material.clone(), + material: self.material, t: root, front_face: false, }; diff --git a/src/vec3.rs b/src/vec3.rs index 748b1ce..b384688 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -92,6 +92,18 @@ impl Vec3{ let ib = (Vec3::clamp(b, 0.0, 0.999) * 256.0) as i32; format!("{} {} {}", ir, ig, ib) } + + pub fn near_zero(&self) -> bool { + let epsilon: f32 = 1e-8; + return + self.x < epsilon && + self.y < epsilon && + self.z < epsilon + } + + pub fn reflect(v: Vec3, n: Vec3) -> Vec3 { + return v - n * Vec3::dot(v, n) * 2.0; + } pub fn dot(left: Vec3, right: Vec3) -> f32{ left.x * right.x +