aboutsummaryrefslogtreecommitdiff
path: root/src/material/dielectric.rs
blob: af6f9b08fa73e5511a403cc8bf79228e1c1ba97b (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
use std::ops::Neg;

use super::Material;
use crate::ray::Ray;
use crate::vec3::Color;
use crate::{hittable::HitRecord, vec3::Vec3};

pub struct DielectricAttenuation {
    pub albedo: Color,
    pub constant: f64,
}

pub struct Dielectric {
    pub index_of_refraction: f64,
    pub attenuation: Option<DielectricAttenuation>,
}

impl Dielectric {
    fn reflectance(cosine: f64, ref_idx: f64) -> f64 {
        // Using Schlick's Approximation:
        let mut r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
        r0 *= r0;
        r0 + (1.0 - r0) * (1.0 - cosine).powi(5)
    }
}

impl Material for Dielectric {
    fn scatter(
        &self,
        ray_in: &Ray,
        hit_record: &HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        if let Some(props) = &self.attenuation {
            let outward_normal = if hit_record.front_face {
                hit_record.normal.clone()
            } else {
                -&hit_record.normal
            };
            if outward_normal.dot(&ray_in.direction) > 0.0 {
                let distance = (&ray_in.origin - &hit_record.p).length();
                let falloff_ratio = (props.constant * distance).neg().exp();
                *attenuation = &props.albedo * falloff_ratio;
            } else {
                *attenuation = props.albedo.clone();
            }
        } else {
            *attenuation = Color {
                x: 1.0,
                y: 1.0,
                z: 1.0,
            };
        }
        let refraction_ratio = if hit_record.front_face {
            1.0 / self.index_of_refraction
        } else {
            self.index_of_refraction
        };
        let unit_direction = ray_in.direction.unit_vector();
        let cos_theta = hit_record.normal.dot(&-&unit_direction).min(1.0);
        let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();

        let cannot_refract = refraction_ratio * sin_theta > 1.0;
        let direction: Vec3;

        if cannot_refract || Self::reflectance(cos_theta, refraction_ratio) > rand::random::<f64>()
        {
            direction = unit_direction.reflect(&hit_record.normal)
        } else {
            direction = unit_direction.refract(&hit_record.normal, refraction_ratio)
        }

        *scattered = Ray {
            origin: hit_record.p.clone(),
            direction,
            time: ray_in.time,
        };
        true
    }
}