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
//! Defines a rectangle centered at the origin, specified by its horizontal
//! and vertical lengths
//!
//! # Scene Usage Example
//! The rectangle takes two parameters, specifying its width and height. The
//! rectangle will be centered at the origin and will have its normal facing
//! along [0, 0, 1]
//!
//! ```json
//! "geometry": {
//!     "type": "rectangle",
//!     "width": 1.2,
//!     "height" 2.5
//! }
//! ```

use std::f32;

use geometry::{Geometry, DifferentialGeometry, Boundable, Sampleable, BBox};
use linalg::{self, Normal, Vector, Ray, Point};

/// A rectangle centered at the origin spanning [-width / 2, -height / 2]
/// to [width / 2, height / 2] with a normal along [0, 0, 1]
#[derive(Clone, Copy)]
pub struct Rectangle {
    width: f32,
    height: f32,
}

impl Rectangle {
    /// Create a new rectangle with the desired width and height
    pub fn new(width: f32, height: f32) -> Rectangle {
        Rectangle { width: width, height: height }
    }
}

impl Geometry for Rectangle {
    fn intersect(&self, ray: &mut Ray) -> Option<DifferentialGeometry> {
        // If the ray is perpindicular to the normal it can't intersect
        if f32::abs(ray.d.z) < 1e-8 {
            return None;
        }
        // Test for intersection against an infinite plane. Later we will
        // check that the hit found here is in the finite plane's extent
        let t = -ray.o.z / ray.d.z;
        if t < ray.min_t || t > ray.max_t {
            return None;
        }
        let p = ray.at(t);
        let half_width = self.width / 2.0;
        let half_height = self.height / 2.0;
        if p.x >= -half_width && p.x <= half_width && p.y >= -half_height && p.y <= half_height {
            ray.max_t = t;
            let n = Normal::new(0.0, 0.0, 1.0);
            let u = (p.x + half_width) / (2.0 * half_width);
            let v = (p.y + half_height) / (2.0 * half_height);
            let dp_du = Vector::new(half_width * 2.0, 0.0, 0.0);
            let dp_dv = Vector::new(0.0, half_height * 2.0, 0.0);
            Some(DifferentialGeometry::new(&p, &n, u, v, ray.time, &dp_du, &dp_dv, self))
        } else {
            None
        }
    }
}

impl Boundable for Rectangle {
    fn bounds(&self, _: f32, _: f32) -> BBox {
        let half_width = self.width / 2.0;
        let half_height = self.height / 2.0;
        BBox::span(Point::new(-half_width, -half_height, 0.0), Point::new(half_width, half_height, 0.0))
    }
}

impl Sampleable for Rectangle {
    /// Uniform sampling for a rect is simple: just scale the two samples into the
    /// rectangle's space and return them as the x,y coordinates of the point chosen
    fn sample_uniform(&self, samples: &(f32, f32)) -> (Point, Normal) {
        (Point::new(samples.0 * self.width - self.width / 2.0, samples.1 * self.height - self.height / 2.0, 0.0),
         Normal::new(0.0, 0.0, 1.0))
    }
    fn sample(&self, _: &Point, samples: &(f32, f32)) -> (Point, Normal) {
        self.sample_uniform(samples)
    }
    /// Compute the sphere's surface area
    fn surface_area(&self) -> f32 {
        self.width * self.height
    }
    /// Compute the PDF that the ray from `p` with direction `w_i` intersects
    /// the shape. This is the same as disk for computing PDF, we just use the
    /// rectangle's surface area instead
    fn pdf(&self, p: &Point, w_i: &Vector) -> f32 {
        // Time doesn't matter here, we're already in the object's space so we're moving
        // with it so to speak
        let mut ray = Ray::segment(p, w_i, 0.001, f32::INFINITY, 0.0);
        match self.intersect(&mut ray) {
            Some(d) => {
                let w = -*w_i;
                let pdf = p.distance_sqr(&ray.at(ray.max_t))
                    / (f32::abs(linalg::dot(&d.n, &w)) * self.surface_area());
                if f32::is_finite(pdf) { pdf } else { 0.0 }
            },
            None => 0.0
        }
    }
}