use std::sync::Arc; use rand::Rng; use crate::hittable::HitRecord; use crate::{Color, Point3, Ray, Vec3}; use crate::isotropic::Isotropic; use crate::texture::{SolidColor, Texture}; pub trait Scatterable { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option, Color)>; fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color; } pub enum Material { Lambertian(Lambertian), Metal(Metal), Dielectric(Dielectric), DiffuseLight(DiffuseLight), Isotropic(Isotropic) } impl Default for Material { fn default() -> Self { Material::solid(0.8, 0.8, 0.8) } } impl Material { pub fn solid(r: f64, g: f64, b: f64) -> Self { Material::Lambertian(Lambertian::from(Color::new(r, g, b))) } pub fn light(r: f64, g: f64, b: f64) -> Self { Material::DiffuseLight(DiffuseLight::from(Color::new(r, g, b))) } pub fn white_light(brightness: f64) -> Self { Material::DiffuseLight(DiffuseLight::from(Color::new(brightness, brightness, brightness))) } } 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), Material::DiffuseLight(l) => l.scatter(ray, hit_record), Material::Isotropic(i) => i.scatter(ray, hit_record) } } fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color { match self { Material::DiffuseLight(l) => l.emitted(u, v, point), _ => Color::new(0.0, 0.0, 0.0) } } } pub struct DiffuseLight { pub emit: Arc } impl From for DiffuseLight { fn from(color: Color) -> Self { DiffuseLight { emit: Arc::new(SolidColor::from(color)) } } } impl DiffuseLight { pub fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color { self.emit.value(u, v, point) } pub fn scatter(&self, _ray: &Ray, _hit_record: &HitRecord) -> Option<(Option, Color)> { None } } pub struct Lambertian { pub albedo: Arc } impl From for Lambertian { fn from(albedo: Color) -> Self { let texture = SolidColor::from(albedo); Lambertian { albedo: Arc::new(texture) } } } impl Lambertian { pub fn textured(albedo: Arc) -> Self { Lambertian { albedo } } } impl Lambertian { pub 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, ray.time()); Some((Some(scattered), self.albedo.value(hit_record.u, hit_record.v, &hit_record.point))) } } #[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 Metal { pub 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(), ray.time()); 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 Dielectric { pub 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 scattered = Ray::new(hit_record.point, reflected, ray.time()); Some((Some(scattered), color)) } else { let direction = unit_direction.refract(&hit_record.normal, refraction_ratio); let scattered = Ray::new(hit_record.point, direction, ray.time()); Some((Some(scattered), color)) } } }