omp_tui/scene/
material.rs1use super::{Vec3, vec3};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct Material {
9 pub base_color: Vec3,
11 pub emission: Vec3,
13 pub roughness: f32,
15 pub metallic: f32,
17 pub transmission: f32,
19 pub ior: f32,
21}
22
23impl Material {
24 pub fn diffuse(color: Vec3) -> Self {
26 Self {
27 base_color: color,
28 emission: Vec3::ZERO,
29 roughness: 1.0,
30 metallic: 0.0,
31 transmission: 0.0,
32 ior: 1.5,
33 }
34 .sanitized()
35 }
36
37 pub fn metal(color: Vec3, roughness: f32) -> Self {
40 Self {
41 base_color: color,
42 emission: Vec3::ZERO,
43 roughness,
44 metallic: 1.0,
45 transmission: 0.0,
46 ior: 1.5,
47 }
48 .sanitized()
49 }
50
51 pub fn dielectric(color: Vec3, ior: f32) -> Self {
56 Self {
57 base_color: color,
58 emission: Vec3::ZERO,
59 roughness: 0.02,
60 metallic: 0.0,
61 transmission: 1.0,
62 ior,
63 }
64 .sanitized()
65 }
66
67 pub fn emissive(color: Vec3, strength: f32) -> Self {
71 Self {
72 base_color: Vec3::ZERO,
73 emission: color * finite_or(strength, 0.0).max(0.0),
74 roughness: 1.0,
75 metallic: 0.0,
76 transmission: 0.0,
77 ior: 1.5,
78 }
79 .sanitized()
80 }
81
82 pub fn sanitized(self) -> Self {
89 let metallic = finite_unit(self.metallic);
90 Self {
91 base_color: finite_unit_color(self.base_color),
92 emission: finite_positive_color(self.emission),
93 roughness: finite_or(self.roughness, 1.0).clamp(0.02, 1.0),
94 metallic,
95 transmission: finite_unit(self.transmission) * (1.0 - metallic),
96 ior: finite_or(self.ior, 1.5).clamp(1.0001, 3.0),
97 }
98 }
99}
100
101impl Default for Material {
102 fn default() -> Self {
103 Self::diffuse(vec3(0.8, 0.8, 0.8))
104 }
105}
106
107const fn finite_or(value: f32, fallback: f32) -> f32 {
108 if value.is_finite() { value } else { fallback }
109}
110
111const fn finite_unit(value: f32) -> f32 {
112 finite_or(value, 0.0).clamp(0.0, 1.0)
113}
114
115const fn finite_unit_color(color: Vec3) -> Vec3 {
116 vec3(finite_unit(color.x), finite_unit(color.y), finite_unit(color.z))
117}
118
119fn finite_positive_color(color: Vec3) -> Vec3 {
120 let channel = |value: f32| {
121 if value.is_finite() {
122 value.max(0.0)
123 } else {
124 0.0
125 }
126 };
127 vec3(channel(color.x), channel(color.y), channel(color.z))
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn sanitization_bounds_scattering_energy_and_keeps_hdr_emission() {
136 let material = Material {
137 base_color: vec3(-1.0, 2.0, f32::NAN),
138 emission: vec3(4.0, -2.0, f32::INFINITY),
139 roughness: 0.0,
140 metallic: 0.75,
141 transmission: 1.0,
142 ior: 99.0,
143 }
144 .sanitized();
145 assert_eq!(material.base_color, vec3(0.0, 1.0, 0.0));
146 assert_eq!(material.emission, vec3(4.0, 0.0, 0.0));
147 assert_eq!(material.roughness, 0.02);
148 assert_eq!(material.transmission, 0.25);
149 assert_eq!(material.ior, 3.0);
150 }
151}