use crate::core::object::Object;
use crate::core::HitRecord;
use crate::geometry::{Geometry, Visitor};
use crate::material::Material;
use crate::math::{Ray, AABB};
use crate::random;
use crate::types::{FSize, TextureCoordinate, Vector3};
use std::error::Error;
use std::ops::Range;
use std::sync::Arc;
pub struct ConstantMedium {
pub id: usize,
pub density: FSize,
pub neg_inv_density: FSize,
pub boundary: Arc<dyn Geometry>,
pub phase_function: Arc<dyn Material>,
}
impl ConstantMedium {
pub fn new(
density: FSize,
boundary: Arc<dyn Geometry>,
phase_function: Arc<dyn Material>,
) -> ConstantMedium {
ConstantMedium {
id: Object::new_id(),
density,
neg_inv_density: -1.0 / density,
boundary,
phase_function,
}
}
pub fn new_id(
id: usize,
density: FSize,
boundary: Arc<dyn Geometry>,
phase_function: Arc<dyn Material>,
) -> ConstantMedium {
ConstantMedium {
id,
density,
neg_inv_density: -1.0 / density,
boundary,
phase_function,
}
}
}
impl Geometry for ConstantMedium {
fn get_id(&self) -> usize {
self.id
}
fn bounding_box(&self, time: Range<FSize>) -> Option<AABB> {
self.boundary.bounding_box(time)
}
fn hit(&self, ray: &Ray, t_range: Range<FSize>) -> Option<HitRecord> {
match self.boundary.hit(ray, FSize::MIN..FSize::MAX) {
Some(hit_record) => {
let mut record_1 = HitRecord::from_hit_record(&hit_record);
match self.boundary.hit(ray, record_1.t + 0.0001..FSize::MAX) {
Some(mut record_2) => {
if record_1.t < t_range.start {
record_1.t = t_range.start;
}
if record_2.t > t_range.end {
record_2.t = t_range.end;
}
if record_1.t >= record_2.t {
return None;
}
if record_1.t < 0.0 {
record_1.t = 0.0;
}
let ray_length = glm::length(ray.direction);
let distance_inside_boundary = (record_2.t - record_1.t) * ray_length;
let hit_distance = self.neg_inv_density * FSize::ln(random::generate_size());
if hit_distance < distance_inside_boundary {
let t = record_1.t + hit_distance / ray_length;
let p = ray.point_at(t);
return Some(HitRecord::new(
t,
TextureCoordinate::from_uv(0.0, 0.0),
p,
Vector3::new(1.0, 0.0, 0.0), self.phase_function.clone(),
self.phase_function
.color_channels(&TextureCoordinate::from_uv(0.0, 0.0), &p),
));
}
None
}
None => None,
}
}
None => None,
}
}
fn accept(&self, visitor: &mut dyn Visitor) -> Result<(), Box<dyn Error>> {
visitor.visit_volume_constant_medium(&self)
}
}
#[cfg(test)]
mod constant_medium_test {
use super::*;
use crate::geometry::shape::Sphere;
use crate::material::{Lambertian, Metal, NoMaterial};
use crate::test;
use crate::texture::ConstantTexture;
use crate::types::ColorRGBA;
use crate::types::{Point3, Vector3};
#[test]
fn bounding_box_test() {
let s = Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0, Arc::new(NoMaterial::new()));
let i = ConstantMedium::new(0.0, Arc::new(s), Arc::new(NoMaterial::new()));
let b = i.bounding_box(0.0..0.0);
match b {
Some(b) => {
test::assert_eq_vector3(&b.min, &Vector3::new(-1.0, -1.0, -1.0), 0.01);
test::assert_eq_vector3(&b.max, &Vector3::new(1.0, 1.0, 1.0), 0.01);
}
_ => assert!(false),
}
}
#[test]
fn hit_test() {
let s = Sphere::new(
Point3::new(0.0, 0.0, 0.0),
1.0,
Arc::new(Metal::new(
0.0,
Arc::new(ConstantTexture::new(ColorRGBA::new(1.0, 1.0, 1.0, 1.0))),
)),
);
let i = ConstantMedium::new(
0.5,
Arc::new(s),
Arc::new(Lambertian::new(Arc::new(ConstantTexture::new(
ColorRGBA::new(1.0, 1.0, 1.0, 1.0),
)))),
);
let ray1 = Ray::new_ray(Vector3::new(0.0, -5.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
let ray2 = Ray::new_ray(Vector3::new(2.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
match i.hit(&ray1, 0.0..10.0) {
Some(_) => {}
None => {}
}
match i.hit(&ray1, 10.0..20.0) {
Some(_) => panic!("unexpected hit"),
None => (),
}
match i.hit(&ray2, 0.0..10.0) {
Some(_) => panic!("unexpected hit"),
None => (),
}
}
}