use std::error::Error;
use brodo::error::TreeError;
use brodo::grid::Grid;
use brodo::topology::{Location, TopologyEditor};
use brodo::tree::{Coord, TileLevel};
type BoxError = Box<dyn Error + Send + Sync>;
const BAND: f32 = 3.0;
#[derive(Clone, Copy)]
struct Sphere {
c: [f32; 3],
r: f32,
}
const FIRST: Sphere = Sphere { c: [0.0, 0.0, 0.0], r: 20.0 };
const SECOND: Sphere = Sphere { c: [16.0, 4.0, 0.0], r: 14.0 };
const LARGE: Sphere = Sphere { c: [64.0, 64.0, 64.0], r: 120.0 };
const REGIONS: std::ops::Range<i32> = -4..6;
const LARGE_REGIONS: std::ops::Range<i32> = -8..24;
fn distance(s: Sphere, v: Coord) -> f32 {
let d = [0, 1, 2].map(|k| v[k] as f32 - s.c[k]);
(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt() - s.r
}
fn clamped(d: f32) -> f32 {
d.clamp(-BAND, BAND)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Class {
Inside,
Band,
Outside,
}
fn classify(s: Sphere, o: Coord) -> Class {
let (mut near, mut far) = (0.0, 0.0);
for (c, start) in s.c.iter().zip(o) {
let (lo, hi) = (start as f32, (start + 7) as f32);
let n = c.clamp(lo, hi) - c;
let f = (lo - c).abs().max((hi - c).abs());
near += n * n;
far += f * f;
}
if far.sqrt() - s.r <= -BAND {
Class::Inside
} else if near.sqrt() - s.r >= BAND {
Class::Outside
} else {
Class::Band
}
}
fn regions_in(r: std::ops::Range<i32>) -> impl Iterator<Item = Coord> {
r.clone().flat_map(move |z| {
let r = r.clone();
r.clone().flat_map(move |y| r.clone().map(move |x| [x * 8, y * 8, z * 8]))
})
}
fn regions() -> impl Iterator<Item = Coord> {
regions_in(REGIONS)
}
#[derive(Clone, Copy)]
enum Edit {
Split(Coord),
Activate(Coord),
AddTile(Coord),
}
fn sphere_edits(s: Sphere, now: impl Fn(Coord) -> Location) -> Vec<Edit> {
sphere_edits_in(s, REGIONS, now)
}
fn sphere_edits_in(
s: Sphere,
range: std::ops::Range<i32>,
now: impl Fn(Coord) -> Location,
) -> Vec<Edit> {
regions_in(range)
.filter_map(|o| match (classify(s, o), now(o)) {
(Class::Band, Location::Tile(..)) => Some(Edit::Split(o)),
(Class::Band, Location::Background) => Some(Edit::Activate(o)),
(Class::Inside, Location::Background) => Some(Edit::AddTile(o)),
(Class::Band | Class::Inside, Location::Leaf(_))
| (Class::Inside, Location::Tile(..))
| (Class::Outside, _) => None,
})
.collect()
}
fn apply(e: &mut TopologyEditor<'_>, edits: &[Edit]) -> Result<(), TreeError> {
for edit in edits {
match *edit {
Edit::Split(o) => e.split_tile(o)?,
Edit::Activate(o) => {
e.activate(o)?;
}
Edit::AddTile(o) => e.add_tile(TileLevel::Lower, o)?,
}
}
Ok(())
}
fn union(g: &mut Grid<f32>, s: Sphere) -> Result<(), BoxError> {
g.par_update_leaves(|t, n, out| -> Result<(), BoxError> {
for (voxel, v) in t.leaf_voxels(n)?.zip(out.iter_mut()) {
*v = v.min(clamped(distance(s, voxel)));
}
Ok(())
})?;
for n in g.topology().tile_numbers() {
g.set_tile(n, -BAND)?;
}
Ok(())
}
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap, reason = "below 8")]
fn voxel(o: Coord, i: usize) -> Coord {
let l = [i % 8, i / 8 % 8, i / 64].map(|c| c as i32);
[o[0] + l[0], o[1] + l[1], o[2] + l[2]]
}
fn build() -> Result<Grid<f32>, BoxError> {
let mut g = Grid::new(BAND);
for s in [FIRST, SECOND] {
let edits = sphere_edits(s, |o| g.topology().locate(o));
g.edit(|e| apply(e, &edits))?;
union(&mut g, s)?;
}
Ok(g)
}
fn prune_leaves(g: &mut Grid<f32>) -> Result<usize, BoxError> {
let (t, f) = (g.topology(), g.field());
let mut uniform = Vec::new();
for n in t.leaf_numbers() {
let v = f.leaf(t, n)?;
if v.iter().all(|x| x.to_bits() == v[0].to_bits()) {
uniform.push(t.leaf_origin(n)?);
}
}
g.edit(|e| uniform.iter().try_for_each(|o| e.merge(TileLevel::Lower, *o)))?;
Ok(uniform.len())
}
fn build_large() -> Result<(Grid<f32>, usize), BoxError> {
let mut g = Grid::new(BAND);
let edits = sphere_edits_in(LARGE, LARGE_REGIONS, |o| g.topology().locate(o));
g.edit(|e| apply(e, &edits))?;
union(&mut g, LARGE)?;
let t = g.topology();
let wholly_inside = |o: Coord| {
(0..4096).all(|i| {
let r = [o[0] + i % 16 * 8, o[1] + i / 16 % 16 * 8, o[2] + i / 256 * 8];
matches!(t.locate(r), Location::Tile(..))
})
};
let lowers: Vec<Coord> =
regions_in(-1..2).map(|o| o.map(|c| c * 16)).filter(|o| wholly_inside(*o)).collect();
g.edit(|e| lowers.iter().try_for_each(|o| e.merge(TileLevel::Upper, *o)))?;
Ok((g, lowers.len()))
}
fn check_large(g: &Grid<f32>) -> Result<(), BoxError> {
for o in regions_in(LARGE_REGIONS) {
for i in (0..512).step_by(37) {
let v = voxel(o, i);
let (want, got) = (clamped(distance(LARGE, v)), g.value_at(v)?);
if got.to_bits() != want.to_bits() {
return Err(format!("{v:?}: {got} instead of {want}").into());
}
}
}
Ok(())
}
fn check(g: &Grid<f32>) -> Result<(), BoxError> {
check_values(g)?;
let split = regions()
.filter(|o| classify(FIRST, *o) == Class::Inside)
.filter(|o| matches!(g.topology().locate(*o), Location::Leaf(_)))
.count();
if split == 0 {
return Err("no tile of the first sphere was split".into());
}
Ok(())
}
fn check_values(g: &Grid<f32>) -> Result<(), BoxError> {
for o in regions() {
for i in 0..512 {
let v = voxel(o, i);
let want = clamped(distance(FIRST, v).min(distance(SECOND, v)));
let got = g.value_at(v)?;
if got.to_bits() != want.to_bits() {
return Err(format!("{v:?}: {got} instead of {want}").into());
}
}
}
Ok(())
}
fn run() -> Result<[usize; 2], BoxError> {
let mut g = build()?;
check(&g)?;
let leaves = prune_leaves(&mut g)?;
check_values(&g)?;
let (large, lowers) = build_large()?;
check_large(&large)?;
Ok([leaves, lowers])
}
fn main() -> Result<(), BoxError> {
run().map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_distances_before_and_after_pruning() {
let [leaves, lowers] = run().unwrap();
assert!(leaves > 0, "some leaves of the union merged");
assert!(lowers > 0, "some lower nodes of the large sphere merged");
}
}