From 4ce43e12af7cd4b224abebb46eca3a2ed0d21d07 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Sun, 17 Sep 2023 12:16:33 -0500 Subject: [PATCH] Gathered up the scene components After nearly a month of not touching the project, I've finally finished collecting the scene parts. :l With that, the rearrange is complete. On to the next thing! --- src/camera.rs | 64 ------------- src/hittable.rs | 89 ------------------ src/main.rs | 15 ++- src/material.rs | 88 ------------------ src/renderer.rs | 6 +- src/scene.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 249 insertions(+), 252 deletions(-) delete mode 100644 src/camera.rs delete mode 100644 src/hittable.rs delete mode 100644 src/material.rs create mode 100644 src/scene.rs diff --git a/src/camera.rs b/src/camera.rs deleted file mode 100644 index 41beaa8..0000000 --- a/src/camera.rs +++ /dev/null @@ -1,64 +0,0 @@ - -use crate::primitives::{Vec3, Ray}; -use crate::degrees_to_radians; - -use rand::rngs::SmallRng; - -#[derive (Clone, Copy)] -pub struct Camera { - origin: Vec3, - lower_left_corner: Vec3, - horizontal: Vec3, - vertical: Vec3, - u: Vec3, v: Vec3, /*w: Vec3,*/ - lens_radius: f32, -} - -impl Camera { - pub fn new( - lookfrom: Vec3, - lookat: Vec3, - vup: Vec3, - vfov: f32, - aspect_ratio: f32, - aperture: f32, - focus_dist: f32 - ) -> Camera { - let theta = degrees_to_radians(vfov); - let h = (theta / 2.0).tan(); - let vp_height = 2.0 * h; - let vp_width = aspect_ratio * vp_height; - - let w = Vec3::as_unit(lookfrom - lookat); - let u = Vec3::as_unit(Vec3::cross(vup, w)); - let v = Vec3::cross(w, u); - - let orig = lookfrom; - let horiz = u * vp_width * focus_dist; - let verti = v * vp_height * focus_dist; - let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist; - - Camera{ - origin: orig, - lower_left_corner, - horizontal: horiz, - vertical: verti, - u, v, /* w,*/ - lens_radius: aperture / 2.0, - } - } - - pub fn get_ray(&self, s: f32, t: f32, srng: &mut SmallRng) -> Ray { - 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{ - orig: self.origin + offset, - dir, - } - } -} diff --git a/src/hittable.rs b/src/hittable.rs deleted file mode 100644 index 988589b..0000000 --- a/src/hittable.rs +++ /dev/null @@ -1,89 +0,0 @@ - -use crate::primitives::{Vec3, Ray}; -use crate::material::Material; - -pub struct HitRecord{ - pub p: Vec3, - pub normal: Vec3, - pub material: Option, - pub t: f32, - pub front_face: bool, -} - -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 }; - } -} - -#[derive (Clone)] -pub enum Hittable { - Sphere { center: Vec3, radius: f32, material: Option }, - HittableList { hittables: Vec } -} - -impl Hittable { - pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { - match self { - Hittable::HittableList { hittables } => { - let mut might_return = HitRecord { - p: Vec3::zero(), - normal: Vec3::zero(), - material: None, - t: t_max, - front_face: false, - }; - let mut hit_anything = false; - - for item in hittables { - if let Some(record) = item.hit(r, t_min, might_return.t){ - hit_anything = true; - might_return = record; - } - } - if hit_anything{ - return Some(might_return); - } else { return None; } - } - - 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; - - if discriminant < 0.0 { - return None; - } - let sqrtd = discriminant.sqrt(); - - // nearest root that lies within tolerance - let mut root = (-half_b - sqrtd) / a; - if root < t_min || root > t_max { - root = (-half_b + sqrtd) / a; - if root < t_min || root > t_max { - return None; - } - } - let mut record = HitRecord{ - p: r.at(root), - normal: (r.at(root) - *center) / *radius, - material: *material, - t: root, - front_face: false, - }; - let outward_normal = (record.p - *center) / *radius; - record.set_face_normal(r, outward_normal); - Some(record) - } - } - } - pub fn push(&mut self, item: Hittable) { - if let Hittable::HittableList { hittables } = self { - hittables.push(item); - } - } -} - diff --git a/src/main.rs b/src/main.rs index 00b99f9..8842dac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ mod primitives; -mod camera; -mod material; -mod hittable; mod renderer; +mod scene; use crate::primitives::Vec3; -use crate::hittable::Hittable; -use crate::material::Material; -use crate::camera::Camera; +use crate::scene::{ + Camera, + Hittable, + Material, +}; use crate::renderer::RenderCommand; use rand::{Rng, SeedableRng}; @@ -213,7 +213,4 @@ fn random_scene(srng: &mut SmallRng) -> Hittable { return world; } -pub fn degrees_to_radians(degrees: f32) -> f32 { - degrees * std::f32::consts::PI / 180.0 -} diff --git a/src/material.rs b/src/material.rs deleted file mode 100644 index 4e7c0d2..0000000 --- a/src/material.rs +++ /dev/null @@ -1,88 +0,0 @@ - -use crate::primitives::{Vec3, Ray}; -use crate::hittable::HitRecord; - -use rand::Rng; -use rand::rngs::SmallRng; -use rand::distributions::Uniform; - -#[derive(Copy, Clone, Debug)] -pub enum Material{ - Lambertian { albedo: Vec3 }, - Metal { albedo:Vec3, fuzz: f32 }, - Dielectric { index_refraction: f32 }, -} - -impl Material { - pub fn scatter( - &self, - ray_in: Ray, - rec: HitRecord, - attenuation: &mut Vec3, - scattered: &mut Ray, - srng: &mut SmallRng, - ) -> bool { - match self { - Material::Lambertian { albedo } => { - 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 - } 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, fuzz } => { - 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 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); - 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 - }; - return true; - }, - } - } - - fn reflectance(cosine: f32, ref_idx: f32) -> f32 { - // Schlick's approximation for reflectance. - let r0 = (1.0 - ref_idx) / (1.0 + ref_idx); - let r0 = r0 * r0; - return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0); - } -} diff --git a/src/renderer.rs b/src/renderer.rs index 49bab8b..082b1a7 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,7 +1,9 @@ use crate::primitives::{Vec3, Ray, Rect}; -use crate::camera::Camera; -use crate::hittable::Hittable; +use crate::scene::{ + Camera, + Hittable, +}; use core::cmp::Ordering; use std::thread; diff --git a/src/scene.rs b/src/scene.rs new file mode 100644 index 0000000..bec9298 --- /dev/null +++ b/src/scene.rs @@ -0,0 +1,239 @@ + +use crate::primitives::{Vec3, Ray}; + +use rand::Rng; +use rand::rngs::SmallRng; +use rand::distributions::Uniform; + +pub struct HitRecord{ + pub p: Vec3, + pub normal: Vec3, + pub material: Option, + pub t: f32, + pub front_face: bool, +} + +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 }; + } +} + +#[derive (Clone)] +pub enum Hittable { + Sphere { center: Vec3, radius: f32, material: Option }, + HittableList { hittables: Vec } +} + +impl Hittable { + pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option { + match self { + Hittable::HittableList { hittables } => { + let mut might_return = HitRecord { + p: Vec3::zero(), + normal: Vec3::zero(), + material: None, + t: t_max, + front_face: false, + }; + let mut hit_anything = false; + + for item in hittables { + if let Some(record) = item.hit(r, t_min, might_return.t){ + hit_anything = true; + might_return = record; + } + } + if hit_anything{ + return Some(might_return); + } else { return None; } + } + + 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; + + if discriminant < 0.0 { + return None; + } + let sqrtd = discriminant.sqrt(); + + // nearest root that lies within tolerance + let mut root = (-half_b - sqrtd) / a; + if root < t_min || root > t_max { + root = (-half_b + sqrtd) / a; + if root < t_min || root > t_max { + return None; + } + } + let mut record = HitRecord{ + p: r.at(root), + normal: (r.at(root) - *center) / *radius, + material: *material, + t: root, + front_face: false, + }; + let outward_normal = (record.p - *center) / *radius; + record.set_face_normal(r, outward_normal); + Some(record) + } + } + } + pub fn push(&mut self, item: Hittable) { + if let Hittable::HittableList { hittables } = self { + hittables.push(item); + } + } +} + + +#[derive(Copy, Clone, Debug)] +pub enum Material{ + Lambertian { albedo: Vec3 }, + Metal { albedo:Vec3, fuzz: f32 }, + Dielectric { index_refraction: f32 }, +} + +impl Material { + pub fn scatter( + &self, + ray_in: Ray, + rec: HitRecord, + attenuation: &mut Vec3, + scattered: &mut Ray, + srng: &mut SmallRng, + ) -> bool { + match self { + Material::Lambertian { albedo } => { + 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 + } 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, fuzz } => { + 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 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); + 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 + }; + return true; + }, + } + } + + fn reflectance(cosine: f32, ref_idx: f32) -> f32 { + // Schlick's approximation for reflectance. + let r0 = (1.0 - ref_idx) / (1.0 + ref_idx); + let r0 = r0 * r0; + return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0); + } +} + +// Camera + +pub fn degrees_to_radians(degrees: f32) -> f32 { + degrees * std::f32::consts::PI / 180.0 +} + +#[derive (Clone, Copy)] +pub struct Camera { + origin: Vec3, + lower_left_corner: Vec3, + horizontal: Vec3, + vertical: Vec3, + u: Vec3, v: Vec3, /*w: Vec3,*/ + lens_radius: f32, +} + +impl Camera { + pub fn new( + lookfrom: Vec3, + lookat: Vec3, + vup: Vec3, + vfov: f32, + aspect_ratio: f32, + aperture: f32, + focus_dist: f32 + ) -> Camera { + let theta = degrees_to_radians(vfov); + let h = (theta / 2.0).tan(); + let vp_height = 2.0 * h; + let vp_width = aspect_ratio * vp_height; + + let w = Vec3::as_unit(lookfrom - lookat); + let u = Vec3::as_unit(Vec3::cross(vup, w)); + let v = Vec3::cross(w, u); + + let orig = lookfrom; + let horiz = u * vp_width * focus_dist; + let verti = v * vp_height * focus_dist; + let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist; + + Camera{ + origin: orig, + lower_left_corner, + horizontal: horiz, + vertical: verti, + u, v, /* w,*/ + lens_radius: aperture / 2.0, + } + } + + pub fn get_ray(&self, s: f32, t: f32, srng: &mut SmallRng) -> Ray { + 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{ + orig: self.origin + offset, + dir, + } + } +} +