material.rs (3699B)
1 use crate::{ 2 color::LinearColor, hittable::HitRecord, random::RandomNumberGenerator, ray::Ray, vec3::Vec3, 3 }; 4 5 #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 6 pub struct ScatteredRay { 7 ray: Ray, 8 attenuation: LinearColor, 9 } 10 11 impl ScatteredRay { 12 pub const fn ray(&self) -> Ray { 13 self.ray 14 } 15 pub const fn attenuation(&self) -> LinearColor { 16 self.attenuation 17 } 18 } 19 20 pub trait Material { 21 fn scatter( 22 &self, 23 r_in: &Ray, 24 hit: &HitRecord, 25 rng: &mut RandomNumberGenerator, 26 ) -> Option<ScatteredRay>; 27 } 28 29 #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 30 pub struct Lambertian { 31 albedo: LinearColor, 32 } 33 34 impl Lambertian { 35 pub const fn new(albedo: LinearColor) -> Self { 36 Lambertian { albedo } 37 } 38 } 39 40 impl Material for Lambertian { 41 fn scatter( 42 &self, 43 _r_in: &Ray, 44 hit: &HitRecord, 45 rng: &mut RandomNumberGenerator, 46 ) -> Option<ScatteredRay> { 47 let scatter_direction = hit.normal() + Vec3::random_unit(rng); 48 Some(ScatteredRay { 49 ray: Ray::new( 50 hit.point(), 51 if scatter_direction.near_zero() { 52 hit.normal() 53 } else { 54 scatter_direction 55 }, 56 ), 57 attenuation: self.albedo, 58 }) 59 } 60 } 61 62 #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 63 pub struct Metal { 64 albedo: LinearColor, 65 fuzz: f64, 66 } 67 68 impl Metal { 69 pub const fn new(albedo: LinearColor, fuzz: f64) -> Self { 70 Metal { 71 albedo, 72 fuzz: fuzz.clamp(0., 1.), 73 } 74 } 75 } 76 77 impl Material for Metal { 78 fn scatter( 79 &self, 80 r_in: &Ray, 81 hit: &HitRecord, 82 rng: &mut RandomNumberGenerator, 83 ) -> Option<ScatteredRay> { 84 let ray = Ray::new( 85 hit.point(), 86 r_in.direction().reflect(&hit.normal()).unit() + (self.fuzz * Vec3::random_unit(rng)), 87 ); 88 if ray.direction().dot(&hit.normal()) > 0. { 89 Some(ScatteredRay { 90 ray, 91 attenuation: self.albedo, 92 }) 93 } else { 94 None 95 } 96 } 97 } 98 99 #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 100 pub struct Dielectric { 101 refractive_index: f64, 102 } 103 104 impl Dielectric { 105 pub const fn new(refractive_index: f64) -> Self { 106 Dielectric { refractive_index } 107 } 108 fn reflectance(cosine: f64, refractive_index: f64) -> f64 { 109 let r0 = ((1. - refractive_index) / (1. + refractive_index)).powi(2); 110 r0 + (1. - r0) * (1. - cosine).powi(5) 111 } 112 } 113 114 impl Material for Dielectric { 115 fn scatter( 116 &self, 117 r_in: &Ray, 118 hit: &HitRecord, 119 rng: &mut RandomNumberGenerator, 120 ) -> Option<ScatteredRay> { 121 let refractive_index = if hit.front_face() { 122 1. / self.refractive_index 123 } else { 124 self.refractive_index 125 }; 126 let unit_direction = r_in.direction().unit(); 127 let cosine_theta = (-unit_direction).dot(&hit.normal()).min(1.); 128 let sine_theta = (1. - cosine_theta * cosine_theta).sqrt(); 129 let cannot_refract = refractive_index * sine_theta > 1.; 130 let direction = if cannot_refract 131 || Dielectric::reflectance(cosine_theta, refractive_index) > rng.next_f64() 132 { 133 unit_direction.reflect(&hit.normal()) 134 } else { 135 unit_direction.refract(&hit.normal(), refractive_index) 136 }; 137 Some(ScatteredRay { 138 ray: Ray::new(hit.point(), direction), 139 attenuation: LinearColor::new(1., 1., 1.), 140 }) 141 } 142 }