1use serde::Serialize;
6
7use super::{LocalShadow, LocalShadows};
8use crate::Entity;
9use crate::render_graph::{Region, TargetId};
10
11#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
16pub struct ShadowFace {
17 pub forward: [f32; 3],
18 pub right: [f32; 3],
19 pub up: [f32; 3],
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
25#[serde(rename_all = "snake_case")]
26pub enum ShadowDepthTerm {
27 FarOverDepthRange,
29 NegatedNearFarOverDepthRange,
31 Constant(f32),
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
38#[serde(rename_all = "snake_case")]
39pub enum ShadowDepth {
40 Reversed,
42 Standard,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize)]
50pub struct LocalShadowAtlas {
51 pub target: TargetId,
53 pub size: [u32; 2],
55 pub region: u32,
57 pub covered: u32,
60 pub regions: Vec<[u32; 2]>,
62 pub faces: Vec<ShadowFace>,
64 pub near_over_range: f32,
66 pub depth: ShadowDepth,
67 pub projection: Vec<ShadowDepthTerm>,
69 pub caster_state: iridia_interface::RenderState,
72 pub why: String,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq)]
79pub struct ShadowedLight {
80 pub light: Entity,
81 pub centre: [f32; 3],
82 pub range: f32,
83}
84
85#[derive(Clone, Debug, PartialEq, Serialize)]
88pub struct ShadowFaceView {
89 pub light: Entity,
90 pub face: u8,
92 pub region: Region,
93 pub view: [[f32; 4]; 4],
95 pub projection: [[f32; 4]; 4],
97 pub near: f32,
98 pub far: f32,
99}
100
101#[derive(Clone, Debug, Default, PartialEq)]
105pub struct ShadowPlacement {
106 pub shadows: LocalShadows,
107 pub faces: Vec<ShadowFaceView>,
108}
109
110fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
111 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
112}
113
114fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
115 [
116 a[1] * b[2] - a[2] * b[1],
117 a[2] * b[0] - a[0] * b[2],
118 a[0] * b[1] - a[1] * b[0],
119 ]
120}
121
122impl LocalShadowAtlas {
123 pub fn capacity(&self) -> usize {
125 self.regions.len().checked_div(self.faces.len()).unwrap_or(0)
126 }
127
128 pub fn verify(&self) -> Result<(), String> {
132 let [width, height] = self.size;
133 if width == 0 || height == 0 || self.region == 0 {
134 return Err("a local shadow atlas with an empty target or region".into());
135 }
136 if self.covered == 0 || self.covered > self.region || !(self.region - self.covered).is_multiple_of(2)
137 {
138 return Err(format!(
139 "a local shadow atlas covering {} of a region's {} texels: not a centred span inside it",
140 self.covered, self.region
141 ));
142 }
143 if self.faces.is_empty() || self.faces.len() > usize::from(u8::MAX) {
144 return Err(format!(
145 "a local shadow atlas of {} faces a light",
146 self.faces.len()
147 ));
148 }
149 if self.regions.is_empty() {
150 return Err("a local shadow atlas with no regions".into());
151 }
152 for (i, [x, y]) in self.regions.iter().enumerate() {
153 if x.checked_add(self.region).is_none_or(|r| r > width)
154 || y.checked_add(self.region).is_none_or(|b| b > height)
155 {
156 return Err(format!(
157 "local shadow region {i} at ({x}, {y}) lies outside the {width} x {height} atlas"
158 ));
159 }
160 for (j, [u, v]) in self.regions[..i].iter().enumerate() {
161 if x.abs_diff(*u) < self.region && y.abs_diff(*v) < self.region {
162 return Err(format!("local shadow regions {j} and {i} overlap"));
163 }
164 }
165 }
166 for (i, f) in self.faces.iter().enumerate() {
167 let unit = [f.forward, f.right, f.up]
168 .iter()
169 .all(|v| v.iter().all(|x| x.is_finite()) && (dot(*v, *v) - 1.0).abs() <= 1e-5);
170 let square = dot(f.forward, f.right).abs() <= 1e-5
171 && dot(f.forward, f.up).abs() <= 1e-5
172 && dot(f.right, f.up).abs() <= 1e-5;
173 let handed = cross(f.right, f.up)
174 .iter()
175 .zip(f.forward)
176 .all(|(a, b)| (a - b).abs() <= 1e-5);
177 if !(unit && square && handed) {
178 return Err(format!(
179 "local shadow face {i} is not a right-handed set of unit vectors at right angles"
180 ));
181 }
182 }
183 if !(self.near_over_range > 0.0 && self.near_over_range < 1.0) {
184 return Err(format!(
185 "a local shadow near plane of {} times the range",
186 self.near_over_range
187 ));
188 }
189 if self.projection.is_empty()
190 || self
191 .projection
192 .iter()
193 .any(|t| matches!(t, ShadowDepthTerm::Constant(v) if !v.is_finite()))
194 {
195 return Err("a local shadow record with no projection terms, or one not finite".into());
196 }
197 self.caster_state
198 .verify()
199 .map_err(|e| format!("the local shadow casters' state: {e}"))?;
200 if self.why.trim().is_empty() {
201 return Err("a local shadow atlas layout with no reason".into());
202 }
203 Ok(())
204 }
205
206 pub fn place(&self, chosen: &[ShadowedLight]) -> Result<ShadowPlacement, String> {
213 self.verify()?;
214 if chosen.len() > self.capacity() {
215 return Err(format!(
216 "{} lights chosen for a local shadow atlas that holds {} ({} regions, {} faces a light)",
217 chosen.len(),
218 self.capacity(),
219 self.regions.len(),
220 self.faces.len()
221 ));
222 }
223 let [width, height] = self.size.map(|s| s as f32);
224 let half = self.covered as f32 / 2.0;
225 let scale = self.covered as f32 / self.region as f32;
226 let mut placed = ShadowPlacement::default();
227 for (i, l) in chosen.iter().enumerate() {
228 if chosen[..i].iter().any(|o| o.light == l.light) {
229 return Err(format!(
230 "light {:?} chosen twice for the local shadow atlas",
231 l.light
232 ));
233 }
234 if !l.centre.iter().all(|x| x.is_finite()) || !(l.range > 0.0 && l.range.is_finite()) {
235 return Err(format!(
236 "light {:?}: a shadow centre {:?} and range {}",
237 l.light, l.centre, l.range
238 ));
239 }
240 let far = l.range;
241 let near = far * self.near_over_range;
242 let depth = far - near;
243 let mut faces = Vec::with_capacity(self.faces.len());
244 for (k, f) in self.faces.iter().enumerate() {
245 let [x, y] = self.regions[i * self.faces.len() + k];
246 let row = |axis: [f32; 3]| [axis[0], axis[1], axis[2], -dot(axis, l.centre)];
247 let (a, b) = match self.depth {
248 ShadowDepth::Reversed => (-near / depth, near * far / depth),
249 ShadowDepth::Standard => (far / depth, -near * far / depth),
250 };
251 placed.faces.push(ShadowFaceView {
252 light: l.light,
253 face: k as u8,
254 region: Region {
255 x,
256 y,
257 width: self.region,
258 height: self.region,
259 },
260 view: [row(f.right), row(f.up), row(f.forward), [0.0, 0.0, 0.0, 1.0]],
261 projection: [
262 [scale, 0.0, 0.0, 0.0],
263 [0.0, scale, 0.0, 0.0],
264 [0.0, 0.0, a, b],
265 [0.0, 0.0, 1.0, 0.0],
266 ],
267 near,
268 far,
269 });
270 let centre = self.region as f32 / 2.0;
272 faces.push([
273 half / width,
274 -half / height,
275 (x as f32 + centre) / width,
276 (y as f32 + centre) / height,
277 ]);
278 }
279 let projection = self
280 .projection
281 .iter()
282 .map(|t| match t {
283 ShadowDepthTerm::FarOverDepthRange => far / depth,
284 ShadowDepthTerm::NegatedNearFarOverDepthRange => -(near * far) / depth,
285 ShadowDepthTerm::Constant(v) => *v,
286 })
287 .collect();
288 placed.shadows.0.insert(
289 l.light,
290 LocalShadow {
291 projection,
292 centre: [l.centre[0], l.centre[1], l.centre[2], 1.0],
293 faces,
294 },
295 );
296 }
297 Ok(placed)
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn cube() -> Vec<ShadowFace> {
307 let face = |forward, right, up| ShadowFace { forward, right, up };
308 vec![
309 face([1., 0., 0.], [0., 0., -1.], [0., 1., 0.]),
310 face([-1., 0., 0.], [0., 0., 1.], [0., 1., 0.]),
311 face([0., 1., 0.], [1., 0., 0.], [0., 0., -1.]),
312 face([0., -1., 0.], [1., 0., 0.], [0., 0., 1.]),
313 face([0., 0., 1.], [1., 0., 0.], [0., 1., 0.]),
314 face([0., 0., -1.], [-1., 0., 0.], [0., 1., 0.]),
315 ]
316 }
317
318 fn atlas(regions: Vec<[u32; 2]>) -> LocalShadowAtlas {
319 LocalShadowAtlas {
320 target: 0,
321 size: [512, 512],
322 region: 64,
323 covered: 58,
324 regions,
325 faces: cube(),
326 near_over_range: 0.001,
327 depth: ShadowDepth::Reversed,
328 projection: vec![
329 ShadowDepthTerm::FarOverDepthRange,
330 ShadowDepthTerm::NegatedNearFarOverDepthRange,
331 ShadowDepthTerm::Constant(0.0),
332 ShadowDepthTerm::Constant(1.0),
333 ],
334 caster_state: iridia_interface::RenderState::default(),
335 why: "test".into(),
336 }
337 }
338
339 fn grid() -> Vec<[u32; 2]> {
340 (0..16).map(|i| [(i % 4) * 128, (i / 4) * 128]).collect()
341 }
342
343 fn mul(m: &[[f32; 4]; 4], v: [f32; 4]) -> [f32; 4] {
344 m.map(|r| r.iter().zip(v).map(|(a, b)| a * b).sum())
345 }
346
347 #[test]
351 fn a_face_camera_and_the_record_place_a_point_alike() {
352 let a = atlas(grid());
353 let light = crate::World::new().spawn();
354 let c = [1.0, 2.0, -3.0];
355 let p = a
356 .place(&[ShadowedLight {
357 light,
358 centre: c,
359 range: 8.0,
360 }])
361 .unwrap();
362 let shadow = &p.shadows.0[&light];
363 assert_eq!(shadow.centre, [1.0, 2.0, -3.0, 1.0]);
364 assert_eq!(p.faces.len(), 6);
365 for (f, face) in p.faces.iter().zip(&a.faces) {
366 for (off, want) in [
367 ([0.0, 0.0], [0.0, 0.0]),
368 ([1.0, 0.0], [1.0, 0.0]),
369 ([0.0, 1.0], [0.0, 1.0]),
370 ] {
371 let z = 4.0;
372 let w: Vec<f32> = (0..3)
373 .map(|k| c[k] + z * (face.forward[k] + off[0] * face.right[k] + off[1] * face.up[k]))
374 .collect();
375 let clip = mul(&f.projection, mul(&f.view, [w[0], w[1], w[2], 1.0]));
376 let ndc = [clip[0] / clip[3], clip[1] / clip[3], clip[2] / clip[3]];
377 let px = f.region.x as f32 + (ndc[0] + 1.0) / 2.0 * 64.0;
379 let py = f.region.y as f32 + (1.0 - ndc[1]) / 2.0 * 64.0;
380 let [sx, sy, ox, oy] = shadow.faces[f.face as usize];
382 let ux = (ox + sx * want[0]) * 512.0;
383 let uy = (oy + sy * want[1]) * 512.0;
384 assert!(
385 (px - ux).abs() < 1e-3 && (py - uy).abs() < 1e-3,
386 "{f:?} {px} {py} {ux} {uy}"
387 );
388 let record = 1.0 - shadow.projection[0] - shadow.projection[1] / z;
390 assert!((ndc[2] - record).abs() < 1e-6, "{} {record}", ndc[2]);
391 }
392 }
393 assert_eq!(shadow.projection[2..], [0.0, 1.0]);
394 }
395
396 #[test]
399 fn the_atlas_places_what_it_is_given_and_refuses_what_does_not_fit() {
400 let a = atlas(grid());
401 assert_eq!(a.capacity(), 2);
402 let mut w = crate::World::new();
403 let e: Vec<Entity> = (0..4).map(|_| w.spawn()).collect();
404 let l = |i: usize, range| ShadowedLight {
405 light: e[i],
406 centre: [0.0; 3],
407 range,
408 };
409 let p = a.place(&[l(2, 4.0), l(1, 8.0)]).unwrap();
410 assert_eq!(p.faces[6].light, e[1]);
411 assert_eq!([p.faces[6].region.x, p.faces[6].region.y], [256, 128]);
412 assert_eq!(p.faces[0].near, 4.0 * 0.001);
413 let err = a.place(&[l(1, 8.0), l(2, 8.0), l(3, 8.0)]).unwrap_err();
414 assert!(err.contains("holds 2"), "{err}");
415 assert!(a.place(&[l(1, 8.0), l(1, 8.0)]).unwrap_err().contains("twice"));
416 assert!(a.place(&[l(1, 0.0)]).is_err());
417 assert!(a.place(&[]).unwrap().faces.is_empty());
418 }
419
420 #[test]
423 fn a_broken_layout_is_refused() {
424 assert!(
425 atlas(vec![[0, 0], [32, 0]])
426 .verify()
427 .unwrap_err()
428 .contains("overlap")
429 );
430 assert!(atlas(vec![[480, 0]]).verify().unwrap_err().contains("outside"));
431 let mut odd = atlas(grid());
432 odd.covered = 57;
433 assert!(odd.verify().is_err());
434 let mut left = atlas(grid());
435 left.faces[0].right = [0.0, 0.0, 1.0];
436 assert!(left.verify().unwrap_err().contains("right-handed"));
437 }
438}