primase_core/
irradiance_volume.rs1use crate::{
3 ColorSpace, Component, Entity, Geometry, JsonError, TextureLayers, TextureShape, Transform, World,
4};
5use serde::Serialize;
6use serde_json::Value;
7
8#[derive(Clone, Debug, PartialEq, Serialize)]
15pub struct IrradianceVolume {
16 pub coefficients: [Entity; 4],
17 pub basis: [[f32; 3]; 3],
19 pub sampling_from_region: Transform,
21 pub normal_offset: bool,
23 pub half_extents: [f32; 3],
25 pub fade_distance: f32,
26 pub diffuse_scale: f32,
27 pub priority: u8,
28}
29impl Component for IrradianceVolume {
30 const NAME: &'static str = "core.irradiance_volume";
31 const GEOMETRY: Geometry = Geometry::None;
32 fn references(&self, out: &mut Vec<Entity>) {
33 out.extend(self.coefficients);
34 }
35 fn to_json(&self) -> Result<Option<Value>, JsonError> {
36 crate::json::to_value(self).map(Some)
37 }
38 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
39 let transform = w
40 .get::<Transform>(e)
41 .ok_or("irradiance volume has no transform")?;
42 if crate::Linear::of(transform).singular() || crate::Linear::of(&self.sampling_from_region).singular()
43 {
44 return Err("irradiance volume has a singular transform".into());
45 }
46 if !self.basis.iter().flatten().all(|x| x.is_finite()) {
47 return Err("irradiance basis is not finite".into());
48 }
49 if crate::Linear::new(self.basis).singular() {
50 return Err("irradiance coefficient basis is singular".into());
51 }
52 if !self
53 .sampling_from_region
54 .cols
55 .iter()
56 .flatten()
57 .all(|x| x.is_finite())
58 {
59 return Err("irradiance sampling transform is not finite".into());
60 }
61 if !self.half_extents.iter().all(|v| v.is_finite() && *v > 0.) {
62 return Err("irradiance half-extents must be finite and positive".into());
63 }
64 if !self.fade_distance.is_finite()
65 || self.fade_distance < 0.
66 || !self.diffuse_scale.is_finite()
67 || self.diffuse_scale < 0.
68 {
69 return Err("irradiance fade or diffuse scale is invalid".into());
70 }
71 let mut dimensions = None;
72 for texture in self.coefficients {
73 let t = w
74 .get::<TextureLayers>(texture)
75 .ok_or("irradiance coefficient is not a layered texture")?;
76 let TextureShape::Volume { depth } = t.shape else {
77 return Err("irradiance coefficient is not 3D".into());
78 };
79 if t.color_space != ColorSpace::Linear {
80 return Err("irradiance coefficient is not linear".into());
81 }
82 if !matches!(
83 t.format,
84 crate::TexFormat::Bc7
85 | crate::TexFormat::Rgba8Unorm
86 | crate::TexFormat::Rgba16Float
87 | crate::TexFormat::Rgba32Float
88 | crate::TexFormat::Rgba16Unorm
89 ) {
90 return Err("irradiance coefficient must store four RGBA channels".into());
91 }
92 let size = (t.width, t.height, depth, t.format);
93 if dimensions.is_some_and(|other| other != size) {
94 return Err("irradiance coefficient layouts differ".into());
95 }
96 dimensions = Some(size);
97 }
98 Ok(())
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 #[test]
106 fn coefficient_references_require_matching_linear_volumes() {
107 let mut w = World::new();
108 let coefficients = std::array::from_fn(|_| {
109 let e = w.spawn();
110 w.insert(
111 e,
112 TextureLayers {
113 shape: TextureShape::Volume { depth: 2 },
114 width: 1,
115 height: 1,
116 format: crate::TexFormat::Rgba8Unorm,
117 levels: vec![vec![vec![0; 4]; 2]],
118 color_space: ColorSpace::Linear,
119 role: crate::TexRole::Other,
120 normal: None,
121 },
122 );
123 e
124 });
125 let e = w.spawn();
126 w.insert(e, Transform::IDENTITY);
127 let volume = IrradianceVolume {
128 coefficients,
129 basis: [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
130 sampling_from_region: Transform::IDENTITY,
131 normal_offset: true,
132 half_extents: [1.; 3],
133 fade_distance: 3.,
134 diffuse_scale: 1.,
135 priority: 20,
136 };
137 assert_eq!(volume.verify(e, &w), Ok(()));
138 w.insert(e, volume.clone());
139 assert!(coefficients.into_iter().all(|t| w.closure([e]).contains(&t)));
140 w.get_mut::<TextureLayers>(coefficients[2])
141 .expect("texture")
142 .color_space = ColorSpace::Srgb;
143 assert_eq!(
144 volume.verify(e, &w),
145 Err("irradiance coefficient is not linear".into())
146 );
147 w.get_mut::<TextureLayers>(coefficients[2])
148 .expect("texture")
149 .color_space = ColorSpace::Linear;
150 w.get_mut::<TextureLayers>(coefficients[2])
151 .expect("texture")
152 .shape = TextureShape::Array { layers: 2 };
153 assert_eq!(
154 volume.verify(e, &w),
155 Err("irradiance coefficient is not 3D".into())
156 );
157 }
158}