rustic-zen 0.3.0

Photon-Garden raytracer for creating artistic renderings
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Materials in Rustic Zen are defined as closures that act on a single ray when intersecting wtth an object.
//!
//! For object safety these closures are stored in a `std::sync::Arc` which can then be cloned to as many users as needed.
//! As there is a constant generic type assosiated with the material, `R` it is usually easiest to implement the Arc inside a
//! generator function that yeilds the Arc and accepts a type argument.
//!
//! # Examples:
//! ## Create a trivial shader and assign it to several `Segments`
//! ```
//! use rustic_zen::prelude::*;
//! use std::sync::Arc;
//!
//! // This shader will fire all incoming rays in the direction of the surface normal.
//! // This is actually surprisingly useful for testing normals.
//! fn always_normal<R>() -> material::Material<R> {
//!     Arc::new(move |_, normal: &Vector, _, _, _: &mut R| {
//!         Some(normal.clone())
//!     })
//! }
//!
//! let m = always_normal(); // R fixed to a concrete type here by the `Scene` below
//!
//! let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m.clone());
//! let s2 = Segment::line_from_points((10.0, 10.0), (20.0, 0.0), m);
//!
//! // you may get unknown type for <R> if you do not add all your segments to a scene.
//! let _ = Scene::new(100, 100).with_object(s).with_object(s2);
//! ```
//!
//! ## Create a more useful shader that refects 50% of incoming light and apply it to several `Segments`:
//! ```
//! use rustic_zen::prelude::*;
//! use std::sync::Arc;
//!
//! // This shader will reflect half of the rays and absorb the other half
//! fn mirror<R: Rng + 'static>() -> material::Material<R> {
//!     let s = Sampler::<f64, R>::new_range(0.0, 1.0);
//!     Arc::new(move |direction: &Vector, normal: &Vector, _, _, rng: &mut R| {
//!         if s.sample(rng) < 0.5 {
//!             None
//!         } else {
//!             Some(direction.reflect(normal))   
//!         }
//!     })
//! }
//!
//! let m = mirror(); // R fixed to a concrete type here by the `Scene` below
//!
//! let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m.clone());
//! let s2 = Segment::line_from_points((10.0, 10.0), (20.0, 0.0), m);
//!
//! // you may get unknown type for <R> if you do not add all your segments to a scene.
//! let _ = Scene::new(100, 100).with_object(s).with_object(s2);
//! ```
//!
//! Admittedly this could probably be less clunky.
//!
//! Creating more imaginative shaders is left as an exersize for the reader.

use crate::geom::Vector;
use rand::prelude::*;

use std::f64::consts::PI;
use std::sync::Arc;

/// Shader Type
///
/// This Closure Represents a shader in the HQZ system. For a given incident ray an outcome is calculated.
///
/// Wavelength is provided instead of colour to encourage the design of physically based
/// shaders. Colour can be calculated from the wavelength using Rustic's spectrum module.
///
/// * It returns a wrapped vector of the direction of the bounced ray.
/// If the ray is absorbed then it returns `None`.
///
/// # Parameters:
///  - __direction__: Vector of the direction of the inbound ray.
///  - __normal__: Computed normal to the hit surface. This can be used in `direction.reflect(normal)`, to get a mirror reflection.
///  - __wavelength__: Wavelength of inbound ray (no way to change this for the outbound ray, sorry).
///  - __alpha__: how far along the object the inbound ray hit. clamped 0.0 to 1.0
///  - __rng__: random number generator for use during the function, (don't spawn your own, way to slow.)

pub type Material<R> =
    Arc<dyn Fn(&Vector, &Vector, f64, f64, &mut R) -> Option<Vector> + Send + Sync + 'static>;

/// Reference / Legacy implementation of Material trait.
///
/// This implementation models the behavour of shaders from the original HQZ.
/// When called this will return an `Arc` containing the shader closure. It only has to be called
/// once for each set of parameters desired as the arc can be cloned to multiple objects.
///
/// # Parameters:
///   * __diffuse__: proportion of rays bounced diffusely (completely random direction)
///   * __relective__: proportion of rays that are perfectly reflected
///   * __transparent__: proportion of rays that pass through the material as if it was transparent.
///
/// The proportion of rays absorbed is `1.0 - d - r - t`
///
/// # Panics:
/// Panics if `d + r + t > 1.0`
pub fn hqz_legacy<R: Rng>(diffuse: f64, reflective: f64, transparent: f64) -> Material<R> {
    if diffuse + reflective + transparent > 1.0 {
        panic!("HQZ Legacy shader cooefficents > 1.0");
    }

    Arc::new(
        move |direction: &Vector, normal: &Vector, _wavelength: f64, _alpha: f64, rng: &mut R| {
            let f: f64 = rng.gen_range(0.0..1.0);

            if f <= diffuse {
                let angle = rng.gen_range(0.0..2.0 * PI);
                return Some(Vector {
                    x: f64::cos(angle),
                    y: f64::sin(angle),
                });
            }

            if f <= diffuse + reflective {
                let angle = direction.reflect(normal);
                return Some(angle);
            }

            if f <= diffuse + reflective + transparent {
                let angle = direction.clone();
                return Some(angle);
            }

            None
        },
    )
}

/// Default Settings for `hqz_legacy`, makes a fast and predictable testing shader where needed.
///
/// Default Values:
///   * __diffuse__: 0.1
///   * __reflective__: 0.4
///   * __transparent__: 0.4
///   * absorbsion is 0.1
pub fn hqz_legacy_default<R: Rng>() -> Material<R> {
    hqz_legacy(0.1, 0.4, 0.4)
}