rustracer/src/material.rs

177 lines
5.3 KiB
Rust

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<Ray>, 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<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),
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<dyn Texture>
}
impl From<Color> 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<Ray>, Color)> {
None
}
}
pub struct Lambertian {
pub albedo: Arc<dyn Texture>
}
impl From<Color> for Lambertian {
fn from(albedo: Color) -> Self {
let texture = SolidColor::from(albedo);
Lambertian { albedo: Arc::new(texture) }
}
}
impl Lambertian {
pub fn textured(albedo: Arc<dyn Texture>) -> Self {
Lambertian { albedo }
}
}
impl Lambertian {
pub 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, 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<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(), 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<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 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))
}
}
}