use rand::Rng; use crate::hittable::HitRecord; use crate::{Color, Ray, Vec3}; pub trait Scatterable { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)>; } #[derive(Debug, Clone)] pub enum Material { Lambertian(Lambertian), Metal(Metal), Dielectric(Dielectric) } impl Default for Material { fn default() -> Self { Material::Lambertian(Lambertian::new(Color::default())) } } impl Scatterable for Material { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)> { match self { Material::Lambertian(l) => l.scatter(ray, hit_record), Material::Metal(m) => m.scatter(ray, hit_record), Material::Dielectric(d) => d.scatter(ray, hit_record) } } } #[derive(Debug, Clone)] pub struct Lambertian { pub albedo: Color } impl Lambertian { pub fn new(albedo: Color) -> Self { Lambertian { albedo } } } impl Scatterable for Lambertian { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)> { let mut direction = hit_record.normal + Vec3::random_unit_vector(); if direction.near_zero() { direction = hit_record.normal; } let scattered = Ray::new(hit_record.point, direction); Some((Some(scattered), self.albedo)) } } #[derive(Debug, Clone)] pub struct Metal { pub albedo: Color, pub fuzz: f64 } impl Metal { pub fn new(albedo: Color, fuzz: f64) -> Self { Metal { albedo, fuzz: if fuzz < 1.0 { fuzz} else { 1.0 } } } } impl Scatterable for Metal { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)> { let reflected = ray.direction().unit_vector().reflected(&hit_record.normal); let scattered = Ray::new( hit_record.point, reflected + self.fuzz * Vec3::random_in_unit_sphere()); if scattered.direction().dot(&hit_record.normal) > 0.0 { Some((Some(scattered), self.albedo)) } else { None } } } #[derive(Debug, Clone)] pub struct Dielectric { // Glass pub index_of_refraction: f64 } impl Dielectric { pub fn new(index_of_refraction: f64) -> Self { Dielectric { index_of_refraction } } fn reflectance(cosine: f64, ref_idx: f64) -> f64 { let r0 = (1.0-ref_idx) / (1.0+ref_idx); let r0 = r0*r0; r0 + (1.0-r0)*((1.0-cosine).powi(5)) } } impl Scatterable for Dielectric { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)> { let color = Color::new(1.0, 1.0, 1.0); let refraction_ratio = if hit_record.front_face { 1.0/self.index_of_refraction } else { self.index_of_refraction }; let unit_direction = ray.direction().unit_vector(); let cos_theta = ((-unit_direction).dot(&hit_record.normal)).min(1.0); let sin_theta = (1.0 - cos_theta*cos_theta).sqrt(); let cannot_refract = refraction_ratio * sin_theta > 1.0; let reflectance = Dielectric::reflectance(cos_theta, refraction_ratio); let mut rng = rand::thread_rng(); if cannot_refract || reflectance > rng.gen::() { let reflected = unit_direction.reflected(&hit_record.normal); let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z()); let scattered = Ray::new(hit_record.point, reflected); Some((Some(scattered), color)) } else { let direction = unit_direction.refract_orig(&hit_record.normal, refraction_ratio); let scattered = Ray::new(hit_record.point, direction); Some((Some(scattered), color)) } } }