Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/boolean45/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0.

use std::mem;
use std::collections::HashMap;
use std::collections::BTreeMap;
use crate::{Manifold, Real, Vec3, Half, Tref, face_of};
use crate::boolean03::Boolean03;
use crate::bounds::BBox;
Expand Down Expand Up @@ -115,8 +115,8 @@ fn add_new_edge_verts(
i12: &[i32],
v12_r: &[i32],
hs_p: &[Half],
pt_old: &mut HashMap<usize, Vec<EdgePt>>,
pt_new: &mut HashMap<(usize, usize), Vec<EdgePt>>,
pt_old: &mut BTreeMap<usize, Vec<EdgePt>>,
pt_new: &mut BTreeMap<(usize, usize), Vec<EdgePt>>,
fwd: bool,
oft: usize,
) {
Expand Down Expand Up @@ -184,7 +184,7 @@ fn append_partial_edges(
fwd: bool, //
hs_r: &mut [Half], // halfedge data of mfd_r, empty yet
rs_r: &mut [Tref], // map from halfedge in mfd_r to triangle info
pt_p: &mut HashMap<usize, Vec<EdgePt>>, //
pt_p: &mut BTreeMap<usize, Vec<EdgePt>>, //
face_ptr_r: &mut [i32], //
whole_flag: &mut [bool], // a flag to find out a halfedge from mfd_p is entirely usable in mfd_r
) {
Expand Down Expand Up @@ -238,7 +238,7 @@ fn append_new_edges(
fid_pq2r: &[i32], //
nf_p: usize, // num of faces in mfd_p
face_ptr_r: &mut[i32], //
pt_new: &mut HashMap<(usize, usize), Vec<EdgePt>>, //
pt_new: &mut BTreeMap<(usize, usize), Vec<EdgePt>>, //
hs_r: &mut [Half], // the halfedge data of mfd_r, empty yet
rs_r: &mut [Tref], //
) {
Expand Down Expand Up @@ -362,9 +362,9 @@ pub fn boolean45<S>(
for i in 0..b03.v12.len() { duplicate_verts(&i12, &vid_12r, &b03.v12, &mut ps_r, i); }
for i in 0..b03.v21.len() { duplicate_verts(&i21, &vid_21r, &b03.v21, &mut ps_r, i); }

let mut pt_p = HashMap::new();
let mut pt_q = HashMap::new();
let mut pt_new = HashMap::new();
let mut pt_p = BTreeMap::new();
let mut pt_q = BTreeMap::new();
let mut pt_new = BTreeMap::new();
add_new_edge_verts(&b03.p1q2, &i12, &vid_12r, &mp.hs, &mut pt_p, &mut pt_new, true, 0);
add_new_edge_verts(&b03.p2q1, &i21, &vid_21r, &mq.hs, &mut pt_q, &mut pt_new, false, b03.p1q2.len());

Expand Down
39 changes: 39 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,42 @@ mod test_values {
}

}

#[cfg(test)]
mod test_determinism {
use crate::{Manifold, OpType, compute_boolean};

fn cube(lo: [f64; 3], hi: [f64; 3]) -> Manifold {
let mut ps = vec![];
for i in 0..8 {
ps.push(if i & 1 == 0 { lo[0] } else { hi[0] });
ps.push(if i & 2 == 0 { lo[1] } else { hi[1] });
ps.push(if i & 4 == 0 { lo[2] } else { hi[2] });
}
let fs = [0, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 1, 4, 1, 5, 4, 2, 6, 3, 3, 6, 7, 0, 4, 2, 2, 4, 6, 1, 3, 5, 3, 7, 5];
Manifold::new(&ps, &fs).unwrap()
}

fn fingerprint(m: &Manifold) -> (Vec<[u64; 3]>, Vec<[usize; 3]>) {
let ps = m.ps.iter().map(|p| [p.x as f64, p.y as f64, p.z as f64].map(f64::to_bits)).collect();
let hs = m.hs.iter().map(|h| [h.tail, h.head, h.pair]).collect();
(ps, hs)
}

// Ties in the ear queue and the order of the edge maps once followed addresses and hash seeds.
#[test]
fn test_same_input_same_output() {
let block = cube([0.; 3], [10.; 3]);
let cases: [(Manifold, fn() -> OpType); 3] = [
(cube([2., 2., 5.], [8., 8., 10.]), || OpType::Subtract),
(cube([10., 2., 2.], [15., 8., 8.]), || OpType::Add),
(cube([4., 4., -1.], [6., 6., 11.]), || OpType::Subtract),
];
for (other, op) in cases {
let first = fingerprint(&compute_boolean(&block, &other, op()).unwrap());
for _ in 0..30 {
assert_eq!(fingerprint(&compute_boolean(&block, &other, op()).unwrap()), first);
}
}
}
}
35 changes: 15 additions & 20 deletions src/triangulation/ear_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct Ecvt {
pub vl: Option<Weak<RefCell<Ecvt>>>,
pub vr: Option<Weak<RefCell<Ecvt>>>,
pub cost: Real,
pub seq: usize, // creation order, breaks ties without depending on addresses
}

impl Ecvt {
Expand All @@ -33,7 +34,7 @@ impl Ecvt {
pub fn ptr_r_of_l(&self) -> EvPtr { self.ptr_l().borrow().ptr_r() }

pub fn new(idx: usize, pos: Vec2) -> Self {
Self { idx, pos, dir: Vec2::new(0., 0.), ear: None, vl: None, vr: None, cost: 0. }
Self { idx, pos, dir: Vec2::new(0., 0.), ear: None, vl: None, vr: None, cost: 0., seq: 0 }
}

// Shorter than half of epsilon, to be conservative so that it doesn't
Expand Down Expand Up @@ -234,11 +235,7 @@ impl Ord for EvPtrMinCost {
self.0.borrow().cost
.partial_cmp(&other.0.borrow().cost)
.unwrap_or(Ordering::Equal)
.then_with(|| {
let ptr1 = Rc::as_ptr(&self.0) as usize;
let ptr2 = Rc::as_ptr(&other.0) as usize;
ptr1.cmp(&ptr2)
})
.then_with(|| self.0.borrow().seq.cmp(&other.0.borrow().seq))
}
}

Expand All @@ -256,11 +253,7 @@ impl Ord for EvPtrMaxPosX {
other.0.borrow().pos.x
.partial_cmp(&self.0.borrow().pos.x)
.unwrap_or(Ordering::Equal)
.then_with(|| {
let ptr1 = Rc::as_ptr(&self.0) as usize;
let ptr2 = Rc::as_ptr(&other.0) as usize;
ptr1.cmp(&ptr2)
})
.then_with(|| self.0.borrow().seq.cmp(&other.0.borrow().seq))
}
}

Expand Down Expand Up @@ -344,6 +337,13 @@ impl EarClip {
std::mem::take(&mut self.tris)
}

fn add(&mut self, mut v: Ecvt) -> EvPtr {
v.seq = self.polygon.len();
let v = Rc::new(RefCell::new(v));
self.polygon.push(Rc::clone(&v));
v
}

// This function and JoinPolygons are the only functions that affect
// the circular list data structure. This helps ensure it remains circular.
fn link(vl: &EvPtr, vr: &EvPtr) {
Expand Down Expand Up @@ -381,17 +381,14 @@ impl EarClip {
let mut bgns = vec![];
for poly in polys.iter() {
let v = poly.first().unwrap();
self.polygon.push(Rc::new(RefCell::new(Ecvt::new(v.idx, v.pos))));

let first = Rc::clone(self.polygon.last().unwrap());
let first = self.add(Ecvt::new(v.idx, v.pos));
let mut last = Rc::clone(&first);
self.bbox.union(first.borrow().pos);
bgns.push(Rc::clone(&first));

for v in poly.iter().skip(1) {
self.bbox.union(v.pos);
self.polygon.push(Rc::new(RefCell::new(Ecvt::new(v.idx, v.pos))));
let next = Rc::clone(self.polygon.last().unwrap());
let next = self.add(Ecvt::new(v.idx, v.pos));
Self::link(&last, &next);
last = Rc::clone(&next);
}
Expand Down Expand Up @@ -533,10 +530,8 @@ impl EarClip {
// To do this, both verts are duplicated and reattached. This process may create degenerate ears,
// so these are clipped if necessary to keep from confusing sub_sequent key-holing operations.
fn join_polygons(&mut self, sta: &EvPtr, con: &EvPtr) {
let sta1 = Rc::new(RefCell::new(sta.borrow().clone()));
let con1 = Rc::new(RefCell::new(con.borrow().clone()));
self.polygon.push(Rc::clone(&sta1));
self.polygon.push(Rc::clone(&con1));
let sta1 = self.add(sta.borrow().clone());
let con1 = self.add(con.borrow().clone());
sta.borrow().ptr_r().borrow_mut().vl = Some(Rc::downgrade(&sta1));
con.borrow().ptr_l().borrow_mut().vr = Some(Rc::downgrade(&con1));
Self::link(sta, con);
Expand Down
Loading