use crate::core::object::Object;
use crate::core::HitRecord;
use crate::core::ScatterRecord;
use crate::material::{Material, Visitor};
use crate::math::{OrthoNormalBase, Ray};
use crate::probability_density_function::CosinePdf;
use crate::random;
use crate::texture::Texture;
use crate::types::{ColorRGB, ColorRGBA, FSize, Point3, TextureCoordinate};
use std::error::Error;
use std::f64::consts::PI;
use std::sync::Arc;
pub struct Lambertian {
pub id: usize,
pub albedo: Arc<dyn Texture>,
}
impl Lambertian {
pub fn new(albedo: Arc<dyn Texture>) -> Lambertian {
Lambertian {
id: Object::new_id(),
albedo,
}
}
}
impl Material for Lambertian {
fn get_id(&self) -> usize {
self.id
}
fn color_channels(&self, uv: &TextureCoordinate, p: &Point3) -> ColorRGBA {
self.albedo.value(uv, p)
}
fn scatter(
&self,
self_material: Arc<dyn Material>,
ray_in: &Ray,
hit_record: &HitRecord,
) -> Option<ScatterRecord> {
let nv = hit_record.normal * -glm::sign(glm::dot(ray_in.direction, hit_record.normal));
let uvw = OrthoNormalBase::form_w(&nv);
let direction = glm::normalize(uvw.local(random::generate_cosine_direction()));
Some(ScatterRecord::new(
Ray::new_ray_with_attributes(hit_record.position, direction, ray_in),
false,
hit_record.color_channels.truncate(3),
hit_record.color_channels.w,
Some(Arc::new(CosinePdf::from_w(&hit_record.normal))),
self_material,
))
}
fn scattering_pdf(&self, _: &Ray, hit_record: &HitRecord, scattered: &Ray) -> FSize {
let n_dot_d = glm::dot(hit_record.normal, glm::normalize(scattered.direction));
if n_dot_d < 0.0 {
0.0
} else {
n_dot_d / PI
}
}
fn has_alpha(&self) -> bool {
self.albedo.has_alpha()
}
fn emitted(&self, _: &Ray, _: &HitRecord) -> ColorRGB {
ColorRGB::new(0.0, 0.0, 0.0)
}
fn accept(&self, visitor: &mut dyn Visitor) -> Result<(), Box<dyn Error>> {
visitor.visit_lambertian(&self)
}
}
#[cfg(test)]
mod lambertian_test {
use super::*;
use crate::material::NoMaterial;
use crate::test;
use crate::texture::ConstantTexture;
use crate::types::{ColorRGBA, Point3, TextureCoordinate, Vector3};
#[test]
fn scatter_test() {
let m = Arc::new(Lambertian::new(Arc::new(ConstantTexture::new(
ColorRGBA::new(0.0, 0.0, 0.0, 1.0),
))));
let result = m.scatter(
m.clone(),
&Ray::new_ray(Point3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 0.0, -1.0)),
&HitRecord::new(
0.0,
TextureCoordinate::from_uv(0.0, 0.0),
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Arc::new(NoMaterial::new()),
ColorRGBA::new(0.0, 0.0, 0.0, 1.0),
),
);
match result {
Some(scatter_record) => {
test::assert_eq_vector3(
&scatter_record.attenuation,
&ColorRGB::new(0.0, 0.0, 0.0),
0.001,
);
test::assert_eq_float(scatter_record.alpha, 1.0, 0.001);
test::assert_eq_vector3(
&scatter_record.ray.origin,
&Point3::new(0.0, 0.0, 0.0),
0.001,
);
test::assert_in_range_vector3(
scatter_record.ray.direction,
Vector3::new(-1.0, -1.0, -1.0)..Vector3::new(1.0, 1.0, 1.0),
);
assert!(glm::length(scatter_record.ray.direction) <= 2.0);
test::assert_eq_float(scatter_record.ray.time, 0.0, 0.001);
}
None => panic!("no result"),
}
}
#[test]
fn emitted_test() {
let m = Lambertian::new(Arc::new(ConstantTexture::new(ColorRGBA::new(
0.0, 0.0, 0.0, 1.0,
))));
let c = m.emitted(
&Ray::new_ray(Point3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 0.0, -1.0)),
&HitRecord::empty(),
);
test::assert_eq_vector3(&c, &ColorRGB::new(0.0, 0.0, 0.0), 0.01);
}
}