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: Material, 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: Material }, 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: Option = None; let mut hit_anything = false; for item in hittables { if let Some(record) = item.hit(r, t_min, t_max){ hit_anything = true; might_return = Some(record); } } if hit_anything{ return 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, } } }