Skip to content

Commit f26b162

Browse files
authored
Merge pull request #230 from Walther/push-yvlqnkvmukps
feat: add support for various CIE standard illuminants
2 parents acf9801 + 77b012a commit f26b162

34 files changed

Lines changed: 2568 additions & 241 deletions

‎clovers-cli/src/scenefile.rs‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,24 +80,26 @@ impl SceneFile {
8080
Object::Triangle(i) => i.priority,
8181
};
8282
let hitable = object_to_hitable(object, materials);
83-
84-
match hitable {
85-
// Flatten any lists we got. Potential sources: `GLTF`, `STL`, `ObjectList`
86-
Hitable::HitableList(l) => {
87-
let flattened = &mut l.flatten();
88-
hitables.append(&mut flattened.clone());
83+
// If we get a HitableList, flatten it
84+
if let Hitable::HitableList(l) = hitable {
85+
for h in l.hitables {
86+
if let Hitable::HitableList(_) = h {
87+
panic!("Recursing too hard in scene initialization")
88+
}
8989
if priority {
90-
priority_hitables.append(&mut flattened.clone());
90+
// FIXME: remove this and then remove Clone from Hitable
91+
priority_hitables.push(h.clone());
9192
}
93+
hitables.push(h);
9294
}
95+
} else {
9396
// Otherwise, push as-is
94-
_ => {
95-
hitables.push(hitable.clone());
96-
if priority {
97-
priority_hitables.push(hitable.clone());
98-
}
97+
if priority {
98+
// FIXME: remove this and then remove Clone from Hitable
99+
priority_hitables.push(hitable.clone());
99100
}
100-
};
101+
hitables.push(hitable);
102+
}
101103
}
102104
info!("All objects parsed into hitables");
103105
info!("Building the BVH root for hitables");

‎clovers-cli/src/trace.rs‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ use clovers::{
66
pdf::{HitablePDF, MixturePDF, PDFTrait, PDF},
77
ray::Ray,
88
scenes::Scene,
9-
spectrum::{spectral_power, spectral_powers},
9+
spectrum::spectral_powers,
1010
wavelength::{rotate_wavelength, WAVE_SAMPLE_COUNT},
1111
Float, EPSILON_SHADOW_ACNE,
1212
};
1313
use nalgebra::Unit;
14-
use palette::{white_point::E, Xyz};
1514
use rand::rngs::SmallRng;
1615

1716
use crate::sampler::SamplerTrait;
@@ -49,8 +48,8 @@ pub fn trace(
4948
};
5049

5150
// Get the emitted color from the surface that we just hit
52-
let emitted: Xyz<E> = hit_record.material.emit(ray, &hit_record);
53-
let emitted = spectral_powers(emitted, wavelengths);
51+
let emitted =
52+
std::array::from_fn(|i| hit_record.material.emit(ray, wavelengths[i], &hit_record));
5453

5554
// Do we scatter?
5655
let Some(scatter_record) = hit_record.material.scatter(ray, &hit_record, rng) else {
@@ -62,10 +61,12 @@ pub fn trace(
6261
let attenuations = match hit_record.material.is_wavelength_dependent() {
6362
true => {
6463
let mut ret = [0.0; WAVE_SAMPLE_COUNT];
65-
ret[0] = spectral_power(scatter_record.attenuation, hero);
64+
ret[0] = hit_record.material.color(ray, hero, &hit_record);
6665
ret
6766
}
68-
false => spectral_powers(scatter_record.attenuation, wavelengths),
67+
false => {
68+
std::array::from_fn(|i| hit_record.material.color(ray, wavelengths[i], &hit_record))
69+
}
6970
};
7071

7172
// Check the material type and recurse accordingly:

‎clovers/Cargo.toml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ serde = { version = "1.0.216", features = [
3131
], default-features = false, optional = true }
3232
stl_io = { version = "0.8.3", optional = true }
3333
tracing = { version = "0.1.41", optional = true }
34+
phf = { version = "0.11.2", default-features = false }
3435

3536
[dev-dependencies]
3637
divan = "0.1.17"
3738
proptest = "1"
3839

40+
[build-dependencies]
41+
phf = { version = "0.11.2", default-features = false }
42+
phf_codegen = "0.11.2"
43+
3944
[[bench]]
4045
name = "random"
4146
harness = false

‎clovers/build.rs‎

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
use std::env;
2+
use std::fs::File;
3+
use std::io::{BufWriter, Write};
4+
use std::path::Path;
5+
6+
use phf_codegen::OrderedMap;
7+
8+
// TODO: learn enough macros to clean this file up a bit
9+
10+
fn main() {
11+
let d50 = include_str!("src/illuminants/CIE_std_illum_D50.csv");
12+
let d50 = parse_pairs(d50);
13+
write_illuminant(d50, "D50");
14+
let d65 = include_str!("src/illuminants/CIE_std_illum_D65.csv");
15+
let d65 = parse_pairs(d65);
16+
write_illuminant(d65, "D65");
17+
18+
let leds = include_str!("src/illuminants/CIE_illum_LEDs_1nm.csv");
19+
let [led_b1, led_b2, led_b3, led_b4, led_b5, led_bh1, led_rgb1, led_v1, led_v2] =
20+
parse_leds(leds);
21+
22+
write_illuminant(led_b1, "LED_B1");
23+
write_illuminant(led_b2, "LED_B2");
24+
write_illuminant(led_b3, "LED_B3");
25+
write_illuminant(led_b4, "LED_B4");
26+
write_illuminant(led_b5, "LED_B5");
27+
write_illuminant(led_bh1, "LED_BH1");
28+
write_illuminant(led_rgb1, "LED_RGB1");
29+
write_illuminant(led_v1, "LED_V1");
30+
write_illuminant(led_v2, "LED_V2");
31+
32+
let fluorescents = include_str!("src/illuminants/CIE_illum_FLs_1nm.csv");
33+
let [fl1, fl2, fl3, fl4, fl5, fl6, fl7, fl8, fl9, fl10, fl11, fl12, fl3_1, fl3_2, fl3_3, fl3_4, fl3_5, fl3_6, fl3_7, fl3_8, fl3_9, fl3_10, fl3_11, fl3_12, fl3_13, fl3_14, fl3_15] =
34+
parse_fluorescents(fluorescents);
35+
write_illuminant(fl1, "FL1");
36+
write_illuminant(fl2, "FL2");
37+
write_illuminant(fl3, "FL3");
38+
write_illuminant(fl4, "FL4");
39+
write_illuminant(fl5, "FL5");
40+
write_illuminant(fl6, "FL6");
41+
write_illuminant(fl7, "FL7");
42+
write_illuminant(fl8, "FL8");
43+
write_illuminant(fl9, "FL9");
44+
write_illuminant(fl10, "FL10");
45+
write_illuminant(fl11, "FL11");
46+
write_illuminant(fl12, "FL12");
47+
write_illuminant(fl3_1, "FL3_1");
48+
write_illuminant(fl3_2, "FL3_2");
49+
write_illuminant(fl3_3, "FL3_3");
50+
write_illuminant(fl3_4, "FL3_4");
51+
write_illuminant(fl3_5, "FL3_5");
52+
write_illuminant(fl3_6, "FL3_6");
53+
write_illuminant(fl3_7, "FL3_7");
54+
write_illuminant(fl3_8, "FL3_8");
55+
write_illuminant(fl3_9, "FL3_9");
56+
write_illuminant(fl3_10, "FL3_10");
57+
write_illuminant(fl3_11, "FL3_11");
58+
write_illuminant(fl3_12, "FL3_12");
59+
write_illuminant(fl3_13, "FL3_13");
60+
write_illuminant(fl3_14, "FL3_14");
61+
write_illuminant(fl3_15, "FL3_15");
62+
}
63+
64+
fn write_illuminant(builder: OrderedMap<usize>, output: &str) {
65+
let outpath = Path::new(&env::var("OUT_DIR").unwrap()).join(format!("{output}.rs"));
66+
let mut outfile = BufWriter::new(File::create(&outpath).unwrap());
67+
let data = builder.build();
68+
write!(
69+
&mut outfile,
70+
"static {output}: phf::OrderedMap<Wavelength, Float> = {data}"
71+
)
72+
.unwrap();
73+
writeln!(&mut outfile, ";").unwrap();
74+
write!(
75+
&mut outfile,
76+
r#"#
77+
[derive(Clone, Debug)]
78+
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
79+
/// Standard illuminant `{output}`
80+
pub struct {output} {{
81+
#[cfg_attr(feature = "serde-derive", serde(default = "default_intensity"))]
82+
intensity: Float,
83+
}}
84+
85+
impl TextureTrait for {output} {{
86+
#[must_use]
87+
fn color(&self, _ray: &Ray, wavelength: Wavelength, _hit_record: &HitRecord) -> Float {{
88+
let color = match {output}.get(&wavelength) {{
89+
Some(&p) => p * self.intensity,
90+
None => 0.0,
91+
}};
92+
color.clamp(0.0, 1.0)
93+
}}
94+
95+
#[must_use]
96+
fn emit(
97+
&self,
98+
_ray: &Ray,
99+
wavelength: Wavelength,
100+
_hit_record: &HitRecord,
101+
) -> Float {{
102+
match {output}.get(&wavelength) {{
103+
Some(&p) => p * self.intensity,
104+
None => 0.0,
105+
}}
106+
}}
107+
}}
108+
"#
109+
)
110+
.unwrap()
111+
}
112+
113+
fn floaty(f: &str) -> String {
114+
if f.contains('.') {
115+
f.to_string()
116+
} else {
117+
format!("{f}.0")
118+
}
119+
}
120+
121+
fn parse_pairs(input: &str) -> OrderedMap<usize> {
122+
let mut builder: OrderedMap<usize> = OrderedMap::new();
123+
124+
for line in input.lines() {
125+
let (wavelength, power) = line.split_once(',').unwrap();
126+
let wavelength: usize = wavelength.parse().unwrap();
127+
builder.entry(wavelength, &floaty(power));
128+
}
129+
builder
130+
}
131+
132+
fn parse_leds(input: &str) -> [OrderedMap<usize>; 9] {
133+
let mut led_b1: OrderedMap<usize> = OrderedMap::new();
134+
let mut led_b2: OrderedMap<usize> = OrderedMap::new();
135+
let mut led_b3: OrderedMap<usize> = OrderedMap::new();
136+
let mut led_b4: OrderedMap<usize> = OrderedMap::new();
137+
let mut led_b5: OrderedMap<usize> = OrderedMap::new();
138+
let mut led_bh1: OrderedMap<usize> = OrderedMap::new();
139+
let mut led_rgb1: OrderedMap<usize> = OrderedMap::new();
140+
let mut led_v1: OrderedMap<usize> = OrderedMap::new();
141+
let mut led_v2: OrderedMap<usize> = OrderedMap::new();
142+
143+
for line in input.lines() {
144+
let split: Vec<&str> = line.split(',').collect();
145+
let wavelength: usize = split[0].parse().unwrap();
146+
led_b1.entry(wavelength, split[1]);
147+
led_b2.entry(wavelength, split[2]);
148+
led_b3.entry(wavelength, split[3]);
149+
led_b4.entry(wavelength, split[4]);
150+
led_b5.entry(wavelength, split[5]);
151+
led_bh1.entry(wavelength, split[6]);
152+
led_rgb1.entry(wavelength, split[7]);
153+
led_v1.entry(wavelength, split[8]);
154+
led_v2.entry(wavelength, split[9]);
155+
}
156+
157+
[
158+
led_b1, led_b2, led_b3, led_b4, led_b5, led_bh1, led_rgb1, led_v1, led_v2,
159+
]
160+
}
161+
162+
fn parse_fluorescents(input: &str) -> [OrderedMap<usize>; 27] {
163+
let mut fl1: OrderedMap<usize> = OrderedMap::new();
164+
let mut fl2: OrderedMap<usize> = OrderedMap::new();
165+
let mut fl3: OrderedMap<usize> = OrderedMap::new();
166+
let mut fl4: OrderedMap<usize> = OrderedMap::new();
167+
let mut fl5: OrderedMap<usize> = OrderedMap::new();
168+
let mut fl6: OrderedMap<usize> = OrderedMap::new();
169+
let mut fl7: OrderedMap<usize> = OrderedMap::new();
170+
let mut fl8: OrderedMap<usize> = OrderedMap::new();
171+
let mut fl9: OrderedMap<usize> = OrderedMap::new();
172+
let mut fl10: OrderedMap<usize> = OrderedMap::new();
173+
let mut fl11: OrderedMap<usize> = OrderedMap::new();
174+
let mut fl12: OrderedMap<usize> = OrderedMap::new();
175+
let mut fl3_1: OrderedMap<usize> = OrderedMap::new();
176+
let mut fl3_2: OrderedMap<usize> = OrderedMap::new();
177+
let mut fl3_3: OrderedMap<usize> = OrderedMap::new();
178+
let mut fl3_4: OrderedMap<usize> = OrderedMap::new();
179+
let mut fl3_5: OrderedMap<usize> = OrderedMap::new();
180+
let mut fl3_6: OrderedMap<usize> = OrderedMap::new();
181+
let mut fl3_7: OrderedMap<usize> = OrderedMap::new();
182+
let mut fl3_8: OrderedMap<usize> = OrderedMap::new();
183+
let mut fl3_9: OrderedMap<usize> = OrderedMap::new();
184+
let mut fl3_10: OrderedMap<usize> = OrderedMap::new();
185+
let mut fl3_11: OrderedMap<usize> = OrderedMap::new();
186+
let mut fl3_12: OrderedMap<usize> = OrderedMap::new();
187+
let mut fl3_13: OrderedMap<usize> = OrderedMap::new();
188+
let mut fl3_14: OrderedMap<usize> = OrderedMap::new();
189+
let mut fl3_15: OrderedMap<usize> = OrderedMap::new();
190+
191+
for line in input.lines() {
192+
let split: Vec<&str> = line.split(',').collect();
193+
let wavelength: usize = split[0].parse().unwrap();
194+
195+
fl1.entry(wavelength, &floaty(split[1]));
196+
fl2.entry(wavelength, &floaty(split[2]));
197+
fl3.entry(wavelength, &floaty(split[3]));
198+
fl4.entry(wavelength, &floaty(split[4]));
199+
fl5.entry(wavelength, &floaty(split[5]));
200+
fl6.entry(wavelength, &floaty(split[6]));
201+
fl7.entry(wavelength, &floaty(split[7]));
202+
fl8.entry(wavelength, &floaty(split[8]));
203+
fl9.entry(wavelength, &floaty(split[9]));
204+
fl10.entry(wavelength, &floaty(split[10]));
205+
fl11.entry(wavelength, &floaty(split[11]));
206+
fl12.entry(wavelength, &floaty(split[12]));
207+
fl3_1.entry(wavelength, &floaty(split[13]));
208+
fl3_2.entry(wavelength, &floaty(split[14]));
209+
fl3_3.entry(wavelength, &floaty(split[15]));
210+
fl3_4.entry(wavelength, &floaty(split[16]));
211+
fl3_5.entry(wavelength, &floaty(split[17]));
212+
fl3_6.entry(wavelength, &floaty(split[18]));
213+
fl3_7.entry(wavelength, &floaty(split[19]));
214+
fl3_8.entry(wavelength, &floaty(split[20]));
215+
fl3_9.entry(wavelength, &floaty(split[21]));
216+
fl3_10.entry(wavelength, &floaty(split[22]));
217+
fl3_11.entry(wavelength, &floaty(split[23]));
218+
fl3_12.entry(wavelength, &floaty(split[24]));
219+
fl3_13.entry(wavelength, &floaty(split[25]));
220+
fl3_14.entry(wavelength, &floaty(split[26]));
221+
fl3_15.entry(wavelength, &floaty(split[27]));
222+
}
223+
224+
[
225+
fl1, fl2, fl3, fl4, fl5, fl6, fl7, fl8, fl9, fl10, fl11, fl12, fl3_1, fl3_2, fl3_3, fl3_4,
226+
fl3_5, fl3_6, fl3_7, fl3_8, fl3_9, fl3_10, fl3_11, fl3_12, fl3_13, fl3_14, fl3_15,
227+
]
228+
}

‎clovers/src/bvh/build/longest_axis_midpoint.rs‎

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn build(mut hitables: Vec<Hitable>) -> BVHNode {
2929
if object_span == 1 {
3030
// If we only have one object, add one and an empty object.
3131
// TODO: can this hack be removed?
32-
left = Box::new(hitables[0].clone());
32+
left = Box::new(hitables.remove(0));
3333
right = Box::new(Hitable::Empty(Empty {}));
3434
let aabb = left.aabb().unwrap().clone(); // TODO: remove unwrap
3535
return BVHNode { left, right, aabb };
@@ -38,12 +38,12 @@ pub fn build(mut hitables: Vec<Hitable>) -> BVHNode {
3838
// Insert the child nodes in order
3939
match comparator(&hitables[0], &hitables[1]) {
4040
Ordering::Less => {
41-
left = Box::new(hitables[0].clone());
42-
right = Box::new(hitables[1].clone());
41+
left = Box::new(hitables.remove(0));
42+
right = Box::new(hitables.remove(0)); // Second item is at index 0 after first removal
4343
}
4444
Ordering::Greater => {
45-
left = Box::new(hitables[1].clone());
46-
right = Box::new(hitables[0].clone());
45+
left = Box::new(hitables.remove(1));
46+
right = Box::new(hitables.remove(0));
4747
}
4848
Ordering::Equal => {
4949
// TODO: what should happen here?
@@ -53,15 +53,14 @@ pub fn build(mut hitables: Vec<Hitable>) -> BVHNode {
5353
} else if object_span == 3 {
5454
// Three objects: create one bare object and one BVHNode with two objects
5555
hitables.sort_by(comparator);
56-
left = Box::new(hitables[0].clone());
56+
left = Box::new(hitables.remove(0));
57+
let rl = hitables.remove(0);
58+
let rr = hitables.remove(0);
59+
let aabb = AABB::combine(rl.aabb().unwrap(), rr.aabb().unwrap());
5760
right = Box::new(Hitable::BVHNode(BVHNode {
58-
left: Box::new(hitables[1].clone()),
59-
right: Box::new(hitables[2].clone()),
60-
aabb: AABB::combine(
61-
// TODO: no unwrap?
62-
hitables[1].aabb().unwrap(),
63-
hitables[2].aabb().unwrap(),
64-
),
61+
left: Box::new(rl),
62+
right: Box::new(rr),
63+
aabb,
6564
}));
6665
} else {
6766
// Otherwise, recurse

0 commit comments

Comments
 (0)