rustic_zen/
material.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//! Materials in Rustic Zen are defined as closures that act on a single ray when intersecting wtth an object.
6//!
7//! For object safety these closures are stored in a `std::sync::Arc` which can then be cloned to as many users as needed.
8//! As there is a constant generic type assosiated with the material, `R` it is usually easiest to implement the Arc inside a
9//! generator function that yeilds the Arc and accepts a type argument.
10//!
11//! # Examples:
12//! ## Create a trivial shader and assign it to several `Segments`
13//! ```
14//! use rustic_zen::prelude::*;
15//! use std::sync::Arc;
16//!
17//! // This shader will fire all incoming rays in the direction of the surface normal.
18//! // This is actually surprisingly useful for testing normals.
19//! fn always_normal<R>() -> material::Material<R> {
20//!     Arc::new(move |_, normal: &Vector, _, _, _: &mut R| {
21//!         Some(normal.clone())
22//!     })
23//! }
24//!
25//! let m = always_normal(); // R fixed to a concrete type here by the `Scene` below
26//!
27//! let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m.clone());
28//! let s2 = Segment::line_from_points((10.0, 10.0), (20.0, 0.0), m);
29//!
30//! // you may get unknown type for <R> if you do not add all your segments to a scene.
31//! let _ = Scene::new(100, 100).with_object(s).with_object(s2);
32//! ```
33//!
34//! ## Create a more useful shader that refects 50% of incoming light and apply it to several `Segments`:
35//! ```
36//! use rustic_zen::prelude::*;
37//! use std::sync::Arc;
38//!
39//! // This shader will reflect half of the rays and absorb the other half
40//! fn mirror<R: Rng + 'static>() -> material::Material<R> {
41//!     let s = Sampler::<f64, R>::new_range(0.0, 1.0);
42//!     Arc::new(move |direction: &Vector, normal: &Vector, _, _, rng: &mut R| {
43//!         if s.sample(rng) < 0.5 {
44//!             None
45//!         } else {
46//!             Some(direction.reflect(normal))   
47//!         }
48//!     })
49//! }
50//!
51//! let m = mirror(); // R fixed to a concrete type here by the `Scene` below
52//!
53//! let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m.clone());
54//! let s2 = Segment::line_from_points((10.0, 10.0), (20.0, 0.0), m);
55//!
56//! // you may get unknown type for <R> if you do not add all your segments to a scene.
57//! let _ = Scene::new(100, 100).with_object(s).with_object(s2);
58//! ```
59//!
60//! Admittedly this could probably be less clunky.
61//!
62//! Creating more imaginative shaders is left as an exersize for the reader.
63
64use crate::geom::Vector;
65use rand::prelude::*;
66
67use std::f64::consts::PI;
68use std::sync::Arc;
69
70/// Shader Type
71///
72/// This Closure Represents a shader in the HQZ system. For a given incident ray an outcome is calculated.
73///
74/// Wavelength is provided instead of colour to encourage the design of physically based
75/// shaders. Colour can be calculated from the wavelength using Rustic's spectrum module.
76///
77/// * It returns a wrapped vector of the direction of the bounced ray.
78/// If the ray is absorbed then it returns `None`.
79///
80/// # Parameters:
81///  - __direction__: Vector of the direction of the inbound ray.
82///  - __normal__: Computed normal to the hit surface. This can be used in `direction.reflect(normal)`, to get a mirror reflection.
83///  - __wavelength__: Wavelength of inbound ray (no way to change this for the outbound ray, sorry).
84///  - __alpha__: how far along the object the inbound ray hit. clamped 0.0 to 1.0
85///  - __rng__: random number generator for use during the function, (don't spawn your own, way to slow.)
86
87pub type Material<R> =
88    Arc<dyn Fn(&Vector, &Vector, f64, f64, &mut R) -> Option<Vector> + Send + Sync + 'static>;
89
90/// Reference / Legacy implementation of Material trait.
91///
92/// This implementation models the behavour of shaders from the original HQZ.
93/// When called this will return an `Arc` containing the shader closure. It only has to be called
94/// once for each set of parameters desired as the arc can be cloned to multiple objects.
95///
96/// # Parameters:
97///   * __diffuse__: proportion of rays bounced diffusely (completely random direction)
98///   * __relective__: proportion of rays that are perfectly reflected
99///   * __transparent__: proportion of rays that pass through the material as if it was transparent.
100///
101/// The proportion of rays absorbed is `1.0 - d - r - t`
102///
103/// # Panics:
104/// Panics if `d + r + t > 1.0`
105pub fn hqz_legacy<R: Rng>(diffuse: f64, reflective: f64, transparent: f64) -> Material<R> {
106    if diffuse + reflective + transparent > 1.0 {
107        panic!("HQZ Legacy shader cooefficents > 1.0");
108    }
109
110    Arc::new(
111        move |direction: &Vector, normal: &Vector, _wavelength: f64, _alpha: f64, rng: &mut R| {
112            let f: f64 = rng.gen_range(0.0..1.0);
113
114            if f <= diffuse {
115                let angle = rng.gen_range(0.0..2.0 * PI);
116                return Some(Vector {
117                    x: f64::cos(angle),
118                    y: f64::sin(angle),
119                });
120            }
121
122            if f <= diffuse + reflective {
123                let angle = direction.reflect(normal);
124                return Some(angle);
125            }
126
127            if f <= diffuse + reflective + transparent {
128                let angle = direction.clone();
129                return Some(angle);
130            }
131
132            None
133        },
134    )
135}
136
137/// Default Settings for `hqz_legacy`, makes a fast and predictable testing shader where needed.
138///
139/// Default Values:
140///   * __diffuse__: 0.1
141///   * __reflective__: 0.4
142///   * __transparent__: 0.4
143///   * absorbsion is 0.1
144pub fn hqz_legacy_default<R: Rng>() -> Material<R> {
145    hqz_legacy(0.1, 0.4, 0.4)
146}