concinnity_render/
area_light.rs1use crate::components::RectAreaLight;
16use crate::geometry::glass_quad::plane_basis;
17use crate::render_types::{AreaLightData, MAX_AREA_LIGHTS};
18use alloc::vec;
19use alloc::vec::Vec;
20
21pub(crate) fn assign_area_light_slots(rect_lights: &[RectAreaLight]) -> Vec<i32> {
24 if rect_lights.len() > MAX_AREA_LIGHTS {
25 tracing::warn!(
26 "GraphicsSystem: {} area lights declared; only {} are supported -- extras ignored",
27 rect_lights.len(),
28 MAX_AREA_LIGHTS
29 );
30 }
31 (0..rect_lights.len())
32 .map(|i| if i < MAX_AREA_LIGHTS { i as i32 } else { -1 })
33 .collect()
34}
35
36pub(crate) fn build_area_light_data(
38 rect_lights: &[RectAreaLight],
39 slots: &[i32],
40) -> Vec<AreaLightData> {
41 let mut out = vec![AreaLightData::ZERO; count_area_lights(slots)];
42 for (light, &slot) in rect_lights.iter().zip(slots) {
43 if slot >= 0 {
44 out[slot as usize] = area_light_data(light);
45 }
46 }
47 out
48}
49
50pub(crate) fn count_area_lights(slots: &[i32]) -> usize {
52 slots.iter().filter(|s| **s >= 0).count()
53}
54
55fn area_light_data(light: &RectAreaLight) -> AreaLightData {
59 let (tangent, bitangent) = plane_basis(light.normal);
60 let hw = light.half_size[0];
61 let hh = light.half_size[1];
62 AreaLightData {
63 right: [tangent[0] * hw, tangent[1] * hw, tangent[2] * hw],
64 two_sided: u32::from(light.two_sided),
65 up: [bitangent[0] * hh, bitangent[1] * hh, bitangent[2] * hh],
66 _pad: 0.0,
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use concinnity_core::math::vec3::{dot, length};
74
75 fn rect(normal: [f32; 3], half_size: [f32; 2]) -> RectAreaLight {
76 RectAreaLight {
77 normal,
78 half_size,
79 ..RectAreaLight::default()
80 }
81 }
82
83 #[test]
84 fn slots_are_handed_out_in_declaration_order() {
85 let lights = vec![rect([0.0, 0.0, 1.0], [1.0, 1.0]); 3];
86 assert_eq!(assign_area_light_slots(&lights), vec![0, 1, 2]);
87 }
88
89 #[test]
90 fn slots_past_the_cap_are_dropped() {
91 let lights = vec![rect([0.0, 0.0, 1.0], [1.0, 1.0]); MAX_AREA_LIGHTS + 2];
92 let slots = assign_area_light_slots(&lights);
93 assert_eq!(count_area_lights(&slots), MAX_AREA_LIGHTS);
94 assert!(slots[MAX_AREA_LIGHTS..].iter().all(|s| *s == -1));
95 }
96
97 #[test]
100 fn edge_vectors_are_scaled_by_the_half_extents() {
101 let d = area_light_data(&rect([0.0, 0.0, 1.0], [3.0, 0.5]));
102 assert!((length(d.right) - 3.0).abs() < 1e-5);
103 assert!((length(d.up) - 0.5).abs() < 1e-5);
104 }
105
106 #[test]
109 fn the_edge_frame_stays_orthogonal_for_any_normal() {
110 for n in [
111 [0.0, 0.0, 1.0],
112 [0.0, -1.0, 0.0],
113 [0.0, 1.0, 0.0],
114 [0.577, 0.577, 0.577],
115 [-0.3, 0.9, 0.31],
116 ] {
117 let len = length(n);
118 let unit = [n[0] / len, n[1] / len, n[2] / len];
119 let d = area_light_data(&rect(unit, [2.0, 2.0]));
120 assert!(
121 dot(d.right, d.up).abs() < 1e-4,
122 "edges perpendicular: {n:?}"
123 );
124 assert!(dot(d.right, unit).abs() < 1e-4, "right in plane: {n:?}");
125 assert!(dot(d.up, unit).abs() < 1e-4, "up in plane: {n:?}");
126 assert!(d.right.iter().chain(&d.up).all(|v| v.is_finite()));
127 }
128 }
129
130 #[test]
131 fn two_sided_flag_is_carried() {
132 let mut l = rect([0.0, 0.0, 1.0], [1.0, 1.0]);
133 assert_eq!(area_light_data(&l).two_sided, 0);
134 l.two_sided = true;
135 assert_eq!(area_light_data(&l).two_sided, 1);
136 }
137
138 #[test]
139 fn data_is_indexed_by_slot() {
140 let lights = vec![
141 rect([0.0, 0.0, 1.0], [5.0, 1.0]),
142 rect([0.0, 0.0, 1.0], [1.0, 7.0]),
143 ];
144 let slots = assign_area_light_slots(&lights);
145 let data = build_area_light_data(&lights, &slots);
146 assert_eq!(data.len(), 2);
147 assert!((length(data[0].right) - 5.0).abs() < 1e-5);
148 assert!((length(data[1].up) - 7.0).abs() < 1e-5);
149 }
150}