use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpherePoint {
pub dir: [f64; 3],
pub weight: f64,
}
const KSET: [[usize; 2]; 30] = [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[0, 5],
[11, 10],
[11, 9],
[11, 8],
[11, 7],
[11, 6],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 1],
[6, 7],
[7, 8],
[8, 9],
[9, 10],
[10, 6],
[1, 6],
[6, 2],
[2, 7],
[7, 3],
[3, 8],
[8, 4],
[4, 9],
[9, 5],
[5, 10],
[10, 1],
];
const FSET: [[usize; 3]; 20] = [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 5],
[0, 5, 1],
[11, 10, 9],
[11, 9, 8],
[11, 8, 7],
[11, 7, 6],
[11, 6, 10],
[1, 2, 6],
[2, 3, 7],
[3, 4, 8],
[4, 5, 9],
[5, 1, 10],
[6, 7, 2],
[7, 8, 3],
[8, 9, 4],
[9, 10, 5],
[10, 6, 1],
];
#[allow(clippy::needless_range_loop)]
pub fn generate_sphere_tessellation(nppa: usize) -> Vec<SpherePoint> {
assert!(
nppa == 12 || nppa == 42 || nppa == 1082,
"nppa must be 12, 42, or 1082"
);
let mut dirvec = vec![[0.0f64; 4]; nppa];
dirvec[0][0] = -1.0;
dirvec[0][1] = 0.0;
dirvec[0][2] = 0.0;
let mut nd = 0;
let r = (0.8f64).sqrt();
let h = (0.2f64).sqrt();
for &i_sign in &[-1.0f64, 1.0f64] {
for j in 1..=5 {
nd += 1;
let beta = 1.0 + (j as f64) * 0.4 * PI + (i_sign + 1.0) * 0.1 * PI;
dirvec[nd][0] = i_sign * h;
dirvec[nd][1] = r * beta.cos();
dirvec[nd][2] = r * beta.sin();
}
}
dirvec[11][0] = 1.0;
dirvec[11][1] = 0.0;
dirvec[11][2] = 0.0;
let cphi = (1.0f64).cos();
let sphi = (1.0f64).sin();
for i in 0..12 {
let xx = dirvec[i][0];
let yy = dirvec[i][1];
dirvec[i][0] = cphi * xx + sphi * yy;
dirvec[i][1] = -sphi * xx + cphi * yy;
}
if nppa > 12 {
nd = 11;
let mut m2 = (nppa - 2) / 10;
let mut m = (m2 as f64).sqrt().round() as usize;
let mut k = 0;
if m2 != m * m {
k = 1;
m2 /= 3;
m = (m2 as f64).sqrt().round() as usize;
}
assert_eq!(
10 * (3usize.pow(k as u32)) * m * m + 2,
nppa,
"Invalid nppa configuration"
);
for i in 0..30 {
let na = KSET[i][0];
let nb = KSET[i][1];
for j in 1..m {
nd += 1;
for ix in 0..3 {
dirvec[nd][ix] =
dirvec[na][ix] * ((m - j) as f64) + dirvec[nb][ix] * (j as f64);
}
}
}
for i in 0..20 {
let na = FSET[i][0];
let nb = FSET[i][1];
let nc = FSET[i][2];
for j1 in 1..m {
for j2 in 1..(m - j1) {
nd += 1;
for ix in 0..3 {
dirvec[nd][ix] = dirvec[na][ix] * ((m - j1 - j2) as f64)
+ dirvec[nb][ix] * (j1 as f64)
+ dirvec[nc][ix] * (j2 as f64);
}
}
}
if k != 0 {
let t = 1.0 / 3.0;
for j1 in 0..m {
for j2 in 0..(m - j1) {
nd += 1;
for ix in 0..3 {
dirvec[nd][ix] = dirvec[na][ix] * ((m - j1 - j2) as f64 - 2.0 * t)
+ dirvec[nb][ix] * (j1 as f64 + t)
+ dirvec[nc][ix] * (j2 as f64 + t);
}
}
}
let t2 = 2.0 / 3.0;
if m >= 2 {
for j1 in 0..(m - 1) {
for j2 in 0..(m - j1 - 1) {
nd += 1;
for ix in 0..3 {
dirvec[nd][ix] = dirvec[na][ix] * ((m - j1 - j2) as f64 - 2.0 * t2)
+ dirvec[nb][ix] * (j1 as f64 + t2)
+ dirvec[nc][ix] * (j2 as f64 + t2);
}
}
}
}
}
}
assert_eq!(nd + 1, nppa, "Point count mismatch in dvfill");
}
let m_val = if nppa > 12 {
let m2 = (nppa - 2) / 10;
let mut m = (m2 as f64).sqrt().round() as usize;
if m2 != m * m {
m = ((m2 / 3) as f64).sqrt().round() as usize;
}
m as f64
} else {
1.0
};
let mut sumar = 0.0f64;
for i in 0..nppa {
let dist = (dirvec[i][0].powi(2) + dirvec[i][1].powi(2) + dirvec[i][2].powi(2)).sqrt();
let inv_dist = 1.0 / dist;
let dist2 = (m_val * inv_dist).powi(2);
dirvec[i][0] *= inv_dist;
dirvec[i][1] *= inv_dist;
dirvec[i][2] *= inv_dist;
let ar = if i < 12 { 5.0 } else { 6.0 * dist2 };
dirvec[i][3] = ar;
sumar += ar;
}
let norm_factor = 4.0 * PI / sumar;
let mut points = Vec::with_capacity(nppa);
for i in 0..nppa {
points.push(SpherePoint {
dir: [dirvec[i][0], dirvec[i][1], dirvec[i][2]],
weight: dirvec[i][3] * norm_factor,
});
}
points
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tessellation_unit_norms_and_surface_integrals() {
for &nppa in &[12, 42, 1082] {
let pts = generate_sphere_tessellation(nppa);
assert_eq!(pts.len(), nppa);
let mut sum_weight = 0.0;
for pt in &pts {
let norm = (pt.dir[0].powi(2) + pt.dir[1].powi(2) + pt.dir[2].powi(2)).sqrt();
assert!(
(norm - 1.0).abs() < 1e-12,
"Tessellation vector is not normalized: norm = {}",
norm
);
sum_weight += pt.weight;
}
let expected_4pi = 4.0 * PI;
assert!(
(sum_weight - expected_4pi).abs() < 1e-10,
"nppa = {}: total surface area weight {} differs from 4*pi ({})",
nppa,
sum_weight,
expected_4pi
);
}
}
}