use crate::mesh::{Indices, Primitive, Topology};
use std::collections::BinaryHeap;
#[derive(Clone, Copy, Debug, Default)]
struct Quadric([f64; 10]);
impl Quadric {
fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Self {
Quadric([
a * a,
a * b,
a * c,
a * d,
b * b,
b * c,
b * d,
c * c,
c * d,
d * d,
])
}
fn add(&self, o: &Quadric) -> Quadric {
let mut q = self.0;
for (k, slot) in q.iter_mut().enumerate() {
*slot += o.0[k];
}
Quadric(q)
}
fn eval(&self, x: f64, y: f64, z: f64) -> f64 {
let q = &self.0;
q[0] * x * x
+ q[4] * y * y
+ q[7] * z * z
+ q[9]
+ 2.0 * (q[1] * x * y + q[2] * x * z + q[3] * x + q[5] * y * z + q[6] * y + q[8] * z)
}
fn optimal_position(&self) -> Option<[f64; 3]> {
let q = &self.0;
let (a00, a01, a02) = (q[0], q[1], q[2]);
let (a11, a12) = (q[4], q[5]);
let a22 = q[7];
let (b0, b1, b2) = (-q[3], -q[6], -q[8]);
let c00 = a11 * a22 - a12 * a12;
let c01 = a02 * a12 - a01 * a22;
let c02 = a01 * a12 - a02 * a11;
let det = a00 * c00 + a01 * c01 + a02 * c02;
if !det.is_finite() || det.abs() < 1e-12 {
return None;
}
let inv_det = 1.0 / det;
let c11 = a00 * a22 - a02 * a02;
let c12 = a02 * a01 - a00 * a12;
let c22 = a00 * a11 - a01 * a01;
let x = (c00 * b0 + c01 * b1 + c02 * b2) * inv_det;
let y = (c01 * b0 + c11 * b1 + c12 * b2) * inv_det;
let z = (c02 * b0 + c12 * b1 + c22 * b2) * inv_det;
if x.is_finite() && y.is_finite() && z.is_finite() {
Some([x, y, z])
} else {
None
}
}
}
#[derive(Clone, Copy, Debug)]
struct Candidate {
cost: f64,
u: u32,
v: u32,
vu: u32,
vv: u32,
}
impl PartialEq for Candidate {
fn eq(&self, o: &Self) -> bool {
self.cost == o.cost
}
}
impl Eq for Candidate {}
impl PartialOrd for Candidate {
fn partial_cmp(&self, o: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(o))
}
}
impl Ord for Candidate {
fn cmp(&self, o: &Self) -> std::cmp::Ordering {
o.cost
.partial_cmp(&self.cost)
.unwrap_or(std::cmp::Ordering::Less)
}
}
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn norm(a: [f64; 3]) -> f64 {
dot(a, a).sqrt()
}
fn face_normal(p0: [f64; 3], p1: [f64; 3], p2: [f64; 3]) -> Option<[f64; 3]> {
let n = cross(sub(p1, p0), sub(p2, p0));
let len = norm(n);
if len.is_finite() && len > 1e-20 {
Some([n[0] / len, n[1] / len, n[2] / len])
} else {
None
}
}
impl Primitive {
pub fn simplify_quadric(&self, target_triangles: usize) -> Primitive {
self.simplify_qem(target_triangles, f64::INFINITY)
}
pub fn simplify_quadric_error(&self, max_error: f64) -> Primitive {
let bound = if max_error.is_finite() && max_error >= 0.0 {
max_error
} else {
f64::INFINITY
};
self.simplify_qem(0, bound)
}
fn simplify_qem(&self, target_triangles: usize, max_error: f64) -> Primitive {
let empty_out = {
let mut o = self.to_triangle_list();
o.positions.clear();
o.normals = o.normals.as_ref().map(|_| Vec::new());
o.tangents = o.tangents.as_ref().map(|_| Vec::new());
for s in &mut o.uvs {
s.clear();
}
for s in &mut o.colors {
s.clear();
}
o.joints = o.joints.as_ref().map(|_| Vec::new());
o.weights = o.weights.as_ref().map(|_| Vec::new());
for t in &mut o.targets {
*t = t.map_buffers(|_| Vec::new());
}
o.indices = Some(Indices::U16(Vec::new()));
o
};
let sn = self.positions.len();
let raw_tris: Vec<[u32; 3]> = self
.triangle_indices()
.into_iter()
.filter(|&[a, b, c]| {
(a as usize) < sn
&& (b as usize) < sn
&& (c as usize) < sn
&& a != b
&& b != c
&& a != c
&& self.positions[a as usize].iter().all(|f| f.is_finite())
&& self.positions[b as usize].iter().all(|f| f.is_finite())
&& self.positions[c as usize].iter().all(|f| f.is_finite())
})
.collect();
if raw_tris.is_empty() {
return empty_out;
}
let clean = {
let mut c = self.to_triangle_list();
let mut flat: Vec<u32> = Vec::with_capacity(raw_tris.len() * 3);
for t in &raw_tris {
flat.extend_from_slice(t);
}
c.indices = Some(Indices::U32(flat));
c
};
let welded = clean.weld_vertices();
let faces0 = welded.triangle_indices();
let nverts = welded.positions.len();
if faces0.is_empty() || nverts == 0 {
return empty_out;
}
let mut state = QemState::new(&welded, faces0);
state.run(target_triangles, max_error);
state.into_primitive(&welded, empty_out)
}
}
struct QemState {
pos: Vec<[f64; 3]>,
quad: Vec<Quadric>,
faces: Vec<Option<[u32; 3]>>,
vfaces: Vec<Vec<u32>>,
alive: Vec<bool>,
ver: Vec<u32>,
boundary: Vec<bool>,
merge_log: Vec<(u32, u32, f64)>,
live_faces: usize,
}
impl QemState {
fn new(welded: &Primitive, faces: Vec<[u32; 3]>) -> Self {
let nverts = welded.positions.len();
let pos: Vec<[f64; 3]> = welded
.positions
.iter()
.map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
.collect();
let mut quad = vec![Quadric::default(); nverts];
let mut vfaces: Vec<Vec<u32>> = vec![Vec::new(); nverts];
for (fi, &[a, b, c]) in faces.iter().enumerate() {
let (pa, pb, pc) = (pos[a as usize], pos[b as usize], pos[c as usize]);
if let Some(n) = face_normal(pa, pb, pc) {
let d = -dot(n, pa);
let kp = Quadric::from_plane(n[0], n[1], n[2], d);
for &v in &[a, b, c] {
quad[v as usize] = quad[v as usize].add(&kp);
vfaces[v as usize].push(fi as u32);
}
} else {
for &v in &[a, b, c] {
vfaces[v as usize].push(fi as u32);
}
}
}
let mut edge_use: std::collections::HashMap<(u32, u32), u32> =
std::collections::HashMap::new();
for f in &faces {
let [a, b, c] = *f;
for (u, v) in [(a, b), (b, c), (c, a)] {
let k = if u < v { (u, v) } else { (v, u) };
*edge_use.entry(k).or_insert(0) += 1;
}
}
let mut boundary = vec![false; nverts];
let mut edge_face: std::collections::HashMap<(u32, u32), usize> =
std::collections::HashMap::new();
for (fi, f) in faces.iter().enumerate() {
let [a, b, c] = *f;
for (u, v) in [(a, b), (b, c), (c, a)] {
let k = if u < v { (u, v) } else { (v, u) };
edge_face.entry(k).or_insert(fi);
}
}
for (&(u, v), &cnt) in &edge_use {
if cnt == 1 {
boundary[u as usize] = true;
boundary[v as usize] = true;
if let Some(&fi) = edge_face.get(&(u, v)) {
let [fa, fb, fc] = faces[fi];
let n = face_normal(pos[fa as usize], pos[fb as usize], pos[fc as usize]);
if let Some(n) = n {
let e = sub(pos[v as usize], pos[u as usize]);
let mut cn = cross(e, n);
let len = norm(cn);
if len.is_finite() && len > 1e-20 {
cn = [cn[0] / len, cn[1] / len, cn[2] / len];
let d = -dot(cn, pos[u as usize]);
let w = dot(e, e);
let kp = Quadric::from_plane(
cn[0] * w.sqrt(),
cn[1] * w.sqrt(),
cn[2] * w.sqrt(),
d * w.sqrt(),
);
quad[u as usize] = quad[u as usize].add(&kp);
quad[v as usize] = quad[v as usize].add(&kp);
}
}
}
}
}
let live_faces = faces.len();
QemState {
pos,
quad,
faces: faces.into_iter().map(Some).collect(),
vfaces,
alive: vec![true; nverts],
ver: vec![0; nverts],
boundary,
merge_log: (0..nverts as u32).map(|i| (i, i, 0.0)).collect(),
live_faces,
}
}
fn incident(&self, v: u32) -> Vec<u32> {
self.vfaces[v as usize]
.iter()
.copied()
.filter(|&fi| {
self.faces[fi as usize]
.map(|t| t.contains(&v))
.unwrap_or(false)
})
.collect()
}
fn collapse_target(&self, u: u32, v: u32) -> ([f64; 3], f64, f64) {
let q = self.quad[u as usize].add(&self.quad[v as usize]);
let pu = self.pos[u as usize];
let pv = self.pos[v as usize];
let bu = self.boundary[u as usize];
let bv = self.boundary[v as usize];
let pos = if bu && bv {
self.best_on_edge(&q, pu, pv)
} else if bu {
pu
} else if bv {
pv
} else if let Some(p) = q.optimal_position() {
p
} else {
self.best_on_edge(&q, pu, pv)
};
let cost = q.eval(pos[0], pos[1], pos[2]).max(0.0);
let e = sub(pv, pu);
let denom = dot(e, e);
let t = if denom > 1e-30 {
(dot(sub(pos, pu), e) / denom).clamp(0.0, 1.0)
} else {
0.0
};
(pos, cost, t)
}
fn best_on_edge(&self, q: &Quadric, pu: [f64; 3], pv: [f64; 3]) -> [f64; 3] {
let mid = [
0.5 * (pu[0] + pv[0]),
0.5 * (pu[1] + pv[1]),
0.5 * (pu[2] + pv[2]),
];
let mut best = pu;
let mut bc = q.eval(pu[0], pu[1], pu[2]);
for cand in [mid, pv] {
let c = q.eval(cand[0], cand[1], cand[2]);
if c < bc {
bc = c;
best = cand;
}
}
best
}
fn would_flip(&self, u: u32, v: u32, np: [f64; 3]) -> bool {
for fi in self.incident(u) {
let [a, b, c] = self.faces[fi as usize].unwrap();
if [a, b, c].contains(&v) {
continue;
}
let old = [
self.pos[a as usize],
self.pos[b as usize],
self.pos[c as usize],
];
let mapped = |w: u32| if w == u { np } else { self.pos[w as usize] };
let new = [mapped(a), mapped(b), mapped(c)];
let no = face_normal(old[0], old[1], old[2]);
let nn = face_normal(new[0], new[1], new[2]);
match (no, nn) {
(Some(no), Some(nn)) if dot(no, nn) < 1e-3 => return true,
(Some(_), None) => return true,
_ => {}
}
}
false
}
fn make_candidate(&self, u: u32, v: u32) -> Option<Candidate> {
if !self.alive[u as usize] || !self.alive[v as usize] || u == v {
return None;
}
if self.boundary[u as usize] != self.boundary[v as usize] {
return None;
}
let mut shared = 0u32;
for fi in self.incident(u) {
let t = self.faces[fi as usize].unwrap();
if t.contains(&v) {
shared += 1;
}
}
if shared == 0 || shared > 2 {
return None;
}
if shared == 2 && self.shared_neighbours(u, v) > 2 {
return None;
}
if shared == 1 && self.shared_neighbours(u, v) > 1 {
return None;
}
let (np, cost, _t) = self.collapse_target(u, v);
if !cost.is_finite() {
return None;
}
if self.would_flip(u, v, np) || self.would_flip(v, u, np) {
return None;
}
Some(Candidate {
cost,
u,
v,
vu: self.ver[u as usize],
vv: self.ver[v as usize],
})
}
fn shared_neighbours(&self, u: u32, v: u32) -> usize {
let nbr = |w: u32| -> std::collections::BTreeSet<u32> {
let mut s = std::collections::BTreeSet::new();
for fi in self.incident(w) {
for &c in self.faces[fi as usize].unwrap().iter() {
if c != w {
s.insert(c);
}
}
}
s
};
let nu = nbr(u);
let nv = nbr(v);
nu.intersection(&nv).filter(|&&w| w != u && w != v).count()
}
fn one_ring(&self, v: u32) -> Vec<u32> {
let mut s = std::collections::BTreeSet::new();
for fi in self.incident(v) {
for &c in self.faces[fi as usize].unwrap().iter() {
if c != v {
s.insert(c);
}
}
}
s.into_iter().collect()
}
fn run(&mut self, target_triangles: usize, max_error: f64) {
let mut heap: BinaryHeap<Candidate> = BinaryHeap::new();
let mut seen: std::collections::HashSet<(u32, u32)> = std::collections::HashSet::new();
let nf = self.faces.len();
for fi in 0..nf {
if let Some([a, b, c]) = self.faces[fi] {
for (u, v) in [(a, b), (b, c), (c, a)] {
let k = if u < v { (u, v) } else { (v, u) };
if seen.insert(k) {
if let Some(cand) = self.make_candidate(k.0, k.1) {
heap.push(cand);
}
}
}
}
}
while self.live_faces > target_triangles {
let cand = match heap.pop() {
Some(c) => c,
None => break,
};
if !self.alive[cand.u as usize]
|| !self.alive[cand.v as usize]
|| self.ver[cand.u as usize] != cand.vu
|| self.ver[cand.v as usize] != cand.vv
{
continue;
}
if cand.cost > max_error {
break;
}
let fresh = match self.make_candidate(cand.u, cand.v) {
Some(c) => c,
None => continue,
};
if fresh.cost > cand.cost + 1e-12 {
heap.push(fresh);
continue;
}
let (u, v) = (fresh.u, fresh.v);
let (np, _cost, t) = self.collapse_target(u, v);
self.apply_collapse(u, v, np, t);
for w in self.one_ring(u) {
if let Some(c) = self.make_candidate(u, w) {
heap.push(c);
}
}
}
}
fn apply_collapse(&mut self, u: u32, v: u32, np: [f64; 3], t: f64) {
let vfs = self.incident(v);
for fi in vfs {
let tri = self.faces[fi as usize].unwrap();
if tri.contains(&u) {
self.faces[fi as usize] = None;
self.live_faces -= 1;
} else {
let nt = tri.map(|c| if c == v { u } else { c });
if nt[0] == nt[1] || nt[1] == nt[2] || nt[0] == nt[2] {
self.faces[fi as usize] = None;
self.live_faces -= 1;
} else {
self.faces[fi as usize] = Some(nt);
self.vfaces[u as usize].push(fi);
}
}
}
self.quad[u as usize] = self.quad[u as usize].add(&self.quad[v as usize]);
self.pos[u as usize] = np;
self.boundary[u as usize] = self.boundary[u as usize] || self.boundary[v as usize];
self.alive[v as usize] = false;
self.ver[u as usize] = self.ver[u as usize].wrapping_add(1);
self.ver[v as usize] = self.ver[v as usize].wrapping_add(1);
self.merge_log[v as usize] = (u, v, t);
for w in self.one_ring(u) {
self.ver[w as usize] = self.ver[w as usize].wrapping_add(1);
}
}
fn into_primitive(self, welded: &Primitive, empty_out: Primitive) -> Primitive {
let live: Vec<[u32; 3]> = self.faces.iter().filter_map(|f| *f).collect();
if live.is_empty() {
return empty_out;
}
let nverts = self.alive.len();
let mut remap = vec![u32::MAX; nverts];
let mut order: Vec<u32> = Vec::new();
for &[a, b, c] in &live {
for v in [a, b, c] {
if remap[v as usize] == u32::MAX {
remap[v as usize] = order.len() as u32;
order.push(v);
}
}
}
let mut out = welded.clone();
out.topology = Topology::Triangles;
let mut npos: Vec<[f32; 3]> = Vec::with_capacity(order.len());
for &v in &order {
let p = self.pos[v as usize];
npos.push([p[0] as f32, p[1] as f32, p[2] as f32]);
}
out.positions = npos;
let blend_t = |v: u32| -> (u32, u32, f32) {
let (kept, absorbed, t) = self.merge_log[v as usize];
if kept == v && absorbed != v {
(v, absorbed, t as f32)
} else {
(v, v, 0.0)
}
};
macro_rules! lerp_set {
($src:expr, $k:expr) => {{
let src = $src;
let mut v: Vec<[f32; $k]> = Vec::with_capacity(order.len());
for &o in &order {
let (a, b, t) = blend_t(o);
let ra = src.get(a as usize).copied().unwrap_or([0.0; $k]);
let rb = src.get(b as usize).copied().unwrap_or([0.0; $k]);
let mut row = [0.0f32; $k];
for k in 0..$k {
row[k] = ra[k] * (1.0 - t) + rb[k] * t;
}
v.push(row);
}
v
}};
}
if let Some(ns) = &welded.normals {
let mut v = lerp_set!(ns, 3);
for nrm in v.iter_mut() {
let len = (nrm[0] * nrm[0] + nrm[1] * nrm[1] + nrm[2] * nrm[2]).sqrt();
if len.is_finite() && len > 0.0 {
nrm[0] /= len;
nrm[1] /= len;
nrm[2] /= len;
}
}
out.normals = Some(v);
}
if let Some(ts) = &welded.tangents {
let mut v: Vec<[f32; 4]> = Vec::with_capacity(order.len());
for &o in &order {
let (a, b, t) = blend_t(o);
let ta = ts.get(a as usize).copied().unwrap_or([1.0, 0.0, 0.0, 1.0]);
let tb = ts.get(b as usize).copied().unwrap_or([1.0, 0.0, 0.0, 1.0]);
let x = ta[0] * (1.0 - t) + tb[0] * t;
let y = ta[1] * (1.0 - t) + tb[1] * t;
let z = ta[2] * (1.0 - t) + tb[2] * t;
let len = (x * x + y * y + z * z).sqrt();
let (nx, ny, nz) = if len.is_finite() && len > 0.0 {
(x / len, y / len, z / len)
} else {
(1.0, 0.0, 0.0)
};
v.push([nx, ny, nz, ta[3]]);
}
out.tangents = Some(v);
}
out.uvs = welded.uvs.iter().map(|set| lerp_set!(set, 2)).collect();
out.colors = welded.colors.iter().map(|set| lerp_set!(set, 4)).collect();
if let Some(ws) = &welded.weights {
let mut v = lerp_set!(ws, 4);
for w in v.iter_mut() {
let s = w[0] + w[1] + w[2] + w[3];
if s > 0.0 && s.is_finite() {
for c in w.iter_mut() {
*c /= s;
}
}
}
out.weights = Some(v);
}
if let Some(js) = &welded.joints {
let mut v: Vec<[u16; 4]> = Vec::with_capacity(order.len());
for &o in &order {
let (a, _b, _t) = blend_t(o);
v.push(js.get(a as usize).copied().unwrap_or([0; 4]));
}
out.joints = Some(v);
}
out.targets = welded
.targets
.iter()
.map(|tgt| tgt.map_buffers(|d| lerp_set!(d, 3)))
.collect();
let mut idx: Vec<u32> = Vec::with_capacity(live.len() * 3);
for &[a, b, c] in &live {
idx.push(remap[a as usize]);
idx.push(remap[b as usize]);
idx.push(remap[c as usize]);
}
let vcount = out.positions.len();
out.indices = Some(if vcount <= 65_536 {
Indices::U16(idx.iter().map(|&i| i as u16).collect())
} else {
Indices::U32(idx)
});
out
}
}