use rand::Rng; use crate::calculus::calculus::{Ray, Vec3}; use crate::common::Color; use crate::object::HitRecord; #[derive(Clone)] pub enum MaterialType { Diffuse, Metallic(f32), // fuzz Transparent(f32), // refraction index } #[derive(Clone)] pub struct Material { pub material_type: MaterialType, pub albedo: Color, } impl Material { pub fn new(albedo: Color, material_type: MaterialType) -> Self { let mut material_type = material_type; if let MaterialType::Metallic(fuzz) = material_type { let fuzz = if fuzz < 1.0 { fuzz } else { fuzz }; material_type = MaterialType::Metallic(fuzz); } Self { albedo, material_type } } pub fn scatter(&self, r_in: &Ray, rec: &HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { scattered.direction = match self.material_type { MaterialType::Diffuse => { let scatter_direction = rec.normal.add(&Vec3::random_unit()); if scatter_direction.is_near_zero() { rec.normal.clone() } else { scatter_direction } } MaterialType::Metallic(fuzz) => { let reflected = r_in.reflect(&rec.normal); reflected.add(&Vec3::random_unit().scalar_mul(fuzz)) } MaterialType::Transparent(refraction_index) => { let ri = if rec.front_face { 1.0 / refraction_index } else { refraction_index }; let cos_theta = f32::min( r_in.direction.unit().scalar_mul(-1.0).dot_prod(&rec.normal), 1.0 ); let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); let cannot_refract = ri * sin_theta > 1.0; let random: f32 = rand::rng().random(); if cannot_refract || Self::reflectance(cos_theta, ri) > random { r_in.direction.unit().reflect(&rec.normal) } else { r_in.refract(&rec.normal, ri) } } }; scattered.origin = rec.position; attenuation.r = self.albedo.r; attenuation.g = self.albedo.g; attenuation.b = self.albedo.b; true } fn reflectance(cosine: f32, refraction_index: f32) -> f32 { let mut r0 = (1.0 - refraction_index) / (1.0 + refraction_index); r0 = r0 * r0; r0 + (1.0 - r0) * (1.0 - cosine).powi(5) } }