use crate::core::{Camera, Configuration};
use crate::environment::Environment;
use crate::geometry::Geometry;
use crate::math::Ray;
use crate::probability_density_function::{GeometryPdf, MixturePdf, ProbabilityDensityFunction};
use crate::types::{ColorRGB, FSize};
use std::error::Error;
use std::sync::Arc;
pub struct Scene {
pub configuration: Configuration,
pub camera: Arc<Camera>,
pub sky: Arc<dyn Environment>,
pub world: Arc<dyn Geometry>,
pub light: Option<Arc<dyn Geometry>>,
}
impl Scene {
pub fn new(
configuration: Configuration,
camera: Arc<Camera>,
sky: Arc<dyn Environment>,
world: Arc<dyn Geometry>,
light: Option<Arc<dyn Geometry>>,
) -> Scene {
Scene {
configuration,
camera,
sky,
world,
light,
}
}
pub fn ray_trace_color(&self, u: FSize, v: FSize) -> ColorRGB {
let color = self.ray_trace_color_loop(u, v, self.light.clone());
if color.x.is_nan() || color.y.is_nan() || color.z.is_nan() {
ColorRGB::new(0.0, 0.0, 0.0)
} else {
color
}
}
pub fn ray_trace_color_loop(
&self,
u: FSize,
v: FSize,
light_shape: Option<Arc<dyn Geometry>>,
) -> ColorRGB {
let mut ray = self.camera.get(u, v);
let mut color = ColorRGB::new(0.0, 0.0, 0.0);
let mut attenuation = ColorRGB::new(1.0, 1.0, 1.0);
for _ in 0..self.configuration.maximum_depth {
if let Some(hit_record) = self.world.hit(&ray, 0.001..FSize::MAX) {
let material = hit_record.material.clone();
let emitted = material.emitted(&ray, &hit_record);
color = color + attenuation * emitted;
if let Some(scatter_record) = hit_record.scatter(&ray) {
if scatter_record.is_specular {
attenuation = attenuation * scatter_record.attenuation;
ray = scatter_record.ray;
} else {
let pdf: Option<Arc<dyn ProbabilityDensityFunction>> =
match scatter_record.pdf {
Some(pdf) => match light_shape {
Some(ref light_shape) => Some(Arc::new(MixturePdf::new(
pdf.clone(),
Arc::new(GeometryPdf::new(
&hit_record.position,
light_shape.clone(),
)),
))),
None => Some(pdf.clone()),
},
None => None,
};
match pdf {
Some(pdf) => {
let scattered = Ray::new_ray_with_attributes(
hit_record.position,
pdf.generate(),
&ray,
);
let s_pdf = scatter_record.material.scattering_pdf(
&ray,
&hit_record,
&scattered,
);
let pdf_value = pdf.value(&scattered.direction);
attenuation =
attenuation * scatter_record.attenuation * s_pdf / pdf_value;
ray = scattered;
}
None => {
let s_pdf = scatter_record.material.scattering_pdf(
&ray,
&hit_record,
&scatter_record.ray,
);
attenuation = attenuation * scatter_record.attenuation * s_pdf;
ray = scatter_record.ray;
}
}
}
} else {
break;
}
} else {
color = color + attenuation * self.sky.get(&ray);
break;
}
}
color
}
pub fn ray_trace_color_recursive(&self, u: FSize, v: FSize) -> ColorRGB {
let ray = self.camera.get(u, v);
self.get_ray_trace_color(&ray, 0)
}
fn get_ray_trace_color(&self, ray: &Ray, depth: usize) -> ColorRGB {
match self.world.hit(ray, 0.001..FSize::MAX) {
Some(hit_record) => {
let material = hit_record.material.clone();
let mut emitted = material.emitted(&ray, &hit_record);
if depth < self.configuration.maximum_depth {
match hit_record.scatter(ray) {
Some(scatter_record) => {
if scatter_record.is_specular {
emitted = emitted
+ scatter_record.attenuation
* self.get_ray_trace_color(&scatter_record.ray, depth + 1);
} else {
let s_pdf = scatter_record.material.scattering_pdf(
&ray,
&hit_record,
&scatter_record.ray,
);
emitted = emitted
+ scatter_record.attenuation
* self.get_ray_trace_color(&scatter_record.ray, depth + 1)
* s_pdf;
}
}
_ => (),
}
}
emitted
}
None => self.sky.get(ray),
}
}
pub fn change_aspect(&mut self, aspect: FSize) {
let mut c = (*self.camera).clone();
c.change_aspect(aspect);
self.camera = Arc::new(c);
}
pub fn from_scene_and_aspect(&self, aspect: FSize) -> Result<Scene, Box<dyn Error>> {
let mut c = (*self.camera).clone();
c.change_aspect(aspect);
Ok(Scene::new(
self.configuration.clone(),
Arc::new(c),
self.sky.clone(),
self.world.clone(),
self.light.clone(),
))
}
}
#[cfg(test)]
mod scene_test {
use super::*;
use crate::random;
use crate::test::TestSceneSimple;
use std::ops::Range;
fn assert_in_range(
pixel_data: &Vec<u8>,
cx: usize,
_cy: usize,
x: usize,
y: usize,
c: usize,
range: Range<i32>,
) {
println!(
"{}..{}, {}",
range.start,
range.end,
pixel_data[(y * cx + x) * 4 + c] as i32
);
assert!(range.contains(&(pixel_data[(y * cx + x) * 4 + c] as i32)));
}
#[test]
fn render_simple_scene_test() {
let cx = 20;
let cy = 10;
let samples = 1000;
let scene = TestSceneSimple::new().scene;
let mut pixel_data: Vec<u8> = Vec::with_capacity(cx * cy * 4);
pixel_data.resize(cx * cy * 4, 0);
for x in 0..cx {
for y in 0..cy {
let mut c = ColorRGB::new(0.0, 0.0, 0.0);
for _ in 0..samples {
let u = (x as FSize + random::generate_size()) / cx as FSize;
let v = 1.0 - (y as FSize + random::generate_size()) / cy as FSize;
c = c + scene.ray_trace_color(u, v);
}
c = c / samples as FSize;
let i = (y * cx) + x;
pixel_data[i * 4] = (c[0].sqrt() * 255.0).round() as u8;
pixel_data[i * 4 + 1] = (c[1].sqrt() * 255.0).round() as u8;
pixel_data[i * 4 + 2] = (c[2].sqrt() * 255.0).round() as u8;
pixel_data[i * 4 + 3] = 255;
}
}
assert_in_range(&pixel_data, cx, cy, 3, 1, 2, 245..256);
assert_in_range(&pixel_data, cx, cy, 10, 3, 0, 110..150);
assert_in_range(&pixel_data, cx, cy, 17, 8, 1, 60..75);
}
}