use brodo::error::{FieldError, Rule};
use brodo::field::{Field, LEAF_VOXELS};
use brodo::hermite::{Crossing, Crossings, Hermite};
use brodo::meta::{GridMeta, SDF_KEY};
use brodo::parallel::{par_reduce, par_update};
use brodo::topology::{LeafNumber, Topology};
use brodo::tree::Coord;
use crate::bvh::Bvh;
use crate::convert::{Converted, Inside, Lattice};
use crate::distance::{Point, cross, has_area, sub, unit};
use crate::error::HermiteError;
use crate::intersect::crossing;
use crate::mesh::Mesh;
use crate::winding::Winding;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Produced {
pub set: Hermite,
pub meta: GridMeta,
pub unhit: u64,
}
fn after(
t: &Topology,
signs: &Field<u8>,
lattice: &Lattice,
axis: usize,
v: Coord,
) -> Result<(u8, Point), FieldError> {
let mut w = v;
if let Some(c) = v[axis].checked_add(1) {
w[axis] = c;
return Ok((signs.value_at(t, w)?, lattice.world(w)));
}
let mut p = v.map(f64::from);
p[axis] += 1.0;
Ok((0, lattice.at(p)))
}
fn normal(t: [Point; 3]) -> Point {
let [a, b, c] = t;
unit(cross(sub(b, a), sub(c, a))).unwrap_or([0.0; 3])
}
#[derive(Debug, Clone, Copy)]
struct Found {
fraction: f64,
triangle: u32,
hit: bool,
}
fn find(bvh: &Bvh, a: Point, b: Point, lower_inside: bool) -> Option<Found> {
let mut best: Option<(f64, u32)> = None;
for i in bvh.near_segment(a, b) {
let Some(t) = bvh.corners(i) else { continue };
let Some(s) = crossing(a, b, t) else { continue };
let better = best.is_none_or(|(b, _)| if lower_inside { s < b } else { s > b });
if better {
best = Some((s, i));
}
}
if let Some((fraction, triangle)) = best {
return Some(Found { fraction, triangle, hit: true });
}
let near = bvh.nearest_segment(a, b, has_area);
near.map(|(fraction, _, triangle)| Found { fraction, triangle, hit: false })
}
pub fn hermite(mesh: &Mesh, c: &Converted) -> Result<Produced, HermiteError> {
mesh.check_groups()?;
let lattice = c.lattice().ok_or(HermiteError::Transform)?;
let winding = Winding::new(mesh)?;
let bvh = winding.bvh();
let t = &c.topology;
let signs = signs(t, &lattice, bvh, &Inside::new(&winding, c.sign, c.accuracy))?;
let mut unhit = 0_u64;
let mut axes = Vec::with_capacity(3);
for axis in 0..3 {
let (crossings, missed) = crossings(mesh, t, &lattice, bvh, &signs, axis)?;
unhit += missed;
axes.push(crossings);
}
let crossings = gathered(t, &axes)?;
let set = Hermite::new(t.clone(), signs, crossings, mesh.group_names.clone()).map_err(|e| {
if let brodo::error::HermiteError::Violation { at: Some(at), rule: Rule::Owner, .. } = e {
HermiteError::SignOutsideBand(at)
} else {
HermiteError::Format(e)
}
})?;
let mut meta = c.meta.clone();
meta.name = "hermite".to_owned();
meta.extra.remove(SDF_KEY);
Ok(Produced { set, meta, unhit })
}
fn gathered(t: &Topology, axes: &[Vec<Placed>]) -> Result<Crossings, HermiteError> {
let mut b = Crossings::builder(t);
let mut rest: Vec<&[Placed]> = axes.iter().map(Vec::as_slice).collect();
for n in t.leaf_numbers() {
let mut lists: [Vec<(usize, Crossing)>; 3] = Default::default();
for (list, axis) in lists.iter_mut().zip(rest.iter_mut()) {
let here = axis.iter().take_while(|(m, _, _)| *m == n).count();
let (mine, after) = axis.split_at(here);
list.extend(mine.iter().map(|(_, i, c)| (*i, *c)));
*axis = after;
}
let [x, y, z] = &lists;
b.leaf(n, [x, y, z]).map_err(HermiteError::Format)?;
}
b.build().map_err(HermiteError::Format)
}
type Placed = (LeafNumber, usize, Crossing);
fn on_mesh(bvh: &Bvh, p: Point) -> Option<u32> {
bvh.nearest_where(p, has_area).filter(|(d, _)| *d == 0.0).map(|(_, i)| i)
}
fn signs(
t: &Topology,
lattice: &Lattice,
bvh: &Bvh,
inside: &Inside<'_>,
) -> Result<Field<u8>, HermiteError> {
let mut signs = Field::new(t, 0_u8);
par_update(t, &mut signs, |n, values| {
for (v, x) in voxels(t, n)?.zip(values) {
let p = lattice.world(v);
let d = bvh.nearest(p).map(|(d, _)| d);
*x = u8::from(inside.at_distance(p, d));
}
Ok::<(), FieldError>(())
})?;
for n in t.tile_numbers() {
signs.set_tile(t, n, 1)?;
}
Ok(signs)
}
fn crossings(
mesh: &Mesh,
t: &Topology,
lattice: &Lattice,
bvh: &Bvh,
signs: &Field<u8>,
axis: usize,
) -> Result<(Vec<Placed>, u64), HermiteError> {
let leaf = |n: LeafNumber, lower: &[u8; LEAF_VOXELS]| -> Result<Vec<_>, FieldError> {
let mut out = Vec::new();
for (i, (v, inside)) in voxels(t, n)?.zip(lower).enumerate() {
let (far, b) = after(t, signs, lattice, axis, v)?;
if far == *inside {
continue;
}
let a = lattice.world(v);
let lower_inside = *inside == 1;
let Some(mut f) = find(bvh, a, b, lower_inside) else { continue };
let (end, fraction) = if lower_inside { (a, 0.0) } else { (b, 1.0) };
if let Some(triangle) = on_mesh(bvh, end) {
f = Found { fraction, triangle, hit: true };
}
out.push((n, i, f));
}
Ok(out)
};
let mut found = par_reduce(t, signs, Ok(Vec::new()), leaf, |all, mine| {
let (mut all, mine) = (all?, mine?);
all.extend(mine);
Ok::<_, FieldError>(all)
})??;
found.sort_unstable_by_key(|(n, i, _)| (*n, *i));
let mut c = Vec::with_capacity(found.len());
let mut unhit = 0_u64;
for (n, i, f) in found {
let inside = signs.leaf(t, n)?.get(i).copied().unwrap_or(0);
let mut m = bvh.corners(f.triangle).map_or([0.0; 3], normal);
let inward = if inside == 1 { m[axis] < 0.0 } else { m[axis] > 0.0 };
if inward {
m = m.map(|x| -x);
}
unhit += u64::from(!f.hit);
#[expect(clippy::cast_possible_truncation, reason = "stored as f32 (ADR-0030)")]
let (fraction, normal) = (f.fraction as f32, m.map(|x| x as f32));
let group = mesh.group(usize::try_from(f.triangle).unwrap_or(usize::MAX));
c.push((n, i, Crossing { fraction, normal, group }));
}
Ok((c, unhit))
}
fn voxels(t: &Topology, n: LeafNumber) -> Result<impl Iterator<Item = Coord>, FieldError> {
t.leaf_voxels(n).map_err(|_| FieldError::UnknownLeaf(n.get()))
}