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
//! Data types and functions related to materials.
use std::sync::Arc;

use nanorand::tls::TlsWyRand;

pub use pbr_material::PbrMaterial;

use crate::color::{Color, Texture};
use crate::math::{Hit, Point2, Ray, Vec3};

mod pbr_material;

/// Characterize optical properties of a surface.
pub trait Material: Send + Sync {
    /// Generate the next ray to be traced.
    ///
    /// If the path is to be terminated, [`None`] is returned.
    fn next_ray(&self, ray: Ray, hit: Hit, rng: &mut TlsWyRand) -> Option<Ray>;

    /// Return the color at the given UV coordinates.
    ///
    /// The first item is the reflected color, the second item is the emitted color.
    fn get_color(&self, uv: Point2) -> (Color, Color);
}

/// Compute the combination of a [`Color`], an optional [`Texture`] and a given `strength`
/// for the given UV coordinates.
pub fn compute_color(
    color: Color,
    texture: &Option<Arc<dyn Texture>>,
    strength: f64,
    uv: Point2,
) -> Color {
    strength as f32
        * color
        * match texture {
            None => Color::WHITE,
            Some(t) => t.get_pixel(uv.x, uv.y),
        }
}

/// Compute the normal at the given UV coordinates using the `normal_texture`.
///
/// If `normal_texture` is [`None`], `normal` is returned.
pub fn compute_normal(
    normal: Vec3,
    tangent: Vec3,
    bitangent: Vec3,
    normal_texture: &Option<Arc<dyn Texture>>,
    uv: Point2,
) -> Vec3 {
    match normal_texture {
        None => normal,
        Some(texture) => {
            let texture_normal = Vec3::from(texture.get_pixel(uv.x, uv.y)) * 2.0 - Vec3::splat(1.0);

            (texture_normal.x * tangent + texture_normal.y * bitangent + texture_normal.z * normal)
                .normalize()
        }
    }
}