1use pfx_materials::{ContentLayer, ContentLook};
2
3use crate::bvh::Triangle;
4use crate::math::{cross, dot, normalize, sub};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct TriangleDetail {
8 pub vertices: [[f32; 3]; 3],
9 pub normals: [[f32; 3]; 3],
10 pub uvs: [[f32; 2]; 3],
11 pub material: u32,
12}
13
14impl TriangleDetail {
15 pub fn flat(triangle: Triangle) -> Self {
16 let [a, b, c] = triangle.vertices;
17 let normal = normalize(cross(sub(b, a), sub(c, a)));
18 Self {
19 vertices: triangle.vertices,
20 normals: [normal; 3],
21 uvs: [[0.0; 2]; 3],
22 material: triangle.material,
23 }
24 }
25
26 pub fn interpolate(self, barycentric: [f32; 3]) -> ([f32; 3], [f32; 2]) {
27 let normal = normalize(std::array::from_fn(|axis| {
28 (0..3)
29 .map(|corner| self.normals[corner][axis] * barycentric[corner])
30 .sum()
31 }));
32 let uv = std::array::from_fn(|axis| {
33 (0..3)
34 .map(|corner| self.uvs[corner][axis] * barycentric[corner])
35 .sum()
36 });
37 (normal, uv)
38 }
39
40 pub fn triangle(self) -> Triangle {
41 Triangle {
42 vertices: self.vertices,
43 material: self.material,
44 }
45 }
46}
47
48pub type Transform = [[f32; 4]; 4];
49
50#[derive(Clone, Debug, PartialEq)]
51pub struct Instance {
52 pub triangles: Vec<TriangleDetail>,
53 pub transform: Transform,
54 pub surface: InstanceSurface,
55 pub shadow_only: bool,
56 pub two_sided: bool,
57}
58
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
60pub enum ContentFace {
61 #[default]
62 Both,
63 Front,
64 Back,
65}
66
67impl ContentFace {
68 pub fn code(self) -> u32 {
69 match self {
70 Self::Both => 0,
71 Self::Front => 1,
72 Self::Back => 2,
73 }
74 }
75
76 pub fn shows(self, front: bool) -> bool {
77 match self {
78 Self::Both => true,
79 Self::Front => front,
80 Self::Back => !front,
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq)]
86pub struct InstanceSurface {
87 pub clip: [[f32; 4]; 2],
88 pub uv_offset: [f32; 2],
89 pub uv_scale: [f32; 2],
90 pub crop: [f32; 4],
91 pub face: ContentFace,
92 pub layer: ContentLayer,
93 pub cutout: bool,
94 pub casts_shadow: bool,
95 pub alpha_cutoff: f32,
96}
97
98impl Default for InstanceSurface {
99 fn default() -> Self {
100 Self {
101 clip: [[0.0; 4]; 2],
102 uv_offset: [0.0; 2],
103 uv_scale: [1.0; 2],
104 crop: [0.0, 0.0, 1.0, 1.0],
105 face: ContentFace::Both,
106 layer: ContentLayer::default(),
107 cutout: false,
108 casts_shadow: true,
109 alpha_cutoff: 0.5,
110 }
111 }
112}
113
114impl InstanceSurface {
115 pub fn valid(&self) -> bool {
116 self.clip
117 .iter()
118 .flatten()
119 .chain(self.uv_offset.iter())
120 .chain(self.uv_scale.iter())
121 .chain(self.crop.iter())
122 .chain([
123 &self.layer.ink_roughness,
124 &self.layer.emboss,
125 &self.layer.strength,
126 ])
127 .all(|value| value.is_finite())
128 && (0.0..=1.0).contains(&self.alpha_cutoff)
129 }
130
131 pub fn cuts(&self, point: [f32; 3]) -> bool {
132 self.clip.iter().any(|plane| {
133 plane[..3].iter().any(|&value| value != 0.0)
134 && plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] > plane[3]
135 })
136 }
137
138 pub fn content_uv(&self, uv: [f32; 2]) -> Option<[f32; 2]> {
139 pfx_materials::content_uv(uv, self.uv_offset, self.uv_scale, self.crop)
140 }
141}
142
143impl Instance {
144 pub fn transformed(&self) -> Result<Vec<TriangleDetail>, String> {
145 let m = self.transform;
146 if m.into_iter().flatten().any(|value| !value.is_finite()) {
147 return Err("instance transform is not finite".into());
148 }
149 if m[0][3] != 0.0 || m[1][3] != 0.0 || m[2][3] != 0.0 || m[3][3] != 1.0 {
150 return Err("instance transform is not affine".into());
151 }
152 let a = |row: usize, column: usize| m[column][row];
153 let cofactor = |row: usize, column: usize| {
154 let (r0, r1) = ((row + 1) % 3, (row + 2) % 3);
155 let (c0, c1) = ((column + 1) % 3, (column + 2) % 3);
156 a(r0, c0) * a(r1, c1) - a(r0, c1) * a(r1, c0)
157 };
158 let determinant: f32 = (0..3)
159 .map(|column| a(0, column) * cofactor(0, column))
160 .sum();
161 if determinant.abs() < 1e-12 {
162 return Err("instance transform is singular".into());
163 }
164 let normal_matrix: [[f32; 3]; 3] = std::array::from_fn(|row| {
165 std::array::from_fn(|column| cofactor(row, column) * determinant.signum())
166 });
167 let triangles = self
168 .triangles
169 .iter()
170 .map(|triangle| TriangleDetail {
171 vertices: triangle.vertices.map(|point| {
172 std::array::from_fn(|row| {
173 m[3][row]
174 + (0..3)
175 .map(|column| m[column][row] * point[column])
176 .sum::<f32>()
177 })
178 }),
179 normals: triangle
180 .normals
181 .map(|normal| normalize(normal_matrix.map(|row| dot(row, normal)))),
182 uvs: triangle.uvs,
183 material: triangle.material,
184 })
185 .collect();
186 Ok(triangles)
187 }
188}
189
190#[derive(Clone, Copy, Debug, PartialEq)]
191pub struct Lens {
192 pub shift: [f32; 2],
193 pub aperture: f32,
194 pub focus_distance: f32,
195}
196
197impl Default for Lens {
198 fn default() -> Self {
199 Self {
200 shift: [0.0; 2],
201 aperture: 0.0,
202 focus_distance: 1.0,
203 }
204 }
205}
206
207impl Lens {
208 pub fn valid(self) -> bool {
209 self.shift.into_iter().all(f32::is_finite)
210 && self.aperture.is_finite()
211 && self.aperture >= 0.0
212 && self.focus_distance.is_finite()
213 && self.focus_distance > 0.0
214 }
215}
216
217#[derive(Clone, Debug, PartialEq)]
218pub struct ContentImage {
219 pub width: u32,
220 pub height: u32,
221 pub texels: Vec<[f32; 4]>,
222}
223
224impl ContentImage {
225 pub fn valid(&self) -> bool {
226 self.width > 0
227 && self.height > 0
228 && (self.width as usize)
229 .checked_mul(self.height as usize)
230 .is_some_and(|count| self.texels.len() == count)
231 && self.texels.iter().flatten().all(|value| value.is_finite())
232 }
233}
234
235#[derive(Clone, Debug, PartialEq)]
236pub struct ContentText {
237 pub width: u32,
238 pub height: u32,
239 pub records: Vec<[f32; 4]>,
240}
241
242impl ContentText {
243 pub fn valid(&self) -> bool {
244 self.width > 0
245 && self.height > 0
246 && self.records.len() > 4
247 && self.records.iter().flatten().all(|value| value.is_finite())
248 }
249}
250
251#[derive(Clone, Debug, Default)]
252pub struct Detail {
253 pub triangles: Vec<TriangleDetail>,
254 pub instances: Vec<Instance>,
255 pub lens: Lens,
256 pub projection: crate::gpu::Projection,
257 pub sun_radius_deg: f32,
258 pub content: Vec<Option<ContentImage>>,
259 pub content_slots: Vec<Option<ContentImage>>,
260 pub text_slots: Vec<Option<ContentText>>,
261 pub leaf: Option<LeafShade>,
262 pub steam: Option<Steam>,
263 pub lights: Vec<crate::lights::LocalLight>,
264 pub content_look: ContentLook,
265 pub bounces: Bounces,
266 pub transmissive_shadows: bool,
267 pub clamp_indirect: f32,
268 pub filter_glossy: f32,
269 pub sampler: crate::sobol::Sampler,
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq)]
273pub struct Bounces {
274 pub total: u32,
275 pub diffuse: u32,
276 pub glossy: u32,
277 pub transmission: u32,
278}
279
280impl Bounces {
281 pub const REFERENCE: Bounces = Bounces {
282 total: 16,
283 diffuse: 4,
284 glossy: 8,
285 transmission: 12,
286 };
287 pub const BLENDER: Bounces = Bounces {
288 total: 12,
289 diffuse: 4,
290 glossy: 4,
291 transmission: 12,
292 };
293
294 pub fn deep(count: u32) -> Bounces {
295 Bounces {
296 total: count,
297 diffuse: count,
298 glossy: count,
299 transmission: count,
300 }
301 }
302
303 pub fn valid(self) -> bool {
304 self.total <= 64 && self.diffuse <= 64 && self.glossy <= 64 && self.transmission <= 64
305 }
306}
307
308impl Default for Bounces {
309 fn default() -> Self {
310 Bounces::REFERENCE
311 }
312}
313
314#[derive(Clone, Copy, Debug, PartialEq)]
315pub struct LeafShade {
316 pub center: [f32; 3],
317 pub strength: f32,
318 pub time: f32,
319}
320
321impl LeafShade {
322 pub fn valid(self) -> bool {
323 self.center.into_iter().all(f32::is_finite)
324 && self.strength.is_finite()
325 && (0.0..=1.0).contains(&self.strength)
326 && self.time.is_finite()
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq)]
331pub struct PlumeLook {
332 pub spread: [f32; 2],
333 pub drift: [[f32; 2]; 2],
334 pub sway: [[f32; 5]; 2],
335 pub fade: [f32; 3],
336 pub grain: [f32; 2],
337 pub lift: f32,
338 pub warp: [f32; 2],
339 pub churn: f32,
340 pub threshold: [f32; 2],
341}
342
343impl Default for PlumeLook {
344 fn default() -> Self {
345 Self {
346 spread: [0.7, 4.0],
347 drift: [[-0.9, -0.12], [0.0, 0.02]],
348 sway: [[17.0, 1.3, 0.012, 0.4, 6.0], [13.0, 0.9, 0.01, 0.4, 5.0]],
349 fade: [0.02, 0.07, 0.2],
350 grain: [70.0, 42.0],
351 lift: 0.05,
352 warp: [2.2, 0.2],
353 churn: 0.7,
354 threshold: [0.42, 0.9],
355 }
356 }
357}
358
359impl PlumeLook {
360 pub const FLOATS: usize = 27;
361 pub const NAMES: [&'static str; Self::FLOATS] = [
362 "STEAM_SPREAD_BASE",
363 "STEAM_SPREAD_GROWTH",
364 "STEAM_DRIFT_X_QUAD",
365 "STEAM_DRIFT_X_LIN",
366 "STEAM_DRIFT_Z_QUAD",
367 "STEAM_DRIFT_Z_LIN",
368 "STEAM_SWAY_X_WAVE",
369 "STEAM_SWAY_X_RATE",
370 "STEAM_SWAY_X_AMP",
371 "STEAM_SWAY_X_BASE",
372 "STEAM_SWAY_X_GROWTH",
373 "STEAM_SWAY_Z_WAVE",
374 "STEAM_SWAY_Z_RATE",
375 "STEAM_SWAY_Z_AMP",
376 "STEAM_SWAY_Z_BASE",
377 "STEAM_SWAY_Z_GROWTH",
378 "STEAM_FADE_IN",
379 "STEAM_FADE_OUT_START",
380 "STEAM_FADE_OUT_END",
381 "STEAM_GRAIN_H",
382 "STEAM_GRAIN_V",
383 "STEAM_LIFT_RATE",
384 "STEAM_WARP_AMP",
385 "STEAM_WARP_RATE",
386 "STEAM_CHURN",
387 "STEAM_THRESHOLD_LOW",
388 "STEAM_THRESHOLD_HIGH",
389 ];
390
391 pub fn floats(self) -> [f32; Self::FLOATS] {
392 let [sx, sz] = self.sway;
393 [
394 self.spread[0],
395 self.spread[1],
396 self.drift[0][0],
397 self.drift[0][1],
398 self.drift[1][0],
399 self.drift[1][1],
400 sx[0],
401 sx[1],
402 sx[2],
403 sx[3],
404 sx[4],
405 sz[0],
406 sz[1],
407 sz[2],
408 sz[3],
409 sz[4],
410 self.fade[0],
411 self.fade[1],
412 self.fade[2],
413 self.grain[0],
414 self.grain[1],
415 self.lift,
416 self.warp[0],
417 self.warp[1],
418 self.churn,
419 self.threshold[0],
420 self.threshold[1],
421 ]
422 }
423
424 pub fn valid(self) -> bool {
425 self.floats().into_iter().all(f32::is_finite)
426 && self.spread[0] > 0.0
427 && self.spread[0] + self.spread[1] * self.fade[2] > 0.0
428 && self.fade[0] > 0.0
429 && self.fade[1] < self.fade[2]
430 && self.fade[2] > 0.0
431 && self.threshold[0] < self.threshold[1]
432 }
433}
434
435#[derive(Clone, Copy, Debug, PartialEq)]
436pub struct Steam {
437 pub source: [f32; 3],
438 pub radius: f32,
439 pub density: f32,
440 pub ambient: f32,
441 pub anisotropy: f32,
442 pub box_lo: [f32; 3],
443 pub box_hi: [f32; 3],
444 pub time: f32,
445 pub look: PlumeLook,
446}
447
448impl Steam {
449 pub fn valid(self) -> bool {
450 self.source
451 .into_iter()
452 .chain(self.box_lo)
453 .chain(self.box_hi)
454 .chain([
455 self.radius,
456 self.density,
457 self.ambient,
458 self.anisotropy,
459 self.time,
460 ])
461 .all(f32::is_finite)
462 && self.radius > 0.0
463 && self.density >= 0.0
464 && self.ambient >= 0.0
465 && (-1.0..1.0).contains(&self.anisotropy)
466 && (0..3).all(|axis| self.box_lo[axis] < self.box_hi[axis])
467 && self.look.valid()
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn the_default_budget_is_the_reference_budget() {
477 assert_eq!(Detail::default().bounces, Bounces::REFERENCE);
478 assert_eq!(
479 Bounces::REFERENCE,
480 Bounces {
481 total: 16,
482 diffuse: 4,
483 glossy: 8,
484 transmission: 12
485 }
486 );
487 assert!(Bounces::deep(64).valid());
488 assert!(!Bounces::deep(65).valid());
489 assert!(
490 !Bounces {
491 glossy: 65,
492 ..Bounces::BLENDER
493 }
494 .valid()
495 );
496 }
497
498 #[test]
499 fn interpolates_normals_and_uvs() {
500 let triangle = TriangleDetail {
501 vertices: [[0.0; 3]; 3],
502 normals: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
503 uvs: [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
504 material: 0,
505 };
506 let (normal, uv) = triangle.interpolate([0.5, 0.25, 0.25]);
507 assert_eq!(uv, [0.25, 0.25]);
508 assert!((normal[0] - 0.8164966).abs() < 1e-6);
509 }
510
511 #[test]
512 fn transforms_instance_positions_and_normals() {
513 let triangle = TriangleDetail {
514 vertices: [[0.0; 3]; 3],
515 normals: [[1.0, 1.0, 0.0]; 3],
516 uvs: [[0.0; 2]; 3],
517 material: 0,
518 };
519 let instance = Instance {
520 triangles: vec![triangle],
521 surface: InstanceSurface::default(),
522 shadow_only: false,
523 two_sided: true,
524 transform: [
525 [2.0, 0.0, 0.0, 0.0],
526 [0.0, 1.0, 0.0, 0.0],
527 [0.0, 0.0, 1.0, 0.0],
528 [3.0, 4.0, 5.0, 1.0],
529 ],
530 };
531 let transformed = instance.transformed().unwrap();
532 assert_eq!(transformed[0].vertices[0], [3.0, 4.0, 5.0]);
533 assert!((transformed[0].normals[0][0] - 0.4472136).abs() < 1e-6);
534 }
535
536 #[test]
537 fn instance_surface_matches_live_clips_and_uvs() {
538 let surface = InstanceSurface {
539 clip: [[1.0, 0.0, 0.0, 0.2], [0.0, -1.0, 0.0, 0.1]],
540 uv_offset: [0.25, 0.0],
541 uv_scale: [0.5, 1.0],
542 crop: [0.25, 0.1, 0.75, 0.9],
543 ..InstanceSurface::default()
544 };
545 assert!(surface.valid());
546 assert!(
547 InstanceSurface {
548 alpha_cutoff: 0.0,
549 ..surface
550 }
551 .valid()
552 );
553 assert!(
554 InstanceSurface {
555 alpha_cutoff: 1.0,
556 ..surface
557 }
558 .valid()
559 );
560 assert!(
561 !InstanceSurface {
562 alpha_cutoff: 1.1,
563 ..surface
564 }
565 .valid()
566 );
567 assert!(
568 !InstanceSurface {
569 alpha_cutoff: f32::NAN,
570 ..surface
571 }
572 .valid()
573 );
574 assert!(!surface.cuts([0.1, 0.0, 0.0]));
575 assert!(surface.cuts([0.3, 0.0, 0.0]));
576 assert!(surface.cuts([0.0, -0.2, 0.0]));
577 assert_eq!(surface.content_uv([0.5, 0.5]), Some([0.5, 0.5]));
578 assert_eq!(surface.content_uv([0.5, 1.0]), None);
579 }
580}