rustic_zen/scene/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Scene Definition and Rendering Control.
6
7mod 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/// Render termination predicate.
26#[derive(Clone, Copy, Debug)]
27pub enum RenderConstraint {
28    /// Render Will terminate after a fixed number of rays have been generated by the light sources.
29    /// This value does not count rays generated by bounces.
30    Count(usize),
31    /// Render will terminate after a fixed number of miliseconds.
32    TimeMS(usize),
33}
34
35impl From<usize> for RenderConstraint {
36    /// `usize` is interpreted as a fixed number of rays, mostly for backwards compatiblity.
37    fn from(value: usize) -> Self {
38        Self::Count(value)
39    }
40}
41
42const BATCH_SIZE: usize = 1000;
43
44type R = Pcg64Mcg;
45
46/// Holds scene Configuration and logic
47#[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    /// Creates new Renderer ready for defining a scene.
57    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    /// Adds a light to the scene
73    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    /// Adds a light to the scene - Chainable variant
79    pub fn with_light(mut self, light: Light<R>) -> Self {
80        self.add_light(light);
81        self
82    }
83
84    /// Adds an object to the scene.
85    /// `Object` is an internal representation used by the raytracer, `A`
86    /// should be anything that implements `Into<Object>` currently only
87    /// `Segment` provides this.
88    ///
89    /// # Example Usage:
90    /// ```
91    /// use rustic_zen::prelude::*;
92    /// let a = (0.0, 0.0);
93    /// let b = (10.0, 10.0);
94    /// // Or other math to construct your scene here,
95    /// let line = Segment::line_from_points(a, b, material::hqz_legacy_default());
96    ///
97    /// let mut s = Scene::new(1920,1080);
98    /// s.add_object(line); //Segment automatically converted to hidden object type here.
99    /// ```
100    #[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    /// Adds an object to the scene - Chainable Variant.
109    /// `Object` is an internal representation used by the raytracer, `A`
110    /// should be anything that implements `Into<Object>` currently only
111    /// `Segment` provides this.
112    ///
113    /// # Example Usage:
114    /// ```
115    /// use rustic_zen::prelude::*;
116    /// let a = Point::new(0.0, 0.0);
117    /// let b = Point::new(10.0, 10.0);
118    /// // Or other math to construct your scene here,
119    /// let line = Segment::line_from_points(a, b, material::hqz_legacy_default());
120    ///
121    /// //Segment automatically converted to hidden object type here.
122    /// let s = Scene::new(1920,1080).with_object(line);
123    /// ```
124    #[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    /// Starts the ray tracing process
189    ///
190    /// Naturally this call is very expensive. It also consumes the Renderer
191    /// and populates the provided `Image` with the rendered image data.
192    /// The total number of rays cast into the scene is returned.
193    ///
194    /// # Parameters:
195    /// __constraint__: A `RenderConstraint` object, which defines the condition to stop rendering.
196    /// __threads__: the number of cpu threads the render will use. The renderer will automatically spawn this many threads.
197    /// __image__: a `Image` to be populated with image data.
198    ///
199    /// The renderer will automatically use the GPU if a `VulkanImage` is passed into the image slot, otherwise the image will be rendered solely in software..
200    ///
201    /// # Returns:
202    /// `usize` The first value is the number of rays added to the scene. This is needed for some image export functions.
203    /// The second value is the resulting image as an `Image` object, which has serveral export functions.
204    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}