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