#![allow(dead_code)]
use std::f32::consts::PI;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct IrrSh9 {
pub coeff: [[f32; 3]; 9],
}
impl Default for IrrSh9 {
fn default() -> Self {
Self {
coeff: [[0.0; 3]; 9],
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct IrrProbe {
pub position: [f32; 3],
pub radius: f32,
pub sh: IrrSh9,
pub weight: f32,
}
impl IrrProbe {
#[allow(dead_code)]
pub fn new(position: [f32; 3], radius: f32) -> Self {
Self {
position,
radius,
sh: IrrSh9::default(),
weight: 1.0,
}
}
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct IrrProbeSet {
probes: Vec<IrrProbe>,
}
impl IrrProbeSet {
#[allow(dead_code)]
pub fn add(&mut self, probe: IrrProbe) {
self.probes.push(probe);
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.probes.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.probes.is_empty()
}
}
#[allow(dead_code)]
pub fn sh_l0(sh: &IrrSh9) -> [f32; 3] {
let scale = 2.0 * (PI).sqrt();
[
sh.coeff[0][0] * scale,
sh.coeff[0][1] * scale,
sh.coeff[0][2] * scale,
]
}
#[allow(dead_code)]
pub fn nearest_probe(set: &IrrProbeSet, point: [f32; 3]) -> Option<usize> {
if set.is_empty() {
return None;
}
let mut best = 0;
let mut best_dist2 = f32::MAX;
for (i, p) in set.probes.iter().enumerate() {
let dx = p.position[0] - point[0];
let dy = p.position[1] - point[1];
let dz = p.position[2] - point[2];
let d2 = dx * dx + dy * dy + dz * dz;
if d2 < best_dist2 {
best_dist2 = d2;
best = i;
}
}
Some(best)
}
#[allow(dead_code)]
pub fn blend_sh(a: &IrrSh9, b: &IrrSh9, t: f32) -> IrrSh9 {
let t = t.clamp(0.0, 1.0);
let mut out = IrrSh9::default();
for i in 0..9 {
for c in 0..3 {
out.coeff[i][c] = a.coeff[i][c] + (b.coeff[i][c] - a.coeff[i][c]) * t;
}
}
out
}
#[allow(dead_code)]
pub fn scale_sh(sh: &mut IrrSh9, s: f32) {
for coeff in sh.coeff.iter_mut() {
for c in coeff.iter_mut() {
*c *= s;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_sh_zero() {
let sh = IrrSh9::default();
assert_eq!(sh.coeff[0], [0.0, 0.0, 0.0]);
}
#[test]
fn sh_l0_scales_by_2_sqrt_pi() {
let mut sh = IrrSh9::default();
sh.coeff[0] = [1.0, 1.0, 1.0];
let l0 = sh_l0(&sh);
let scale = 2.0 * PI.sqrt();
assert!((l0[0] - scale).abs() < 1e-5);
}
#[test]
fn nearest_probe_empty_returns_none() {
let set = IrrProbeSet::default();
assert!(nearest_probe(&set, [0.0, 0.0, 0.0]).is_none());
}
#[test]
fn nearest_probe_single() {
let mut set = IrrProbeSet::default();
set.add(IrrProbe::new([0.0, 0.0, 0.0], 1.0));
assert_eq!(nearest_probe(&set, [0.5, 0.0, 0.0]), Some(0));
}
#[test]
fn nearest_probe_picks_closer() {
let mut set = IrrProbeSet::default();
set.add(IrrProbe::new([0.0, 0.0, 0.0], 1.0));
set.add(IrrProbe::new([10.0, 0.0, 0.0], 1.0));
assert_eq!(nearest_probe(&set, [1.0, 0.0, 0.0]), Some(0));
}
#[test]
fn blend_sh_midpoint() {
let a = IrrSh9::default();
let mut b = IrrSh9::default();
b.coeff[0] = [2.0, 2.0, 2.0];
let mid = blend_sh(&a, &b, 0.5);
assert!((mid.coeff[0][0] - 1.0).abs() < 1e-5);
}
#[test]
fn scale_sh_doubles() {
let mut sh = IrrSh9::default();
sh.coeff[0] = [1.0, 1.0, 1.0];
scale_sh(&mut sh, 2.0);
assert!((sh.coeff[0][0] - 2.0).abs() < 1e-5);
}
#[test]
fn probe_set_len() {
let mut set = IrrProbeSet::default();
assert!(set.is_empty());
set.add(IrrProbe::new([0.0; 3], 1.0));
assert_eq!(set.len(), 1);
}
#[test]
fn probe_new_default_weight() {
let p = IrrProbe::new([1.0, 2.0, 3.0], 5.0);
assert_eq!(p.weight, 1.0);
}
}