oxihuman_export/
light_probe_export.rs1#![allow(dead_code)]
4
5use std::f32::consts::PI;
6
7#[allow(dead_code)]
9pub enum ProbeType {
10 Irradiance,
11 Reflection,
12 Combined,
13}
14
15#[allow(dead_code)]
16pub struct LightProbe {
17 pub name: String,
18 pub position: [f32; 3],
19 pub probe_type: ProbeType,
20 pub radius: f32,
21 pub intensity: f32,
22 pub sh_coefficients: Vec<[f32; 3]>, }
24
25#[allow(dead_code)]
26pub struct LightProbeExport {
27 pub probes: Vec<LightProbe>,
28 pub grid_size: [u32; 3],
29}
30
31#[allow(dead_code)]
32pub fn new_light_probe_export() -> LightProbeExport {
33 LightProbeExport {
34 probes: vec![],
35 grid_size: [1, 1, 1],
36 }
37}
38
39#[allow(dead_code)]
40pub fn add_probe(export: &mut LightProbeExport, probe: LightProbe) {
41 export.probes.push(probe);
42}
43
44#[allow(dead_code)]
45pub fn probe_count(export: &LightProbeExport) -> usize {
46 export.probes.len()
47}
48
49#[allow(dead_code)]
50pub fn default_irradiance_probe(name: &str, position: [f32; 3]) -> LightProbe {
51 LightProbe {
52 name: name.to_string(),
53 position,
54 probe_type: ProbeType::Irradiance,
55 radius: 5.0,
56 intensity: 1.0,
57 sh_coefficients: vec![[0.0; 3]; 9],
58 }
59}
60
61#[allow(dead_code)]
63pub fn eval_sh_l1(sh: &[[f32; 3]], dir: [f32; 3]) -> [f32; 3] {
64 if sh.is_empty() {
65 return [0.0; 3];
66 }
67 let l0 = sh[0];
68 let mut result = [l0[0] * 0.282095, l0[1] * 0.282095, l0[2] * 0.282095];
69 if sh.len() >= 4 {
70 let coeff = 0.488603;
71 for c in 0..3 {
72 result[c] += sh[1][c] * coeff * dir[1];
73 result[c] += sh[2][c] * coeff * dir[2];
74 result[c] += sh[3][c] * coeff * dir[0];
75 }
76 }
77 result
78}
79
80#[allow(dead_code)]
81pub fn probe_to_json(probe: &LightProbe) -> String {
82 let ptype = match probe.probe_type {
83 ProbeType::Irradiance => "irradiance",
84 ProbeType::Reflection => "reflection",
85 ProbeType::Combined => "combined",
86 };
87 format!(
88 "{{\"name\":\"{}\",\"type\":\"{}\",\"radius\":{},\"intensity\":{}}}",
89 probe.name, ptype, probe.radius, probe.intensity
90 )
91}
92
93#[allow(dead_code)]
94pub fn light_probe_export_to_json(export: &LightProbeExport) -> String {
95 format!("{{\"probe_count\":{}}}", export.probes.len())
96}
97
98#[allow(dead_code)]
99pub fn validate_probe(probe: &LightProbe) -> bool {
100 !probe.name.is_empty()
101 && probe.radius > 0.0
102 && probe.intensity >= 0.0
103 && (probe.sh_coefficients.is_empty() || probe.sh_coefficients.len() == 9)
104}
105
106#[allow(dead_code)]
107pub fn find_probe<'a>(export: &'a LightProbeExport, name: &str) -> Option<&'a LightProbe> {
108 export.probes.iter().find(|p| p.name == name)
109}
110
111#[allow(dead_code)]
113pub fn probe_grid(origin: [f32; 3], grid: [u32; 3], spacing: f32) -> LightProbeExport {
114 let mut e = new_light_probe_export();
115 e.grid_size = grid;
116 for xi in 0..grid[0] {
117 for zi in 0..grid[2] {
118 let pos = [
119 origin[0] + xi as f32 * spacing,
120 origin[1],
121 origin[2] + zi as f32 * spacing,
122 ];
123 let name = format!("probe_{xi}_{zi}");
124 e.probes.push(default_irradiance_probe(&name, pos));
125 }
126 }
127 e
128}
129
130#[allow(dead_code)]
132pub fn total_coverage_area(export: &LightProbeExport) -> f32 {
133 export.probes.iter().map(|p| PI * p.radius * p.radius).sum()
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_add_probe() {
142 let mut e = new_light_probe_export();
143 add_probe(&mut e, default_irradiance_probe("p1", [0.0; 3]));
144 assert_eq!(probe_count(&e), 1);
145 }
146
147 #[test]
148 fn test_validate_probe() {
149 let p = default_irradiance_probe("test", [0.0; 3]);
150 assert!(validate_probe(&p));
151 }
152
153 #[test]
154 fn test_find_probe_found() {
155 let mut e = new_light_probe_export();
156 add_probe(&mut e, default_irradiance_probe("main", [0.0; 3]));
157 assert!(find_probe(&e, "main").is_some());
158 }
159
160 #[test]
161 fn test_find_probe_missing() {
162 let e = new_light_probe_export();
163 assert!(find_probe(&e, "none").is_none());
164 }
165
166 #[test]
167 fn test_probe_grid_count() {
168 let e = probe_grid([0.0; 3], [3, 1, 3], 2.0);
169 assert_eq!(probe_count(&e), 9);
170 }
171
172 #[test]
173 fn test_total_coverage_positive() {
174 let mut e = new_light_probe_export();
175 add_probe(&mut e, default_irradiance_probe("p1", [0.0; 3]));
176 assert!(total_coverage_area(&e) > 0.0);
177 }
178
179 #[test]
180 fn test_eval_sh_l1_constant() {
181 let sh = vec![[1.0f32, 1.0, 1.0]; 9];
182 let c = eval_sh_l1(&sh, [0.0, 1.0, 0.0]);
183 assert!(c[0] > 0.0);
184 }
185
186 #[test]
187 fn test_eval_sh_empty() {
188 let c = eval_sh_l1(&[], [0.0, 1.0, 0.0]);
189 assert_eq!(c, [0.0; 3]);
190 }
191
192 #[test]
193 fn test_to_json() {
194 let p = default_irradiance_probe("test", [0.0; 3]);
195 let j = probe_to_json(&p);
196 assert!(j.contains("irradiance"));
197 }
198
199 #[test]
200 fn test_export_to_json() {
201 let mut e = new_light_probe_export();
202 add_probe(&mut e, default_irradiance_probe("p", [0.0; 3]));
203 let j = light_probe_export_to_json(&e);
204 assert!(j.contains("probe_count"));
205 }
206}