diff --git a/Cargo.lock b/Cargo.lock index 0c25d65..cb280ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,13 +21,6 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "project_bin" -version = "0.1.0" -dependencies = [ - "lib", -] - [[package]] name = "rand" version = "0.3.23" diff --git a/Cargo.toml b/Cargo.toml index 34bd15c..257d96f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,13 @@ authors = ["Sen"] lib ={ path = "lib"} # needed for the tests to work [[bin]] -name = "project_bin" +name = "bin" path = "src/main.rs" # Check https://rust-classes.com/chapter_4_3 [workspace] members = [ "lib", - "project_bin", ] resolver = "2" diff --git a/lib/src/utilities/camera.rs b/lib/src/utilities/camera.rs index 1d1bc18..462b4aa 100644 --- a/lib/src/utilities/camera.rs +++ b/lib/src/utilities/camera.rs @@ -92,33 +92,9 @@ impl Camera { fn ray_color(ray: Ray, depth: i32, world: &[Box]) -> Color { // If we've exceeded the ray bounce limit, no more light is gathered. - // Problem: Recursion long enough to blow the stack - // Solution: To guard against that, let's limit the maximum recursion depth, - // returning no light contribution at the maximum depth. if depth <= 0 { return Color::default(); } - // let mut record: HitRecord = HitRecord::default(); // needed since to mut this, we need to initialize it - // if world.hit(ray, Interval::new(0.001, std::f64::INFINITY), &mut record) { - // // let ray_bounce_direction: Vector3 = record.normal + Vector3::random_unit_vector(); - // // return (Self::ray_color( - // // // note recursion here - // // &Ray::new(record.point, ray_bounce_direction), - // // depth - 1, - // // world, - // // )) * 0.5; - // let scattered_ray: Ray = Ray::default(); - // let attenuation: Color = Color::default(); - - // if record - // .material - // .clone() - // .unwrap() - // .scatter(ray, record, attenuation, scattered_ray) - // { - // return (Self::ray_color(scattered_ray, depth - 1, world)) * attenuation; - // } - // return Color::new(0.0, 0.0, 0.0); if let Some(hit) = world.hit(ray, Interval::new(0.001, std::f64::INFINITY)) { if let Some(scatter) = hit.material.scatter(ray, &hit) { diff --git a/lib/src/utilities/geometry.rs b/lib/src/utilities/geometry.rs index 3085efb..aee7af3 100644 --- a/lib/src/utilities/geometry.rs +++ b/lib/src/utilities/geometry.rs @@ -7,6 +7,30 @@ pub trait Hittable { fn hit(&self, ray: Ray, ray_interval: Interval) -> Option; } +impl Hittable for T +where + T: AsRef<[Box]>, +{ + fn hit(&self, ray: Ray, ray_interval: Interval) -> Option { + let t_min: f64 = ray_interval.min; + let t_max: f64 = ray_interval.max; + + let mut closest_so_far: Option = None; + + for object in self.as_ref().iter() { + let t_max_local = closest_so_far + .as_ref() + .map(|hit| hit.parameter) + .unwrap_or(t_max); + if let Some(record) = object.hit(ray, Interval::new(t_min, t_max_local)) { + closest_so_far = Some(record); + } + } + + closest_so_far + } +} + pub struct Sphere { center: Point3, radius: f64, @@ -67,27 +91,3 @@ impl Hittable for Sphere { )) } } - -impl Hittable for T -where - T: AsRef<[Box]>, -{ - fn hit(&self, ray: Ray, ray_interval: Interval) -> Option { - let t_min: f64 = ray_interval.min; - let t_max: f64 = ray_interval.max; - - let mut closest_so_far: Option = None; - - for object in self.as_ref().iter() { - let t_max_local = closest_so_far - .as_ref() - .map(|hit| hit.parameter) - .unwrap_or(t_max); - if let Some(record) = object.hit(ray, Interval::new(t_min, t_max_local)) { - closest_so_far = Some(record); - } - } - - closest_so_far - } -} diff --git a/lib/src/utilities/material.rs b/lib/src/utilities/material.rs index 25f4b38..1f6240a 100644 --- a/lib/src/utilities/material.rs +++ b/lib/src/utilities/material.rs @@ -13,7 +13,7 @@ pub trait Material { #[derive(Clone)] pub struct Lambertian { - pub albedo: Color, + albedo: Color, } impl Lambertian { @@ -50,11 +50,15 @@ impl Material for Lambertian { #[derive(Clone)] pub struct Metal { albedo: Color, + fuzz: f64, } impl Metal { - pub fn new(albedo: Color) -> Self { - Self { albedo: albedo } + pub fn new(albedo: Color, fuzz: f64) -> Self { + Self { + albedo, + fuzz: if fuzz < 1.0 { fuzz } else { 1.0 }, + } } } @@ -62,6 +66,7 @@ impl Default for Metal { fn default() -> Self { Self { albedo: Color::new(0.8, 0.8, 0.8), + fuzz: 1.0, } } } @@ -69,13 +74,72 @@ impl Default for Metal { impl Material for Metal { fn scatter(&self, incoming_ray: Ray, record: &HitRecord) -> Option { // Metal material with reflectance function - let reflect_direction: Vector3 = incoming_ray + let reflect_direction: Vector3 = (incoming_ray .get_direction() .unit_vector() - .reflection(&record.normal); + .reflection(&record.normal)) + + (Vector3::random_unit_vector() * self.fuzz); + let scattered_ray = Ray::new(record.point, reflect_direction); + let attenuation = self.albedo; + if scattered_ray.get_direction().dot_prod(record.normal) > 0.0 { + return Some(Scatter { + scattered_ray, + attenuation, + }); + } + None + } +} + +#[derive(Clone)] +pub struct Dielectric { + refractive_index: f64, +} + +impl Dielectric { + pub fn new(refractive_index: f64) -> Self { + Self { refractive_index } + } + + pub fn reflectance(cos_theta: f64, r_index: f64) -> f64 { + let mut r0: f64 = (1.0 - r_index) / (1.0 + r_index); + r0 = r0 * r0; + r0 + ((1.0 - r0) * ((1.0 - cos_theta).powi(5))) + } +} + +impl Default for Dielectric { + fn default() -> Self { + Self { + refractive_index: 1.0, + } + } +} + +impl Material for Dielectric { + fn scatter(&self, incoming_ray: Ray, record: &HitRecord) -> Option { + let attenuation: Color = Color::new(1.0, 1.0, 1.0); + let r_index: f64 = if record.is_face_front { + 1.0 / self.refractive_index + } else { + self.refractive_index + }; + let unit_direction: Vector3 = incoming_ray.get_direction().unit_vector(); + + let cos_theta: f64 = (-unit_direction.dot_prod(record.normal)).min(1.0); // std::fmin + let sin_theta: f64 = (1.0 - (cos_theta * cos_theta)).sqrt(); + let can_refract: bool = r_index * sin_theta <= 1.0; + + let ray_direction: Vector3 = if can_refract { + unit_direction.refraction(&record.normal, r_index) + } else { + unit_direction.reflection(&record.normal) + }; + let scattered_ray: Ray = Ray::new(record.point, ray_direction); + Some(Scatter { - scattered_ray: Ray::new(record.point, reflect_direction), - attenuation: self.albedo, + scattered_ray, + attenuation, }) } } diff --git a/lib/src/utilities/vector3.rs b/lib/src/utilities/vector3.rs index d2a0495..a4048c6 100644 --- a/lib/src/utilities/vector3.rs +++ b/lib/src/utilities/vector3.rs @@ -99,6 +99,15 @@ impl Vector3 { pub fn reflection(&self, normal_vec: &Self) -> Self { return *self - ((*normal_vec * (self.dot_prod(*normal_vec))) * 2.0); } + + pub fn refraction(&self, normal_vec: &Self, ratio_refractive_index: f64) -> Self { + let cos_theta: f64 = (-self.dot_prod(*normal_vec)).min(1.0); // std::fmin + let ray_out_perp: Vector3 = (*self + (*normal_vec * cos_theta)) * ratio_refractive_index; + let ray_out_par: Vector3 = + *normal_vec * (-(((1.0 - ray_out_perp.length_squared()).abs()).sqrt())); + + ray_out_par + ray_out_perp + } } impl Default for Vector3 { diff --git a/project_bin/Cargo.toml b/src/Cargo.toml similarity index 87% rename from project_bin/Cargo.toml rename to src/Cargo.toml index e1c5ca0..4fd0dfb 100644 --- a/project_bin/Cargo.toml +++ b/src/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "project_bin" +name = "bin" version = "0.1.0" edition = "2021" license = "GNU GENERAL PUBLIC LICENSE" diff --git a/project_bin/src/main.rs b/src/main.rs similarity index 78% rename from project_bin/src/main.rs rename to src/main.rs index 4358620..e01eabe 100644 --- a/project_bin/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use lib::utilities::{ camera::Camera, color::Color, geometry::{Hittable, Sphere}, - material::{Lambertian, Metal}, + material::{Dielectric, Lambertian, Metal}, point::Point3, }; @@ -15,12 +15,13 @@ fn main() { // https://raytracing.github.io/books/RayTracingInOneWeekend.html // World - let mut world: Vec> = Vec::with_capacity(11 * 11); + let mut world: Vec> = Vec::new(); let material_ground = Box::new(Lambertian::new(Color::new(0.8, 0.8, 0.0))); let material_center = Box::new(Lambertian::new(Color::new(0.1, 0.2, 0.5))); - let material_left = Box::new(Metal::new(Color::new(0.8, 0.8, 0.8))); - let material_right = Box::new(Metal::new(Color::new(0.8, 0.6, 0.2))); + let material_left = Box::new(Dielectric::new(1.33)); + let material_right = Box::new(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0)); + let material_bubble = Box::new(Dielectric::new(1.0 / 1.33)); world.push(Box::new(Sphere::new( Point3::new(0.0, -100.5, -1.0), @@ -42,6 +43,11 @@ fn main() { 0.5, material_right, ))); + world.push(Box::new(Sphere::new( + Point3::new(-1.0, 0.0, -1.0), + 0.4, + material_bubble, + ))); // Camera let mut cam: Camera = Camera::new();