mod light;
mod object;
pub use self::light::Light;
pub(crate) use self::object::Object;
pub use self::object::Segment;
use crate::geom::{Point, Rect};
use crate::image::RenderImage;
use crate::ray::{Ray, RayResult};
use rand::prelude::*;
use rand_pcg::Pcg64Mcg;
use std::sync::Arc;
use std::thread;
use std::time;
#[derive(Clone, Copy, Debug)]
pub enum RenderConstraint {
Count(usize),
TimeMS(usize),
}
impl From<usize> for RenderConstraint {
fn from(value: usize) -> Self {
Self::Count(value)
}
}
const BATCH_SIZE: usize = 1000;
type R = Pcg64Mcg;
#[derive(Clone)]
pub struct Scene {
lights: Vec<Light<R>>,
objects: Vec<Object<R>>,
total_light_power: f32,
viewport: Rect,
}
impl Scene {
pub fn new(resolution_x: usize, resolution_y: usize) -> Self {
Self {
lights: vec![],
objects: vec![],
viewport: Rect::from_points(
&Point { x: 0.0, y: 0.0 },
&Point {
x: resolution_x as f64,
y: resolution_y as f64,
},
),
total_light_power: 0.0,
}
}
pub fn add_light(&mut self, light: Light<R>) {
self.total_light_power += light.power.bounds().1 as f32;
self.lights.push(light);
}
pub fn with_light(mut self, light: Light<R>) -> Self {
self.add_light(light);
self
}
#[allow(private_bounds)]
pub fn add_object<A>(&mut self, object: A)
where
A: Into<Object<R>>,
{
self.objects.push(object.into());
}
#[allow(private_bounds)]
pub fn with_object<A>(mut self, object: A) -> Self
where
A: Into<Object<R>>,
{
self.add_object(object);
self
}
#[inline(always)]
fn choose_light(&self, rng: &mut R) -> &Light<R> {
let threshold = rng.gen_range(0.0..self.total_light_power) as f64;
let mut sum: f64 = 0.0;
for light in &self.lights {
sum += light.power.sample(rng);
if threshold <= sum {
return light;
}
}
return self.lights.last().expect("Scene has no lights");
}
#[inline(always)]
fn trace_ray<F>(&self, rng: &mut R, mut on_ray: F) -> usize
where
F: FnMut(RayResult) -> (),
{
let l = self.choose_light(rng);
let mut rays = 0;
let mut ray = Some(Ray::new(l, rng));
while ray.is_some() {
let (result, new_ray) = ray.unwrap().collision_list(&self.objects, self.viewport);
rays += 1;
if result.is_some() {
(on_ray)(result.unwrap());
}
ray = new_ray;
}
rays
}
#[inline(always)]
fn render_thread<F>(&self, constraint: RenderConstraint, mut on_ray: F) -> usize
where
F: FnMut(RayResult) -> (),
{
let mut local_rng = R::from_entropy();
match constraint {
RenderConstraint::Count(n) => (0..n).fold(0usize, |r, _| {
r + self.trace_ray(&mut local_rng, &mut on_ray)
}),
RenderConstraint::TimeMS(t) => {
let start = time::Instant::now();
let mut rays = 0;
while start.elapsed().as_millis() < t as u128 {
rays += (0..BATCH_SIZE).fold(0usize, |r, _| {
r + self.trace_ray(&mut local_rng, &mut on_ray)
});
}
rays
}
}
}
pub fn render<C>(
self,
constraint: C,
threads: usize,
image: &mut Arc<impl RenderImage + 'static>,
) -> usize
where
C: Into<RenderConstraint>,
{
let threads = match threads {
0 => num_cpus::get(),
i => i,
};
let con = match constraint.into() {
RenderConstraint::Count(n) => RenderConstraint::Count(n / (threads)),
RenderConstraint::TimeMS(t) => RenderConstraint::TimeMS(t),
};
Arc::get_mut(image)
.expect("need exclusive access to image")
.prepare_render(self.total_light_power);
let s = Arc::new(self);
let join_handles: Vec<_> = (0..threads)
.map(|_| {
let local_s = s.clone();
let local_image = image.clone();
thread::spawn(move || local_s.render_thread(con, |r| local_image.draw_line(r)))
})
.collect();
let rays_cast = join_handles
.into_iter()
.fold(0, |r, h| r + h.join().unwrap());
Arc::get_mut(image).unwrap().finish_render();
rays_cast
}
}
#[cfg(test)]
mod tests {
use super::object::Segment;
use super::Scene;
use crate::image::Image;
use crate::material::hqz_legacy;
use crate::sampler::Sampler;
use crate::scene::Light;
use std::sync::Arc;
#[test]
fn nrt_works() {
let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), hqz_legacy(0.3, 0.3, 0.3));
let l = Light {
power: Sampler::new_const(1.0),
location: (10.0, 10.0).into(),
polar_angle: Sampler::new_range(360.0, 0.0),
polar_distance: Sampler::new_const(0.0),
ray_angle: Sampler::new_range(360.0, 0.0),
wavelength: Sampler::new_blackbody(5800.0),
};
let r = Scene::new(1920, 1080).with_light(l).with_object(obj);
let mut img = Arc::new(Image::new(1920, 1080));
r.render(super::RenderConstraint::TimeMS(1000), 1, &mut img);
}
}