use rayon::prelude::*;
use super::Connectivity;
use crate::error::AnalysisError;
use crate::field::{Field, Value};
use crate::gather::{GATHER_EDGE, GATHER_VOXELS, gather};
use crate::manifest::Offset;
use crate::topology::{LeafNumber, Location, TileNumber, Topology};
use crate::tree::Coord;
const CELLS: [(u32, u8); 8] = [
(0, 0xff), (1, 0xaa), (1, 0xcc), (1, 0xf0), (2, 0x88), (2, 0xa0), (2, 0xc0), (3, 0x80), ];
const TWENTY_SIX: [i8; 256] = table(false);
const SIX: [i8; 256] = table(true);
const fn table(six: bool) -> [i8; 256] {
let mut out = [0_i8; 256];
let mut pattern = 0;
while pattern < 256 {
let mut sum = 0_i8;
let mut c = 0;
while c < CELLS.len() {
let (dim, around) = CELLS[c];
#[expect(clippy::cast_possible_truncation, reason = "pattern is below 256")]
let met = pattern as u8 & around;
let (counted, odd) =
if six { (met == around, (3 - dim) % 2 == 1) } else { (met != 0, dim % 2 == 1) };
if counted {
sum += if odd { -1 } else { 1 };
}
c += 1;
}
out[pattern] = sum;
pattern += 1;
}
out
}
pub fn euler<T: Value>(
t: &Topology,
f: &Field<T>,
meets: impl Fn(T) -> bool + Sync,
conn: Connectivity,
) -> Result<i64, AnalysisError> {
f.belongs_to(t)?;
if meets(f.background()) {
return Err(AnalysisError::Unbounded);
}
let table = match conn {
Connectivity::Six => &SIX,
Connectivity::TwentySix => &TWENTY_SIX,
};
let numbers: Vec<LeafNumber> = t.leaf_numbers().collect();
let leaves: Vec<Result<i64, AnalysisError>> =
numbers.par_iter().map(|n| leaf_vertices(t, f, *n, &meets, table)).collect();
let leaves = leaves.into_iter().collect::<Result<Vec<i64>, _>>()?;
Ok(leaves.iter().sum::<i64>() + tile_edges(t, f, &meets, table)?)
}
fn block_of(c: [i32; 3]) -> usize {
let step = |x: i32| usize::from(x >= 0) + usize::from(x > 7);
step(c[0]) + 3 * step(c[1]) + 9 * step(c[2])
}
fn leaf_vertices<T: Value>(
t: &Topology,
f: &Field<T>,
n: LeafNumber,
meets: &impl Fn(T) -> bool,
table: &[i8; 256],
) -> Result<i64, AnalysisError> {
let mut g = [f.background(); GATHER_VOXELS];
gather(t, f, n, &mut g)?;
let met: Vec<bool> = g.iter().map(|v| meets(*v)).collect();
let mut is_leaf = [false; 27];
is_leaf[block_of([0; 3])] = true;
for (d, loc) in Offset::all().zip(t.neighbours(n)?) {
let [x, y, z] = d.delta();
is_leaf[block_of([x * 8, y * 8, z * 8])] = matches!(loc, Location::Leaf(_));
}
let centre = block_of([0; 3]);
let mut sum = 0_i64;
for c in 0..=8_i32 {
for b in 0..=8_i32 {
for a in 0..=8_i32 {
let voxels = (0..8)
.map(|bit| [a - 1 + (bit & 1), b - 1 + (bit >> 1 & 1), c - 1 + (bit >> 2)]);
let first = voxels.clone().map(block_of).find(|k| is_leaf[*k]);
if first != Some(centre) {
continue;
}
let pattern = voxels.enumerate().fold(0_usize, |p, (bit, v)| {
let [x, y, z] = v.map(|q| usize::try_from(q + 1).unwrap_or(0));
p | usize::from(met[x + GATHER_EDGE * (y + GATHER_EDGE * z)]) << bit
});
sum += i64::from(table[pattern]);
}
}
}
Ok(sum)
}
fn voxel(v: [i64; 3]) -> Option<Coord> {
Some([i32::try_from(v[0]).ok()?, i32::try_from(v[1]).ok()?, i32::try_from(v[2]).ok()?])
}
fn tile_edges<T: Value>(
t: &Topology,
f: &Field<T>,
meets: &(impl Fn(T) -> bool + Sync),
table: &[i8; 256],
) -> Result<i64, AnalysisError> {
let numbers: Vec<TileNumber> = t.tile_numbers().collect();
let sums: Vec<Result<i64, AnalysisError>> =
numbers.par_iter().map(|n| tile_vertices(t, f, *n, meets, table)).collect();
sums.into_iter().sum()
}
fn on_edge(o: [i64; 3], edge: i64, p: [i64; 3]) -> bool {
(0..3).filter(|k| p[*k] == o[*k] || p[*k] == o[*k] + edge).count() >= 2
}
fn edge_vertices(o: [i64; 3], edge: i64) -> Vec<[i64; 3]> {
let mut out = Vec::new();
for axis in 0..3 {
let (u, w) = ((axis + 1) % 3, (axis + 2) % 3);
for du in [0, edge] {
for dw in [0, edge] {
for along in 1..edge {
let mut p = o;
p[axis] += along;
p[u] += du;
p[w] += dw;
out.push(p);
}
}
}
}
for corner in 0..8_usize {
let at = |k: usize| if corner >> k & 1 == 1 { edge } else { 0 };
out.push([o[0] + at(0), o[1] + at(1), o[2] + at(2)]);
}
out
}
fn tile_vertices<T: Value>(
t: &Topology,
f: &Field<T>,
n: TileNumber,
meets: &impl Fn(T) -> bool,
table: &[i8; 256],
) -> Result<i64, AnalysisError> {
if !meets(f.tile(t, n)?) {
return Ok(0);
}
let (level, origin) = t.tile(n)?;
let edge = i64::from(level.edge());
let o = origin.map(i64::from);
let mut sum = 0_i64;
'vertex: for p in edge_vertices(o, edge) {
let mut pattern = 0_usize;
let mut first = None;
for bit in 0..8_usize {
let step = |k: usize| i64::from(bit >> k & 1 == 1);
let v = [p[0] - 1 + step(0), p[1] - 1 + step(1), p[2] - 1 + step(2)];
let value = match voxel(v).map(|c| t.locate(c)) {
Some(Location::Leaf(_)) => continue 'vertex,
Some(Location::Tile(m, _)) => {
let value = f.tile(t, m)?;
if first.is_none() && meets(value) {
let (l, at) = t.tile(m)?;
if on_edge(at.map(i64::from), i64::from(l.edge()), p) {
first = Some(m);
}
}
value
}
Some(Location::Background) | None => f.background(),
};
pattern |= usize::from(meets(value)) << bit;
}
if first == Some(n) {
sum += i64::from(table[pattern]);
}
}
Ok(sum)
}