use std::{fmt::Debug, sync::Arc}; use rand::{self, Rng}; use crate::{ hitable::HitRecord, ray::Ray, texture::Texture, vec3::{dot, Vec3}, }; fn random_in_unit_sphere() -> Vec3 { let mut rng = rand::thread_rng(); let v = Vec3::new(1., 1., 1.); loop { let p = 2. * Vec3::new(rng.gen(), rng.gen(), rng.gen()) - v; if p.squared_length() < 1. { return p; } } } #[derive(Default, Debug)] pub struct ScatterResponse { pub scattered: Ray, pub attenutation: Vec3, pub reflected: bool, } pub trait Material: Send + Sync + Debug { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse; fn emitted(&self, _u: f32, _v: f32, _p: Vec3) -> Vec3 { Vec3::new(0., 0., 0.) } } impl Material for Arc { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { (**self).scatter(r_in, rec) } fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 { (**self).emitted(u, v, p) } } impl Material for Box { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { (**self).scatter(r_in, rec) } fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 { (**self).emitted(u, v, p) } } #[derive(Clone, Debug)] pub struct Isotropic where T: Texture, { albedo: T, } impl Isotropic where T: Texture, { pub fn new(texture: T) -> Isotropic { Isotropic { albedo: texture } } } impl Material for Isotropic where T: Texture, { fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> ScatterResponse { let (u, v) = rec.uv; ScatterResponse { attenutation: self.albedo.value(u, v, rec.p), scattered: Ray::new(rec.p, random_in_unit_sphere(), 0.), reflected: true, } } } #[derive(Clone, Debug)] pub struct Lambertian where T: Texture, { albedo: T, } impl Lambertian where T: Texture, { pub fn new(texture: T) -> Lambertian { Lambertian { albedo: texture } } } impl Material for Lambertian where T: Texture, { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { let target = rec.p + rec.normal + random_in_unit_sphere(); let (u, v) = rec.uv; ScatterResponse { attenutation: self.albedo.value(u, v, rec.p), scattered: Ray::new(rec.p, target - rec.p, r_in.time), reflected: true, } } } #[derive(Clone, Debug)] pub struct Metal { albedo: Vec3, fuzzy: f32, } fn reflect(v: Vec3, n: Vec3) -> Vec3 { v - 2. * dot(v, n) * n } impl Metal { pub fn new(albedo: V, fuzzy: f32) -> Metal where V: Into, { let fuzzy = fuzzy.min(1.); Metal { albedo: albedo.into(), fuzzy, } } } impl Material for Metal { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { let reflected = reflect(r_in.direction.unit_vector(), rec.normal); let scattered = Ray::new( rec.p, reflected + self.fuzzy * random_in_unit_sphere(), r_in.time, ); ScatterResponse { scattered, attenutation: self.albedo, reflected: dot(scattered.direction, rec.normal) > 0., } } } fn refract(v: Vec3, n: Vec3, ni_over_nt: f32) -> Option { let uv = v.unit_vector(); let dt = dot(uv, n); let discriminant = 1. - ni_over_nt * ni_over_nt * (1. - dt * dt); if discriminant > 0. { return Some(ni_over_nt * (uv - n * dt) - n * discriminant.sqrt()); } None } fn schlick(cosine: f32, ref_idx: f32) -> f32 { let mut r0 = (1. - ref_idx) / (1. + ref_idx); r0 = r0 * r0; r0 + (1. - r0) * (1. - cosine).powf(5.) } #[derive(Clone, Debug)] pub struct Dielectric { ref_idx: f32, } impl Dielectric { pub fn new(ref_idx: f32) -> Dielectric { Dielectric { ref_idx } } } impl Material for Dielectric { fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { let reflected = reflect(r_in.direction, rec.normal); let (outward_normal, ni_over_nt, cosine) = if dot(r_in.direction, rec.normal) > 0. { ( -rec.normal, self.ref_idx, self.ref_idx * dot(r_in.direction, rec.normal) / r_in.direction.length(), ) } else { ( rec.normal, 1. / self.ref_idx, -dot(r_in.direction, rec.normal) / r_in.direction.length(), ) }; let scattered = if let Some(refracted) = refract(r_in.direction, outward_normal, ni_over_nt) { let mut rng = rand::thread_rng(); if rng.gen::() < schlick(cosine, self.ref_idx) { Ray::new(rec.p, reflected, r_in.time) } else { Ray::new(rec.p, refracted, r_in.time) } } else { Ray::new(rec.p, reflected, r_in.time) }; ScatterResponse { attenutation: Vec3::new(1., 1., 1.), scattered, reflected: true, } } } #[derive(Debug)] pub struct DiffuseLight where T: Texture, { emit: T, } impl DiffuseLight where T: Texture, { pub fn new(emit: T) -> DiffuseLight { DiffuseLight { emit } } } impl Material for DiffuseLight where T: Texture, { fn scatter(&self, _r_in: &Ray, _rec: &HitRecord) -> ScatterResponse { ScatterResponse { scattered: Default::default(), attenutation: Default::default(), reflected: false, } } fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 { self.emit.value(u, v, p) } } #[derive(Debug)] pub struct DebugMaterial {} impl Material for DebugMaterial { fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> ScatterResponse { let dir = Vec3::new(0., -1., -1.).unit_vector(); ScatterResponse { scattered: Ray::new(rec.p, dir, 0.), attenutation: [1., 1., 1.].into(), reflected: true, } } } #[cfg(test)] mod tests { use super::*; use crate::texture::ConstantTexture; #[test] fn arc_material() { let white: Arc = Arc::new(Lambertian::new(ConstantTexture::new([0.73, 0.73, 0.73]))); fn material_fn(m: M) where M: Material, { let _ = m; } material_fn(Arc::clone(&white)); material_fn(Arc::clone(&white)); } }