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
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! An emitter is an instance of geometry that both receives and emits light
//!
//! # Scene Usage Example
//! An emitter is an object in the scene that emits light, it can be a point light
//! or an area light. The emitter takes an extra 'emitter' parameter to specify
//! whether the instance is an area or point emitter and an 'emission' parameter
//! to set the color and strength of emitted light.
//!
//! ## Point Light Example
//! The point light has no geometry, material or transformation since it's not a
//! physical object. Instead it simply takes a position to place the light at in the scene.
//!
//! ```json
//! "objects": [
//!     {
//!         "name": "my_light",
//!         "type": "emitter",
//!         "emitter": "point",
//!         "emission": [1, 1, 1, 100],
//!         "transform": [
//!             {
//!                 "type": "translate",
//!                 "translation": [0, 0, 22]
//!             }
//!         ]
//!     },
//!     ...
//! ]
//! ```
//!
//! ## Area Light Example
//! The area light looks similar to a regular receiver except it has an additional emission
//! parameter. Area lights are also restricted somewhat in which geometry they can use as
//! it needs to be possible to sample the geometry. Area lights can only accept geometry
//! that implements `geometry::Sampleable`.
//!
//! ```json
//! "objects": [
//!     {
//!         "name": "my_area_light",
//!         "type": "emitter",
//!         "emitter": "area",
//!         "emission": [1, 1, 1, 100],
//!         "material": "white_matte",
//!         "geometry": {
//!             "type": "sphere",
//!              "radius": 2.5
//!         },
//!         "transform": [
//!             {
//!                 "type": "translate",
//!                 "translation": [0, 0, 22]
//!             }
//!         ]
//!     },
//!     ...
//! ]
//! ```

use std::sync::Arc;

use geometry::{Boundable, BBox, SampleableGeom, DifferentialGeometry};
use material::Material;
use linalg::{self, AnimatedTransform, Point, Ray, Vector, Normal};
use film::{AnimatedColor, Colorf};
use light::{Light, OcclusionTester};

/// The type of emitter, either a point light or an area light
/// in which case the emitter has associated geometry and a material
/// TODO: Am I happy with this design?
enum EmitterType {
    Point,
    /// The area light holds the geometry that is emitting the light
    /// and the material for the geometry
    Area(Arc<SampleableGeom + Send + Sync>, Arc<Material + Send + Sync>),
}

/// An instance of geometry in the scene that receives and emits light.
pub struct Emitter {
    emitter: EmitterType,
    /// The light intensity emitted
    pub emission: AnimatedColor,
    /// The transform to world space
    transform: AnimatedTransform,
    /// Tag to identify the instance
    pub tag: String,
}

impl Emitter {
    /// Create a new area light using the geometry passed to emit light
    /// TODO: We need sample methods for geometry to do this
    /// We also need MIS in the path tracer's direct light sampling so we get
    /// good quality
    pub fn area(geom: Arc<SampleableGeom + Send + Sync>, material: Arc<Material + Send + Sync>,
                emission: AnimatedColor, transform: AnimatedTransform, tag: String) -> Emitter {
        // TODO: How to change this transform to handle scaling within the animation?
        /*
        if transform.has_scale() {
            println!("Warning: scaling detected in area light transform, this may give incorrect results");
        }
        */
        Emitter { emitter: EmitterType::Area(geom, material),
                  emission: emission,
                  transform: transform,
                  tag: tag }
    }
    /// Create a point light at the origin that is transformed by `transform` to its location
    /// in the world
    pub fn point(transform: AnimatedTransform, emission: AnimatedColor, tag: String) -> Emitter {
        Emitter { emitter: EmitterType::Point,
                  emission: emission,
                  transform: transform,
                  tag: tag }
    }
    /// Test the ray for intersection against this insance of geometry.
    /// returns Some(Intersection) if an intersection was found and None if not.
    /// If an intersection is found `ray.max_t` will be set accordingly
    pub fn intersect(&self, ray: &mut Ray) -> Option<(DifferentialGeometry, &Material)> {
        match self.emitter {
            EmitterType::Point => None,
            EmitterType::Area(ref geom, ref mat) => {
                let transform = self.transform.transform(ray.time);
                let mut local = transform.inv_mul_ray(ray);
                let mut dg = match geom.intersect(&mut local) {
                    Some(dg) => dg,
                    None => return None,
                };
                ray.max_t = local.max_t;
                dg.p = transform * dg.p;
                dg.n = transform * dg.n;
                dg.ng = transform * dg.ng;
                dg.dp_du = transform * dg.dp_du;
                dg.dp_dv = transform * dg.dp_dv;
                Some((dg, &**mat))
            },
        }
    }
    /// Return the radiance emitted by the light in the direction `w`
    /// from point `p` on the light's surface with normal `n`
    pub fn radiance(&self, w: &Vector, _: &Point, n: &Normal, time: f32) -> Colorf {
        if linalg::dot(w, n) > 0.0 { self.emission.color(time) } else { Colorf::black() }
    }
    /// Get the transform to place the emitter into world space
    pub fn get_transform(&self) -> &AnimatedTransform {
        &self.transform
    }
    /// Set the transform to place the emitter into world space
    pub fn set_transform(&mut self, transform: AnimatedTransform) {
        self.transform = transform;
    }
}

impl Boundable for Emitter {
    fn bounds(&self, start: f32, end: f32) -> BBox {
        match self.emitter {
            EmitterType::Point => self.transform.animation_bounds(&BBox::singular(Point::broadcast(0.0)), start, end),
            EmitterType::Area(ref g, _) => {
                self.transform.animation_bounds(&g.bounds(start, end), start, end)
            },
        }
    }
}

impl Light for Emitter {
    fn sample_incident(&self, p: &Point, samples: &(f32, f32), time: f32)
        -> (Colorf, Vector, f32, OcclusionTester)
    {
        match self.emitter {
            EmitterType::Point => {
                let transform = self.transform.transform(time);
                let pos = transform * Point::broadcast(0.0);
                let w_i = (pos - *p).normalized();
                (self.emission.color(time) / pos.distance_sqr(p), w_i, 1.0, OcclusionTester::test_points(p, &pos, time))
            }
            EmitterType::Area(ref g, _) => {
                let transform = self.transform.transform(time);
                let p_l = transform.inv_mul_point(p);
                let (p_sampled, normal) = g.sample(&p_l, samples);
                let w_il = (p_sampled - p_l).normalized();
                let pdf = g.pdf(&p_l, &w_il);
                let radiance = self.radiance(&-w_il, &p_sampled, &normal, time);
                let p_w = transform * p_sampled;
                (radiance, transform * w_il, pdf, OcclusionTester::test_points(p, &p_w, time))
            },
        }
    }
    fn delta_light(&self) -> bool {
        match self.emitter {
            EmitterType::Point => true,
            _ => false,
        }
    }
    fn pdf(&self, p: &Point, w_i: &Vector, time: f32) -> f32 {
        match self.emitter {
            EmitterType::Point => 0.0,
            EmitterType::Area(ref g, _ ) => {
                let transform = self.transform.transform(time);
                let p_l = transform.inv_mul_point(p);
                let w = (transform.inv_mul_vector(w_i)).normalized();
                g.pdf(&p_l, &w)
            }
        }
    }
}