1mod light;
8mod object;
9
10pub use self::light::Light;
11pub(crate) use self::object::Object;
12pub use self::object::Segment;
13
14use crate::geom::{Point, Rect};
15use crate::image::RenderImage;
16use crate::ray::{Ray, RayResult};
17
18use rand::prelude::*;
19use rand_pcg::Pcg64Mcg;
20
21use std::sync::Arc;
22use std::thread;
23use std::time;
24
25#[derive(Clone, Copy, Debug)]
27pub enum RenderConstraint {
28 Count(usize),
31 TimeMS(usize),
33}
34
35impl From<usize> for RenderConstraint {
36 fn from(value: usize) -> Self {
38 Self::Count(value)
39 }
40}
41
42const BATCH_SIZE: usize = 1000;
43
44type R = Pcg64Mcg;
45
46#[derive(Clone)]
48pub struct Scene {
49 lights: Vec<Light<R>>,
50 objects: Vec<Object<R>>,
51 total_light_power: f32,
52 viewport: Rect,
53}
54
55impl Scene {
56 pub fn new(resolution_x: usize, resolution_y: usize) -> Self {
58 Self {
59 lights: vec![],
60 objects: vec![],
61 viewport: Rect::from_points(
62 &Point { x: 0.0, y: 0.0 },
63 &Point {
64 x: resolution_x as f64,
65 y: resolution_y as f64,
66 },
67 ),
68 total_light_power: 0.0,
69 }
70 }
71
72 pub fn add_light(&mut self, light: Light<R>) {
74 self.total_light_power += light.power.bounds().1 as f32;
75 self.lights.push(light);
76 }
77
78 pub fn with_light(mut self, light: Light<R>) -> Self {
80 self.add_light(light);
81 self
82 }
83
84 #[allow(private_bounds)]
101 pub fn add_object<A>(&mut self, object: A)
102 where
103 A: Into<Object<R>>,
104 {
105 self.objects.push(object.into());
106 }
107
108 #[allow(private_bounds)]
125 pub fn with_object<A>(mut self, object: A) -> Self
126 where
127 A: Into<Object<R>>,
128 {
129 self.add_object(object);
130 self
131 }
132
133 #[inline(always)]
134 fn choose_light(&self, rng: &mut R) -> &Light<R> {
135 let threshold = rng.gen_range(0.0..self.total_light_power) as f64;
136 let mut sum: f64 = 0.0;
137 for light in &self.lights {
138 sum += light.power.sample(rng);
139 if threshold <= sum {
140 return light;
141 }
142 }
143 return self.lights.last().expect("Scene has no lights");
144 }
145
146 #[inline(always)]
147 fn trace_ray<F>(&self, rng: &mut R, mut on_ray: F) -> usize
148 where
149 F: FnMut(RayResult) -> (),
150 {
151 let l = self.choose_light(rng);
152 let mut rays = 0;
153 let mut ray = Some(Ray::new(l, rng));
154 while ray.is_some() {
155 let (result, new_ray) = ray.unwrap().collision_list(&self.objects, self.viewport);
156 rays += 1;
157 if result.is_some() {
158 (on_ray)(result.unwrap());
159 }
160 ray = new_ray;
161 }
162 rays
163 }
164
165 #[inline(always)]
166 fn render_thread<F>(&self, constraint: RenderConstraint, mut on_ray: F) -> usize
167 where
168 F: FnMut(RayResult) -> (),
169 {
170 let mut local_rng = R::from_entropy();
171 match constraint {
172 RenderConstraint::Count(n) => (0..n).fold(0usize, |r, _| {
173 r + self.trace_ray(&mut local_rng, &mut on_ray)
174 }),
175 RenderConstraint::TimeMS(t) => {
176 let start = time::Instant::now();
177 let mut rays = 0;
178 while start.elapsed().as_millis() < t as u128 {
179 rays += (0..BATCH_SIZE).fold(0usize, |r, _| {
180 r + self.trace_ray(&mut local_rng, &mut on_ray)
181 });
182 }
183 rays
184 }
185 }
186 }
187
188 pub fn render<C>(
205 self,
206 constraint: C,
207 threads: usize,
208 image: &mut Arc<impl RenderImage + 'static>,
209 ) -> usize
210 where
211 C: Into<RenderConstraint>,
212 {
213 let threads = match threads {
214 0 => num_cpus::get(),
215 i => i,
216 };
217
218 let con = match constraint.into() {
219 RenderConstraint::Count(n) => RenderConstraint::Count(n / (threads)),
220 RenderConstraint::TimeMS(t) => RenderConstraint::TimeMS(t),
221 };
222
223 Arc::get_mut(image)
224 .expect("need exclusive access to image")
225 .prepare_render(self.total_light_power);
226
227 let s = Arc::new(self);
228 let join_handles: Vec<_> = (0..threads)
229 .map(|_| {
230 let local_s = s.clone();
231 let local_image = image.clone();
232 thread::spawn(move || local_s.render_thread(con, |r| local_image.draw_line(r)))
233 })
234 .collect();
235
236 let rays_cast = join_handles
237 .into_iter()
238 .fold(0, |r, h| r + h.join().unwrap());
239
240 Arc::get_mut(image).unwrap().finish_render();
241
242 rays_cast
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::object::Segment;
249 use super::Scene;
250 use crate::image::Image;
251 use crate::material::hqz_legacy;
252 use crate::sampler::Sampler;
253 use crate::scene::Light;
254 use std::sync::Arc;
255
256 #[test]
257 fn nrt_works() {
258 let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), hqz_legacy(0.3, 0.3, 0.3));
259
260 let l = Light {
261 power: Sampler::new_const(1.0),
262 location: (10.0, 10.0).into(),
263 polar_angle: Sampler::new_range(360.0, 0.0),
264 polar_distance: Sampler::new_const(0.0),
265 ray_angle: Sampler::new_range(360.0, 0.0),
266 wavelength: Sampler::new_blackbody(5800.0),
267 };
268
269 let r = Scene::new(1920, 1080).with_light(l).with_object(obj);
270
271 let mut img = Arc::new(Image::new(1920, 1080));
272
273 r.render(super::RenderConstraint::TimeMS(1000), 1, &mut img);
274 }
275}