use crate::bvh::Bvh;
use crate::reduce::SimplifyResult;
use crate::Mesh;
use glam::Vec3;
use rayon::prelude::*;
pub(crate) struct Parts {
part_tris: Vec<Vec<u32>>,
samples: Vec<Vec<(Vec3, Vec3)>>,
normals: Vec<Vec<Vec3>>,
boxes: Vec<(Vec3, Vec3)>,
bvhs: Vec<Bvh>,
all: Bvh,
tri_part: Vec<u32>,
occluder: Vec<bool>,
reach: f32,
pub removed: Vec<bool>,
heap: std::collections::BinaryHeap<std::cmp::Reverse<(Key, usize)>>,
exact_at: Vec<Option<usize>>,
removals: usize,
}
impl Parts {
pub fn analyze(input: &Mesh, locked: Option<&[bool]>, transparent: &[u32]) -> Option<Parts> {
let (tri_part, np) = parts_of(input);
if np < 2 {
return None;
}
let mut vpart = vec![usize::MAX; input.vertex_count()];
let mut ids: Vec<Vec<u32>> = vec![Vec::new(); np];
let mut samples: Vec<Vec<(Vec3, Vec3)>> = vec![Vec::new(); np];
let mut normals: Vec<Vec<Vec3>> = vec![Vec::new(); np];
let mut part_tris: Vec<Vec<[Vec3; 3]>> = vec![Vec::new(); np];
let mut pinned = vec![false; np];
for t in 0..input.triangle_count() {
let k = tri_part[t];
let tri = input.triangle_positions(t);
let n = (tri[1] - tri[0]).cross(tri[2] - tri[0]).normalize_or_zero();
for (i, v) in input.triangle(t).into_iter().enumerate() {
if vpart[v as usize] == usize::MAX {
vpart[v as usize] = k;
samples[k].push((tri[i], n));
if locked.is_some_and(|l| l.get(v as usize) == Some(&true)) {
pinned[k] = true;
}
}
}
samples[k].push(((tri[0] + tri[1] + tri[2]) / 3.0, n));
part_tris[k].push(tri);
normals[k].push(n);
ids[k].push(t as u32);
}
for s in &mut samples {
let step = s.len().div_ceil(256).max(1);
if step > 1 {
*s = s.iter().step_by(step).copied().collect();
}
}
let boxes = part_tris
.iter()
.map(|ts| {
ts.iter().flatten().fold(
(Vec3::splat(f32::INFINITY), Vec3::splat(f32::NEG_INFINITY)),
|(lo, hi), &p| (lo.min(p), hi.max(p)),
)
})
.collect();
let bvhs = part_tris.into_iter().map(Bvh::build).collect();
let all = Bvh::build(
(0..input.triangle_count())
.map(|t| input.triangle_positions(t))
.collect(),
);
let mut parts = Parts {
part_tris: ids,
samples,
normals,
boxes,
bvhs,
all,
tri_part: tri_part.iter().map(|&k| k as u32).collect(),
occluder: input
.material_ids
.iter()
.map(|m| !transparent.contains(m))
.collect(),
reach: input.bounds().extent().length() * 2.0 + 1.0,
removed: vec![false; np],
heap: Default::default(),
exact_at: vec![None; np],
removals: 0,
};
let coarse: Vec<f32> = {
use rayon::prelude::*;
let p = &parts;
(0..np)
.into_par_iter()
.map(|k| p.error_of(k, true))
.collect()
};
parts.heap = (0..np)
.filter(|&k| !pinned[k])
.map(|k| std::cmp::Reverse((Key(coarse[k]), k)))
.collect();
Some(parts)
}
fn error_of(&self, k: usize, coarse: bool) -> f32 {
self.distance_of(k, coarse)
}
fn sample_visible(&self, k: usize, p: Vec3, n: Vec3) -> bool {
let eps = self.reach * 1e-6;
vis_dirs().iter().any(|d| {
d.dot(n) > 0.2
&& !self
.all
.occluded_where(p + n * eps, *d, eps, self.reach, |t| {
let j = self.tri_part[t as usize] as usize;
j != k && !self.removed[j] && self.occluder[t as usize]
})
})
}
fn distance_of(&self, k: usize, coarse: bool) -> f32 {
let np = self.bvhs.len();
let box_gap = |a: (Vec3, Vec3), b: (Vec3, Vec3)| -> f32 {
(a.0 - b.1).max(b.0 - a.1).max(Vec3::ZERO).length()
};
let point_gap =
|p: Vec3, b: (Vec3, Vec3)| -> f32 { (b.0 - p).max(p - b.1).max(Vec3::ZERO).length() };
let mut others: Vec<(f32, usize)> = (0..np)
.filter(|&j| j != k && !self.removed[j] && !self.bvhs[j].is_empty())
.map(|j| (box_gap(self.boxes[k], self.boxes[j]), j))
.collect();
others.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
let size = (self.boxes[k].1 - self.boxes[k].0).length();
let mut worst = 0.0f32;
let step = if coarse { 8 } else { 1 };
for &(p, n) in self.samples[k].iter().step_by(step) {
if !self.sample_visible(k, p, n) {
continue;
}
let mut any = self.reach;
for &(gap, j) in &others {
if gap >= any {
break;
}
if point_gap(p, self.boxes[j]) >= any {
continue;
}
if let Some(c) = self.bvhs[j].closest_point(p, any) {
any = any.min(c.distance);
}
}
let mut best = (any + size).min(self.reach);
for &(gap, j) in &others {
if gap >= best {
break;
}
if point_gap(p, self.boxes[j]) >= best {
continue;
}
let nj = &self.normals[j];
if let Some(c) =
self.bvhs[j].closest_point_where(p, best, |t| nj[t as usize].dot(n) > 0.5)
{
best = best.min(c.distance);
}
}
worst = worst.max(best);
if worst >= self.reach {
break;
}
}
worst
}
pub fn triangles(&self, k: usize) -> &[u32] {
&self.part_tris[k]
}
pub fn take(
&mut self,
threshold: f32,
max_error: Option<f32>,
live: impl Fn(&[u32]) -> usize,
alive: usize,
floor: usize,
) -> Option<(usize, f32)> {
let mut passed = Vec::new();
let mut taken = None;
while let Some(std::cmp::Reverse((Key(e), k))) = self.heap.pop() {
if e > threshold || max_error.is_some_and(|m| e > m) {
self.heap.push(std::cmp::Reverse((Key(e), k)));
break;
}
let n = live(&self.part_tris[k]);
if n == 0 {
continue; }
let now = if self.exact_at[k] == Some(self.removals) {
e
} else {
self.error_of(k, false)
};
self.exact_at[k] = Some(self.removals);
if now != e {
self.heap.push(std::cmp::Reverse((Key(now), k)));
continue;
}
if 2 * n >= alive || alive - n < floor {
passed.push(std::cmp::Reverse((Key(e), k)));
continue;
}
self.removed[k] = true;
self.removals += 1;
taken = Some((k, e));
break;
}
self.heap.extend(passed);
taken
}
}
fn vis_dirs() -> &'static [Vec3; 64] {
static DIRS: std::sync::OnceLock<[Vec3; 64]> = std::sync::OnceLock::new();
DIRS.get_or_init(|| {
let golden = std::f32::consts::PI * (3.0 - 5f32.sqrt());
std::array::from_fn(|i| {
let z = 1.0 - (i as f32 + 0.5) * 2.0 / 64.0;
let r = (1.0 - z * z).max(0.0).sqrt();
let a = golden * i as f32;
Vec3::new(r * a.cos(), r * a.sin(), z).normalize() + Vec3::new(1e-3, 2e-3, 3e-3)
})
.map(|d: Vec3| d.normalize())
})
}
#[derive(Clone, Copy)]
struct Key(f32);
impl PartialEq for Key {
fn eq(&self, o: &Self) -> bool {
self.0.total_cmp(&o.0).is_eq()
}
}
impl Eq for Key {}
impl PartialOrd for Key {
fn partial_cmp(&self, o: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(o))
}
}
impl Ord for Key {
fn cmp(&self, o: &Self) -> std::cmp::Ordering {
self.0.total_cmp(&o.0)
}
}
pub(crate) fn parts_of(m: &Mesh) -> (Vec<usize>, usize) {
let mut ids: std::collections::HashMap<[u32; 3], u32> = Default::default();
let pos_id: Vec<u32> = m
.positions
.iter()
.map(|p| {
let n = ids.len() as u32;
*ids.entry(p.to_array().map(f32::to_bits)).or_insert(n)
})
.collect();
let mut parent: Vec<u32> = (0..ids.len() as u32).collect();
fn find(parent: &mut [u32], x: u32) -> u32 {
let mut r = x;
while parent[r as usize] != r {
r = parent[r as usize];
}
let mut y = x;
while parent[y as usize] != r {
let z = parent[y as usize];
parent[y as usize] = r;
y = z;
}
r
}
let nt = m.triangle_count();
for t in 0..nt {
let [a, b, c] = m.triangle(t).map(|v| pos_id[v as usize]);
let (ra, rb) = (find(&mut parent, a), find(&mut parent, b));
parent[ra as usize] = rb;
let (rb, rc) = (find(&mut parent, b), find(&mut parent, c));
parent[rb as usize] = rc;
}
let roots: Vec<u32> = (0..nt)
.map(|t| find(&mut parent, pos_id[m.triangle(t)[0] as usize]))
.collect();
let mut ids: Vec<u32> = roots.clone();
ids.sort_unstable();
ids.dedup();
(
roots
.iter()
.map(|r| ids.binary_search(r).unwrap())
.collect(),
ids.len(),
)
}
pub(crate) fn remap_orphans(input: &Mesh, result: &mut SimplifyResult) {
let m = &result.mesh;
if m.triangle_count() == 0
|| !result
.vertex_remap
.iter()
.any(|&r| r as usize >= m.vertex_count())
{
return;
}
let bvh = Bvh::build(
(0..m.triangle_count())
.map(|t| m.triangle_positions(t))
.collect(),
);
let reach = m
.bounds()
.extent()
.length()
.max(input.bounds().extent().length())
* 4.0
+ 1.0;
result
.vertex_remap
.par_iter_mut()
.enumerate()
.with_min_len(256)
.for_each(|(v, r)| {
if (*r as usize) < m.vertex_count() {
return;
}
let p = input.positions[v];
if let Some(c) = bvh.closest_point(p, reach) {
let tri = m.triangle(c.triangle as usize);
*r = *tri
.iter()
.min_by(|&&a, &&b| {
(m.positions[a as usize] - p)
.length_squared()
.total_cmp(&(m.positions[b as usize] - p).length_squared())
})
.unwrap();
}
});
}