1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! Optical material.

use crate::{
    geom::{Emit, Mesh, Ray},
    math::{rand_isotropic_dir, Pos3},
};
use rand::Rng;

/// Ray emission structure.
pub enum Emitter {
    /// Single beam.
    Beam(Ray),
    /// Points.
    Points(Vec<Pos3>),
    /// Weighted points.
    WeightedPoints(Vec<Pos3>, Vec<f64>),
    /// Surface mesh.
    Surface(Mesh),
}

impl Emitter {
    /// Construct a new beam instance.
    #[inline]
    #[must_use]
    pub const fn new_beam(ray: Ray) -> Self {
        Self::Beam(ray)
    }

    /// Construct a new points instance.
    #[inline]
    #[must_use]
    pub fn new_points(points: Vec<Pos3>) -> Self {
        debug_assert!(!points.is_empty());

        Self::Points(points)
    }

    /// Construct a new points instance.
    #[inline]
    #[must_use]
    pub fn new_weighted_points(points: Vec<Pos3>, weights: &[f64]) -> Self {
        debug_assert!(!points.is_empty());
        debug_assert!(points.len() == weights.len());

        let sum: f64 = weights.iter().sum();
        let mut cumulative_weight = Vec::with_capacity(weights.len());
        let mut total = 0.0;
        for w in weights {
            total += w;
            cumulative_weight.push(total / sum);
        }

        Self::WeightedPoints(points, cumulative_weight)
    }

    /// Construct a new surface instance.
    #[inline]
    #[must_use]
    pub const fn new_surface(mesh: Mesh) -> Self {
        Self::Surface(mesh)
    }

    /// Emit a new ray.
    #[inline]
    #[must_use]
    pub fn emit<R: Rng>(&self, rng: &mut R) -> Ray {
        match *self {
            Self::Beam(ref ray) => ray.clone(),
            Self::Points(ref ps) => {
                Ray::new(ps[rng.gen_range(0, ps.len())], rand_isotropic_dir(rng))
            }
            Self::WeightedPoints(ref ps, ref ws) => {
                let r: f64 = rng.gen();
                for (p, w) in ps.iter().zip(ws) {
                    if r <= *w {
                        return Ray::new(*p, rand_isotropic_dir(rng));
                    }
                }
                unreachable!("Failed to determine weighted point to emit from.");
            }
            Self::Surface(ref mesh) => mesh.cast(rng),
        }
    }
}