aboutsummaryrefslogtreecommitdiff
path: root/src/world/shapes/rectangle.rs
blob: 313ebb89bb28f3c53673b29e80fb3898c75a7df1 (plain)
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
use crate::{NEAR_ZERO, Float};
use crate::core::{Ray, Vector3f, Bound3f};
use crate::world::{Hittable, DynHittable, Intersection, Instancable};

const OUT_XY: Vector3f = Vector3f { x: 0.0, y: 0.0, z: 1.0 };

pub enum Plane {
    XY,
}

pub struct Rect {
    // Size of rectangle in first dimension
    d1: Float,
    // Size in second
    d2: Float,
    // Offset of plane plane. Should only be used when composing other types
    offset: Float,

    plane: Plane,
}

impl Rect {
    pub fn new(width: Float, height: Float, plane: Plane) -> Self {
        Rect {d1: width, d2: height, offset: 0.0, plane}
    }

    pub fn new_with_offset(d1: Float, d2: Float, offset: Float, plane: Plane) -> Self {
        Rect {
            d1, d2,
            offset,
            plane
        }
    }
}

impl Hittable for Rect {
    fn intersect(&self, ray: &Ray) -> Option<Intersection> {
        let t = (self.offset-ray.origin.z) / ray.direction.z;
        if t <= NEAR_ZERO {
            return None;
        }

        let poi = ray.at(t);
        // Check if at is inside rectangle on plane
        if poi.x.abs() > (self.d1/2.0) || poi.y.abs() > (self.d2/2.0) {
            // No collision
            return None;
        }

        Some(Intersection::new(OUT_XY, poi, ray, t))
    }

    fn bounding_box(&self) -> Bound3f {
        // A rectangle has no area, but we will give it some for its bounding box
        Bound3f::new(
            Vector3f::new_xyz(-self.d1, -self.d2, self.offset - 0.0001),
            Vector3f::new_xyz(self.d1, self.d2, self.offset + 0.0001)
            )
    }
}

impl Into<DynHittable> for Rect {
    fn into(self) -> DynHittable {
        DynHittable::new(Box::new(self))
    }
}

impl Instancable for Rect {}

impl Plane {
    pub fn switch(&self, v: Vector3f) -> Vector3f {
        match self {
            Plane::XY => v,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn xy_rect() {
        let rect = Rect::new_xy(5.0, 5.0);
        let ray = Ray {
            origin: Vector3f::new_xyz(0.0, 1.0, 1.5),
            direction: Vector3f::new_xyz(0.0, 0.0, -1.0),
        };

        let t = rect.intersect(&ray).unwrap().t;
        assert!((t - 1.5).abs() < 0.01);
    }

    #[test]
    fn xy_rect_offset() {
        let rect = Rect::new_with_offset(5.0, 5.0, -1.0, Plane::XY);
        let ray = Ray {
            origin: Vector3f::new_xyz(0.0, 1.0, 1.5),
            direction: Vector3f::new_xyz(0.0, 0.0, -1.0),
        };

        let t = rect.intersect(&ray).unwrap().t;
        assert!((t - 2.5).abs() < 0.01);
    }
}