aboutsummaryrefslogtreecommitdiff
path: root/src/hittable/triangle.rs
blob: 82b9e18e6e459a2fc85ce8ca0fa44bd8da0b6884 (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
use std::sync::Arc;

use crate::{
    hittable::{HitRecord, Hittable, AABB},
    material::Material,
    ray::Ray,
    vec3::{Point3, Vec3},
};

pub struct Triangle {
    pub v0: Point3,
    pub v1: Point3,
    pub v2: Point3,
    pub material: Arc<dyn Material>,
    pub custom_normal: Option<Vec3>,
}

impl Triangle {
    fn has_vertex_at_infinity(&self) -> bool {
        self.v0.has_infinite_member()
            || self.v1.has_infinite_member()
            || self.v2.has_infinite_member()
    }
}

impl Hittable for Triangle {
    // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
    fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
        let epsilon: f64 = 0.0000001;
        let edge1 = &self.v1 - &self.v0;
        let edge2 = &self.v2 - &self.v0;
        let h = ray.direction.cross(&edge2);
        let a = edge1.dot(&h);

        if a > -epsilon && a < epsilon {
            return None; // This ray is parallel to the triangle.
        }
        let f = 1.0 / a;
        let s = &ray.origin - &self.v0;
        let u = f * s.dot(&h);
        if u < 0.0 || u > 1.0 {
            return None;
        }
        let q = s.cross(&edge1);
        let v = f * ray.direction.dot(&q);
        if v < 0.0 || u + v > 1.0 {
            return None;
        }
        // At this point, we can compute the point of intersection.
        let t = f * edge2.dot(&q);
        if t < t_min || t > t_max {
            return None;
        }
        let mut hit_record = HitRecord::new();
        hit_record.u = u;
        hit_record.v = v;
        hit_record.t = t;
        hit_record.p = ray.at(t);
        // TODO: i don't love this, but it allows for custom surface normals from OBJ data.
        if let Some(normal) = &self.custom_normal {
            hit_record.set_face_normal(ray, &normal);
        } else {
            let outward_normal = edge2.cross(&edge1).unit_vector();
            hit_record.set_face_normal(ray, &outward_normal);
        }
        hit_record.material = Some(self.material.clone());
        Some(hit_record)
    }
    fn bounding_box(&self, _: f64, _: f64) -> Option<AABB> {
        match self.has_vertex_at_infinity() {
            true => None,
            false => Some(AABB {
                minimum: Point3 {
                    x: self.v0.x.min(self.v1.x).min(self.v2.x) - 0.0001,
                    y: self.v0.y.min(self.v1.y).min(self.v2.y) - 0.0001,
                    z: self.v0.z.min(self.v1.z).min(self.v2.z) - 0.0001,
                },
                maximum: Point3 {
                    x: self.v0.x.max(self.v1.x).max(self.v2.x) + 0.0001,
                    y: self.v0.y.max(self.v1.y).max(self.v2.y) + 0.0001,
                    z: self.v0.z.max(self.v1.z).max(self.v2.z) + 0.0001,
                },
            }),
        }
    }
}