use crate::{
core::{math::factorial, vec::Vector},
prelude::Vec3f,
};
use num_traits::{NumOps, Zero};
use std::{
f32::consts::PI,
ops::{Add, Mul},
};
pub fn xyz2spherical(xyz: Vec3f) -> Vec3f {
let r = xyz.norm();
assert!(r > 0f32);
let u = xyz.normalize();
let theta = u[1].acos();
let phi = u[0].atan2(u[2]);
Vec3f::vec([r, theta, phi])
}
pub fn sh_legendre(l: i32, m: i32, x: f32) -> f32 {
assert!(m >= 0);
let mut pmm = 1f32;
if m > 0 {
let sqrtfactor = ((1f32 - x) * (1f32 + x)).sqrt();
let mut fact = 1f32;
for _ in 0..m {
pmm *= (-fact) * sqrtfactor;
fact += 2f32;
}
}
if l == m {
return pmm;
}
let mut pmm1 = x * (2f32 * m as f32 + 1f32) * pmm;
if l == m + 1 {
return pmm1;
}
let mut pll = 0f32;
for ll in m + 2..l + 1 {
pll = (x * (2 * ll - 1) as f32 * pmm1 - (ll + m - 1) as f32 * pmm) / (ll - m) as f32;
pmm = pmm1;
pmm1 = pll;
}
pll
}
pub fn sh_k(l: i32, m: i32) -> f32 {
let fac0 = (2f32 * l as f32 + 1f32) / (4f32 * PI);
let fac1 = factorial(l - m) / factorial(l + m);
let res = fac0 * fac1;
res.sqrt()
}
pub fn sh_eval(l: i32, m: i32, theta: f32, phi: f32) -> f32 {
if m == 0 {
return sh_k(l, m) * sh_legendre(l, m, theta.cos());
}
let sqrt2 = 2f32.sqrt();
if m > 0 {
return sqrt2 * sh_k(l, m) * (m as f32 * phi).cos() * sh_legendre(l, m, theta.cos());
}
let m = -m;
sqrt2 * sh_k(l, m) * (m as f32 * phi).sin() * sh_legendre(l, m, theta.cos())
}
#[derive(Clone, Debug)]
pub struct SHSample {
pub xyz: Vec3f,
pub coeff: Vec<f32>,
}
pub fn sh_samples(nsamples: usize, l: i32) -> Vec<SHSample> {
use crate::core::sampling::{radical_inverse, square2unitsphere};
use rayon::prelude::*;
assert!(l >= 0);
let mut samples = vec![
SHSample {
xyz: Vec3f::vec([0.; 3]),
coeff: Vec::new()
};
nsamples
];
let task = |isample: usize, sample: &mut SHSample| {
let rx: f32 = radical_inverse(isample, 2);
let ry: f32 = radical_inverse(isample, 3);
let xyz = square2unitsphere([rx, ry]);
let spherial = xyz2spherical(Vec3f::vec(xyz));
let theta = spherial[1];
let phi = spherial[2];
sample.xyz = Vec3f::vec(xyz);
for il in 0..l + 1 {
for im in -il..il + 1 {
let sh = sh_eval(il, im, theta, phi);
sample.coeff.push(sh);
}
}
};
samples
.par_iter_mut()
.enumerate()
.for_each(|(isample, sample)| task(isample, sample));
samples
}
pub fn sh_project_fn<F, T>(l: i32, nsamples: usize, f: F) -> Vec<T>
where
T: Mul<f32, Output = T> + NumOps + Zero + Send + Sync + Clone,
F: Fn(Vec3f) -> T + Sync,
{
use rayon::prelude::*;
let sh_samples = sh_samples(nsamples, l);
let zero = T::zero();
let mut coeffs = vec![zero.clone(); ((l + 1) * (l + 1)) as usize];
let ci_task = |ic: usize, coeff: &mut T| {
let mut acc = zero.clone();
for sh_sample in sh_samples.iter().take(nsamples) {
let coeff = sh_sample.coeff[ic];
let val = f(sh_sample.xyz);
acc = acc + val * coeff;
}
acc = acc * (4f32 * PI / nsamples as f32);
*coeff = acc;
};
coeffs
.par_iter_mut()
.enumerate()
.for_each(|(ic, coeff)| ci_task(ic, coeff));
coeffs
}
pub fn sh_project_one<T>(val: T, l: i32, dir: Vec3f) -> T
where
T: Mul<f32, Output = T> + NumOps + Zero + Clone,
{
let sph = xyz2spherical(dir);
let theta = sph[1];
let phi = sph[2];
let mut res = T::zero();
for il in 0..l + 1 {
for im in -il..il + 1 {
let sh = sh_eval(il, im, theta, phi);
res = res + val.clone() * sh;
}
}
res
}
pub fn sh_reconstruct_fn<T>(coeffs: &[T], l: i32) -> impl Fn(Vec3f) -> T
where
T: Add<f32, Output = T> + Mul<f32, Output = T> + NumOps + Zero + Clone,
{
move |dir: Vec3f| sh_reconstruct_one(coeffs, l, dir)
}
pub fn sh_reconstruct_one<T>(coeffs: &[T], l: i32, dir: Vec3f) -> T
where
T: Add<T, Output = T> + Mul<f32, Output = T> + Zero + Clone,
{
let sph = xyz2spherical(dir);
let theta = sph[1];
let phi = sph[2];
let mut res = T::zero();
for il in 0..l + 1 {
for im in -il..il + 1 {
let sh = sh_eval(il, im, theta, phi);
let ic = (il * (il + 1) + im) as usize;
let coeff = coeffs[ic].clone();
res = res + coeff * sh;
}
}
res
}