#![allow(dead_code)]
use std::f32::consts::{FRAC_1_PI, PI};
pub const SH_COEFF_COUNT: usize = 9;
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct EnvDiffuseProbe {
pub sh_r: [f32; SH_COEFF_COUNT],
pub sh_g: [f32; SH_COEFF_COUNT],
pub sh_b: [f32; SH_COEFF_COUNT],
pub intensity: f32,
pub enabled: bool,
}
impl Default for EnvDiffuseProbe {
fn default() -> Self {
Self {
sh_r: [0.0; SH_COEFF_COUNT],
sh_g: [0.0; SH_COEFF_COUNT],
sh_b: [0.0; SH_COEFF_COUNT],
intensity: 1.0,
enabled: true,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct EnvDiffuseConfig {
pub max_probes: usize,
pub blend_radius: f32,
}
impl Default for EnvDiffuseConfig {
fn default() -> Self {
Self {
max_probes: 8,
blend_radius: 10.0,
}
}
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct EnvDiffuse {
pub config: EnvDiffuseConfig,
pub probes: Vec<EnvDiffuseProbe>,
}
#[allow(dead_code)]
pub fn new_env_diffuse(cfg: EnvDiffuseConfig) -> EnvDiffuse {
EnvDiffuse {
config: cfg,
probes: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_probe(e: &mut EnvDiffuse, probe: EnvDiffuseProbe) -> Option<usize> {
if e.probes.len() >= e.config.max_probes {
return None;
}
let idx = e.probes.len();
e.probes.push(probe);
Some(idx)
}
#[allow(dead_code)]
pub fn probe_count_env(e: &EnvDiffuse) -> usize {
e.probes.len()
}
#[allow(dead_code)]
pub fn sample_sh_irradiance(probe: &EnvDiffuseProbe, normal: [f32; 3]) -> [f32; 3] {
let (nx, ny, nz) = (normal[0], normal[1], normal[2]);
let y0 = 0.282_094_8; let y1 = 0.488_602_5 * ny; let y2 = 0.488_602_5 * nz; let y3 = 0.488_602_5 * nx; let sh = [y0, y1, y2, y3, 0.0, 0.0, 0.0, 0.0, 0.0];
let mut r = 0.0f32;
let mut g = 0.0f32;
let mut b = 0.0f32;
#[allow(clippy::needless_range_loop)]
for i in 0..SH_COEFF_COUNT {
r += probe.sh_r[i] * sh[i];
g += probe.sh_g[i] * sh[i];
b += probe.sh_b[i] * sh[i];
}
[
r * probe.intensity,
g * probe.intensity,
b * probe.intensity,
]
}
#[allow(dead_code)]
pub fn ambient_color(probe: &EnvDiffuseProbe) -> [f32; 3] {
let scale = FRAC_1_PI * probe.intensity;
[
probe.sh_r[0] * scale,
probe.sh_g[0] * scale,
probe.sh_b[0] * scale,
]
}
#[allow(dead_code)]
pub fn hemisphere_pdf() -> f32 {
1.0 / (2.0 * PI)
}
#[allow(dead_code)]
pub fn blend_probes_env(a: &EnvDiffuseProbe, b: &EnvDiffuseProbe, t: f32) -> EnvDiffuseProbe {
let t = t.clamp(0.0, 1.0);
let mut result = EnvDiffuseProbe::default();
#[allow(clippy::needless_range_loop)]
for i in 0..SH_COEFF_COUNT {
result.sh_r[i] = a.sh_r[i] + (b.sh_r[i] - a.sh_r[i]) * t;
result.sh_g[i] = a.sh_g[i] + (b.sh_g[i] - a.sh_g[i]) * t;
result.sh_b[i] = a.sh_b[i] + (b.sh_b[i] - a.sh_b[i]) * t;
}
result.intensity = a.intensity + (b.intensity - a.intensity) * t;
result
}
#[allow(dead_code)]
pub fn env_diffuse_to_json(e: &EnvDiffuse) -> String {
format!(
r#"{{"probe_count":{},"max_probes":{}}}"#,
e.probes.len(),
e.config.max_probes
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_system_empty() {
let e = new_env_diffuse(EnvDiffuseConfig::default());
assert_eq!(probe_count_env(&e), 0);
}
#[test]
fn add_probe_ok() {
let mut e = new_env_diffuse(EnvDiffuseConfig::default());
let r = add_probe(&mut e, EnvDiffuseProbe::default());
assert!(r.is_some());
}
#[test]
fn add_probe_capacity_limit() {
let mut e = new_env_diffuse(EnvDiffuseConfig {
max_probes: 1,
..Default::default()
});
add_probe(&mut e, EnvDiffuseProbe::default());
let r = add_probe(&mut e, EnvDiffuseProbe::default());
assert!(r.is_none());
}
#[test]
fn sample_sh_neutral_probe() {
let probe = EnvDiffuseProbe::default();
let color = sample_sh_irradiance(&probe, [0.0, 1.0, 0.0]);
assert_eq!(color, [0.0, 0.0, 0.0]);
}
#[test]
fn ambient_color_uses_frac1pi() {
let mut probe = EnvDiffuseProbe::default();
probe.sh_r[0] = PI;
let c = ambient_color(&probe);
assert!((c[0] - 1.0).abs() < 1e-4);
}
#[test]
fn hemisphere_pdf_correct() {
let pdf = hemisphere_pdf();
assert!((pdf - 1.0 / (2.0 * PI)).abs() < 1e-6);
}
#[test]
fn blend_probes_at_zero() {
let a = EnvDiffuseProbe {
intensity: 0.5,
..Default::default()
};
let b = EnvDiffuseProbe {
intensity: 1.0,
..Default::default()
};
let m = blend_probes_env(&a, &b, 0.0);
assert!((m.intensity - 0.5).abs() < 1e-6);
}
#[test]
fn blend_probes_at_one() {
let a = EnvDiffuseProbe {
intensity: 0.0,
..Default::default()
};
let b = EnvDiffuseProbe {
intensity: 1.0,
..Default::default()
};
let m = blend_probes_env(&a, &b, 1.0);
assert!((m.intensity - 1.0).abs() < 1e-6);
}
#[test]
fn json_contains_probe_count() {
let e = new_env_diffuse(EnvDiffuseConfig::default());
assert!(env_diffuse_to_json(&e).contains("probe_count"));
}
#[test]
fn probes_slice_not_empty() {
let mut e = new_env_diffuse(EnvDiffuseConfig::default());
add_probe(&mut e, EnvDiffuseProbe::default());
assert!(!e.probes.is_empty());
}
}