125 lines
3.7 KiB
Rust
125 lines
3.7 KiB
Rust
|
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<Ray>, 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<Ray>, 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<Ray>, 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<Ray>, 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<Ray>, 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::<f64>() {
|
||
|
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))
|
||
|
}
|
||
|
}
|
||
|
}
|