mod emit;
pub(super) use emit::emit_plans;
use crate::mesh::Mesh;
use nalgebra::{Point3, Vector3};
use rustc_hash::FxHashMap;
pub(super) fn count_open_boundary_edges_at(mesh: &Mesh, scale: f64) -> usize {
if mesh.positions.len() < 9 || mesh.indices.len() < 3 {
return 0;
}
let q = |v: f32| (v as f64 * scale).round() as i64;
let mut vid: FxHashMap<(i64, i64, i64), u32> = FxHashMap::default();
let mut id_of = |i: usize| -> u32 {
let k = (
q(mesh.positions[i * 3]),
q(mesh.positions[i * 3 + 1]),
q(mesh.positions[i * 3 + 2]),
);
let next = vid.len() as u32;
*vid.entry(k).or_insert(next)
};
let mut bal: FxHashMap<(u32, u32), i32> = FxHashMap::default();
for tri in mesh.indices.chunks_exact(3) {
let (a, b, c) = (
id_of(tri[0] as usize),
id_of(tri[1] as usize),
id_of(tri[2] as usize),
);
for (x, y) in [(a, b), (b, c), (c, a)] {
let (key, s) = if x < y { ((x, y), 1) } else { ((y, x), -1) };
*bal.entry(key).or_insert(0) += s;
}
}
bal.values().filter(|&&v| v != 0).count()
}
pub(super) const CONFORM_TOL: f64 = 1.0e-4;
pub(super) struct SeamVert {
pos: Point3<f64>,
buckets: u32,
first: u32,
last: u32,
}
pub(super) type SeamMap = std::collections::BTreeMap<(i64, i64, i64), SeamVert>;
fn seam_bounds(lo: [f64; 3], hi: [f64; 3]) -> ((i64, i64, i64), (i64, i64, i64)) {
let q = |v: f64| (v * 1.0e4).round() as i64;
(
(q(lo[0]) - 1, i64::MIN, i64::MIN),
(q(hi[0]) + 1, i64::MAX, i64::MAX),
)
}
pub(super) fn record_seam_vert(map: &mut SeamMap, bid: u32, p: Point3<f64>) {
let key = (
(p.x * 1.0e4).round() as i64,
(p.y * 1.0e4).round() as i64,
(p.z * 1.0e4).round() as i64,
);
match map.get_mut(&key) {
Some(e) => {
if e.last != bid {
e.buckets += 1;
e.last = bid;
}
}
None => {
map.insert(
key,
SeamVert {
pos: p,
buckets: 1,
first: bid,
last: bid,
},
);
}
}
}
pub(super) fn conform_ring(
ring: &mut Vec<nalgebra::Point2<f64>>,
cands: &[nalgebra::Point2<f64>],
) -> bool {
let n = ring.len();
if n < 3 || cands.is_empty() {
return false;
}
let mut out: Vec<nalgebra::Point2<f64>> = Vec::with_capacity(n + cands.len());
let mut inserted = false;
let mut hits: Vec<(f64, nalgebra::Point2<f64>)> = Vec::new();
for i in 0..n {
let a = ring[i];
let b = ring[(i + 1) % n];
out.push(a);
let (ex, ey) = (b.x - a.x, b.y - a.y);
let len2 = ex * ex + ey * ey;
if len2 <= 0.0 {
continue;
}
let len = len2.sqrt();
hits.clear();
for &q in cands {
let (dx, dy) = (q.x - a.x, q.y - a.y);
let t = (dx * ex + dy * ey) / len2;
if t * len <= CONFORM_TOL || (1.0 - t) * len <= CONFORM_TOL {
continue;
}
if (dx * ey - dy * ex).abs() / len > CONFORM_TOL {
continue;
}
hits.push((t, q));
}
if hits.is_empty() {
continue;
}
hits.sort_by(|x, y| x.0.total_cmp(&y.0));
let mut last_t = f64::NEG_INFINITY;
for &(t, q) in hits.iter() {
if (t - last_t) * len <= CONFORM_TOL {
continue;
}
out.push(q);
last_t = t;
inserted = true;
}
}
if inserted {
*ring = out;
}
inserted
}
pub(super) struct PlanRegion {
pub(super) outer: Vec<nalgebra::Point2<f64>>,
pub(super) holes: Vec<Vec<nalgebra::Point2<f64>>>,
pub(super) outer_conformed: Vec<nalgebra::Point2<f64>>,
pub(super) holes_conformed: Vec<Vec<nalgebra::Point2<f64>>>,
pub(super) changed: bool,
pub(super) cached: Option<(Vec<nalgebra::Point2<f64>>, Vec<usize>)>,
}
pub(super) struct PlanBucket {
pub(super) bid: u32,
pub(super) normal: Vector3<f64>,
pub(super) origin: Point3<f64>,
pub(super) u_axis: Vector3<f64>,
pub(super) v_axis: Vector3<f64>,
pub(super) raw: Vec<[Point3<f64>; 3]>,
pub(super) regions: Vec<PlanRegion>,
}
pub(super) fn build_seam_map(plans: &[PlanBucket]) -> SeamMap {
let mut seam = SeamMap::new();
for plan in plans {
for tri in &plan.raw {
for p in tri {
record_seam_vert(&mut seam, plan.bid, *p);
}
}
for region in &plan.regions {
for p in region.outer.iter().chain(region.holes.iter().flatten()) {
let lifted = plan.origin + plan.u_axis * p.x + plan.v_axis * p.y;
record_seam_vert(&mut seam, plan.bid, lifted);
}
}
}
seam
}
pub(super) fn conform_plans(plans: &mut [PlanBucket], seam: &SeamMap) -> bool {
let mut changed = false;
for plan in plans.iter_mut() {
if plan.regions.is_empty() {
continue;
}
let (mut minx, mut miny, mut maxx, mut maxy) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
for r in &plan.regions {
for p in r.outer.iter().chain(r.holes.iter().flatten()) {
minx = minx.min(p.x);
miny = miny.min(p.y);
maxx = maxx.max(p.x);
maxy = maxy.max(p.y);
}
}
let (mut lo, mut hi) = ([f64::MAX; 3], [f64::MIN; 3]);
for r in &plan.regions {
for p in r.outer.iter().chain(r.holes.iter().flatten()) {
let w = plan.origin + plan.u_axis * p.x + plan.v_axis * p.y;
for k in 0..3 {
lo[k] = lo[k].min(w[k]);
hi[k] = hi[k].max(w[k]);
}
}
}
let mut cands: Vec<nalgebra::Point2<f64>> = Vec::new();
let (klo, khi) = seam_bounds(lo, hi);
for sv in seam.range(klo..=khi).map(|(_, v)| v) {
if sv.buckets < 2 && sv.first == plan.bid {
continue;
}
let d = sv.pos - plan.origin;
if plan.normal.dot(&d).abs() > 1.0e-5 {
continue;
}
let (x, y) = (d.dot(&plan.u_axis), d.dot(&plan.v_axis));
if x < minx - CONFORM_TOL
|| x > maxx + CONFORM_TOL
|| y < miny - CONFORM_TOL
|| y > maxy + CONFORM_TOL
{
continue;
}
cands.push(nalgebra::Point2::new(x, y));
}
if cands.is_empty() {
continue;
}
for region in plan.regions.iter_mut() {
let local: Vec<nalgebra::Point2<f64>> = cands
.iter()
.copied()
.filter(|q| {
!region
.outer
.iter()
.chain(region.holes.iter().flatten())
.any(|p| {
(p.x - q.x).abs() <= CONFORM_TOL && (p.y - q.y).abs() <= CONFORM_TOL
})
})
.collect();
if local.is_empty() {
continue;
}
let mut this = conform_ring(&mut region.outer_conformed, &local);
for hole in region.holes_conformed.iter_mut() {
this |= conform_ring(hole, &local);
}
region.changed = this;
changed |= this;
}
}
changed
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conform_ring_inserts_only_interior_on_edge_candidates() {
use nalgebra::Point2;
let square = vec![
Point2::new(0.0, 0.0),
Point2::new(2.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
let mut ring = square.clone();
assert!(conform_ring(&mut ring, &[Point2::new(1.0, 0.0)]));
assert_eq!(ring.len(), 5);
assert_eq!(ring[1], Point2::new(1.0, 0.0));
for q in [
Point2::new(1.0, 1.0),
Point2::new(3.0, 0.0),
Point2::new(2.0, 0.0),
] {
let mut ring = square.clone();
assert!(!conform_ring(&mut ring, &[q]), "wrongly inserted {q:?}");
assert_eq!(ring.len(), 4);
}
let mut ring = square.clone();
assert!(conform_ring(
&mut ring,
&[Point2::new(1.0, 0.0), Point2::new(1.000_05, 0.0)]
));
assert_eq!(ring.len(), 5);
}
}