summaryrefslogtreecommitdiff
path: root/src/object.rs
blob: 0161d38ca420edc193f932d61e4f297bcfa9a7df (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
106
107
108
109
110
111
112
113
114
115
116
117
use crate::calculus::calculus::{Point3, Ray, Vec3};
use crate::common::Color;
use crate::interval::Interval;
use crate::material::{Material, MaterialType};

#[derive(Clone)]
pub struct HitRecord {
    pub position: Point3,
    pub normal: Vec3,
    pub t: f32,
    pub front_face: bool,
    pub material: Material,
}

impl Default for HitRecord {
    fn default() -> Self {
        Self {
            position: Point3 {x:0.0, y:0.0, z:0.0},
            normal: Vec3 {x:0.0, y:0.0, z:0.0},
            t: 0.0,
            front_face: false,
            material: Material::new(Color::new(1.0, 1.0, 1.0), MaterialType::Diffuse),
        }
    }
}

impl HitRecord {
    pub fn set_face_normal(&mut self, r: &Ray, outward_normal: Vec3) {
        self.front_face = r.direction.dot_prod(&outward_normal) < 0.0;
        self.normal = if self.front_face { outward_normal } else { outward_normal.scalar_mul(-1.0) };
    }
}

pub trait Hittable {
    fn hit(&self, r: &Ray, ray_t: Interval, rec: &mut HitRecord) -> bool;
}

pub struct Sphere {
    center: Point3,
    radius: f32,
    material: Material,
}

impl Sphere {
    pub fn new(center: Vec3, radius: f32, material: Material) -> Self {
        let radius = f32::max(0.0, radius);
        Self { center, radius, material }
    }
}

impl Hittable for Sphere {
    fn hit(&self, r: &Ray, ray_t: Interval, rec: &mut HitRecord) -> bool {
        let oc = self.center.sub(&r.origin);
        let a = r.direction.mag_sqr();
        let h = r.direction.dot_prod(&oc);
        let c  = oc.mag_sqr() - self.radius * self.radius;
        let discriminant = h * h - a * c;
        if discriminant < 0.0 {
            return false;
        }

        let sqrtd = discriminant.sqrt();

        let mut root = (h - sqrtd) / a;
        if root <= ray_t.min || ray_t.max <= root {
            root = (h + sqrtd) / a;
            if root <= ray_t.min || ray_t.max <= root {
                return false;
            }
        }

        rec.t = root;
        rec.position = r.at(rec.t);
        let outward_normal = rec.position
            .sub(&self.center)
            .scalar_mul(1.0  / self.radius);
        rec.set_face_normal(r, outward_normal);
        rec.material = self.material.clone();

        true
    }
}

pub struct HittableList {
    objects: Vec<Box<dyn Hittable>>,
}

impl HittableList {
    pub fn new() -> Self {
        Self { objects: vec![] }
    }

    pub fn push<T: Hittable + 'static>(&mut self, object: T) {
        self.objects.push(Box::new(object));
    }
}

impl Hittable for HittableList {
    fn hit(&self, r: &Ray, ray_t: Interval, rec: &mut HitRecord) -> bool {
        let mut temp_rec = rec.clone();
        let mut hit_anything = false;
        let mut closest_so_far = ray_t.max;

        self.objects.iter().for_each(
            |object| {
                let interval = Interval::new(ray_t.min, closest_so_far);
                if object.hit(r, interval, &mut temp_rec) {
                    hit_anything = true;
                    closest_so_far = temp_rec.t;
                    *rec = temp_rec.clone();
                }
            }
        );

        hit_anything
    }
}