From 598dda93d5b99031bd3a234fe731c2dd3eaf9cd4 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 8 Jul 2024 17:02:15 -0400 Subject: [PATCH 01/45] WIP simplified mesh interseciton logic --- crates/parry3d-f64/Cargo.toml | 3 + src/shape/triangle.rs | 2 + .../mesh_intersection/mesh_intersection.rs | 759 +++++++++++++++--- .../triangle_triangle_intersection.rs | 50 +- 4 files changed, 721 insertions(+), 93 deletions(-) diff --git a/crates/parry3d-f64/Cargo.toml b/crates/parry3d-f64/Cargo.toml index 208b47a4..5bd35fd1 100644 --- a/crates/parry3d-f64/Cargo.toml +++ b/crates/parry3d-f64/Cargo.toml @@ -60,10 +60,13 @@ cust_core = { version = "0.1", optional = true } spade = { version = "2", optional = true } # Make this optional? rayon = { version = "1", optional = true } bytemuck = { version = "1", features = ["derive"], optional = true } +rstar = "0.12.0" [target.'cfg(not(target_os = "cuda"))'.dependencies] cust = { version = "0.3", optional = true } +obj = "0.10.2" + [dev-dependencies] oorandom = "11" ptree = "0.4.0" diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index d3931460..457bffca 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -233,6 +233,8 @@ impl Triangle { /// product). #[inline] pub fn scaled_normal(&self) -> Vector { + // Note: on thin triangles this can cause numerical issues. A more robust + // way to do this is to look for the incident angle closest to 90 degrees. let ab = self.b - self.a; let ac = self.c - self.a; ab.cross(&ac) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index e81d15df..205f442d 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -1,11 +1,20 @@ use super::{MeshIntersectionError, TriangleTriangleIntersection, EPS}; use crate::math::{Isometry, Point, Real, Vector}; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; -use crate::shape::{FeatureId, TriMesh, Triangle}; -use crate::utils::WBasis; -use na::{Point2, Vector2}; +use crate::shape::{FeatureId, GenericTriMesh, TriMesh, Triangle}; +use crate::utils::{hashmap, DefaultStorage, WBasis}; +use core::f64::consts::PI; +use na::{constraint, ComplexField, Point2, Point3, Vector2, Vector3}; use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _}; +use std::collections::BTreeMap; use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; +// Dbg +use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; +use rstar::RTree; + +const EPSILON: f64 = f64::EPSILON * 100.0; +// const EPSILON: f64 = 0.001; /// Computes the intersection of two meshes. /// @@ -19,6 +28,13 @@ pub fn intersect_meshes( mesh2: &TriMesh, flip2: bool, ) -> Result, MeshIntersectionError> { + mesh_to_obj(&mesh1, &PathBuf::from("input1.obj")); + mesh_to_obj(&mesh2, &PathBuf::from("input2.obj")); + + // NOTE: remove this, used for debugging only. + mesh1.assert_half_edge_topology_is_valid(); + mesh2.assert_half_edge_topology_is_valid(); + if mesh1.topology().is_none() || mesh2.topology().is_none() { return Err(MeshIntersectionError::MissingTopology); } @@ -27,18 +43,14 @@ pub fn intersect_meshes( return Err(MeshIntersectionError::MissingPseudoNormals); } - // NOTE: remove this, used for debugging only. - mesh1.assert_half_edge_topology_is_valid(); - mesh2.assert_half_edge_topology_is_valid(); - let pos12 = pos1.inv_mul(pos2); // 1: collect all the potential triangle-triangle intersections. - let mut intersections = vec![]; + let mut intersection_candidates = vec![]; let mut visitor = BoundingVolumeIntersectionsSimultaneousVisitor::with_relative_pos( pos12, |tri1: &u32, tri2: &u32| { - intersections.push((*tri1, *tri2)); + intersection_candidates.push((*tri1, *tri2)); true }, ); @@ -50,16 +62,41 @@ pub fn intersect_meshes( let mut new_indices1 = vec![]; let mut new_indices2 = vec![]; - for (fid1, fid2) in &intersections { + let mut dbg_intersections = vec![]; + let mut intersections = vec![]; + for (fid1, fid2) in &intersection_candidates { let tri1 = mesh1.triangle(*fid1); let tri2 = mesh2.triangle(*fid2).transformed(&pos12); if super::triangle_triangle_intersection(&tri1, &tri2).is_some() { + intersections.push((*fid1, *fid2)); let _ = deleted_faces1.insert(*fid1); let _ = deleted_faces2.insert(*fid2); + + dbg_intersections.push(tri1.a); + dbg_intersections.push(tri1.b); + dbg_intersections.push(tri1.c); + + dbg_intersections.push(tri2.a); + dbg_intersections.push(tri2.b); + dbg_intersections.push(tri2.c); } } + let n = dbg_intersections.len(); + if !intersections.is_empty() { + mesh_to_obj( + &TriMesh::new( + dbg_intersections, + (0..n) + .step_by(3) + .map(|i| [i as u32, (i + 1) as u32, (i + 2) as u32]) + .collect(), + ), + &PathBuf::from(format!("intersections_{}.obj", intersections.len())), + ); + } + extract_connected_components( &pos12, mesh1, @@ -77,84 +114,121 @@ pub fn intersect_meshes( &mut new_indices2, ); - let mut new_vertices12 = vec![]; - let mut new_indices12 = vec![]; - - cut_and_triangulate_intersections( - &pos12, - mesh1, - flip1, - mesh2, - flip2, - &mut new_vertices12, - &mut new_indices12, - &mut intersections, - ); - - let old_vertices1 = mesh1.vertices(); - let old_vertices2 = mesh2.vertices(); + let mut point_set = RTree::::new(); + let mut topology_indices = Vec::new(); - // At this point, we know what triangles we want from the first mesh, - // and the ones we want from the second mesh. Now we need to build the - // vertex buffer and adjust the indices accordingly. - let mut new_vertices = vec![]; + { + let mut insert_point = + |position: Vector3| insert_into_set(position, &mut point_set, EPSILON) as u32; + // Add the inside vertices and triangles from mesh1 + for mut face in new_indices1 { + if flip1 { + face.swap(0, 1); + } + topology_indices.push([ + insert_point(mesh1.vertices()[face[0] as usize].coords), + insert_point(mesh1.vertices()[face[1] as usize].coords), + insert_point(mesh1.vertices()[face[2] as usize].coords), + ]); + } - // Maps from unified index to the final vertex index. - let mut index_map = HashMap::new(); - let base_id2 = mesh1.vertices().len() as u32; + // Add the inside vertices and triangles from mesh2 + for mut face in new_indices2 { + if flip2 { + face.swap(0, 1); + } - // Grab all the triangles from the connected component extracted from the first mesh. - for idx1 in &mut new_indices1 { - for k in 0..3 { - let new_id = *index_map.entry(idx1[k]).or_insert_with(|| { - let vtx = old_vertices1[idx1[k] as usize]; - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - idx1[k] = new_id as u32; + topology_indices.push([ + insert_point(mesh2.vertices()[face[0] as usize].coords), + insert_point(mesh2.vertices()[face[1] as usize].coords), + insert_point(mesh2.vertices()[face[2] as usize].coords), + ]); } } - // Grab all the triangles from the connected component extracted from the second mesh. - for idx2 in &mut new_indices2 { - for k in 0..3 { - let new_id = *index_map.entry(base_id2 + idx2[k]).or_insert_with(|| { - let vtx = pos12 * old_vertices2[idx2[k] as usize]; - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - idx2[k] = new_id as u32; - } - } + let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); + dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); - // Grab all the trinangles from the intersections. - for idx12 in &mut new_indices12 { - for id12 in idx12 { - let new_id = *index_map.entry(*id12).or_insert_with(|| { - let vtx = unified_vertex(mesh1, mesh2, &new_vertices12, &pos12, *id12); - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - *id12 = new_id as u32; + mesh_to_obj( + &TriMesh::new( + dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), + topology_indices.clone(), + ), + &PathBuf::from("stage1_merging.obj"), + ); + + // For each intersecting triangle, get their intersection points. + let mut constraints1 = std::collections::BTreeMap::new(); + let mut constraints2 = std::collections::BTreeMap::new(); + + for (fid1, fid2) in &intersections { + let tri1 = mesh1.triangle(*fid1); + let tri2 = mesh2.triangle(*fid2).transformed(&pos12); + + let list1 = constraints1.entry(fid1).or_insert(vec![]); + let list2 = constraints2.entry(fid2).or_insert(vec![]); + + let intersection = super::triangle_triangle_intersection(&tri1, &tri2); + if intersection.is_some() { + match intersection.unwrap() { + TriangleTriangleIntersection::Segment { a, b } => { + // For both triangles, add the points in the intersection + // and their associated edge to the set. + // Note this necessarily introduces duplicate points to the + // set that need to be filtered out. + list1.push([a.p1, b.p1]); + list2.push([a.p1, b.p1]); + } + TriangleTriangleIntersection::Polygon(polygon) => { + panic!() + } + } } } - if flip1 { - new_indices1.iter_mut().for_each(|idx| idx.swap(1, 2)); - } + merge_triangle_sets( + mesh1, + mesh2, + &constraints1, + &pos12, + flip2, + &mut point_set, + &mut topology_indices, + ); - if flip2 { - new_indices2.iter_mut().for_each(|idx| idx.swap(1, 2)); - } + let dbg_vertices: Vec<_> = point_set.iter().copied().collect(); + let pts: Vec<_> = dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(); + let (_, d) = find_closest_distinct_points(&pts); + + let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); + dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); + mesh_to_obj( + &TriMesh::new( + dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), + topology_indices.clone(), + ), + &PathBuf::from("stage2_merging.obj"), + ); - new_indices1.append(&mut new_indices2); - new_indices1.append(&mut new_indices12); + merge_triangle_sets( + mesh2, + mesh1, + &constraints2, + &Isometry::identity(), + flip1, + &mut point_set, + &mut topology_indices, + ); - if !new_indices1.is_empty() { - Ok(Some(TriMesh::new(new_vertices, new_indices1))) - } else { - Ok(None) - } + let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); + dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); + mesh_to_obj( + &TriMesh::new( + dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), + topology_indices, + ), + &PathBuf::from("stage3_merging.obj"), + ); } fn extract_connected_components( @@ -330,11 +404,12 @@ impl Triangulation { let param = ab.dot(&ap) / ab.norm_squared(); let shift = Vector2::new(ab.y, -ab.x); + // Why not use `insert_and_split`? // NOTE: if we have intersections exactly on the edge, we nudge // their projection slightly outside of the triangle. That // way, the triangle’s edge gets split automatically by // the triangulation (or, rather, it will be split when we - // add the contsraint involving that point). + // add the constraint involving that point). // NOTE: this is not ideal though, so we should find a way to simply // delete spurious triangles that are outside of the intersection // curve. @@ -351,8 +426,8 @@ fn cut_and_triangulate_intersections( flip1: bool, mesh2: &TriMesh, flip2: bool, - new_vertices12: &mut Vec>, - new_indices12: &mut Vec<[u32; 3]>, + merged_vertices: &mut Vec>, + merged_indices: &mut Vec<[u32; 3]>, intersections: &mut Vec<(u32, u32)>, ) { let mut triangulations1 = HashMap::new(); @@ -362,6 +437,7 @@ fn cut_and_triangulate_intersections( let mut spade_infos = [HashMap::new(), HashMap::new()]; let mut spade_handle_to_intersection = [HashMap::new(), HashMap::new()]; + let mut dbg_points = Vec::new(); for (i1, i2) in intersections.drain(..) { let tris = [mesh1.triangle(i1), mesh2.triangle(i2).transformed(pos12)]; let vids = [mesh1.indices()[i1 as usize], mesh2.indices()[i2 as usize]]; @@ -424,6 +500,11 @@ fn cut_and_triangulate_intersections( let key_a = (fa_1, fa_2); let key_b = (fb_1, fb_2); + dbg_points.push(inter_a.p1); + dbg_points.push(inter_a.p2); + dbg_points.push(inter_b.p1); + dbg_points.push(inter_b.p2); + let ins_a = *intersection_points .entry(key_a) .or_insert([inter_a.p1, inter_a.p2]); @@ -471,6 +552,9 @@ fn cut_and_triangulate_intersections( } } + if !dbg_points.is_empty() { + points_to_obj(&dbg_points, &PathBuf::from("inter_points.obj")); + } extract_result( pos12, mesh1, @@ -481,8 +565,8 @@ fn cut_and_triangulate_intersections( &intersection_points, &triangulations1, &triangulations2, - new_vertices12, - new_indices12, + merged_vertices, + merged_indices, ); } @@ -505,7 +589,7 @@ fn convert_fid(mesh: &TriMesh, tri: u32, fid: FeatureId) -> FeatureId { fn unified_vertex( mesh1: &TriMesh, mesh2: &TriMesh, - new_vertices12: &[Point], + merged_vertices: &[Point], pos12: &Isometry, vid: u32, ) -> Point { @@ -517,7 +601,7 @@ fn unified_vertex( } else if vid < base_id12 { pos12 * mesh2.vertices()[(vid - base_id2) as usize] } else { - new_vertices12[(vid - base_id12) as usize] + merged_vertices[(vid - base_id12) as usize] } } @@ -531,8 +615,8 @@ fn extract_result( intersection_points: &HashMap<(FeatureId, FeatureId), [Point; 2]>, triangulations1: &HashMap, triangulations2: &HashMap, - new_vertices12: &mut Vec>, - new_indices12: &mut Vec<[u32; 3]>, + merged_vertices: &mut Vec>, + merged_indices: &mut Vec<[u32; 3]>, ) { // Base ids for indexing in the first mesh vertices, second mesh vertices, and new vertices, as if they // are part of a single big array. @@ -543,7 +627,7 @@ fn extract_result( let mut vertex_remaping = HashMap::new(); // Generate the new points and setup the mapping between indices from - // the second mesh, to vertices from the first mash (for cases of vertex/vertex intersections). + // the second mesh, to vertices from the first mesh (for cases of vertex/vertex intersections). for (fids, pts) in intersection_points.iter() { match *fids { (FeatureId::Vertex(vid1), FeatureId::Vertex(vid2)) => { @@ -552,8 +636,8 @@ fn extract_result( (FeatureId::Vertex(_), _) | (_, FeatureId::Vertex(_)) => {} _ => { let _ = added_vertices.entry(fids).or_insert_with(|| { - new_vertices12.push(pts[0]); - new_vertices12.len() as u32 - 1 + merged_vertices.push(pts[0]); + merged_vertices.len() as u32 - 1 }); } } @@ -568,18 +652,23 @@ fn extract_result( _ => base_id12 + added_vertices[&fids], }; + let mut dbg_triangles = Vec::new(); for (tri_id, triangulation) in triangulations1.iter() { for face in triangulation.delaunay.inner_faces() { let vtx = face.vertices(); let mut tri = [Point::origin(); 3]; let mut idx = [0; 3]; + + let mut dbg_pts = Vec::new(); for k in 0..3 { let fids = spade_handle_to_intersection[0][&(*tri_id, vtx[k].fix())]; let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, new_vertices12, pos12, vid); + let vertex = unified_vertex(mesh1, mesh2, merged_vertices, pos12, vid); idx[k] = vid; tri[k] = vertex; + + dbg_pts.push(vertex); } let tri = Triangle::from(tri); @@ -590,14 +679,27 @@ fn extract_result( && ((flip2 ^ projection.is_inside) || (projection.point - center).norm() <= EPS * 10.0) { + dbg_triangles.extend(dbg_pts); if flip1 { idx.swap(1, 2); } - new_indices12.push(idx); + merged_indices.push(idx); } } } + let n = dbg_triangles.len(); + mesh_to_obj( + &TriMesh::new( + dbg_triangles, + (0..n) + .step_by(3) + .map(|i| [i as u32, (i + 1) as u32, (i + 2) as u32]) + .collect(), + ), + &PathBuf::from(format!("sorted_intersections_{}.obj", n)), + ); + for (tri_id, triangulation) in triangulations2.iter() { for face in triangulation.delaunay.inner_faces() { let vtx = face.vertices(); @@ -606,7 +708,7 @@ fn extract_result( for k in 0..3 { let fids = spade_handle_to_intersection[1][&(*tri_id, vtx[k].fix())]; let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, new_vertices12, pos12, vid); + let vertex = unified_vertex(mesh1, mesh2, merged_vertices, pos12, vid); idx[k] = vid; tri[k] = vertex; @@ -629,7 +731,482 @@ fn extract_result( if flip2 { idx.swap(1, 2); } - new_indices12.push(idx); + merged_indices.push(idx); + } + } + } +} + +fn test_triangle_crossing( + tri: &Triangle, + pos12: &Isometry, + mesh: &GenericTriMesh, +) -> usize { + let projections = [ + mesh.project_point(pos12, &tri.a, false), + mesh.project_point(pos12, &tri.b, false), + mesh.project_point(pos12, &tri.c, false), + ]; + let distances = [ + (projections[0].point - tri.a).norm(), + (projections[1].point - tri.b).norm(), + (projections[2].point - tri.c).norm(), + ]; + let inside_tests = [ + projections[0].is_inside || distances[0] <= EPS * 10.0, + projections[1].is_inside || distances[1] <= EPS * 10.0, + projections[2].is_inside || distances[2] <= EPS * 10.0, + ]; + let inside_count: usize = inside_tests.iter().map(|b| *b as usize).sum(); + + inside_count +} + +fn syncretize_triangulation( + tri: &Triangle, + constraints: &[[Point3; 2]], +) -> (Triangulation, Vec>) { + let mut constraints = constraints.to_vec(); + + let epsilon = EPSILON * 10.0; + // Add the triangle points to the triangulation. + let mut point_set = RTree::::new(); + let _ = insert_into_set(tri.a.coords, &mut point_set, epsilon); + let _ = insert_into_set(tri.b.coords, &mut point_set, epsilon); + let _ = insert_into_set(tri.c.coords, &mut point_set, epsilon); + + // Sometimes, points on the edge of a triangle are slightly off, and this makes + // spade think that there is a super thin triangle. Project points close to an edge + // onto the edge to get better performance. + let triangle = [tri.a.coords, tri.b.coords, tri.c.coords]; + for point_pair in constraints.iter_mut() { + let p1 = point_pair[0]; + let p2 = point_pair[1]; + + for i in 0..3 { + let q1 = triangle[i]; + let q2 = triangle[(i + 1) % 3]; + + let proj1 = project_point_to_segment(&p1.coords, &[q1, q2]); + if (p1.coords - proj1).norm() < epsilon { + point_pair[0] = Point3::from(proj1); + } + + let proj2 = project_point_to_segment(&p2.coords, &[q1, q2]); + if (p2.coords - proj2).norm() < epsilon { + point_pair[1] = Point3::from(proj2); + } + } + } + + // Generate edge, taking care to merge duplicate vertices. + let mut edges = Vec::new(); + for point_pair in constraints { + let p1_id = insert_into_set(point_pair[0].coords, &mut point_set, EPSILON); + let p2_id = insert_into_set(point_pair[1].coords, &mut point_set, EPSILON); + + edges.push([p1_id, p2_id]); + } + + let mut points: Vec<_> = point_set.iter().cloned().collect(); + points.sort_by(|a, b| a.id.cmp(&b.id)); + + let tri_points = [tri.a.coords, tri.b.coords, tri.c.coords]; + let best_source = select_angle_closest_to_90(&tri_points); + let d1 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; + let d2 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; + let (e1, e2) = planar_gram_schmidt(d1, d2); + let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); + + // Project points into 2D and triangulate the resulting set. + let mut triangulation = Triangulation::new(*tri); + + let planar_points: Vec<_> = points + .iter() + .copied() + .map(|point| { + let point_proj = project(&point.point); //triangulation.project(Point3::from(point.point), FeatureId::Unknown); + spade::Point2::new(point_proj.x, point_proj.y) + }) + .collect(); + let cdt_triangulation = + ConstrainedDelaunayTriangulation::>::bulk_load_cdt_stable( + planar_points, + edges, + ) + .unwrap(); + debug_assert!(cdt_triangulation.vertices().len() == points.len()); + triangulation.delaunay = cdt_triangulation; + + let points = points.into_iter().map(|p| Point3::from(p.point)).collect(); + (triangulation, points) +} + +fn mesh_to_obj(mesh: &TriMesh, path: &PathBuf) { + let mut file = std::fs::File::create(path).unwrap(); + + ObjData { + position: mesh + .vertices() + .into_iter() + .map(|v| [v.x as f32, v.y as f32, v.z as f32]) + .collect(), + objects: vec![Object { + groups: vec![Group { + polys: mesh + .indices() + .into_iter() + .map(|tri| { + SimplePolygon(vec![ + IndexTuple(tri[0] as usize, None, None), + IndexTuple(tri[1] as usize, None, None), + IndexTuple(tri[2] as usize, None, None), + ]) + }) + .collect(), + name: "".to_string(), + index: 0, + material: None, + }], + name: "".to_string(), + }], + ..Default::default() + } + .write_to_buf(&mut file) + .unwrap(); +} + +fn points_to_obj(mesh: &[Point3], path: &PathBuf) { + use std::io::Write; + let mut file = std::fs::File::create(path).unwrap(); + + for p in mesh { + writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap(); + } +} + +fn points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { + use std::io::Write; + let mut file = std::fs::File::create(path).unwrap(); + + for p in mesh { + writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap(); + } + + for e in edges { + writeln!(file, "l {} {}", e[0] + 1, e[1] + 1).unwrap(); + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Default)] +struct TreePoint { + point: Vector3, + id: usize, +} + +impl rstar::Point for TreePoint { + type Scalar = f64; + const DIMENSIONS: usize = 3; + + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + TreePoint { + point: Vector3::new(generator(0), generator(1), generator(2)), + id: usize::MAX, + } + } + + fn nth(&self, index: usize) -> Self::Scalar { + match index { + 0 => self.point.x, + 1 => self.point.y, + 2 => self.point.z, + _ => unreachable!(), + } + } + + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + match index { + 0 => &mut self.point.x, + 1 => &mut self.point.y, + 2 => &mut self.point.z, + _ => unreachable!(), + } + } +} + +fn insert_into_set( + position: Vector3, + point_set: &mut RTree, + epsilon: f64, +) -> usize { + let point_count = point_set.size(); + let point_to_insert = TreePoint { + point: position, + id: point_count, + }; + + match point_set.nearest_neighbor(&point_to_insert) { + Some(tree_point) => { + if (tree_point.point - position).norm_squared() <= epsilon { + return tree_point.id; + } else { + point_set.insert(point_to_insert); + debug_assert!(point_set.size() == point_count + 1); + return point_count; + } + } + None => { + point_set.insert(point_to_insert); + debug_assert!(point_set.size() == point_count + 1); + return point_count; + } + } +} + +fn spade_to_tri_mesh(delaunay: &ConstrainedDelaunayTriangulation>) -> TriMesh { + let pts = delaunay + .vertices() + .map(|v| { + let p = v.position(); + Point3::::new(p.x, p.y, 0.0) + }) + .collect::>(); + let topology = delaunay + .inner_faces() + .map(|f| { + [ + f.vertices()[0].index() as u32, + f.vertices()[1].index() as u32, + f.vertices()[2].index() as u32, + ] + }) + .collect(); + + TriMesh::new(pts, topology) +} + +fn find_closest_distinct_points(points: &[Point3]) -> ([Point3; 2], f64) { + let mut distance = f64::MAX; + let mut pair_points = [points[0], points[1]]; + for i in 0..points.len() { + for j in 0..points.len() { + if i == j { + continue; + } + + let d = (points[i].coords - points[j].coords).norm(); + + if d < distance { + distance = d; + pair_points[0] = points[i]; + pair_points[1] = points[j]; + } + } + } + + (pair_points, distance) +} + +fn select_angle_closest_to_90(points: &[Vector3]) -> usize { + let n = points.len(); + + let mut best_cos = 2.0; + let mut selected_i = 0; + for i in 0..points.len() { + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + + let cos = d1.dot(&d2); + + if cos.abs() < best_cos { + best_cos = cos.abs(); + selected_i = i; + } + } + + selected_i +} + +fn smallest_angle(points: &[Vector3]) -> f64 { + let n = points.len(); + + let mut worst_cos = 2.0; + for i in 0..points.len() { + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + + let cos = d1.dot(&d2); + + if cos < worst_cos { + worst_cos = cos.abs(); + } + } + + worst_cos.acos() * 180. / PI +} + +fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) { + let u1 = v1; + let u2 = v2 - (v2.dot(&u1) / u1.norm_squared()) * u1; + + let e1 = u1.normalize(); + let e2 = u2.normalize(); + + (e1, e2) +} + +fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) -> Vector3 { + let dir = segment[1] - segment[0]; + let local = point - segment[0]; + + let norm = dir.norm(); + // restrict the result to the segment portion of the line. + let coeff = (dir.dot(&local) / norm).clamp(0., norm); + + segment[0] + coeff * dir.normalize() +} + +fn project_to_triangle(point: &Vector3, tri: &Triangle) -> (Vector3, f64) { + let points = [tri.a.coords, tri.b.coords, tri.c.coords]; + + let mut selected_point = Vector3::default(); + let mut distance = f64::MAX; + for i in 0..3 { + let proj = project_point_to_segment(point, &[points[i], points[(i + 1) % 3]]); + let d = (proj - point).norm(); + + if d < distance { + distance = d; + selected_point = proj; + } + } + + (selected_point, distance) +} + +fn largest_side(tri: &Triangle) -> f64 { + let mut side = (tri.a.coords - tri.b.coords).norm(); + side = side.max((tri.b.coords - tri.c.coords).norm()); + side = side.max((tri.c.coords - tri.a.coords).norm()); + side +} + +fn closest_vertex(point: &Vector3, tri: &Triangle) -> (Vector3, f64) { + let points = [tri.a.coords, tri.b.coords, tri.c.coords]; + + let mut selected_point = Vector3::default(); + let mut distance = f64::MAX; + for i in 0..3 { + let d = (points[i] - point).norm(); + + if d < distance { + distance = d; + selected_point = points[i]; + } + } + + (selected_point, distance) +} + +/// No matter how smart we are about computing intersections. It is always possible +/// to create ultra thin triangles when a point lies on an edge of a tirangle. These +/// are degenerate and need to be terminated with extreme prejudice. +fn is_triangle_degenerate( + triangle: &[Vector3; 3], + epsilon_degrees: f64, + epsilon_distance: f64, +) -> bool { + if smallest_angle(triangle) < epsilon_degrees { + return true; + } + + let mut shortest_side = f64::MAX; + for i in 0..3 { + let p1 = triangle[i]; + let p2 = triangle[(i + 1) % 3]; + + shortest_side = shortest_side.min((p1 - p2).norm()); + } + + let mut worse_projection_distance = f64::MAX; + for i in 0..3 { + let dir = triangle[(i + 1) % 3] - triangle[(i + 2) % 3]; + if dir.norm() < epsilon_distance { + return true; + } + + let dir = dir.normalize(); + let proj = (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir + triangle[(i + 2) % 3]; + + worse_projection_distance = worse_projection_distance.min((proj - triangle[i]).norm()); + } + + if worse_projection_distance < epsilon_distance { + return true; + } + + false +} + +fn merge_triangle_sets( + mesh1: &GenericTriMesh, + mesh2: &GenericTriMesh, + triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, + pos12: &Isometry, + flip2: bool, + mut point_set: &mut RTree, + topology_indices: &mut Vec<[u32; 3]>, +) { + // For each triangle, and each constraint edge associated to that triangle, + // make a triangulation of the face and sort wether or not each generated + // sub-triangle is part of the intersection. + // For each sub-triangle that is part of the intersection, add them to the + // output mesh. + for (triangle_id, constraints) in triangle_constraints.iter() { + let tri = mesh1.triangle(**triangle_id); + + let (triangulation, points) = syncretize_triangulation(&tri, &constraints); + + for face in triangulation.delaunay.inner_faces() { + let verts = face.vertices(); + let p1 = points[verts[0].index()]; + let p2 = points[verts[1].index()]; + let p3 = points[verts[2].index()]; + + // Sometimes the triangulation is messed up due to numerical errors. If + // a triangle does not survive this test. You can bet it should be put out + // of its misery. + if is_triangle_degenerate(&[p1.coords, p2.coords, p3.coords], 0.005, EPSILON) { + continue; + } + + let center = Triangle { + a: p1, + b: p2, + c: p3, + } + .center(); + + if flip2 ^ (mesh2.contains_local_point(&pos12.inverse_transform_point(¢er))) { + topology_indices.push([ + insert_into_set(p1.coords, &mut point_set, EPSILON) as u32, + insert_into_set(p2.coords, &mut point_set, EPSILON) as u32, + insert_into_set(p3.coords, &mut point_set, EPSILON) as u32, + ]); + + if flip2 { + topology_indices.last_mut().unwrap().swap(0, 1) + } + + let id1 = topology_indices.last().unwrap()[0]; + let id2 = topology_indices.last().unwrap()[1]; + let id3 = topology_indices.last().unwrap()[2]; + + // If this triggers, yell at Camilo because his algorithm is + // disfunctional. + if id1 == id2 || id1 == id3 || id2 == id3 { + panic!(); + } } } } diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index db309856..7d0a0f94 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -35,8 +35,8 @@ pub fn triangle_triangle_intersection( tri1: &Triangle, tri2: &Triangle, ) -> Option { - let normal1 = tri1.normal()?; - let normal2 = tri2.normal()?; + let normal1 = robust_triangle_normal(&tri1); + let normal2 = robust_triangle_normal(&tri2); if let Some(intersection_dir) = normal1.cross(&normal2).try_normalize(1.0e-6) { let mut range1 = [ @@ -250,6 +250,52 @@ pub fn triangle_triangle_intersection( } } +fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { + let pts = [tri.a.coords, tri.b.coords, tri.c.coords]; + let best_vertex = select_angle_closest_to_90(&pts); + + let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; + let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; + + // TODO: verify if this is actually necessary or if we can get away with the cross product directly. + let (e1, e2) = planar_gram_schmidt(d1, d2); + + e1.cross(&e2) +} + +fn planar_gram_schmidt( + v1: na::Vector3, + v2: na::Vector3, +) -> (na::Vector3, na::Vector3) { + let u1 = v1; + let u2 = v2 - (v2.dot(&u1) / u1.norm_squared()) * u1; + + let e1 = u1.normalize(); + let e2 = u2.normalize(); + + (e1, e2) +} + +fn select_angle_closest_to_90(points: &[na::Vector3]) -> usize { + let n = points.len(); + + let mut best_cos = 2.0; + let mut selected_i = 0; + for i in 0..points.len() { + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + + let cos = d1.dot(&d2); + + if cos.abs() < best_cos { + best_cos = cos.abs(); + selected_i = i; + } + } + + selected_i +} + fn segment_plane_intersection( plane_center: &Point, plane_normal: &Vector, From 39f026dd6604ce2e7ecc1a06425c0fc28f384714 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Tue, 9 Jul 2024 14:32:55 -0400 Subject: [PATCH 02/45] Make mesh intersection code more robust --- src/shape/trimesh.rs | 19 +- .../convex_hull3/initial_mesh.rs | 4 +- .../mesh_intersection/mesh_intersection.rs | 756 +++--------------- .../triangle_triangle_intersection.rs | 28 +- 4 files changed, 140 insertions(+), 667 deletions(-) diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index 514bae5d..be123b82 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -45,8 +45,8 @@ pub enum TopologyError { } #[cfg(feature = "std")] -impl std::fmt::Display for TopologyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for TopologyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BadTriangle(fid) => { f.pad(&format!("the triangle {fid} has at least two identical vertices.")) @@ -239,21 +239,6 @@ impl TriMeshTopology { } } -impl TriMeshTopology { - #[cfg(feature = "dim3")] - pub(crate) fn face_half_edges_ids(&self, fid: u32) -> [u32; 3] { - let first_half_edge = self.faces[fid as usize].half_edge; - - let mut result = [first_half_edge; 3]; - for k in 1..3 { - let half_edge = self.half_edges[result[k - 1] as usize]; - result[k] = half_edge.next; - } - - result - } -} - #[cfg(all(feature = "std", feature = "cuda"))] impl TriMeshTopology { fn to_cuda(&self) -> CudaResult> { diff --git a/src/transformation/convex_hull3/initial_mesh.rs b/src/transformation/convex_hull3/initial_mesh.rs index 30ba8b47..62f54b6a 100644 --- a/src/transformation/convex_hull3/initial_mesh.rs +++ b/src/transformation/convex_hull3/initial_mesh.rs @@ -46,7 +46,7 @@ pub fn try_get_initial_mesh( #[cfg(not(feature = "improved_fixed_point_support"))] { - let cov_mat = crate::utils::cov(normalized_points); + let cov_mat = utils::cov(normalized_points); let eig = cov_mat.symmetric_eigen(); eigvec = eig.eigenvectors; eigval = eig.eigenvalues; @@ -140,7 +140,7 @@ pub fn try_get_initial_mesh( 3 => { // The hull is a polyhedron. // Find a initial triangle lying on the principal halfspace… - let center = crate::utils::center(normalized_points); + let center = utils::center(normalized_points); for point in normalized_points.iter_mut() { *point = Point3::from((*point - center) / eigval.amax()); diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 205f442d..1c26f418 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -1,20 +1,45 @@ -use super::{MeshIntersectionError, TriangleTriangleIntersection, EPS}; -use crate::math::{Isometry, Point, Real, Vector}; +use super::{MeshIntersectionError, TriangleTriangleIntersection}; +use crate::math::{Isometry, Real}; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; -use crate::shape::{FeatureId, GenericTriMesh, TriMesh, Triangle}; -use crate::utils::{hashmap, DefaultStorage, WBasis}; +use crate::shape::{GenericTriMesh, TriMesh, Triangle}; +use crate::transformation::mesh_intersection::angle_closest_to_90; +use crate::utils::DefaultStorage; use core::f64::consts::PI; -use na::{constraint, ComplexField, Point2, Point3, Vector2, Vector3}; -use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _}; +use na::{Point3, Vector3}; +use spade::{ConstrainedDelaunayTriangulation, Triangulation as _}; use std::collections::BTreeMap; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; +use std::collections::HashSet; +use std::path::PathBuf; // Dbg use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; use rstar::RTree; -const EPSILON: f64 = f64::EPSILON * 100.0; -// const EPSILON: f64 = 0.001; +/// Metadata that specifies thresholds to use when making construction choices +/// in mesh intersections. +pub struct MeshIntersectionMetadata { + /// The smallest angle (in degrees) that will be tolerated. A triangle with + /// a smaller angle is considered degenerate and will be deleted. + pub angle_epsilon: f64, + /// The maximum distance at which two points are considered to overlap in space + /// if `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered + /// to be the same point. + pub global_insertion_epsilon: f64, + /// A multiplier coefficient to scale `global_insertion_epsilon` when checking for + /// point duplicatin within a single triangle. Inside of an individual triangle + /// the distance at wich two points are considered to be the same is + /// `global_insertion_epsilon * local_insertion_epsilon_mod`. + pub local_insertion_epsilon_mod: f64, +} + +impl Default for MeshIntersectionMetadata { + fn default() -> Self { + Self { + angle_epsilon: 0.005, // degrees + global_insertion_epsilon: f64::EPSILON * 100.0, + local_insertion_epsilon_mod: 10., + } + } +} /// Computes the intersection of two meshes. /// @@ -28,12 +53,35 @@ pub fn intersect_meshes( mesh2: &TriMesh, flip2: bool, ) -> Result, MeshIntersectionError> { - mesh_to_obj(&mesh1, &PathBuf::from("input1.obj")); - mesh_to_obj(&mesh2, &PathBuf::from("input2.obj")); + intersect_meshes_with_metadata( + pos1, + mesh1, + flip1, + pos2, + mesh2, + flip2, + MeshIntersectionMetadata::default(), + ) +} +/// Similar to `intersect_meshes`. +/// +/// It allows to specify epsilons for how the algorithm will behave. +/// See `MeshIntersectionMetadata` for details. +pub fn intersect_meshes_with_metadata( + pos1: &Isometry, + mesh1: &TriMesh, + flip1: bool, + pos2: &Isometry, + mesh2: &TriMesh, + flip2: bool, + meta_data: MeshIntersectionMetadata, +) -> Result, MeshIntersectionError> { // NOTE: remove this, used for debugging only. - mesh1.assert_half_edge_topology_is_valid(); - mesh2.assert_half_edge_topology_is_valid(); + if cfg!(debug_assertions) { + mesh1.assert_half_edge_topology_is_valid(); + mesh2.assert_half_edge_topology_is_valid(); + } if mesh1.topology().is_none() || mesh2.topology().is_none() { return Err(MeshIntersectionError::MissingTopology); @@ -62,6 +110,7 @@ pub fn intersect_meshes( let mut new_indices1 = vec![]; let mut new_indices2 = vec![]; + // 2: Identify all triangles that do actually intersect. let mut dbg_intersections = vec![]; let mut intersections = vec![]; for (fid1, fid2) in &intersection_candidates { @@ -83,20 +132,7 @@ pub fn intersect_meshes( } } - let n = dbg_intersections.len(); - if !intersections.is_empty() { - mesh_to_obj( - &TriMesh::new( - dbg_intersections, - (0..n) - .step_by(3) - .map(|i| [i as u32, (i + 1) as u32, (i + 2) as u32]) - .collect(), - ), - &PathBuf::from(format!("intersections_{}.obj", intersections.len())), - ); - } - + // 3: Grab all triangles that are inside the other mesh but tdo not intersect it. extract_connected_components( &pos12, mesh1, @@ -114,12 +150,14 @@ pub fn intersect_meshes( &mut new_indices2, ); + // 4: Initialize a new mesh by inserting points into a set. Duplicate points should + // hash to the same index. let mut point_set = RTree::::new(); let mut topology_indices = Vec::new(); - { - let mut insert_point = - |position: Vector3| insert_into_set(position, &mut point_set, EPSILON) as u32; + let mut insert_point = |position: Vector3| { + insert_into_set(position, &mut point_set, meta_data.global_insertion_epsilon) as u32 + }; // Add the inside vertices and triangles from mesh1 for mut face in new_indices1 { if flip1 { @@ -146,21 +184,10 @@ pub fn intersect_meshes( } } - let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); - dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); - - mesh_to_obj( - &TriMesh::new( - dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), - topology_indices.clone(), - ), - &PathBuf::from("stage1_merging.obj"), - ); - - // For each intersecting triangle, get their intersection points. - let mut constraints1 = std::collections::BTreeMap::new(); - let mut constraints2 = std::collections::BTreeMap::new(); - + // 5: Associate constraint edges generated by a tringle-triangle intersection + // to each intersecting triangle where they occur. + let mut constraints1 = BTreeMap::new(); + let mut constraints2 = BTreeMap::new(); for (fid1, fid2) in &intersections { let tri1 = mesh1.triangle(*fid1); let tri2 = mesh2.triangle(*fid2).transformed(&pos12); @@ -180,55 +207,53 @@ pub fn intersect_meshes( list2.push([a.p1, b.p1]); } TriangleTriangleIntersection::Polygon(polygon) => { - panic!() + for i in 0..polygon.len() { + let a = polygon[i]; + let b = polygon[(i + 1) % polygon.len()]; + + list1.push([a.p1, b.p1]); + list2.push([a.p1, b.p1]); + } } } } } + // 6: Collect all triangles that intersect and their associated constraint edges. + // For each such triangle, compute a CDT of its constraints. For each face in this CDT, + // if the face is contained in the opposite mesh, add it to the intersection mesh. merge_triangle_sets( mesh1, mesh2, &constraints1, &pos12, flip2, + &meta_data, &mut point_set, &mut topology_indices, ); - let dbg_vertices: Vec<_> = point_set.iter().copied().collect(); - let pts: Vec<_> = dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(); - let (_, d) = find_closest_distinct_points(&pts); - - let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); - dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); - mesh_to_obj( - &TriMesh::new( - dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), - topology_indices.clone(), - ), - &PathBuf::from("stage2_merging.obj"), - ); - merge_triangle_sets( mesh2, mesh1, &constraints2, &Isometry::identity(), flip1, + &meta_data, &mut point_set, &mut topology_indices, ); - let mut dbg_vertices: Vec<_> = point_set.iter().copied().collect(); - dbg_vertices.sort_by(|a, b| a.id.cmp(&b.id)); - mesh_to_obj( - &TriMesh::new( - dbg_vertices.iter().map(|p| Point3::from(p.point)).collect(), - topology_indices, - ), - &PathBuf::from("stage3_merging.obj"), - ); + // 7: Sort the ouput points by insertion order. + let mut vertices: Vec<_> = point_set.iter().copied().collect(); + vertices.sort_by(|a, b| a.id.cmp(&b.id)); + let vertices: Vec<_> = vertices.iter().map(|p| Point3::from(p.point)).collect(); + + if !topology_indices.is_empty() { + Ok(Some(TriMesh::new(vertices, topology_indices))) + } else { + Ok(None) + } } fn extract_connected_components( @@ -330,445 +355,15 @@ fn extract_connected_components( } } -#[derive(Copy, Clone, Debug)] -struct SpadeInfo { - handle: FixedVertexHandle, -} - -struct Triangulation { - delaunay: ConstrainedDelaunayTriangulation>, - basis: [Vector; 2], - vtx_handles: [FixedVertexHandle; 3], - ref_pt: Point, - ref_proj: [Point2; 3], - normalization: na::Matrix2, -} - -impl Triangulation { - fn new(triangle: Triangle) -> Self { - let mut delaunay = ConstrainedDelaunayTriangulation::>::new(); - let normal = triangle.normal().unwrap(); - let basis = normal.orthonormal_basis(); - - let ab = triangle.b - triangle.a; - let ac = triangle.c - triangle.a; - - let mut ref_proj = [ - Point2::origin(), - Point2::new(ab.dot(&basis[0]), ab.dot(&basis[1])), - Point2::new(ac.dot(&basis[0]), ac.dot(&basis[1])), - ]; - - let normalization_inv = - na::Matrix2::from_columns(&[ref_proj[1].coords, ref_proj[2].coords]); - let normalization = normalization_inv - .try_inverse() - .unwrap_or(na::Matrix2::identity()); - - for ref_proj in &mut ref_proj { - *ref_proj = normalization * *ref_proj; - } - - let vtx_handles = [ - delaunay - .insert(spade::Point2::new(ref_proj[0].x, ref_proj[0].y)) - .unwrap(), - delaunay - .insert(spade::Point2::new(ref_proj[1].x, ref_proj[1].y)) - .unwrap(), - delaunay - .insert(spade::Point2::new(ref_proj[2].x, ref_proj[2].y)) - .unwrap(), - ]; - - Self { - delaunay, - basis, - vtx_handles, - ref_pt: triangle.a, - ref_proj, - normalization, - } - } - - fn project(&self, pt: Point, orig_fid: FeatureId) -> spade::Point2 { - let dpt = pt - self.ref_pt; - let mut proj = - self.normalization * Point2::new(dpt.dot(&self.basis[0]), dpt.dot(&self.basis[1])); - - if let FeatureId::Edge(i) = orig_fid { - let a = self.ref_proj[i as usize]; - let b = self.ref_proj[(i as usize + 1) % 3]; - let ab = b - a; - let ap = proj - a; - let param = ab.dot(&ap) / ab.norm_squared(); - let shift = Vector2::new(ab.y, -ab.x); - - // Why not use `insert_and_split`? - // NOTE: if we have intersections exactly on the edge, we nudge - // their projection slightly outside of the triangle. That - // way, the triangle’s edge gets split automatically by - // the triangulation (or, rather, it will be split when we - // add the constraint involving that point). - // NOTE: this is not ideal though, so we should find a way to simply - // delete spurious triangles that are outside of the intersection - // curve. - proj = a + ab * param + shift * EPS * 10.0; - } - - spade::Point2::new(proj.x, proj.y) - } -} - -fn cut_and_triangulate_intersections( - pos12: &Isometry, - mesh1: &TriMesh, - flip1: bool, - mesh2: &TriMesh, - flip2: bool, - merged_vertices: &mut Vec>, - merged_indices: &mut Vec<[u32; 3]>, - intersections: &mut Vec<(u32, u32)>, -) { - let mut triangulations1 = HashMap::new(); - let mut triangulations2 = HashMap::new(); - let mut intersection_points = HashMap::new(); - - let mut spade_infos = [HashMap::new(), HashMap::new()]; - let mut spade_handle_to_intersection = [HashMap::new(), HashMap::new()]; - - let mut dbg_points = Vec::new(); - for (i1, i2) in intersections.drain(..) { - let tris = [mesh1.triangle(i1), mesh2.triangle(i2).transformed(pos12)]; - let vids = [mesh1.indices()[i1 as usize], mesh2.indices()[i2 as usize]]; - - if let Some(intersection) = super::triangle_triangle_intersection(&tris[0], &tris[1]) { - let tri_ids = [i1, i2]; - - let triangulation1 = triangulations1.entry(tri_ids[0]).or_insert_with(|| { - let triangulation = Triangulation::new(tris[0]); - for k in 0..3 { - let _ = spade_handle_to_intersection[0].insert( - (tri_ids[0], triangulation.vtx_handles[k]), - (FeatureId::Vertex(vids[0][k]), FeatureId::Unknown), - ); - } - triangulation - }); - - let triangulation2 = triangulations2.entry(tri_ids[1]).or_insert_with(|| { - let triangulation = Triangulation::new(tris[1]); - for k in 0..3 { - let _ = spade_handle_to_intersection[1].insert( - (tri_ids[1], triangulation.vtx_handles[k]), - (FeatureId::Unknown, FeatureId::Vertex(vids[1][k])), - ); - } - triangulation - }); - - let triangulations = [triangulation1, triangulation2]; - - let mut insert_point = - |pt: [_; 2], key: (FeatureId, FeatureId), orig_fid: [FeatureId; 2], i: usize| { - let spade_key = (tri_ids[i], key); - - spade_infos[i] - .entry(spade_key) - .or_insert_with(|| { - let point2d = triangulations[i].project(pt[i], orig_fid[i]); - let handle = triangulations[i].delaunay.insert(point2d).unwrap(); - let _ = - spade_handle_to_intersection[i].insert((tri_ids[i], handle), key); - SpadeInfo { handle } - }) - .handle - }; - - match intersection { - TriangleTriangleIntersection::Segment { - a: inter_a, - b: inter_b, - } => { - let fa_1 = convert_fid(mesh1, i1, inter_a.f1); - let fa_2 = convert_fid(mesh2, i2, inter_a.f2); - let fb_1 = convert_fid(mesh1, i1, inter_b.f1); - let fb_2 = convert_fid(mesh2, i2, inter_b.f2); - - let orig_fid_a = [inter_a.f1, inter_a.f2]; - let orig_fid_b = [inter_b.f1, inter_b.f2]; - let key_a = (fa_1, fa_2); - let key_b = (fb_1, fb_2); - - dbg_points.push(inter_a.p1); - dbg_points.push(inter_a.p2); - dbg_points.push(inter_b.p1); - dbg_points.push(inter_b.p2); - - let ins_a = *intersection_points - .entry(key_a) - .or_insert([inter_a.p1, inter_a.p2]); - let ins_b = *intersection_points - .entry(key_b) - .or_insert([inter_b.p1, inter_b.p2]); - - let handles_a = [ - insert_point(ins_a, key_a, orig_fid_a, 0), - insert_point(ins_a, key_a, orig_fid_a, 1), - ]; - - let handles_b = [ - insert_point(ins_b, key_b, orig_fid_b, 0), - insert_point(ins_b, key_b, orig_fid_b, 1), - ]; - - for i in 0..2 { - // NOTE: the naming of the `ConstrainedDelaunayTriangulation::can_add_constraint` method is misleading. - if !triangulations[i] - .delaunay - .can_add_constraint(handles_a[i], handles_b[i]) - { - let _ = triangulations[i] - .delaunay - .add_constraint(handles_a[i], handles_b[i]); - } - } - } - TriangleTriangleIntersection::Polygon(intersections) => { - for inter in intersections { - let f1 = convert_fid(mesh1, i1, inter.f1); - let f2 = convert_fid(mesh2, i2, inter.f2); - let orig_fid = [inter.f1, inter.f2]; - let key = (f1, f2); - let ins = *intersection_points - .entry(key) - .or_insert([inter.p1, inter.p2]); - - let _ = insert_point(ins, key, orig_fid, 0); - let _ = insert_point(ins, key, orig_fid, 1); - } - } - } - } - } - - if !dbg_points.is_empty() { - points_to_obj(&dbg_points, &PathBuf::from("inter_points.obj")); - } - extract_result( - pos12, - mesh1, - flip1, - mesh2, - flip2, - &spade_handle_to_intersection, - &intersection_points, - &triangulations1, - &triangulations2, - merged_vertices, - merged_indices, - ); -} - -fn convert_fid(mesh: &TriMesh, tri: u32, fid: FeatureId) -> FeatureId { - match fid { - FeatureId::Edge(eid) => { - let topology = mesh.topology().unwrap(); - let half_edge_id = topology.face_half_edges_ids(tri)[eid as usize]; - let half_edge = &topology.half_edges[half_edge_id as usize]; - // NOTE: if the twin doesn’t exist, it’s equal to u32::MAX. So the `min` will - // automatically filter it out. - FeatureId::Edge(half_edge_id.min(half_edge.twin)) - } - FeatureId::Vertex(vid) => FeatureId::Vertex(mesh.indices()[tri as usize][vid as usize]), - FeatureId::Face(_) => FeatureId::Face(tri), - FeatureId::Unknown => FeatureId::Unknown, - } -} - -fn unified_vertex( - mesh1: &TriMesh, - mesh2: &TriMesh, - merged_vertices: &[Point], - pos12: &Isometry, - vid: u32, -) -> Point { - let base_id2 = mesh1.vertices().len() as u32; - let base_id12 = (mesh1.vertices().len() + mesh2.vertices().len()) as u32; - - if vid < base_id2 { - mesh1.vertices()[vid as usize] - } else if vid < base_id12 { - pos12 * mesh2.vertices()[(vid - base_id2) as usize] - } else { - merged_vertices[(vid - base_id12) as usize] - } -} - -fn extract_result( - pos12: &Isometry, - mesh1: &TriMesh, - flip1: bool, - mesh2: &TriMesh, - flip2: bool, - spade_handle_to_intersection: &[HashMap<(u32, FixedVertexHandle), (FeatureId, FeatureId)>; 2], - intersection_points: &HashMap<(FeatureId, FeatureId), [Point; 2]>, - triangulations1: &HashMap, - triangulations2: &HashMap, - merged_vertices: &mut Vec>, - merged_indices: &mut Vec<[u32; 3]>, -) { - // Base ids for indexing in the first mesh vertices, second mesh vertices, and new vertices, as if they - // are part of a single big array. - let base_id2 = mesh1.vertices().len() as u32; - let base_id12 = (mesh1.vertices().len() + mesh2.vertices().len()) as u32; - - let mut added_vertices = HashMap::new(); - let mut vertex_remaping = HashMap::new(); - - // Generate the new points and setup the mapping between indices from - // the second mesh, to vertices from the first mesh (for cases of vertex/vertex intersections). - for (fids, pts) in intersection_points.iter() { - match *fids { - (FeatureId::Vertex(vid1), FeatureId::Vertex(vid2)) => { - let _ = vertex_remaping.insert(vid2, vid1); - } - (FeatureId::Vertex(_), _) | (_, FeatureId::Vertex(_)) => {} - _ => { - let _ = added_vertices.entry(fids).or_insert_with(|| { - merged_vertices.push(pts[0]); - merged_vertices.len() as u32 - 1 - }); - } - } - } - - let fids_to_unified_index = |fids| match fids { - (FeatureId::Vertex(vid1), _) => vid1, - (_, FeatureId::Vertex(vid2)) => vertex_remaping - .get(&vid2) - .copied() - .unwrap_or(base_id2 + vid2), - _ => base_id12 + added_vertices[&fids], - }; - - let mut dbg_triangles = Vec::new(); - for (tri_id, triangulation) in triangulations1.iter() { - for face in triangulation.delaunay.inner_faces() { - let vtx = face.vertices(); - let mut tri = [Point::origin(); 3]; - let mut idx = [0; 3]; - - let mut dbg_pts = Vec::new(); - for k in 0..3 { - let fids = spade_handle_to_intersection[0][&(*tri_id, vtx[k].fix())]; - let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, merged_vertices, pos12, vid); - - idx[k] = vid; - tri[k] = vertex; - - dbg_pts.push(vertex); - } - - let tri = Triangle::from(tri); - let center = tri.center(); - let projection = mesh2.project_point(pos12, &tri.center(), false); - - if !tri.is_affinely_dependent_eps(EPS * 10.0) - && ((flip2 ^ projection.is_inside) - || (projection.point - center).norm() <= EPS * 10.0) - { - dbg_triangles.extend(dbg_pts); - if flip1 { - idx.swap(1, 2); - } - merged_indices.push(idx); - } - } - } - - let n = dbg_triangles.len(); - mesh_to_obj( - &TriMesh::new( - dbg_triangles, - (0..n) - .step_by(3) - .map(|i| [i as u32, (i + 1) as u32, (i + 2) as u32]) - .collect(), - ), - &PathBuf::from(format!("sorted_intersections_{}.obj", n)), - ); - - for (tri_id, triangulation) in triangulations2.iter() { - for face in triangulation.delaunay.inner_faces() { - let vtx = face.vertices(); - let mut tri = [Point::origin(); 3]; - let mut idx = [0; 3]; - for k in 0..3 { - let fids = spade_handle_to_intersection[1][&(*tri_id, vtx[k].fix())]; - let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, merged_vertices, pos12, vid); - - idx[k] = vid; - tri[k] = vertex; - } - - let tri = Triangle::from(tri); - let center = tri.center(); - - // TODO: when two faces are coplanar, they will be present in both `triangulation1` and - // `triangulation2`. So we need to only pick one of them. Here we already picked the - // face from `triangulation1`. So now we need to ignore the duplicate face. Such face - // is detected by looking at the distance from the triangle’s center to the other mesh. - // If the center lies on the other mesh, then that we have a duplicate face that was already - // added in the previous loop. - let projection = mesh1.project_local_point(¢er, false); - if !tri.is_affinely_dependent_eps(EPS * 10.0) - && ((flip1 ^ projection.is_inside) - && (projection.point - center).norm() > EPS * 10.0) - { - if flip2 { - idx.swap(1, 2); - } - merged_indices.push(idx); - } - } - } -} - -fn test_triangle_crossing( - tri: &Triangle, - pos12: &Isometry, - mesh: &GenericTriMesh, -) -> usize { - let projections = [ - mesh.project_point(pos12, &tri.a, false), - mesh.project_point(pos12, &tri.b, false), - mesh.project_point(pos12, &tri.c, false), - ]; - let distances = [ - (projections[0].point - tri.a).norm(), - (projections[1].point - tri.b).norm(), - (projections[2].point - tri.c).norm(), - ]; - let inside_tests = [ - projections[0].is_inside || distances[0] <= EPS * 10.0, - projections[1].is_inside || distances[1] <= EPS * 10.0, - projections[2].is_inside || distances[2] <= EPS * 10.0, - ]; - let inside_count: usize = inside_tests.iter().map(|b| *b as usize).sum(); - - inside_count -} - fn syncretize_triangulation( tri: &Triangle, constraints: &[[Point3; 2]], -) -> (Triangulation, Vec>) { + epsilon: f64, +) -> ( + ConstrainedDelaunayTriangulation>, + Vec>, +) { let mut constraints = constraints.to_vec(); - - let epsilon = EPSILON * 10.0; // Add the triangle points to the triangulation. let mut point_set = RTree::::new(); let _ = insert_into_set(tri.a.coords, &mut point_set, epsilon); @@ -777,7 +372,7 @@ fn syncretize_triangulation( // Sometimes, points on the edge of a triangle are slightly off, and this makes // spade think that there is a super thin triangle. Project points close to an edge - // onto the edge to get better performance. + // onto the edge to get better results. let triangle = [tri.a.coords, tri.b.coords, tri.c.coords]; for point_pair in constraints.iter_mut() { let p1 = point_pair[0]; @@ -802,8 +397,8 @@ fn syncretize_triangulation( // Generate edge, taking care to merge duplicate vertices. let mut edges = Vec::new(); for point_pair in constraints { - let p1_id = insert_into_set(point_pair[0].coords, &mut point_set, EPSILON); - let p2_id = insert_into_set(point_pair[1].coords, &mut point_set, EPSILON); + let p1_id = insert_into_set(point_pair[0].coords, &mut point_set, epsilon); + let p2_id = insert_into_set(point_pair[1].coords, &mut point_set, epsilon); edges.push([p1_id, p2_id]); } @@ -812,20 +407,19 @@ fn syncretize_triangulation( points.sort_by(|a, b| a.id.cmp(&b.id)); let tri_points = [tri.a.coords, tri.b.coords, tri.c.coords]; - let best_source = select_angle_closest_to_90(&tri_points); + let best_source = angle_closest_to_90(&tri_points); let d1 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; let d2 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; let (e1, e2) = planar_gram_schmidt(d1, d2); let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); // Project points into 2D and triangulate the resulting set. - let mut triangulation = Triangulation::new(*tri); let planar_points: Vec<_> = points .iter() .copied() .map(|point| { - let point_proj = project(&point.point); //triangulation.project(Point3::from(point.point), FeatureId::Unknown); + let point_proj = project(&point.point); spade::Point2::new(point_proj.x, point_proj.y) }) .collect(); @@ -836,13 +430,13 @@ fn syncretize_triangulation( ) .unwrap(); debug_assert!(cdt_triangulation.vertices().len() == points.len()); - triangulation.delaunay = cdt_triangulation; let points = points.into_iter().map(|p| Point3::from(p.point)).collect(); - (triangulation, points) + (cdt_triangulation, points) } -fn mesh_to_obj(mesh: &TriMesh, path: &PathBuf) { +// I heavily recommend that this is left here in case one needs to debug the above code. +fn _mesh_to_obj(mesh: &TriMesh, path: &PathBuf) { let mut file = std::fs::File::create(path).unwrap(); ObjData { @@ -876,7 +470,8 @@ fn mesh_to_obj(mesh: &TriMesh, path: &PathBuf) { .unwrap(); } -fn points_to_obj(mesh: &[Point3], path: &PathBuf) { +// I heavily recommend that this is left here in case one needs to debug the above code. +fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -885,7 +480,8 @@ fn points_to_obj(mesh: &[Point3], path: &PathBuf) { } } -fn points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { +// I heavily recommend that this is left here in case one needs to debug the above code. +fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -963,70 +559,6 @@ fn insert_into_set( } } -fn spade_to_tri_mesh(delaunay: &ConstrainedDelaunayTriangulation>) -> TriMesh { - let pts = delaunay - .vertices() - .map(|v| { - let p = v.position(); - Point3::::new(p.x, p.y, 0.0) - }) - .collect::>(); - let topology = delaunay - .inner_faces() - .map(|f| { - [ - f.vertices()[0].index() as u32, - f.vertices()[1].index() as u32, - f.vertices()[2].index() as u32, - ] - }) - .collect(); - - TriMesh::new(pts, topology) -} - -fn find_closest_distinct_points(points: &[Point3]) -> ([Point3; 2], f64) { - let mut distance = f64::MAX; - let mut pair_points = [points[0], points[1]]; - for i in 0..points.len() { - for j in 0..points.len() { - if i == j { - continue; - } - - let d = (points[i].coords - points[j].coords).norm(); - - if d < distance { - distance = d; - pair_points[0] = points[i]; - pair_points[1] = points[j]; - } - } - } - - (pair_points, distance) -} - -fn select_angle_closest_to_90(points: &[Vector3]) -> usize { - let n = points.len(); - - let mut best_cos = 2.0; - let mut selected_i = 0; - for i in 0..points.len() { - let d1 = (points[i] - points[(i + 1) % n]).normalize(); - let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); - - let cos = d1.dot(&d2); - - if cos.abs() < best_cos { - best_cos = cos.abs(); - selected_i = i; - } - } - - selected_i -} - fn smallest_angle(points: &[Vector3]) -> f64 { let n = points.len(); @@ -1066,48 +598,6 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) - segment[0] + coeff * dir.normalize() } -fn project_to_triangle(point: &Vector3, tri: &Triangle) -> (Vector3, f64) { - let points = [tri.a.coords, tri.b.coords, tri.c.coords]; - - let mut selected_point = Vector3::default(); - let mut distance = f64::MAX; - for i in 0..3 { - let proj = project_point_to_segment(point, &[points[i], points[(i + 1) % 3]]); - let d = (proj - point).norm(); - - if d < distance { - distance = d; - selected_point = proj; - } - } - - (selected_point, distance) -} - -fn largest_side(tri: &Triangle) -> f64 { - let mut side = (tri.a.coords - tri.b.coords).norm(); - side = side.max((tri.b.coords - tri.c.coords).norm()); - side = side.max((tri.c.coords - tri.a.coords).norm()); - side -} - -fn closest_vertex(point: &Vector3, tri: &Triangle) -> (Vector3, f64) { - let points = [tri.a.coords, tri.b.coords, tri.c.coords]; - - let mut selected_point = Vector3::default(); - let mut distance = f64::MAX; - for i in 0..3 { - let d = (points[i] - point).norm(); - - if d < distance { - distance = d; - selected_point = points[i]; - } - } - - (selected_point, distance) -} - /// No matter how smart we are about computing intersections. It is always possible /// to create ultra thin triangles when a point lies on an edge of a tirangle. These /// are degenerate and need to be terminated with extreme prejudice. @@ -1154,6 +644,7 @@ fn merge_triangle_sets( triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, pos12: &Isometry, flip2: bool, + metadata: &MeshIntersectionMetadata, mut point_set: &mut RTree, topology_indices: &mut Vec<[u32; 3]>, ) { @@ -1165,9 +656,13 @@ fn merge_triangle_sets( for (triangle_id, constraints) in triangle_constraints.iter() { let tri = mesh1.triangle(**triangle_id); - let (triangulation, points) = syncretize_triangulation(&tri, &constraints); + let (delaunay, points) = syncretize_triangulation( + &tri, + &constraints, + metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, + ); - for face in triangulation.delaunay.inner_faces() { + for face in delaunay.inner_faces() { let verts = face.vertices(); let p1 = points[verts[0].index()]; let p2 = points[verts[1].index()]; @@ -1176,7 +671,11 @@ fn merge_triangle_sets( // Sometimes the triangulation is messed up due to numerical errors. If // a triangle does not survive this test. You can bet it should be put out // of its misery. - if is_triangle_degenerate(&[p1.coords, p2.coords, p3.coords], 0.005, EPSILON) { + if is_triangle_degenerate( + &[p1.coords, p2.coords, p3.coords], + metadata.angle_epsilon, + metadata.global_insertion_epsilon, + ) { continue; } @@ -1187,11 +686,12 @@ fn merge_triangle_sets( } .center(); + let epsilon = metadata.global_insertion_epsilon; if flip2 ^ (mesh2.contains_local_point(&pos12.inverse_transform_point(¢er))) { topology_indices.push([ - insert_into_set(p1.coords, &mut point_set, EPSILON) as u32, - insert_into_set(p2.coords, &mut point_set, EPSILON) as u32, - insert_into_set(p3.coords, &mut point_set, EPSILON) as u32, + insert_into_set(p1.coords, &mut point_set, epsilon) as u32, + insert_into_set(p2.coords, &mut point_set, epsilon) as u32, + insert_into_set(p3.coords, &mut point_set, epsilon) as u32, ]); if flip2 { diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index 7d0a0f94..a53ebe64 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -250,38 +250,26 @@ pub fn triangle_triangle_intersection( } } +/// Smarter, but more expensive, mechanism to find a triangle normal. fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { let pts = [tri.a.coords, tri.b.coords, tri.c.coords]; - let best_vertex = select_angle_closest_to_90(&pts); + let best_vertex = angle_closest_to_90(&pts); let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; - // TODO: verify if this is actually necessary or if we can get away with the cross product directly. - let (e1, e2) = planar_gram_schmidt(d1, d2); - - e1.cross(&e2) -} - -fn planar_gram_schmidt( - v1: na::Vector3, - v2: na::Vector3, -) -> (na::Vector3, na::Vector3) { - let u1 = v1; - let u2 = v2 - (v2.dot(&u1) / u1.norm_squared()) * u1; - - let e1 = u1.normalize(); - let e2 = u2.normalize(); - - (e1, e2) + d1.cross(&d2).normalize() } -fn select_angle_closest_to_90(points: &[na::Vector3]) -> usize { +/// Find the index of a vertex in a poly line, such that the two +/// edges incident in that vertex form the angle closest to 90 +/// degrees in the poly line. +pub fn angle_closest_to_90(points: &[na::Vector3]) -> usize { let n = points.len(); let mut best_cos = 2.0; let mut selected_i = 0; - for i in 0..points.len() { + for i in 0..n { let d1 = (points[i] - points[(i + 1) % n]).normalize(); let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); From 1781d716218386eff1da8839a405cd4801cbb439 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Thu, 11 Jul 2024 15:41:56 -0400 Subject: [PATCH 03/45] Fixed logic errors and addet tests --- crates/parry3d-f64/Cargo.toml | 3 +- src/query/point/point_query.rs | 5 + .../mesh_intersection/mesh_intersection.rs | 265 ++++++++++++++++-- .../mesh_intersection_error.rs | 6 +- 4 files changed, 251 insertions(+), 28 deletions(-) diff --git a/crates/parry3d-f64/Cargo.toml b/crates/parry3d-f64/Cargo.toml index 5bd35fd1..bb9c9a5f 100644 --- a/crates/parry3d-f64/Cargo.toml +++ b/crates/parry3d-f64/Cargo.toml @@ -61,12 +61,11 @@ spade = { version = "2", optional = true } # Make this optional? rayon = { version = "1", optional = true } bytemuck = { version = "1", features = ["derive"], optional = true } rstar = "0.12.0" +obj = "0.10.2" [target.'cfg(not(target_os = "cuda"))'.dependencies] cust = { version = "0.3", optional = true } -obj = "0.10.2" - [dev-dependencies] oorandom = "11" ptree = "0.4.0" diff --git a/src/query/point/point_query.rs b/src/query/point/point_query.rs index 57eff479..67428170 100644 --- a/src/query/point/point_query.rs +++ b/src/query/point/point_query.rs @@ -33,6 +33,11 @@ impl PointProjection { point: pos * self.point, } } + + /// Returns `true` if `Self::is_inside` is `true` or if the distance between the projected point and `point` is smaller than `min_dist`. + pub fn is_inside_eps(&self, original_point: &Point, min_dist: Real) -> bool { + self.is_inside || na::distance_squared(original_point, &self.point) < min_dist * min_dist + } } /// Trait of objects that can be tested for point inclusion and projection. diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 1c26f418..2a9648a9 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -1,18 +1,18 @@ use super::{MeshIntersectionError, TriangleTriangleIntersection}; use crate::math::{Isometry, Real}; +use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; use crate::shape::{GenericTriMesh, TriMesh, Triangle}; use crate::transformation::mesh_intersection::angle_closest_to_90; use crate::utils::DefaultStorage; use core::f64::consts::PI; use na::{Point3, Vector3}; +use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; +use rstar::RTree; use spade::{ConstrainedDelaunayTriangulation, Triangulation as _}; use std::collections::BTreeMap; use std::collections::HashSet; use std::path::PathBuf; -// Dbg -use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; -use rstar::RTree; /// Metadata that specifies thresholds to use when making construction choices /// in mesh intersections. @@ -111,7 +111,6 @@ pub fn intersect_meshes_with_metadata( let mut new_indices2 = vec![]; // 2: Identify all triangles that do actually intersect. - let mut dbg_intersections = vec![]; let mut intersections = vec![]; for (fid1, fid2) in &intersection_candidates { let tri1 = mesh1.triangle(*fid1); @@ -121,18 +120,10 @@ pub fn intersect_meshes_with_metadata( intersections.push((*fid1, *fid2)); let _ = deleted_faces1.insert(*fid1); let _ = deleted_faces2.insert(*fid2); - - dbg_intersections.push(tri1.a); - dbg_intersections.push(tri1.b); - dbg_intersections.push(tri1.c); - - dbg_intersections.push(tri2.a); - dbg_intersections.push(tri2.b); - dbg_intersections.push(tri2.c); } } - // 3: Grab all triangles that are inside the other mesh but tdo not intersect it. + // 3: Grab all triangles that are inside the other mesh but do not intersect it. extract_connected_components( &pos12, mesh1, @@ -175,7 +166,6 @@ pub fn intersect_meshes_with_metadata( if flip2 { face.swap(0, 1); } - topology_indices.push([ insert_point(mesh2.vertices()[face[0] as usize].coords), insert_point(mesh2.vertices()[face[1] as usize].coords), @@ -211,8 +201,8 @@ pub fn intersect_meshes_with_metadata( let a = polygon[i]; let b = polygon[(i + 1) % polygon.len()]; + // Triangles overlap in space, so only one constraint is needed. list1.push([a.p1, b.p1]); - list2.push([a.p1, b.p1]); } } } @@ -227,22 +217,24 @@ pub fn intersect_meshes_with_metadata( mesh2, &constraints1, &pos12, + flip1, flip2, &meta_data, &mut point_set, &mut topology_indices, - ); + )?; merge_triangle_sets( mesh2, mesh1, &constraints2, &Isometry::identity(), + flip2, flip1, &meta_data, &mut point_set, &mut topology_indices, - ); + )?; // 7: Sort the ouput points by insertion order. let mut vertices: Vec<_> = point_set.iter().copied().collect(); @@ -408,9 +400,10 @@ fn syncretize_triangulation( let tri_points = [tri.a.coords, tri.b.coords, tri.c.coords]; let best_source = angle_closest_to_90(&tri_points); - let d1 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; - let d2 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; + let d1 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; + let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; let (e1, e2) = planar_gram_schmidt(d1, d2); + let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); // Project points into 2D and triangulate the resulting set. @@ -643,13 +636,14 @@ fn merge_triangle_sets( mesh2: &GenericTriMesh, triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, pos12: &Isometry, + flip1: bool, flip2: bool, metadata: &MeshIntersectionMetadata, mut point_set: &mut RTree, topology_indices: &mut Vec<[u32; 3]>, -) { +) -> Result<(), MeshIntersectionError> { // For each triangle, and each constraint edge associated to that triangle, - // make a triangulation of the face and sort wether or not each generated + // make a triangulation of the face and sort whether or not each generated // sub-triangle is part of the intersection. // For each sub-triangle that is part of the intersection, add them to the // output mesh. @@ -687,14 +681,18 @@ fn merge_triangle_sets( .center(); let epsilon = metadata.global_insertion_epsilon; - if flip2 ^ (mesh2.contains_local_point(&pos12.inverse_transform_point(¢er))) { + let projection = mesh2 + .project_local_point_and_get_location(&pos12.inverse_transform_point(¢er), true) + .0; + + if flip2 ^ (projection.is_inside_eps(¢er, epsilon)) { topology_indices.push([ insert_into_set(p1.coords, &mut point_set, epsilon) as u32, insert_into_set(p2.coords, &mut point_set, epsilon) as u32, insert_into_set(p3.coords, &mut point_set, epsilon) as u32, ]); - if flip2 { + if flip1 { topology_indices.last_mut().unwrap().swap(0, 1) } @@ -705,9 +703,228 @@ fn merge_triangle_sets( // If this triggers, yell at Camilo because his algorithm is // disfunctional. if id1 == id2 || id1 == id3 || id2 == id3 { - panic!(); + return Err(MeshIntersectionError::DuplicateVertices); } } } } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::shape::TriMeshFlags; + + use super::*; + use obj::Obj; + + #[test] + fn test_same_mesh_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/low_poly_bunny.obj") + .unwrap(); + + let mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &mesh, + false, + &Isometry::identity(), + &mesh, + false, + ) + .unwrap() + .unwrap(); + + _mesh_to_obj(&res, &PathBuf::from("same_test.obj")) + } + + #[test] + fn test_offset_cylinder_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/offset_cylinder.obj") + .unwrap(); + + let offset_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/center_cylinder.obj") + .unwrap(); + + let center_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + ¢er_mesh, + false, + &Isometry::identity(), + &offset_mesh, + false, + ) + .unwrap() + .unwrap(); + + _mesh_to_obj(&res, &PathBuf::from("offset_test.obj")) + } + + #[test] + fn test_stair_bar_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/stairs.obj").unwrap(); + + let stair_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/bar.obj").unwrap(); + + let bar_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &stair_mesh, + false, + &Isometry::identity(), + &bar_mesh, + false, + ) + .unwrap() + .unwrap(); + + _mesh_to_obj(&res, &PathBuf::from("stair_test.obj")) + } + + #[test] + fn test_complex_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/low_poly_bunny.obj") + .unwrap(); + + let bunny_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../src/transformation/mesh_intersection/test_data/poly_cylinder.obj") + .unwrap(); + + let cylinder_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &bunny_mesh, + false, + &Isometry::identity(), + &cylinder_mesh, + true, + ) + .unwrap() + .unwrap(); + + _mesh_to_obj(&res, &PathBuf::from("complex_test.obj")) + } } diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index 2dba1a17..d9b65269 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -6,18 +6,20 @@ pub enum MeshIntersectionError { MissingTopology, MissingPseudoNormals, TriTriError, + DuplicateVertices, } impl fmt::Display for MeshIntersectionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MissingTopology => { - f.pad("at least on of the meshes is missing its topology information. Call `mesh.compute_topology` on the mesh") + f.pad("at least one of the meshes is missing its topology information. Call `mesh.compute_topology` on the mesh") } Self::MissingPseudoNormals => { - f.pad("at least on of the meshes is missing its pseudo-normals. Call `mesh.compute_pseudo_normals` on the mesh") + f.pad("at least one of the meshes is missing its pseudo-normals. Call `mesh.compute_pseudo_normals` on the mesh") } Self::TriTriError => f.pad("internal failure while intersecting two triangles"), + Self::DuplicateVertices => f.pad("internal failure while merging faces resulting from intersections"), } } } From 7a1d96776424ea61357651985cd25fa3e94cefaf Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Thu, 11 Jul 2024 15:42:41 -0400 Subject: [PATCH 04/45] add test data --- .../mesh_intersection/test_data/bar.obj | 204 +++ .../test_data/center_cylinder.obj | 192 +++ .../test_data/low_poly_bunny.obj | 1396 +++++++++++++++++ .../test_data/offset_cylinder.obj | 192 +++ .../test_data/poly_cylinder.obj | 142 ++ .../mesh_intersection/test_data/stairs.obj | 72 + 6 files changed, 2198 insertions(+) create mode 100644 src/transformation/mesh_intersection/test_data/bar.obj create mode 100644 src/transformation/mesh_intersection/test_data/center_cylinder.obj create mode 100644 src/transformation/mesh_intersection/test_data/low_poly_bunny.obj create mode 100644 src/transformation/mesh_intersection/test_data/offset_cylinder.obj create mode 100644 src/transformation/mesh_intersection/test_data/poly_cylinder.obj create mode 100644 src/transformation/mesh_intersection/test_data/stairs.obj diff --git a/src/transformation/mesh_intersection/test_data/bar.obj b/src/transformation/mesh_intersection/test_data/bar.obj new file mode 100644 index 00000000..57c8370a --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/bar.obj @@ -0,0 +1,204 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object bar.obj +# +# Vertices: 64 +# Faces: 124 +# +#### +v 0.467654 13.305400 -6.892637 +v -0.550473 -3.651697 6.193181 +v 0.451122 13.300038 -6.900873 +v -0.567006 -3.657059 6.184945 +v 0.436815 13.292733 -6.911451 +v -0.581312 -3.664363 6.174367 +v 0.425285 13.283769 -6.923967 +v -0.592842 -3.673329 6.161853 +v 0.416975 13.273486 -6.937937 +v -0.601152 -3.683610 6.147883 +v 0.412204 13.262284 -6.952824 +v -0.605924 -3.694813 6.132995 +v 0.411154 13.250590 -6.968058 +v -0.606973 -3.706506 6.117761 +v 0.413868 13.238854 -6.983053 +v -0.604259 -3.718241 6.102765 +v 0.420240 13.227530 -6.997232 +v -0.597888 -3.729566 6.088586 +v 0.430025 13.217050 -7.010054 +v -0.588102 -3.740046 6.075767 +v 0.442847 13.207818 -7.021019 +v -0.575280 -3.749279 6.064799 +v 0.458215 13.200187 -7.029711 +v -0.559913 -3.756910 6.056108 +v 0.475536 13.194451 -7.035795 +v -0.542591 -3.762644 6.050025 +v 0.494146 13.190835 -7.039036 +v -0.523982 -3.766263 6.046782 +v 0.513329 13.189469 -7.039310 +v -0.504798 -3.767626 6.046510 +v 0.532348 13.190415 -7.036608 +v -0.485779 -3.766682 6.049212 +v 0.550472 13.193629 -7.031031 +v -0.467655 -3.763467 6.054788 +v 0.567005 13.198991 -7.022795 +v -0.451122 -3.758105 6.063024 +v 0.581311 13.206296 -7.012217 +v -0.436816 -3.750801 6.073602 +v 0.592841 13.215261 -6.999702 +v -0.425286 -3.741835 6.086116 +v 0.601151 13.225543 -6.985735 +v -0.416976 -3.731554 6.100085 +v 0.605923 13.236745 -6.970846 +v -0.412204 -3.720351 6.114974 +v 0.606972 13.248439 -6.955610 +v -0.411155 -3.708658 6.130208 +v 0.604259 13.260175 -6.940615 +v -0.413868 -3.696923 6.145204 +v 0.597887 13.271499 -6.926435 +v -0.420240 -3.685598 6.159383 +v 0.588102 13.281980 -6.913616 +v -0.430025 -3.675117 6.172202 +v 0.575279 13.291211 -6.902651 +v -0.442848 -3.665884 6.183169 +v 0.559912 13.298842 -6.893957 +v -0.458215 -3.658254 6.191861 +v 0.542591 13.304578 -6.887875 +v -0.475536 -3.652520 6.197946 +v 0.523981 13.308194 -6.884634 +v -0.494146 -3.648901 6.201186 +v 0.504798 13.309560 -6.884359 +v -0.513329 -3.647538 6.201461 +v 0.485779 13.308614 -6.887062 +v -0.532348 -3.648482 6.198758 +# 64 vertices, 0 vertices normals + +f 3 2 1 +f 6 3 5 +f 8 5 7 +f 10 7 9 +f 11 10 9 +f 13 12 11 +f 15 14 13 +f 17 16 15 +f 19 18 17 +f 21 20 19 +f 24 21 23 +f 26 23 25 +f 28 25 27 +f 29 28 27 +f 31 30 29 +f 33 32 31 +f 35 34 33 +f 37 36 35 +f 40 37 39 +f 42 39 41 +f 44 41 43 +f 45 44 43 +f 48 45 47 +f 50 47 49 +f 52 49 51 +f 54 51 53 +f 56 53 55 +f 57 56 55 +f 60 57 59 +f 62 59 61 +f 14 22 6 +f 64 61 63 +f 2 63 1 +f 63 55 31 +f 4 2 3 +f 4 3 6 +f 6 5 8 +f 8 7 10 +f 12 10 11 +f 14 12 13 +f 16 14 15 +f 18 16 17 +f 20 18 19 +f 22 20 21 +f 22 21 24 +f 24 23 26 +f 26 25 28 +f 30 28 29 +f 32 30 31 +f 34 32 33 +f 36 34 35 +f 38 36 37 +f 38 37 40 +f 40 39 42 +f 42 41 44 +f 46 44 45 +f 46 45 48 +f 48 47 50 +f 50 49 52 +f 52 51 54 +f 54 53 56 +f 58 56 57 +f 58 57 60 +f 60 59 62 +f 4 6 62 +f 2 4 62 +f 64 2 62 +f 60 62 58 +f 56 58 62 +f 54 56 62 +f 52 54 50 +f 48 50 46 +f 44 46 38 +f 42 44 38 +f 40 42 38 +f 36 38 30 +f 34 36 30 +f 32 34 30 +f 28 30 26 +f 24 26 22 +f 20 22 14 +f 18 20 14 +f 16 18 14 +f 12 14 6 +f 10 12 6 +f 8 10 6 +f 50 54 38 +f 46 50 38 +f 26 30 22 +f 62 6 38 +f 54 62 38 +f 30 38 6 +f 22 30 6 +f 62 61 64 +f 64 63 2 +f 1 63 3 +f 5 3 63 +f 7 5 63 +f 9 7 15 +f 11 9 15 +f 13 11 15 +f 17 15 19 +f 21 19 15 +f 23 21 15 +f 25 23 27 +f 29 27 31 +f 33 31 35 +f 37 35 31 +f 39 37 31 +f 41 39 43 +f 45 43 47 +f 49 47 51 +f 53 51 47 +f 55 53 47 +f 57 55 59 +f 61 59 63 +f 27 23 15 +f 31 27 15 +f 43 39 31 +f 47 43 31 +f 59 55 63 +f 7 63 31 +f 15 7 31 +f 55 47 31 +# 124 faces, 0 coords texture + +# End of File diff --git a/src/transformation/mesh_intersection/test_data/center_cylinder.obj b/src/transformation/mesh_intersection/test_data/center_cylinder.obj new file mode 100644 index 00000000..565a3cdc --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/center_cylinder.obj @@ -0,0 +1,192 @@ +# Blender v3.0.1 OBJ File: '' +# www.blender.org +o Cylinder +v 0.000000 -1.000000 -1.000000 +v 0.000000 1.000000 -1.000000 +v 0.195090 -1.000000 -0.980785 +v 0.195090 1.000000 -0.980785 +v 0.382683 -1.000000 -0.923880 +v 0.382683 1.000000 -0.923880 +v 0.555570 -1.000000 -0.831470 +v 0.555570 1.000000 -0.831470 +v 0.707107 -1.000000 -0.707107 +v 0.707107 1.000000 -0.707107 +v 0.831470 -1.000000 -0.555570 +v 0.831470 1.000000 -0.555570 +v 0.923880 -1.000000 -0.382683 +v 0.923880 1.000000 -0.382683 +v 0.980785 -1.000000 -0.195090 +v 0.980785 1.000000 -0.195090 +v 1.000000 -1.000000 0.000000 +v 1.000000 1.000000 0.000000 +v 0.980785 -1.000000 0.195090 +v 0.980785 1.000000 0.195090 +v 0.923880 -1.000000 0.382683 +v 0.923880 1.000000 0.382683 +v 0.831470 -1.000000 0.555570 +v 0.831470 1.000000 0.555570 +v 0.707107 -1.000000 0.707107 +v 0.707107 1.000000 0.707107 +v 0.555570 -1.000000 0.831470 +v 0.555570 1.000000 0.831470 +v 0.382683 -1.000000 0.923880 +v 0.382683 1.000000 0.923880 +v 0.195090 -1.000000 0.980785 +v 0.195090 1.000000 0.980785 +v -0.000000 -1.000000 1.000000 +v -0.000000 1.000000 1.000000 +v -0.195090 -1.000000 0.980785 +v -0.195090 1.000000 0.980785 +v -0.382683 -1.000000 0.923880 +v -0.382683 1.000000 0.923880 +v -0.555570 -1.000000 0.831470 +v -0.555570 1.000000 0.831470 +v -0.707107 -1.000000 0.707107 +v -0.707107 1.000000 0.707107 +v -0.831469 -1.000000 0.555570 +v -0.831469 1.000000 0.555570 +v -0.923880 -1.000000 0.382684 +v -0.923880 1.000000 0.382684 +v -0.980785 -1.000000 0.195090 +v -0.980785 1.000000 0.195090 +v -1.000000 -1.000000 -0.000000 +v -1.000000 1.000000 -0.000000 +v -0.980785 -1.000000 -0.195090 +v -0.980785 1.000000 -0.195090 +v -0.923879 -1.000000 -0.382684 +v -0.923879 1.000000 -0.382684 +v -0.831470 -1.000000 -0.555570 +v -0.831470 1.000000 -0.555570 +v -0.707107 -1.000000 -0.707107 +v -0.707107 1.000000 -0.707107 +v -0.555570 -1.000000 -0.831470 +v -0.555570 1.000000 -0.831470 +v -0.382683 -1.000000 -0.923880 +v -0.382683 1.000000 -0.923880 +v -0.195090 -1.000000 -0.980785 +v -0.195090 1.000000 -0.980785 +s off +f 2 3 1 +f 4 5 3 +f 6 7 5 +f 8 9 7 +f 10 11 9 +f 12 13 11 +f 14 15 13 +f 16 17 15 +f 18 19 17 +f 20 21 19 +f 22 23 21 +f 24 25 23 +f 26 27 25 +f 28 29 27 +f 30 31 29 +f 32 33 31 +f 34 35 33 +f 36 37 35 +f 38 39 37 +f 40 41 39 +f 42 43 41 +f 44 45 43 +f 46 47 45 +f 48 49 47 +f 50 51 49 +f 52 53 51 +f 54 55 53 +f 56 57 55 +f 58 59 57 +f 60 61 59 +f 54 38 22 +f 62 63 61 +f 64 1 63 +f 15 31 47 +f 2 4 3 +f 4 6 5 +f 6 8 7 +f 8 10 9 +f 10 12 11 +f 12 14 13 +f 14 16 15 +f 16 18 17 +f 18 20 19 +f 20 22 21 +f 22 24 23 +f 24 26 25 +f 26 28 27 +f 28 30 29 +f 30 32 31 +f 32 34 33 +f 34 36 35 +f 36 38 37 +f 38 40 39 +f 40 42 41 +f 42 44 43 +f 44 46 45 +f 46 48 47 +f 48 50 49 +f 50 52 51 +f 52 54 53 +f 54 56 55 +f 56 58 57 +f 58 60 59 +f 60 62 61 +f 6 4 62 +f 4 2 62 +f 2 64 62 +f 62 60 58 +f 58 56 62 +f 56 54 62 +f 54 52 50 +f 50 48 46 +f 46 44 42 +f 42 40 38 +f 38 36 34 +f 34 32 30 +f 30 28 26 +f 26 24 22 +f 22 20 18 +f 18 16 14 +f 14 12 10 +f 10 8 6 +f 54 50 38 +f 50 46 38 +f 46 42 38 +f 38 34 30 +f 30 26 38 +f 26 22 38 +f 22 18 6 +f 18 14 6 +f 14 10 6 +f 6 62 54 +f 6 54 22 +f 62 64 63 +f 64 2 1 +f 63 1 3 +f 3 5 7 +f 7 9 11 +f 11 13 15 +f 15 17 19 +f 19 21 23 +f 23 25 27 +f 27 29 31 +f 31 33 35 +f 35 37 39 +f 39 41 43 +f 43 45 47 +f 47 49 51 +f 51 53 47 +f 53 55 47 +f 55 57 63 +f 57 59 63 +f 59 61 63 +f 63 3 15 +f 3 7 15 +f 7 11 15 +f 15 19 23 +f 23 27 15 +f 27 31 15 +f 31 35 47 +f 35 39 47 +f 39 43 47 +f 47 55 63 +f 63 15 47 diff --git a/src/transformation/mesh_intersection/test_data/low_poly_bunny.obj b/src/transformation/mesh_intersection/test_data/low_poly_bunny.obj new file mode 100644 index 00000000..1cd8160a --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/low_poly_bunny.obj @@ -0,0 +1,1396 @@ +v 0.088770 1.286703 -0.402328 +v -0.237358 1.595642 0.123569 +v -1.125000 1.521824 0.796579 +v -1.675977 1.201319 0.040754 +v 0.637293 -0.006644 -0.160961 +v 0.245401 1.587681 0.486845 +v 0.306515 -0.284975 -0.426354 +v -0.776760 -0.116857 0.826870 +v -0.027808 1.027334 0.883640 +v 0.015771 0.886230 -0.670367 +v -0.779321 -0.300516 0.858120 +v -0.345437 1.465395 -0.130014 +v 0.484914 -0.299720 0.238641 +v -0.094634 -0.230486 0.200918 +v -0.690734 -0.322259 -0.419404 +v -1.002323 0.279923 0.693748 +v -1.691673 0.992154 -0.063988 +v -1.187880 -0.206945 0.398413 +v -0.700454 0.446739 0.836718 +v -1.688780 2.021224 0.632541 +v -0.179516 1.468788 0.700518 +v -1.163733 -0.016451 0.133963 +v 0.614266 1.331982 0.573045 +v -1.292738 2.067869 -0.002609 +v -0.697589 0.903487 0.895772 +v -0.553115 0.265960 0.743723 +v -0.907673 2.381575 0.025717 +v 1.141912 0.273601 0.516107 +v 0.437403 -0.303291 0.447249 +v 0.211614 -0.304826 0.541249 +v -0.302450 1.004593 0.881693 +v -0.361474 0.918233 1.043957 +v 0.777422 0.297245 -0.219951 +v -1.125659 0.244187 -0.128440 +v 0.771131 0.726767 -0.311357 +v -1.077368 0.171055 0.477747 +v -1.731113 1.183966 0.317156 +v -1.018205 0.710677 0.919940 +v -1.124609 -0.228734 0.958223 +v -0.211456 -0.217407 0.751920 +v -1.472710 2.505241 -0.837040 +v -1.306590 2.631176 -1.173387 +v -1.122428 -0.058767 -0.095476 +v -0.754680 -0.231501 -0.193115 +v -1.398670 2.112914 0.038787 +v 0.234889 1.104241 -0.401347 +v 0.097765 0.096534 -0.616585 +v -1.500235 1.146758 0.718896 +v -0.670191 2.478831 -0.020642 +v -0.930667 0.534924 -0.369422 +v 0.534702 0.396707 0.864832 +v -1.790483 0.963998 0.196709 +v -0.979360 1.620475 0.595260 +v -0.224198 2.292585 -0.273762 +v 0.318656 -0.053112 0.879959 +v -0.657217 -0.149747 -0.604688 +v -0.850177 2.437413 -0.172501 +v -1.285508 0.245704 -0.055830 +v -1.479020 0.435043 0.653007 +v 0.968271 0.211250 0.628987 +v 0.863557 0.332947 0.540158 +v -0.919580 -0.328048 0.107806 +v -0.718748 -0.317132 0.206641 +v -0.644636 -0.326972 0.400391 +v -1.342952 -0.223018 -0.128304 +v -0.720823 0.576003 -0.404734 +v -0.337128 0.710368 -0.791895 +v -1.270818 2.026028 0.739459 +v -0.978303 0.925187 0.884035 +v -0.470121 -0.037386 1.032238 +v 0.986357 -0.077431 0.492021 +v -1.611931 1.761430 -0.064956 +v -0.710258 -0.305473 0.775053 +v -1.425370 1.234800 0.977332 +v 0.919860 0.806306 0.057630 +v 0.844152 1.087791 0.126640 +v -1.152274 0.152310 0.109967 +v -1.816931 1.730446 0.332613 +v -0.522118 1.086486 0.851781 +v -0.410911 1.302278 -0.331441 +v -0.651317 1.297510 -0.323072 +v -0.507949 -0.241648 0.236043 +v -0.966024 1.413036 -0.267413 +v -1.253759 -0.300415 0.896741 +v 0.434215 -0.085258 -0.413636 +v 0.201754 0.746022 1.119187 +v -1.345331 2.044793 -0.961809 +v -0.982281 1.879877 0.151544 +v 0.169619 1.644939 0.280768 +v -0.365136 -0.297437 1.042007 +v -0.243028 2.514544 -0.440292 +v 0.504131 -0.154731 0.757366 +v -1.103126 2.070624 0.559535 +v -1.249353 -0.313633 0.394476 +v -1.280850 -0.163914 0.539172 +v -1.824687 1.996111 0.277494 +v -0.452083 0.862621 -0.687707 +v -1.011868 -0.072330 0.344236 +v -1.210809 2.417327 -1.241960 +v 0.888214 -0.015508 -0.118325 +v -1.317414 1.211810 0.747668 +v -0.250178 -0.046735 0.968372 +v -1.371637 2.358822 -0.455232 +v -0.357283 0.635818 1.149879 +v -0.162430 1.178153 0.874590 +v 0.307308 0.555817 -0.592592 +v -1.439766 1.703510 1.007824 +v 0.940102 0.470482 0.314923 +v -0.869262 0.824713 -0.444856 +v -0.000708 -0.277414 0.938915 +v 0.405405 0.312149 0.968318 +v 0.053858 -0.319867 0.086221 +v -1.697227 0.824448 -0.077251 +v -1.374595 -0.094946 0.107979 +v -1.345137 -0.294970 0.613325 +v -1.046990 -0.019141 0.507989 +v -0.413104 2.562906 -0.313807 +v -1.210733 1.437931 0.859106 +v -1.318737 1.431845 1.040827 +v -1.288047 1.929522 -0.425011 +v -0.318706 1.189421 -0.413767 +v -0.312010 2.730358 -0.517358 +v 0.031319 -0.314576 0.340269 +v -1.172122 0.851391 0.911670 +v -0.876298 1.513891 0.426197 +v -0.123102 1.508613 -0.178811 +v 0.601453 0.574139 0.888465 +v -1.224890 2.104145 0.070637 +v -1.663550 0.635510 0.589829 +v -0.633995 0.530108 -0.640556 +v -0.869803 -0.041610 -0.212523 +v 1.161832 0.100970 0.462145 +v -1.607313 1.392937 1.003375 +v -1.501493 1.992898 -0.532615 +v -1.339678 2.460943 -0.891817 +v 0.338816 -0.307084 0.047956 +v 0.708091 0.794717 0.800908 +v -1.265469 2.162605 -0.889537 +v -1.250326 0.443967 -0.331626 +v 0.899016 0.612787 -0.015984 +v -0.556916 0.696451 -0.727762 +v -1.366082 0.643331 0.860208 +v -1.703072 1.876421 0.128359 +v 0.250180 0.115686 1.025581 +v -1.256550 -0.077663 0.741935 +v -1.183592 -0.331282 -0.208945 +v 0.204146 1.421923 -0.272873 +v -0.955854 1.143830 -0.392172 +v -1.716935 1.490338 -0.074950 +v -0.908799 0.429790 0.834956 +v -1.538323 2.078191 -0.001137 +v -1.049203 0.010663 -0.139785 +v 0.704164 0.029651 0.639615 +v -1.458649 2.333704 -0.873450 +v 0.004969 0.372179 1.129627 +v -1.596361 2.397274 -0.800259 +v -1.341676 2.560165 -1.199939 +v 0.419052 0.131983 0.957949 +v -0.202714 -0.310556 0.887632 +v -0.115134 -0.319651 -0.478370 +v -1.438816 2.126505 0.550931 +v -0.783046 -0.082089 -0.454203 +v -0.544058 1.502474 0.347908 +v -0.081829 0.661604 -0.753178 +v 0.430927 0.243353 -0.527954 +v 0.342511 0.329771 -0.591951 +v -0.628960 0.667973 0.841685 +v -1.765286 1.762439 0.830310 +v -1.593169 0.428926 0.368551 +v -0.205053 -0.314581 -0.368238 +v -0.975391 1.573702 -0.067315 +v -1.080629 1.779159 0.582179 +v -0.286128 0.227649 -0.731269 +v -1.308441 2.218786 -1.174929 +v 0.165365 0.561680 1.120613 +v 0.937182 0.428210 0.078141 +v -1.476833 1.282447 -0.142186 +v -0.433806 2.714358 -0.250615 +v -0.012886 1.014192 -0.456335 +v -0.565310 1.352252 0.629078 +v 1.234315 0.254007 0.176538 +v -0.373302 -0.098943 -0.562156 +v -1.040106 -0.320691 0.505978 +v 1.099452 -0.069639 0.291576 +v -1.193130 1.964145 0.734747 +v -1.645526 0.502254 0.046539 +v -0.540201 0.908352 -0.491425 +v 0.570428 0.829597 -0.420398 +v -0.576187 0.086198 0.723778 +v 0.231792 -0.311216 -0.263775 +v 0.732278 0.083778 -0.132954 +v 1.141719 0.423271 0.086702 +v 0.643705 1.365763 0.110519 +v -0.776583 2.232682 0.011187 +v -1.419341 2.034858 -0.926695 +v -0.640916 0.032052 -0.209933 +v -0.608278 -0.226524 0.718841 +v -0.865838 0.052745 0.790804 +v -1.564612 2.166453 -0.413847 +v -0.905788 -0.262625 -0.475934 +v -0.301342 2.214073 -0.248339 +v 0.005170 0.251326 1.138458 +v -0.573455 1.514294 0.097736 +v -1.020513 2.030979 0.251655 +v 0.137022 -0.214321 0.616751 +v 0.711103 0.237502 0.768200 +v 0.127122 -0.214120 -0.225272 +v -1.806110 1.916116 0.569366 +v -1.277569 0.937860 -0.357568 +v 0.176886 0.443417 -0.667591 +v -1.113210 -0.108873 0.906066 +v -1.011517 0.021087 0.698274 +v -1.677539 0.679915 -0.076252 +v -1.528158 0.911803 -0.264668 +v 1.087176 -0.001257 0.029786 +v -1.506097 0.355873 0.058008 +v -0.470996 2.196668 -0.034051 +v 0.358495 -0.274806 0.859045 +v -0.458061 0.429134 -0.766367 +v -0.511038 0.890224 0.898911 +v -0.668958 0.184383 -0.231929 +v -1.381548 1.578558 -0.185131 +v -0.439620 0.025928 0.886406 +v 0.398810 0.591481 1.038245 +v -0.494721 0.660059 1.094944 +v -0.838277 0.707370 0.848427 +v 0.742359 -0.206825 -0.021296 +v -0.141354 1.596759 0.500398 +v -1.091671 0.943650 -0.446658 +v -1.764174 1.241225 0.871106 +v -1.210576 0.307507 0.664774 +v -0.074613 -0.145208 -0.508665 +v 0.342399 0.906800 0.957341 +v 0.155755 0.982028 0.982627 +v -0.022443 0.245175 -0.693814 +v -0.238162 0.501004 -0.767078 +v -1.040084 1.855343 0.020213 +v 0.550221 -0.014567 -0.376243 +v -1.413349 0.920041 0.836655 +v -1.865590 1.292859 0.256558 +v 1.131363 0.447779 0.402172 +v -0.426494 2.265286 -0.326361 +v -0.124100 0.698862 1.151555 +v -0.698915 -0.324743 -0.621120 +v -1.573628 0.825916 0.736794 +v -0.967770 1.370468 0.610515 +v -0.991614 0.085067 0.318619 +v 0.268490 -0.042224 -0.521637 +v -1.428416 1.826239 -0.158769 +v 0.574640 0.188610 -0.399206 +v -1.777570 1.746828 0.528811 +v 0.123443 1.610684 -0.003350 +v -1.492246 1.359446 1.073136 +v 0.681170 -0.289601 0.228171 +v -1.405828 0.224329 0.285109 +v 0.493389 0.901867 0.927212 +v 0.019799 0.960872 1.059991 +v -1.617425 1.577763 1.071003 +v -0.800488 2.131342 0.115520 +v -0.542822 -0.227411 -0.359371 +v -0.615801 0.255370 -0.410325 +v -1.254180 2.352787 -0.934974 +v 0.814158 0.980996 -0.083952 +v -1.464121 -0.279284 0.155621 +v 0.369606 1.022056 -0.451923 +v 0.367000 0.888857 -0.480172 +v -0.810730 1.496291 -0.188235 +v -1.548075 2.587439 -1.020768 +v 1.017473 0.202480 -0.094421 +v 0.608082 0.484621 -0.418798 +v 0.250226 1.215063 0.788707 +v 0.494691 1.230701 -0.257862 +v -1.228326 1.399494 -0.187916 +v -0.183017 2.542578 -0.614849 +v 0.341084 1.428278 0.677115 +v -1.811080 1.649729 0.105226 +v -1.563434 2.122153 0.411995 +v -1.632931 1.191555 0.573243 +v 0.248308 0.323535 1.074198 +v 0.886995 0.829980 0.479410 +v -1.105279 1.280555 0.705321 +v -0.672986 -0.155706 0.986040 +v -0.565846 2.088861 -0.077108 +v -1.223974 2.175265 -0.454547 +v -1.864966 1.422518 0.849826 +v 0.474841 1.468868 -0.016535 +v 0.406291 -0.293454 -0.046927 +v -0.173724 2.371577 -0.487548 +v -1.543734 0.457225 -0.109284 +v 0.691319 1.116551 -0.214524 +v -0.262840 0.992519 -0.489889 +v -0.536299 1.112677 -0.457167 +v -1.777837 0.993397 0.440722 +v -1.486874 2.096864 -0.439157 +v -0.798384 0.990198 0.831084 +v -0.741115 2.246469 -0.276917 +v -1.524776 2.305617 -0.417891 +v -0.563827 -0.018832 -0.486544 +v -0.263735 0.858539 1.123588 +v -1.267050 1.841575 -0.136076 +v -0.954582 1.133881 0.803952 +v 0.490460 1.125336 0.795452 +v -0.126344 0.362811 -0.708438 +v -0.891146 1.598640 0.220774 +v -0.070018 0.479193 1.187786 +v -1.199467 0.496492 0.838375 +v -0.330814 0.231838 1.053564 +v -1.242676 1.743577 0.713482 +v 0.051788 1.279042 0.835333 +v 0.845702 -0.038298 0.654686 +v -1.794907 0.805548 0.393436 +v -1.162147 1.151608 -0.375568 +v -1.118677 2.115250 0.232827 +v -1.905789 1.514014 0.420251 +v 0.540473 0.218553 0.876277 +v 0.428260 0.093019 -0.543882 +v -1.548273 1.898256 0.895887 +v -0.711679 1.104614 -0.412796 +v 0.440645 1.512562 0.443111 +v 0.018526 0.028101 1.082396 +v -0.752117 1.416690 0.597296 +v -1.738766 0.651866 0.192682 +v -1.479460 0.663107 -0.305621 +v -1.543330 2.001675 -0.088456 +v -0.667335 0.730400 -0.503039 +v -0.243881 0.388424 1.100894 +v -1.084792 2.018689 -0.051406 +v -1.240990 1.939813 -0.059667 +v -0.475402 2.527752 -0.418690 +v -1.180211 0.612930 -0.416339 +v 0.746305 1.060499 0.609414 +v -1.725012 1.542070 0.984246 +v -0.542900 0.218017 -0.587722 +v -1.865483 1.396082 0.595172 +v -0.095870 0.157725 1.102226 +v -0.075278 -0.027518 -0.617613 +v -0.651977 1.446782 -0.145062 +v -1.779600 0.752411 0.135209 +v 0.624036 -0.241836 0.499252 +v -1.138333 1.017194 0.857497 +v -0.756902 0.644740 0.876448 +v -1.727128 0.608488 0.348094 +v -0.095799 1.219323 -0.430231 +v -1.142637 1.746613 -0.105149 +v -1.446628 -0.324673 -0.014624 +v 0.154657 -0.313672 0.815652 +v 0.207536 0.828623 -0.626683 +v -0.911998 0.294079 -0.290498 +v -0.516087 -0.228438 1.105902 +v -0.965888 -0.328775 -0.264877 +vn 0.3426 -0.8427 0.4152 +vn 0.7137 -0.6992 0.0409 +vn 0.5350 -0.7232 0.4367 +vn 0.7303 0.6516 0.2052 +vn 0.5477 0.6918 -0.4706 +vn 0.6836 0.4825 -0.5477 +vn -0.4756 0.1252 -0.8707 +vn -0.6597 -0.0478 -0.7500 +vn -0.0488 0.0570 -0.9972 +vn -0.6065 -0.5049 0.6142 +vn -0.7001 0.6814 0.2135 +vn -0.8887 -0.4572 -0.0357 +vn 0.1656 0.7894 -0.5912 +vn -0.0596 0.3574 -0.9320 +vn -0.0401 0.5918 -0.8051 +vn -0.7305 -0.6504 0.2082 +vn -0.8793 0.4170 -0.2301 +vn -0.6142 0.5555 0.5605 +vn -0.8180 -0.3774 -0.4341 +vn -0.8045 0.5373 -0.2530 +vn -0.4600 0.1647 -0.8725 +vn -0.2088 -0.2977 0.9316 +vn 0.3507 0.3619 0.8637 +vn -0.3733 -0.2847 0.8829 +vn 0.1900 -0.9807 -0.0454 +vn 0.1934 -0.9717 -0.1355 +vn 0.5358 -0.8219 0.1935 +vn 0.8484 0.3150 -0.4253 +vn 0.8942 0.4415 -0.0734 +vn 0.9704 0.1656 -0.1755 +vn 0.3394 -0.7741 -0.5345 +vn 0.3296 -0.9135 -0.2386 +vn 0.5452 -0.4688 -0.6950 +vn 0.7484 -0.4452 -0.4916 +vn 0.5837 -0.5469 -0.6002 +vn 0.4636 -0.1905 -0.8653 +vn -0.0436 0.3244 0.9449 +vn 0.0903 0.4742 0.8757 +vn 0.1590 0.2098 0.9647 +vn -0.9872 0.0597 0.1479 +vn -0.9746 0.2092 -0.0803 +vn -0.9894 0.1271 0.0708 +vn -0.2404 -0.5754 -0.7818 +vn -0.6521 -0.5784 -0.4901 +vn -0.5611 -0.1786 -0.8082 +vn -0.8618 0.4550 -0.2243 +vn -0.9283 0.2885 0.2345 +vn -0.4987 0.7761 0.3861 +vn -0.1982 0.4065 -0.8919 +vn -0.8221 -0.1058 -0.5594 +vn -0.5135 0.7182 -0.4696 +vn -0.0158 -0.9993 0.0328 +vn 0.0865 -0.9960 -0.0243 +vn 0.0820 0.9955 0.0470 +vn -0.6447 0.6741 0.3604 +vn -0.6107 0.7883 -0.0754 +vn 0.4471 -0.6123 0.6521 +vn 0.1743 -0.5517 0.8156 +vn -0.0442 -0.9981 0.0435 +vn 0.6526 0.0258 -0.7572 +vn -0.0156 -0.5983 -0.8011 +vn 0.3386 -0.9403 0.0341 +vn 0.7793 0.3531 0.5176 +vn 0.5718 0.1304 -0.8099 +vn 0.1365 0.8806 -0.4539 +vn 0.7225 -0.1222 0.6805 +vn 0.3230 0.1616 0.9325 +vn 0.5580 0.2046 0.8042 +vn 0.6760 0.6274 0.3866 +vn 0.0809 0.9344 0.3468 +vn -0.0609 0.9632 0.2619 +vn -0.4330 0.5375 0.7236 +vn -0.0034 -0.0647 0.9979 +vn -0.5024 -0.2688 0.8218 +vn 0.5920 0.2534 0.7651 +vn 0.1527 0.8770 0.4556 +vn 0.3349 0.4961 0.8011 +vn -0.8285 -0.4312 0.3573 +vn -0.9550 -0.2778 0.1040 +vn -0.9589 -0.2702 0.0861 +vn -0.8741 -0.4553 -0.1691 +vn -0.8419 -0.0508 -0.5372 +vn -0.9364 -0.2849 0.2046 +vn -0.8414 0.5178 0.1546 +vn -0.2568 0.9657 0.0396 +vn -0.2355 0.9010 -0.3644 +vn -0.4672 -0.8829 -0.0463 +vn -0.0120 -0.9515 -0.3075 +vn 0.4191 -0.7146 -0.5601 +vn -0.6720 0.7359 0.0828 +vn -0.5670 0.5585 -0.6055 +vn -0.6226 0.2899 -0.7269 +vn 0.4303 0.3229 0.8430 +vn 0.4516 0.0463 0.8910 +vn 0.6007 -0.0644 0.7969 +vn 0.3807 0.2200 -0.8981 +vn -0.7021 -0.7022 0.1183 +vn -0.7981 -0.5187 -0.3066 +vn -0.4663 -0.8499 0.2456 +vn -0.3695 -0.9141 -0.1672 +vn 0.0482 -0.9988 -0.0116 +vn 0.6007 -0.1522 -0.7848 +vn -0.3162 -0.0712 -0.9460 +vn -0.3613 -0.2318 -0.9032 +vn -0.4531 -0.4804 0.7509 +vn 0.2551 -0.2389 0.9369 +vn 0.2301 -0.7603 0.6074 +vn 0.5181 -0.0518 -0.8537 +vn 0.4267 -0.1009 -0.8987 +vn 0.6424 -0.0366 -0.7655 +vn -0.0187 -0.0736 0.9971 +vn -0.2920 -0.1236 0.9484 +vn -0.1841 0.1166 0.9760 +vn 0.7665 -0.4567 -0.4516 +vn 0.8583 -0.0856 0.5059 +vn -0.1776 0.8686 -0.4626 +vn -0.0644 0.6608 -0.7478 +vn 0.0033 0.5807 -0.8141 +vn -0.8960 -0.4405 -0.0560 +vn -0.8055 -0.3476 0.4800 +vn -0.9589 -0.1808 0.2187 +vn -0.3672 0.3360 0.8673 +vn -0.6780 0.0461 0.7336 +vn 0.1242 0.5300 -0.8389 +vn 0.2084 0.4047 -0.8904 +vn 0.2847 0.4773 -0.8314 +vn -0.7638 0.5383 -0.3561 +vn -0.7104 0.7034 -0.0237 +vn -0.0837 0.7561 0.6491 +vn -0.1505 0.7227 0.6745 +vn -0.0646 0.9641 0.2575 +vn 0.9679 -0.1526 0.1999 +vn 0.7733 0.2808 0.5684 +vn -0.1433 0.4000 0.9052 +vn 0.0362 0.7660 0.6419 +vn -0.5553 -0.4357 0.7084 +vn -0.2047 -0.4671 0.8602 +vn -0.3092 -0.7768 0.5486 +vn -0.2712 -0.6027 -0.7505 +vn 0.2025 0.8502 -0.4859 +vn -0.3543 0.1125 -0.9284 +vn -0.2344 0.1964 -0.9521 +vn 0.3349 0.8659 0.3716 +vn 0.6122 -0.0741 0.7873 +vn 0.4813 -0.8660 0.1358 +vn -0.1167 -0.2780 0.9535 +vn 0.5229 0.7700 0.3656 +vn 0.9820 0.0297 -0.1867 +vn -0.3797 -0.8331 -0.4022 +vn -0.6427 -0.7398 -0.1989 +vn 0.5701 -0.7730 -0.2783 +vn 0.6006 -0.2295 -0.7659 +vn -0.3601 -0.3949 -0.8452 +vn -0.8063 0.3939 0.4413 +vn 0.1591 -0.3946 -0.9050 +vn 0.1931 -0.5181 -0.8332 +vn 0.2447 -0.3310 -0.9113 +vn -0.0649 0.3823 0.9218 +vn 0.2160 -0.1580 -0.9635 +vn 0.1196 -0.1383 -0.9831 +vn -0.4369 -0.7529 -0.4922 +vn 0.2158 -0.1663 0.9622 +vn 0.7199 0.2118 0.6609 +vn 0.8415 -0.3006 0.4489 +vn 0.0297 -0.9858 -0.1651 +vn -0.4272 -0.8742 0.2306 +vn -0.0457 -0.9989 0.0084 +vn -0.9275 0.1671 0.3343 +vn -0.5768 -0.2795 -0.7676 +vn -0.0662 -0.3587 -0.9311 +vn -0.0862 -0.0453 -0.9952 +vn -0.2455 -0.9656 0.0858 +vn -0.1435 0.4377 0.8876 +vn -0.2133 -0.0325 0.9764 +vn 0.0171 0.1288 0.9915 +vn 0.7556 0.3396 0.5601 +vn -0.1675 -0.2187 -0.9613 +vn -0.1971 0.6597 0.7252 +vn 0.2758 0.6566 0.7020 +vn 0.7606 0.0756 -0.6448 +vn 0.6788 0.2354 0.6956 +vn 0.5872 0.0624 0.8070 +vn -0.5787 -0.6946 0.4274 +vn 0.4096 0.2410 0.8798 +vn -0.0968 -0.6580 -0.7468 +vn 0.4860 -0.8727 -0.0471 +vn -0.2362 -0.9717 -0.0074 +vn -0.4839 -0.5874 -0.6487 +vn -0.7779 -0.6278 -0.0267 +vn -0.8490 -0.4669 -0.2473 +vn -0.6610 0.6323 -0.4040 +vn -0.3863 0.7952 0.4673 +vn 0.1676 0.9857 0.0176 +vn -0.1522 0.9327 0.3269 +vn 0.2260 0.8994 0.3742 +vn -0.3571 -0.2155 0.9089 +vn -0.3032 0.2664 0.9149 +vn 0.7269 -0.4996 -0.4712 +vn -0.7586 -0.2943 -0.5814 +vn -0.8366 -0.1244 -0.5335 +vn -0.3514 -0.2035 -0.9138 +vn 0.0814 0.7005 0.7090 +vn -0.0177 0.6348 0.7724 +vn -0.3925 0.6990 0.5978 +vn 0.1502 -0.9820 0.1147 +vn -0.6315 -0.6765 -0.3789 +vn -0.6901 -0.0133 0.7236 +vn -0.3943 0.1783 0.9015 +vn 0.9095 -0.0128 -0.4156 +vn 0.8159 -0.1909 -0.5457 +vn 0.2008 -0.3908 0.8983 +vn 0.1394 -0.9672 0.2124 +vn 0.0355 -0.8495 0.5263 +vn 0.3063 -0.9519 -0.0003 +vn 0.4003 0.2449 -0.8830 +vn -0.4490 0.3074 -0.8390 +vn -0.9892 0.0242 -0.1444 +vn 0.6429 -0.4686 0.6059 +vn 0.1501 0.4162 -0.8968 +vn -0.0388 0.2941 0.9550 +vn 0.1367 0.3194 0.9377 +vn 0.3290 0.8786 -0.3461 +vn 0.3878 -0.1484 0.9097 +vn 0.3687 -0.3298 0.8691 +vn 0.5691 -0.1234 0.8130 +vn 0.0517 -0.9987 -0.0025 +vn 0.4554 0.0788 -0.8868 +vn 0.5274 0.2360 -0.8162 +vn -0.9325 0.0351 -0.3594 +vn -0.1049 0.3355 -0.9362 +vn 0.3955 0.5216 0.7559 +vn 0.2253 0.4448 0.8668 +vn 0.1537 0.6513 0.7431 +vn -0.9405 -0.2408 -0.2398 +vn -0.9691 -0.2378 -0.0648 +vn 0.3931 -0.3091 0.8660 +vn 0.0945 -0.4971 0.8625 +vn 0.3898 0.4327 0.8129 +vn -0.8203 -0.5367 -0.1973 +vn -0.0537 -0.1847 -0.9813 +vn 0.2318 -0.4039 -0.8849 +vn -0.5737 0.3267 -0.7511 +vn 0.7911 0.3964 0.4659 +vn 0.7264 0.1545 0.6697 +vn 0.9357 0.1886 0.2983 +vn 0.1855 -0.3253 0.9273 +vn 0.2335 0.2371 0.9430 +vn 0.5402 0.3444 0.7678 +vn 0.1300 -0.8009 -0.5845 +vn -0.2580 -0.0229 0.9659 +vn -0.0201 -0.1369 0.9904 +vn 0.3384 -0.9107 -0.2367 +vn 0.1301 -0.9909 -0.0350 +vn -0.2088 -0.5878 -0.7816 +vn -0.1166 0.1144 0.9866 +vn 0.4329 -0.0985 -0.8961 +vn 0.7391 -0.1754 -0.6504 +vn 0.9453 -0.2994 -0.1298 +vn 0.3819 -0.8117 -0.4420 +vn -0.3257 0.4494 -0.8319 +vn -0.3433 0.6871 -0.6403 +vn -0.6563 0.1388 -0.7416 +vn -0.0447 0.4931 -0.8688 +vn -0.1363 0.4069 -0.9032 +vn 0.3530 0.6798 -0.6428 +vn 0.4251 0.5568 -0.7136 +vn 0.5038 0.7953 -0.3372 +vn 0.6884 0.4254 -0.5875 +vn -0.8354 -0.2527 -0.4881 +vn -0.9552 -0.2164 -0.2021 +vn -0.8470 -0.0251 -0.5310 +vn -0.9223 -0.2065 0.3268 +vn 0.9513 0.2560 0.1715 +vn 0.6482 0.6769 0.3487 +vn -0.6243 -0.0827 -0.7768 +vn -0.1759 -0.7897 0.5877 +vn 0.0879 -0.1576 0.9836 +vn 0.2887 -0.0042 0.9574 +vn 0.7151 0.6878 -0.1242 +vn 0.5129 0.8340 0.2036 +vn 0.6570 0.6009 0.4552 +vn -0.6043 0.1230 -0.7872 +vn 0.0999 -0.0844 0.9914 +vn -0.0088 0.0029 1.0000 +vn -0.6472 0.2352 -0.7251 +vn -0.8818 0.2468 -0.4020 +vn -0.8340 0.1349 -0.5351 +vn 0.4974 0.7572 0.4235 +vn 0.6940 0.3859 -0.6078 +vn 0.1152 0.5752 -0.8099 +vn -0.0726 -0.8899 0.4504 +vn -0.1857 -0.2350 0.9541 +vn -0.6501 0.3010 0.6977 +vn -0.1643 0.6143 0.7718 +vn 0.3429 -0.3485 -0.8723 +vn -0.1503 0.0283 0.9882 +vn -0.7577 -0.6347 0.1519 +vn -0.2384 0.4328 0.8694 +vn -0.2574 -0.3028 -0.9176 +vn -0.4710 -0.2972 -0.8306 +vn -0.1915 0.9708 -0.1442 +vn 0.1182 0.9396 -0.3212 +vn -0.1266 0.8217 -0.5557 +vn -0.0632 0.8997 0.4320 +vn 0.5512 -0.1910 0.8122 +vn 0.7059 -0.2009 0.6792 +vn 0.3346 0.3881 -0.8587 +vn 0.3166 -0.0885 -0.9444 +vn 0.2364 0.5971 -0.7666 +vn -0.5864 0.1391 -0.7980 +vn -0.2323 0.2642 -0.9361 +vn -0.7547 0.5948 0.2768 +vn 0.5743 0.0921 0.8134 +vn -0.1443 0.5360 0.8318 +vn -0.1355 0.3853 -0.9128 +vn -0.0497 0.2330 -0.9712 +vn 0.2686 0.2618 0.9270 +vn -0.9220 -0.0919 0.3762 +vn -0.8305 0.1980 -0.5206 +vn -0.0735 0.6562 0.7510 +vn 0.2398 0.6940 -0.6789 +vn -0.9776 0.1451 -0.1528 +vn 0.6672 0.7439 0.0397 +vn 0.8073 0.3331 0.4872 +vn 0.0364 0.9885 -0.1469 +vn -0.6598 0.0449 0.7501 +vn -0.5067 -0.8582 0.0826 +vn -0.1436 -0.2482 0.9580 +vn -0.5641 -0.6250 0.5396 +vn 0.4818 0.1195 0.8681 +vn -0.0321 0.6014 -0.7983 +vn -0.0241 0.4170 -0.9086 +vn -0.1583 -0.1923 0.9685 +vn -0.0529 -0.1448 0.9880 +vn -0.5747 0.3958 -0.7163 +vn -0.9176 -0.2231 -0.3288 +vn -0.3776 -0.2547 0.8903 +vn -0.6125 0.2527 -0.7490 +vn 0.5612 -0.1604 0.8120 +vn 0.3301 0.6045 0.7250 +vn 0.9687 0.2229 0.1092 +vn -0.9776 -0.1581 0.1393 +vn 0.5660 -0.3290 0.7559 +vn -0.0994 -0.0641 0.9930 +vn 0.2604 0.0657 -0.9633 +vn 0.0081 -0.2936 -0.9559 +vn 0.1141 -0.7767 0.6195 +vn 0.1131 0.4315 -0.8950 +vn 0.1528 -0.1302 -0.9796 +vn 0.6308 -0.1048 0.7689 +f 339//1 184//2 71//3 +f 108//4 192//5 176//6 +f 177//7 149//8 222//9 +f 84//10 145//11 115//12 +f 267//13 148//14 83//15 +f 36//16 116//17 212//18 +f 154//19 268//20 157//21 +f 253//22 107//23 133//24 +f 82//25 63//26 44//27 +f 263//28 76//29 75//30 +f 227//31 287//32 5//33 +f 238//34 85//35 316//36 +f 295//37 301//38 69//39 +f 251//40 78//41 314//42 +f 139//43 289//44 323//45 +f 96//46 208//47 20//48 +f 56//49 200//50 162//51 +f 183//52 62//53 63//26 +f 45//54 297//55 151//56 +f 5//33 287//32 238//34 +f 218//57 110//58 346//59 +f 99//60 174//61 157//21 +f 183//52 63//26 64//62 +f 91//63 274//64 122//65 +f 118//66 281//67 3//68 +f 64//62 63//26 82//25 +f 93//69 313//70 161//71 +f 223//72 102//73 307//74 +f 287//32 85//35 238//34 +f 194//75 178//76 49//77 +f 278//78 334//79 37//80 +f 240//81 149//8 4//82 +f 247//83 98//84 116//17 +f 277//85 128//86 45//54 +f 205//87 346//59 40//88 +f 85//35 287//32 7//89 +f 114//90 43//91 65//92 +f 256//93 224//94 127//95 +f 192//5 269//96 176//6 +f 94//97 345//98 183//52 +f 123//99 112//100 13//101 +f 237//102 327//103 296//104 +f 48//105 101//106 74//107 +f 165//108 166//109 270//110 +f 341//111 167//112 25//113 +f 288//114 274//64 54//115 +f 12//116 80//117 81//118 +f 322//119 129//120 311//121 +f 220//122 167//112 225//123 +f 179//124 265//125 266//126 +f 94//97 95//127 18//128 +f 180//129 21//130 163//131 +f 88//132 204//133 93//69 +f 79//134 321//135 295//37 +f 295//37 321//135 301//38 +f 16//136 306//137 231//138 +f 345//98 65//92 146//139 +f 84//10 115//12 94//97 +f 62//53 345//98 146//139 +f 267//13 337//140 81//118 +f 209//141 148//14 229//142 +f 45//54 103//143 297//55 +f 119//144 107//23 253//22 +f 237//102 283//145 88//132 +f 150//146 306//137 16//136 +f 192//5 241//147 181//148 +f 289//44 58//149 216//150 +f 120//151 300//152 249//153 +f 20//48 208//47 168//154 +f 232//155 336//156 47//157 +f 301//38 340//158 69//39 +f 336//156 303//159 235//160 +f 350//161 62//53 146//139 +f 341//111 150//146 19//162 +f 181//148 28//163 132//164 +f 346//59 159//165 40//88 +f 112//100 207//166 190//167 +f 278//78 37//80 293//168 +f 66//169 50//170 109//171 +f 277//85 45//54 151//56 +f 205//87 30//172 346//59 +f 299//173 104//174 243//175 +f 204//133 313//70 93//69 +f 172//176 88//132 93//69 +f 209//141 330//177 323//45 +f 317//178 68//179 161//71 +f 75//30 35//180 263//28 +f 308//181 185//182 68//179 +f 74//107 133//24 230//183 +f 88//132 283//145 259//184 +f 242//185 201//186 283//145 +f 207//166 112//100 14//187 +f 34//188 139//43 50//170 +f 34//188 77//189 58//149 +f 30//172 123//99 13//101 +f 120//151 249//153 134//190 +f 57//191 27//192 49//77 +f 89//193 228//194 6//195 +f 201//186 242//185 288//114 +f 116//17 36//16 247//83 +f 223//72 26//196 189//197 +f 269//96 181//148 215//198 +f 333//199 130//200 219//201 +f 180//129 79//134 21//130 +f 257//202 9//203 32//204 +f 30//172 13//101 29//205 +f 27//192 57//191 128//86 +f 195//206 154//19 157//21 +f 48//105 245//207 239//208 +f 62//53 44//27 63//26 +f 208//47 78//41 251//40 +f 128//86 277//85 161//71 +f 74//107 118//66 119//144 +f 140//209 33//210 270//110 +f 310//211 71//3 132//164 +f 242//185 237//102 296//104 +f 44//27 15//212 260//213 +f 289//44 139//43 58//149 +f 227//31 184//2 254//214 +f 27//192 204//133 49//77 +f 249//153 344//215 222//9 +f 249//153 300//152 344//215 +f 146//139 131//216 350//161 +f 165//108 316//36 47//157 +f 268//20 154//19 156//217 +f 19//162 167//112 341//111 +f 48//105 239//208 101//106 +f 115//12 95//127 94//97 +f 218//57 29//205 92//218 +f 46//219 265//125 179//124 +f 330//177 50//170 139//43 +f 21//130 105//220 309//221 +f 339//1 92//218 29//205 +f 42//222 99//60 157//21 +f 279//223 144//224 111//225 +f 7//89 287//32 190//167 +f 136//226 13//101 112//100 +f 108//4 176//6 75//30 +f 270//110 106//227 188//228 +f 324//229 96//46 151//56 +f 318//230 229//142 148//14 +f 302//231 271//232 234//233 +f 194//75 204//133 259//184 +f 36//16 77//189 247//83 +f 156//217 294//234 199//235 +f 102//73 55//236 320//237 +f 321//135 246//238 301//38 +f 186//239 289//44 216//150 +f 232//155 182//240 336//156 +f 316//36 85//35 248//241 +f 196//242 162//51 131//216 +f 128//86 313//70 27//192 +f 331//243 137//244 280//245 +f 153//246 60//247 61//248 +f 7//89 160//249 232//155 +f 205//87 40//88 14//187 +f 198//250 19//162 150//146 +f 202//251 144//224 279//223 +f 82//25 197//252 73//253 +f 4//82 37//80 240//81 +f 32//204 225//123 299//173 +f 200//50 244//254 350//161 +f 340//158 124//255 69//39 +f 191//256 5//33 33//210 +f 167//112 26//196 225//123 +f 165//108 270//110 250//257 +f 174//61 138//258 87//259 +f 187//260 97//261 325//262 +f 87//259 120//151 195//206 +f 21//130 79//134 105//220 +f 121//263 292//264 80//117 +f 171//265 267//13 83//15 +f 7//89 190//167 160//249 +f 228//194 21//130 6//195 +f 272//266 286//267 290//268 +f 213//269 338//270 113//271 +f 101//106 118//66 74//107 +f 229//142 330//177 209//141 +f 55//236 110//58 218//57 +f 114//90 345//98 264//272 +f 42//222 262//273 99//60 +f 42//222 135//274 262//273 +f 77//189 152//275 43//91 +f 73//253 197//252 159//165 +f 84//10 183//52 11//276 +f 344//215 171//265 222//9 +f 155//277 279//223 175//278 +f 82//25 14//187 40//88 +f 193//279 319//280 23//281 +f 122//65 274//64 329//282 +f 38//283 150//146 226//284 +f 188//228 265//125 290//268 +f 72//285 276//286 143//287 +f 246//238 321//135 125//288 +f 328//289 24//290 128//86 +f 60//247 153//246 310//211 +f 57//191 178//76 329//282 +f 277//85 20//48 161//71 +f 274//64 242//185 329//282 +f 64//62 73//253 183//52 +f 170//291 15//212 160//249 +f 235//160 166//109 47//157 +f 18//128 95//127 116//17 +f 81//118 80//117 318//230 +f 349//292 282//293 11//276 +f 105//220 9//203 271//232 +f 211//294 212//18 145//11 +f 269//96 100//295 191//256 +f 146//139 65//92 131//216 +f 317//178 168//154 258//296 +f 169//297 186//239 216//150 +f 198//250 8//298 189//197 +f 278//78 230//183 334//79 +f 40//88 159//165 197//252 +f 50//170 348//299 34//188 +f 221//300 348//299 66//169 +f 163//131 321//135 180//129 +f 114//90 65//92 345//98 +f 2//301 252//302 126//303 +f 183//52 345//98 62//53 +f 297//55 41//304 156//217 +f 51//305 206//306 127//95 +f 104//174 225//123 307//74 +f 290//268 193//279 76//29 +f 179//124 266//126 347//307 +f 106//227 210//308 10//309 +f 209//141 214//310 312//311 +f 18//128 98//84 22//312 +f 294//234 156//217 154//19 +f 183//52 73//253 11//276 +f 339//1 71//3 310//211 +f 217//313 194//75 259//184 +f 23//281 331//243 76//29 +f 105//220 79//134 31//314 +f 316//36 248//241 47//157 +f 29//205 254//214 339//1 +f 273//315 171//265 83//15 +f 318//230 292//264 325//262 +f 54//115 201//186 288//114 +f 302//231 137//244 331//243 +f 62//53 15//212 44//27 +f 185//182 308//181 93//69 +f 184//2 132//164 71//3 +f 206//306 153//246 61//248 +f 200//50 56//49 244//254 +f 108//4 241//147 192//5 +f 166//109 210//308 106//227 +f 160//249 244//254 232//155 +f 10//309 67//316 97//261 +f 201//186 217//313 283//145 +f 201//186 54//115 217//313 +f 233//317 256//93 234//233 +f 195//206 294//234 154//19 +f 318//230 109//171 229//142 +f 26//196 198//250 189//197 +f 224//94 279//223 111//225 +f 94//97 18//128 114//90 +f 48//105 230//183 278//78 +f 285//318 334//79 230//183 +f 43//91 114//90 22//312 +f 213//269 113//271 214//310 +f 177//7 17//319 4//82 +f 105//220 31//314 9//203 +f 70//320 102//73 223//72 +f 64//62 82//25 73//253 +f 314//42 276//286 240//81 +f 176//6 191//256 33//210 +f 176//6 269//96 191//256 +f 126//303 252//302 147//321 +f 270//110 33//210 250//257 +f 15//212 350//161 244//254 +f 227//31 215//198 184//2 +f 273//315 83//15 312//311 +f 4//82 52//322 37//80 +f 120//151 134//190 195//206 +f 174//61 87//259 195//206 +f 318//230 80//117 292//264 +f 61//248 241//147 108//4 +f 304//323 53//324 125//288 +f 66//169 325//262 130//200 +f 346//59 110//58 159//165 +f 61//248 60//247 241//147 +f 125//288 321//135 163//131 +f 288//114 242//185 274//64 +f 2//301 12//116 203//325 +f 38//283 69//39 124//255 +f 330//177 109//171 50//170 +f 99//60 138//258 174//61 +f 332//326 285//318 230//183 +f 53//324 88//132 172//176 +f 169//297 216//150 255//327 +f 13//101 287//32 254//214 +f 287//32 227//31 254//214 +f 335//328 102//73 320//237 +f 304//323 88//132 53//324 +f 15//212 62//53 350//161 +f 231//138 306//137 59//329 +f 234//233 86//330 233//317 +f 163//131 203//325 304//323 +f 240//81 334//79 314//42 +f 208//47 96//46 78//41 +f 290//268 263//28 188//228 +f 193//279 23//281 76//29 +f 157//21 268//20 42//222 +f 13//101 136//226 190//167 +f 291//331 121//263 343//332 +f 324//229 143//287 96//46 +f 134//190 294//234 195//206 +f 326//333 155//277 305//334 +f 55//236 144//224 320//237 +f 333//199 182//240 298//335 +f 188//228 106//227 347//307 +f 281//67 340//158 301//38 +f 102//73 335//328 307//74 +f 196//242 261//336 298//335 +f 8//298 198//250 212//18 +f 129//120 142//337 245//207 +f 271//232 9//203 234//233 +f 168//154 317//178 20//48 +f 36//16 59//329 255//327 +f 153//246 206//306 339//1 +f 82//25 40//88 197//252 +f 116//17 145//11 212//18 +f 235//160 47//157 336//156 +f 67//316 219//201 141//338 +f 299//173 243//175 257//202 +f 315//339 206//306 51//305 +f 244//254 160//249 15//212 +f 121//263 291//331 292//264 +f 78//41 276//286 314//42 +f 55//236 92//218 206//306 +f 167//112 19//162 26//196 +f 148//14 81//118 318//230 +f 111//225 51//305 127//95 +f 31//314 32//204 9//203 +f 225//123 104//174 299//173 +f 139//43 34//188 58//149 +f 3//68 53//324 172//176 +f 309//221 271//232 275//340 +f 262//273 103//143 284//341 +f 271//232 23//281 275//340 +f 95//127 145//11 116//17 +f 145//11 95//127 115//12 +f 313//70 204//133 27//192 +f 101//106 340//158 281//67 +f 342//342 129//120 322//119 +f 260//213 82//25 44//27 +f 249//153 72//285 324//229 +f 45//54 128//86 24//290 +f 203//325 267//13 171//265 +f 261//336 130//200 333//199 +f 158//343 144//224 55//236 +f 252//302 2//301 89//193 +f 113//271 17//319 214//310 +f 74//107 119//144 253//22 +f 93//69 308//181 172//176 +f 219//201 182//240 333//199 +f 190//167 207//166 170//291 +f 322//119 338//270 213//269 +f 168//154 251//40 334//79 +f 317//178 107//23 68//179 +f 258//296 332//326 133//24 +f 316//36 250//257 238//34 +f 133//24 332//326 230//183 +f 4//82 149//8 177//7 +f 29//205 218//57 346//59 +f 163//131 228//194 2//301 +f 307//74 26//196 223//72 +f 328//289 344//215 300//152 +f 243//175 305//334 175//278 +f 306//137 124//255 142//337 +f 293//168 311//121 129//120 +f 206//306 315//339 55//236 +f 72//285 249//153 149//8 +f 327//103 237//102 328//289 +f 276//286 78//41 143//287 +f 322//119 169//297 342//342 +f 324//229 294//234 134//190 +f 324//229 199//235 294//234 +f 220//122 225//123 32//204 +f 289//44 213//269 323//45 +f 341//111 25//113 226//284 +f 296//104 327//103 57//191 +f 329//282 242//185 296//104 +f 113//271 338//270 52//322 +f 80//117 126//303 121//263 +f 39//344 84//10 11//276 +f 170//291 160//249 190//167 +f 45//54 284//341 103//143 +f 156//217 199//235 297//55 +f 10//309 210//308 164//345 +f 129//120 245//207 293//168 +f 296//104 57//191 329//282 +f 304//323 171//265 88//132 +f 243//175 104//174 326//333 +f 337//140 12//116 81//118 +f 277//85 96//46 20//48 +f 100//295 215//198 227//31 +f 207//166 260//213 170//291 +f 54//115 194//75 217//313 +f 113//271 52//322 17//319 +f 106//227 10//309 347//307 +f 25//113 79//134 295//37 +f 226//284 25//113 295//37 +f 173//346 303//159 336//156 +f 75//30 140//209 35//180 +f 220//122 32//204 31//314 +f 138//258 99//60 262//273 +f 3//68 308//181 118//66 +f 268//20 156//217 41//304 +f 179//124 10//309 97//261 +f 174//61 195//206 157//21 +f 189//197 70//320 223//72 +f 189//197 282//293 70//320 +f 315//339 158//343 55//236 +f 215//198 181//148 184//2 +f 18//128 116//17 98//84 +f 233//317 86//330 175//278 +f 127//95 224//94 111//225 +f 324//229 134//190 249//153 +f 175//278 224//94 233//317 +f 257//202 234//233 9//203 +f 91//63 54//115 274//64 +f 335//328 320//237 144//224 +f 131//216 348//299 221//300 +f 118//66 101//106 281//67 +f 305//334 155//277 175//278 +f 228//194 89//193 2//301 +f 214//310 17//319 177//7 +f 76//29 280//245 75//30 +f 159//165 90//347 73//253 +f 276//286 149//8 240//81 +f 127//95 137//244 256//93 +f 155//277 202//251 279//223 +f 97//261 187//260 291//331 +f 8//298 212//18 211//294 +f 263//28 290//268 76//29 +f 4//82 17//319 52//322 +f 110//58 349//292 90//347 +f 330//177 229//142 109//171 +f 109//171 325//262 66//169 +f 178//76 57//191 49//77 +f 226//284 150//146 341//111 +f 151//56 96//46 277//85 +f 245//207 278//78 293//168 +f 13//101 190//167 287//32 +f 86//330 257//202 243//175 +f 198//250 26//196 19//162 +f 94//97 114//90 264//272 +f 65//92 43//91 131//216 +f 173//346 182//240 219//201 +f 254//214 184//2 339//1 +f 234//233 257//202 86//330 +f 302//231 23//281 271//232 +f 18//128 22//312 114//90 +f 72//285 149//8 276//286 +f 250//257 33//210 5//33 +f 182//240 56//49 298//335 +f 248//241 232//155 47//157 +f 228//194 163//131 21//130 +f 298//335 162//51 196//242 +f 108//4 280//245 61//248 +f 207//166 14//187 260//213 +f 46//219 179//124 343//332 +f 176//6 33//210 140//209 +f 147//321 272//266 1//348 +f 327//103 128//86 57//191 +f 328//289 300//152 284//341 +f 102//73 70//320 110//58 +f 103//143 41//304 297//55 +f 286//267 89//193 319//280 +f 70//320 349//292 110//58 +f 317//178 161//71 20//48 +f 325//262 97//261 141//338 +f 284//341 300//152 120//151 +f 316//36 165//108 250//257 +f 272//266 265//125 46//219 +f 164//345 67//316 10//309 +f 236//349 303//159 173//346 +f 220//122 25//113 167//112 +f 66//169 261//336 221//300 +f 42//222 41//304 135//274 +f 295//37 69//39 226//284 +f 254//214 29//205 13//101 +f 215//198 100//295 269//96 +f 346//59 30//172 29//205 +f 222//9 171//265 273//315 +f 347//307 266//126 188//228 +f 118//66 308//181 119//144 +f 308//181 107//23 119//144 +f 77//189 255//327 58//149 +f 11//276 8//298 211//294 +f 192//5 181//148 269//96 +f 303//159 236//349 164//345 +f 74//107 230//183 48//105 +f 221//300 261//336 196//242 +f 206//306 137//244 127//95 +f 166//109 106//227 270//110 +f 47//157 166//109 165//108 +f 216//150 58//149 255//327 +f 283//145 237//102 242//185 +f 343//332 179//124 291//331 +f 37//80 52//322 293//168 +f 323//45 330//177 139//43 +f 169//297 59//329 342//342 +f 322//119 186//239 169//297 +f 92//218 339//1 206//306 +f 163//131 2//301 203//325 +f 84//10 39//344 211//294 +f 293//168 52//322 311//121 +f 82//25 260//213 14//187 +f 282//293 189//197 8//298 +f 152//275 34//188 348//299 +f 43//91 152//275 131//216 +f 336//156 182//240 173//346 +f 282//293 349//292 70//320 +f 343//332 126//303 1//348 +f 155//277 326//333 335//328 +f 246//238 281//67 301//38 +f 112//100 190//167 136//226 +f 85//35 7//89 248//241 +f 194//75 54//115 91//63 +f 265//125 188//228 266//126 +f 55//236 218//57 92//218 +f 141//338 130//200 325//262 +f 348//299 50//170 66//169 +f 335//328 144//224 202//251 +f 298//335 56//49 162//51 +f 2//301 126//303 12//116 +f 289//44 186//239 213//269 +f 322//119 213//269 186//239 +f 321//135 79//134 180//129 +f 319//280 275//340 23//281 +f 231//138 59//329 36//16 +f 79//134 25//113 220//122 +f 332//326 258//296 168//154 +f 307//74 225//123 26//196 +f 131//216 162//51 200//50 +f 275//340 6//195 21//130 +f 5//33 100//295 227//31 +f 179//124 97//261 291//331 +f 335//328 326//333 307//74 +f 209//141 323//45 214//310 +f 183//52 84//10 94//97 +f 258//296 107//23 317//178 +f 286//267 147//321 252//302 +f 204//133 194//75 49//77 +f 312//311 177//7 273//315 +f 11//276 73//253 90//347 +f 270//110 35//180 140//209 +f 122//65 329//282 178//76 +f 91//63 122//65 178//76 +f 77//189 43//91 22//312 +f 284//341 120//151 138//258 +f 87//259 138//258 120//151 +f 270//110 188//228 35//180 +f 177//7 312//311 214//310 +f 226//284 69//39 38//283 +f 338//270 322//119 311//121 +f 298//335 261//336 333//199 +f 292//264 291//331 187//260 +f 211//294 145//11 84//10 +f 194//75 117//350 178//76 +f 148//14 267//13 81//118 +f 267//13 203//325 337//140 +f 150//146 38//283 306//137 +f 249//153 222//9 149//8 +f 66//169 130//200 261//336 +f 285//318 168//154 334//79 +f 327//103 328//289 128//86 +f 235//160 303//159 166//109 +f 248//241 7//89 232//155 +f 307//74 326//333 104//174 +f 213//269 214//310 323//45 +f 15//212 170//291 260//213 +f 318//230 325//262 109//171 +f 121//263 126//303 343//332 +f 262//273 284//341 138//258 +f 246//238 125//288 53//324 +f 83//15 148//14 312//311 +f 172//176 308//181 3//68 +f 176//6 140//209 75//30 +f 14//187 112//100 123//99 +f 152//275 348//299 131//216 +f 36//16 255//327 77//189 +f 143//287 324//229 72//285 +f 135//274 41//304 103//143 +f 175//278 279//223 224//94 +f 24//290 328//289 45//54 +f 97//261 67//316 141//338 +f 217//313 259//184 283//145 +f 16//136 231//138 36//16 +f 210//308 166//109 303//159 +f 164//345 236//349 67//316 +f 80//117 12//116 126//303 +f 131//216 200//50 350//161 +f 143//287 78//41 96//46 +f 171//265 344//215 237//102 +f 141//338 219//201 130//200 +f 272//266 147//321 286//267 +f 340//158 101//106 239//208 +f 196//242 131//216 221//300 +f 234//233 256//93 302//231 +f 144//224 158//343 111//225 +f 30//172 205//87 123//99 +f 252//302 89//193 286//267 +f 45//54 328//289 284//341 +f 126//303 147//321 1//348 +f 3//68 246//238 53//324 +f 3//68 281//67 246//238 +f 110//58 55//236 102//73 +f 326//333 305//334 243//175 +f 331//243 23//281 302//231 +f 237//102 88//132 171//265 +f 233//317 224//94 256//93 +f 268//20 41//304 42//222 +f 5//33 238//34 250//257 +f 6//195 319//280 89//193 +f 32//204 299//173 257//202 +f 128//86 161//71 313//70 +f 152//275 77//189 34//188 +f 239//208 124//255 340//158 +f 159//165 110//58 90//347 +f 203//325 12//116 337//140 +f 331//243 280//245 76//29 +f 91//63 178//76 117//350 +f 11//276 90//347 349//292 +f 222//9 273//315 177//7 +f 204//133 88//132 259//184 +f 107//23 308//181 68//179 +f 280//245 137//244 206//306 +f 93//69 161//71 68//179 +f 38//283 124//255 306//137 +f 343//332 1//348 46//219 +f 339//1 310//211 153//246 +f 309//221 275//340 21//130 +f 117//350 194//75 91//63 +f 163//131 304//323 125//288 +f 314//42 334//79 251//40 +f 188//228 263//28 35//180 +f 193//279 290//268 286//267 +f 37//80 334//79 240//81 +f 206//306 61//248 280//245 +f 332//326 168//154 285//318 +f 94//97 264//272 345//98 +f 132//164 184//2 181//148 +f 39//344 11//276 211//294 +f 56//49 232//155 244//254 +f 56//49 182//240 232//155 +f 14//187 123//99 205//87 +f 11//276 282//293 8//298 +f 46//219 1//348 272//266 +f 309//221 105//220 271//232 +f 129//120 342//342 59//329 +f 191//256 100//295 5//33 +f 93//69 68//179 185//182 +f 325//262 292//264 187//260 +f 304//323 203//325 171//265 +f 148//14 209//141 312//311 +f 175//278 86//330 243//175 +f 262//273 135//274 103//143 +f 36//16 212//18 16//136 +f 198//250 150//146 16//136 +f 236//349 173//346 219//201 +f 60//247 28//163 241//147 +f 303//159 164//345 210//308 +f 98//84 77//189 22//312 +f 107//23 258//296 133//24 +f 67//316 236//349 219//201 +f 347//307 10//309 179//124 +f 324//229 297//55 199//235 +f 6//195 275//340 319//280 +f 256//93 137//244 302//231 +f 132//164 60//247 310//211 +f 278//78 245//207 48//105 +f 77//189 98//84 247//83 +f 272//266 290//268 265//125 +f 108//4 75//30 280//245 +f 111//225 158//343 51//305 +f 133//24 74//107 253//22 +f 212//18 198//250 16//136 +f 306//137 142//337 59//329 +f 208//47 251//40 168//154 +f 124//255 239//208 142//337 +f 245//207 142//337 239//208 +f 142//337 129//120 59//329 +f 59//329 169//297 255//327 +f 193//279 286//267 319//280 +f 51//305 158//343 315//339 +f 60//247 132//164 28//163 +f 181//148 241//147 28//163 +f 220//122 31//314 79//134 +f 335//328 202//251 155//277 +f 324//229 151//56 297//55 +f 338//270 311//121 52//322 +f 344//215 328//289 237//102 diff --git a/src/transformation/mesh_intersection/test_data/offset_cylinder.obj b/src/transformation/mesh_intersection/test_data/offset_cylinder.obj new file mode 100644 index 00000000..0824c435 --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/offset_cylinder.obj @@ -0,0 +1,192 @@ +# Blender v3.0.1 OBJ File: '' +# www.blender.org +o Cylinder.001 +v -0.010000 -1.010000 -1.780000 +v -0.010000 0.990000 -1.780000 +v 0.185090 -1.010000 -1.760785 +v 0.185090 0.990000 -1.760785 +v 0.372683 -1.010000 -1.703879 +v 0.372683 0.990000 -1.703879 +v 0.545570 -1.010000 -1.611470 +v 0.545570 0.990000 -1.611470 +v 0.697107 -1.010000 -1.487107 +v 0.697107 0.990000 -1.487107 +v 0.821470 -1.010000 -1.335570 +v 0.821470 0.990000 -1.335570 +v 0.913880 -1.010000 -1.162683 +v 0.913880 0.990000 -1.162683 +v 0.970785 -1.010000 -0.975090 +v 0.970785 0.990000 -0.975090 +v 0.990000 -1.010000 -0.780000 +v 0.990000 0.990000 -0.780000 +v 0.970785 -1.010000 -0.584910 +v 0.970785 0.990000 -0.584910 +v 0.913880 -1.010000 -0.397317 +v 0.913880 0.990000 -0.397317 +v 0.821470 -1.010000 -0.224430 +v 0.821470 0.990000 -0.224430 +v 0.697107 -1.010000 -0.072893 +v 0.697107 0.990000 -0.072893 +v 0.545570 -1.010000 0.051470 +v 0.545570 0.990000 0.051470 +v 0.372683 -1.010000 0.143880 +v 0.372683 0.990000 0.143880 +v 0.185090 -1.010000 0.200785 +v 0.185090 0.990000 0.200785 +v -0.010000 -1.010000 0.220000 +v -0.010000 0.990000 0.220000 +v -0.205090 -1.010000 0.200785 +v -0.205090 0.990000 0.200785 +v -0.392683 -1.010000 0.143880 +v -0.392683 0.990000 0.143880 +v -0.565570 -1.010000 0.051470 +v -0.565570 0.990000 0.051470 +v -0.717107 -1.010000 -0.072893 +v -0.717107 0.990000 -0.072893 +v -0.841469 -1.010000 -0.224430 +v -0.841469 0.990000 -0.224430 +v -0.933879 -1.010000 -0.397316 +v -0.933879 0.990000 -0.397316 +v -0.990785 -1.010000 -0.584910 +v -0.990785 0.990000 -0.584910 +v -1.010000 -1.010000 -0.780000 +v -1.010000 0.990000 -0.780000 +v -0.990785 -1.010000 -0.975090 +v -0.990785 0.990000 -0.975090 +v -0.933879 -1.010000 -1.162684 +v -0.933879 0.990000 -1.162684 +v -0.841470 -1.010000 -1.335570 +v -0.841470 0.990000 -1.335570 +v -0.717107 -1.010000 -1.487107 +v -0.717107 0.990000 -1.487107 +v -0.565570 -1.010000 -1.611470 +v -0.565570 0.990000 -1.611470 +v -0.392683 -1.010000 -1.703880 +v -0.392683 0.990000 -1.703880 +v -0.205090 -1.010000 -1.760785 +v -0.205090 0.990000 -1.760785 +s off +f 2 3 1 +f 4 5 3 +f 6 7 5 +f 8 9 7 +f 10 11 9 +f 12 13 11 +f 14 15 13 +f 16 17 15 +f 18 19 17 +f 20 21 19 +f 22 23 21 +f 24 25 23 +f 26 27 25 +f 28 29 27 +f 30 31 29 +f 32 33 31 +f 34 35 33 +f 36 37 35 +f 38 39 37 +f 40 41 39 +f 42 43 41 +f 44 45 43 +f 46 47 45 +f 48 49 47 +f 50 51 49 +f 52 53 51 +f 54 55 53 +f 56 57 55 +f 58 59 57 +f 60 61 59 +f 54 38 22 +f 62 63 61 +f 64 1 63 +f 15 31 47 +f 2 4 3 +f 4 6 5 +f 6 8 7 +f 8 10 9 +f 10 12 11 +f 12 14 13 +f 14 16 15 +f 16 18 17 +f 18 20 19 +f 20 22 21 +f 22 24 23 +f 24 26 25 +f 26 28 27 +f 28 30 29 +f 30 32 31 +f 32 34 33 +f 34 36 35 +f 36 38 37 +f 38 40 39 +f 40 42 41 +f 42 44 43 +f 44 46 45 +f 46 48 47 +f 48 50 49 +f 50 52 51 +f 52 54 53 +f 54 56 55 +f 56 58 57 +f 58 60 59 +f 60 62 61 +f 6 4 62 +f 4 2 62 +f 2 64 62 +f 62 60 58 +f 58 56 62 +f 56 54 62 +f 54 52 50 +f 50 48 46 +f 46 44 42 +f 42 40 38 +f 38 36 34 +f 34 32 30 +f 30 28 26 +f 26 24 22 +f 22 20 18 +f 18 16 14 +f 14 12 10 +f 10 8 6 +f 54 50 38 +f 50 46 38 +f 46 42 38 +f 38 34 30 +f 30 26 38 +f 26 22 38 +f 22 18 6 +f 18 14 6 +f 14 10 6 +f 6 62 54 +f 6 54 22 +f 62 64 63 +f 64 2 1 +f 63 1 3 +f 3 5 7 +f 7 9 11 +f 11 13 15 +f 15 17 19 +f 19 21 23 +f 23 25 27 +f 27 29 31 +f 31 33 35 +f 35 37 39 +f 39 41 43 +f 43 45 47 +f 47 49 51 +f 51 53 47 +f 53 55 47 +f 55 57 63 +f 57 59 63 +f 59 61 63 +f 63 3 15 +f 3 7 15 +f 7 11 15 +f 15 19 23 +f 23 27 15 +f 27 31 15 +f 31 35 47 +f 35 39 47 +f 39 43 47 +f 47 55 63 +f 63 15 47 diff --git a/src/transformation/mesh_intersection/test_data/poly_cylinder.obj b/src/transformation/mesh_intersection/test_data/poly_cylinder.obj new file mode 100644 index 00000000..f99f2c3c --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/poly_cylinder.obj @@ -0,0 +1,142 @@ +# Blender v3.0.1 OBJ File: '' +# www.blender.org +o Plane +v -2.274960 1.634560 -0.654264 +v -2.230466 1.592349 -0.700534 +v -2.173552 1.441081 -0.682305 +v -2.182022 1.422727 -0.652495 +v -2.246091 1.550951 -0.639542 +v -2.230519 1.501727 -0.628318 +v -2.234529 1.544985 -0.655547 +v -2.201307 1.484995 -0.667436 +v -2.227519 1.484150 -0.619714 +v -2.262122 1.436412 -0.519611 +v -2.217010 1.449139 -0.610715 +v -2.274961 1.468401 -0.522026 +v -2.309084 1.548492 -0.524516 +v -2.268742 1.480160 -0.542545 +v -2.248044 1.503023 -0.597894 +v -2.283536 1.531561 -0.556898 +v -2.265529 1.557428 -0.609807 +v -2.301338 1.565738 -0.552144 +v -2.310203 1.613862 -0.574532 +v -2.296426 1.575467 -0.568704 +v -2.295412 1.616367 -0.603075 +v -2.270176 1.600751 -0.635943 +v -2.270445 1.576701 -0.616320 +v -2.286738 1.585906 -0.594400 +v -2.284624 1.597595 -0.607498 +v -2.291954 1.595247 -0.592472 +v -2.286412 1.570707 -0.582891 +v -2.256446 1.568496 -0.634918 +v 0.196502 2.730363 0.722639 +v 0.240997 2.688151 0.676368 +v 0.225372 2.646754 0.737360 +v 0.240944 2.597529 0.748584 +v 0.236933 2.640788 0.721355 +v 0.270155 2.580798 0.709466 +v 0.243943 2.579952 0.757189 +v 0.297910 2.536883 0.694597 +v 0.289440 2.518529 0.724407 +v 0.209340 2.532215 0.857292 +v 0.254453 2.544942 0.766188 +v 0.196502 2.564204 0.854877 +v 0.162379 2.644295 0.852386 +v 0.202720 2.575963 0.834357 +v 0.223419 2.598826 0.779009 +v 0.187926 2.627364 0.820004 +v 0.205934 2.653231 0.767095 +v 0.170124 2.661540 0.824758 +v 0.161259 2.709665 0.802371 +v 0.175036 2.671269 0.808199 +v 0.176051 2.712170 0.773827 +v 0.201286 2.696553 0.740959 +v 0.201017 2.672503 0.760582 +v 0.184724 2.681708 0.782502 +v 0.186838 2.693398 0.769405 +v 0.179508 2.691049 0.784430 +v 0.185050 2.666510 0.794011 +v 0.215016 2.664299 0.741984 +s off +f 4 38 10 +f 45 56 55 +f 23 52 24 +f 10 39 11 +f 24 53 25 +f 11 40 12 +f 25 54 26 +f 12 41 13 +f 1 30 2 +f 26 55 27 +f 13 42 14 +f 27 56 28 +f 14 43 15 +f 28 29 1 +f 15 44 16 +f 2 31 5 +f 16 45 17 +f 5 32 6 +f 17 46 18 +f 6 33 7 +f 18 47 19 +f 7 34 8 +f 19 48 20 +f 8 35 9 +f 20 49 21 +f 9 36 3 +f 21 50 22 +f 3 37 4 +f 22 51 23 +f 4 37 38 +f 31 30 56 +f 30 29 56 +f 34 33 32 +f 32 31 43 +f 31 56 45 +f 35 34 32 +f 37 36 35 +f 39 38 37 +f 42 41 40 +f 45 44 43 +f 43 42 39 +f 42 40 39 +f 39 37 35 +f 45 43 31 +f 43 39 35 +f 35 32 43 +f 48 47 46 +f 51 50 53 +f 50 49 53 +f 53 52 51 +f 48 46 55 +f 46 45 55 +f 54 53 49 +f 54 49 48 +f 55 54 48 +f 23 51 52 +f 10 38 39 +f 24 52 53 +f 11 39 40 +f 25 53 54 +f 12 40 41 +f 1 29 30 +f 26 54 55 +f 13 41 42 +f 27 55 56 +f 14 42 43 +f 28 56 29 +f 15 43 44 +f 2 30 31 +f 16 44 45 +f 5 31 32 +f 17 45 46 +f 6 32 33 +f 18 46 47 +f 7 33 34 +f 19 47 48 +f 8 34 35 +f 20 48 49 +f 9 35 36 +f 21 49 50 +f 3 36 37 +f 22 50 51 diff --git a/src/transformation/mesh_intersection/test_data/stairs.obj b/src/transformation/mesh_intersection/test_data/stairs.obj new file mode 100644 index 00000000..32ae616a --- /dev/null +++ b/src/transformation/mesh_intersection/test_data/stairs.obj @@ -0,0 +1,72 @@ +# Blender v3.0.1 OBJ File: '' +# www.blender.org +o Cube_Cube.001 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 4.915915 +v -1.000000 1.000000 4.915915 +v 1.000000 1.000000 4.915915 +v 1.000000 -1.000000 4.915915 +v -1.000000 4.391501 1.000000 +v -1.000000 4.391501 -1.000000 +v 1.000000 4.391501 -1.000000 +v 1.000000 4.391501 1.000000 +v -1.000000 1.000000 -7.119346 +v 1.000000 1.000000 -7.119346 +v -1.000000 4.391501 -7.119346 +v 1.000000 4.391501 -7.119346 +v -1.000000 11.844163 -1.000000 +v 1.000000 11.844163 -1.000000 +v -1.000000 11.844163 -7.119346 +v 1.000000 11.844163 -7.119346 +s off +f 2 3 1 +f 4 7 3 +f 8 5 7 +f 2 11 6 +f 7 1 3 +f 2 14 4 +f 11 9 12 +f 6 12 5 +f 1 10 2 +f 5 9 1 +f 14 16 15 +f 6 13 2 +f 4 18 8 +f 8 16 6 +f 17 20 18 +f 8 20 15 +f 14 17 4 +f 14 23 19 +f 22 23 21 +f 19 24 20 +f 20 22 15 +f 15 21 14 +f 2 4 3 +f 4 8 7 +f 8 6 5 +f 2 10 11 +f 7 5 1 +f 2 13 14 +f 11 10 9 +f 6 11 12 +f 1 9 10 +f 5 12 9 +f 14 13 16 +f 6 16 13 +f 4 17 18 +f 8 15 16 +f 17 19 20 +f 8 18 20 +f 14 19 17 +f 14 21 23 +f 22 24 23 +f 19 23 24 +f 20 24 22 +f 15 22 21 From c6e3adf3eed6745b4b7a3f4ab19c67ef5ae36773 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Thu, 11 Jul 2024 20:07:50 -0400 Subject: [PATCH 05/45] Hide wavefront functionality behind a feature flag --- crates/parry3d-f64/Cargo.toml | 3 +- .../mesh_intersection/mesh_intersection.rs | 120 +++++++----------- .../triangle_triangle_intersection.rs | 2 +- src/transformation/mod.rs | 3 + 4 files changed, 52 insertions(+), 76 deletions(-) diff --git a/crates/parry3d-f64/Cargo.toml b/crates/parry3d-f64/Cargo.toml index bb9c9a5f..1d247393 100644 --- a/crates/parry3d-f64/Cargo.toml +++ b/crates/parry3d-f64/Cargo.toml @@ -30,6 +30,7 @@ simd-nightly = ["simba/packed_simd", "simd-is-enabled"] enhanced-determinism = ["simba/libm_force", "indexmap"] cuda = ["cust_core", "cust", "nalgebra/cuda"] parallel = ["rayon"] +wavefront = ["obj"] # Do not enable this feature directly. It is automatically # enabled with the "simd-stable" or "simd-nightly" feature. @@ -61,7 +62,7 @@ spade = { version = "2", optional = true } # Make this optional? rayon = { version = "1", optional = true } bytemuck = { version = "1", features = ["derive"], optional = true } rstar = "0.12.0" -obj = "0.10.2" +obj = { version = "0.10.2", optional = true } [target.'cfg(not(target_os = "cuda"))'.dependencies] cust = { version = "0.3", optional = true } diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 2a9648a9..b750da63 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -7,11 +7,13 @@ use crate::transformation::mesh_intersection::angle_closest_to_90; use crate::utils::DefaultStorage; use core::f64::consts::PI; use na::{Point3, Vector3}; +#[cfg(feature = "wavefront")] use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; use rstar::RTree; use spade::{ConstrainedDelaunayTriangulation, Triangulation as _}; use std::collections::BTreeMap; use std::collections::HashSet; +#[cfg(feature = "wavefront")] use std::path::PathBuf; /// Metadata that specifies thresholds to use when making construction choices @@ -146,7 +148,7 @@ pub fn intersect_meshes_with_metadata( let mut point_set = RTree::::new(); let mut topology_indices = Vec::new(); { - let mut insert_point = |position: Vector3| { + let mut insert_point = |position: Point3| { insert_into_set(position, &mut point_set, meta_data.global_insertion_epsilon) as u32 }; // Add the inside vertices and triangles from mesh1 @@ -155,9 +157,9 @@ pub fn intersect_meshes_with_metadata( face.swap(0, 1); } topology_indices.push([ - insert_point(mesh1.vertices()[face[0] as usize].coords), - insert_point(mesh1.vertices()[face[1] as usize].coords), - insert_point(mesh1.vertices()[face[2] as usize].coords), + insert_point(mesh1.vertices()[face[0] as usize]), + insert_point(mesh1.vertices()[face[1] as usize]), + insert_point(mesh1.vertices()[face[2] as usize]), ]); } @@ -167,9 +169,9 @@ pub fn intersect_meshes_with_metadata( face.swap(0, 1); } topology_indices.push([ - insert_point(mesh2.vertices()[face[0] as usize].coords), - insert_point(mesh2.vertices()[face[1] as usize].coords), - insert_point(mesh2.vertices()[face[2] as usize].coords), + insert_point(mesh2.vertices()[face[0] as usize]), + insert_point(mesh2.vertices()[face[1] as usize]), + insert_point(mesh2.vertices()[face[2] as usize]), ]); } } @@ -358,9 +360,9 @@ fn syncretize_triangulation( let mut constraints = constraints.to_vec(); // Add the triangle points to the triangulation. let mut point_set = RTree::::new(); - let _ = insert_into_set(tri.a.coords, &mut point_set, epsilon); - let _ = insert_into_set(tri.b.coords, &mut point_set, epsilon); - let _ = insert_into_set(tri.c.coords, &mut point_set, epsilon); + let _ = insert_into_set(tri.a, &mut point_set, epsilon); + let _ = insert_into_set(tri.b, &mut point_set, epsilon); + let _ = insert_into_set(tri.c, &mut point_set, epsilon); // Sometimes, points on the edge of a triangle are slightly off, and this makes // spade think that there is a super thin triangle. Project points close to an edge @@ -389,8 +391,8 @@ fn syncretize_triangulation( // Generate edge, taking care to merge duplicate vertices. let mut edges = Vec::new(); for point_pair in constraints { - let p1_id = insert_into_set(point_pair[0].coords, &mut point_set, epsilon); - let p2_id = insert_into_set(point_pair[1].coords, &mut point_set, epsilon); + let p1_id = insert_into_set(point_pair[0], &mut point_set, epsilon); + let p2_id = insert_into_set(point_pair[1], &mut point_set, epsilon); edges.push([p1_id, p2_id]); } @@ -407,12 +409,11 @@ fn syncretize_triangulation( let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); // Project points into 2D and triangulate the resulting set. - let planar_points: Vec<_> = points .iter() .copied() .map(|point| { - let point_proj = project(&point.point); + let point_proj = project(&point.point.coords); spade::Point2::new(point_proj.x, point_proj.y) }) .collect(); @@ -428,42 +429,8 @@ fn syncretize_triangulation( (cdt_triangulation, points) } -// I heavily recommend that this is left here in case one needs to debug the above code. -fn _mesh_to_obj(mesh: &TriMesh, path: &PathBuf) { - let mut file = std::fs::File::create(path).unwrap(); - - ObjData { - position: mesh - .vertices() - .into_iter() - .map(|v| [v.x as f32, v.y as f32, v.z as f32]) - .collect(), - objects: vec![Object { - groups: vec![Group { - polys: mesh - .indices() - .into_iter() - .map(|tri| { - SimplePolygon(vec![ - IndexTuple(tri[0] as usize, None, None), - IndexTuple(tri[1] as usize, None, None), - IndexTuple(tri[2] as usize, None, None), - ]) - }) - .collect(), - name: "".to_string(), - index: 0, - material: None, - }], - name: "".to_string(), - }], - ..Default::default() - } - .write_to_buf(&mut file) - .unwrap(); -} - -// I heavily recommend that this is left here in case one needs to debug the above code. +// We heavily recommend that this is left here in case one needs to debug the above code. +#[cfg(feature = "wavefront")] fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -473,7 +440,8 @@ fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { } } -// I heavily recommend that this is left here in case one needs to debug the above code. +// We heavily recommend that this is left here in case one needs to debug the above code. +#[cfg(feature = "wavefront")] fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -489,7 +457,7 @@ fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &P #[derive(Copy, Clone, PartialEq, Debug, Default)] struct TreePoint { - point: Vector3, + point: Point3, id: usize, } @@ -499,7 +467,7 @@ impl rstar::Point for TreePoint { fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { TreePoint { - point: Vector3::new(generator(0), generator(1), generator(2)), + point: Point3::new(generator(0), generator(1), generator(2)), id: usize::MAX, } } @@ -523,11 +491,7 @@ impl rstar::Point for TreePoint { } } -fn insert_into_set( - position: Vector3, - point_set: &mut RTree, - epsilon: f64, -) -> usize { +fn insert_into_set(position: Point3, point_set: &mut RTree, epsilon: f64) -> usize { let point_count = point_set.size(); let point_to_insert = TreePoint { point: position, @@ -552,13 +516,13 @@ fn insert_into_set( } } -fn smallest_angle(points: &[Vector3]) -> f64 { +fn smallest_angle(points: &[Point3]) -> f64 { let n = points.len(); let mut worst_cos = 2.0; for i in 0..points.len() { - let d1 = (points[i] - points[(i + 1) % n]).normalize(); - let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + let d1 = (points[i].coords - points[(i + 1) % n].coords).normalize(); + let d2 = (points[(i + 2) % n].coords - points[(i + 1) % n].coords).normalize(); let cos = d1.dot(&d2); @@ -595,7 +559,7 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) - /// to create ultra thin triangles when a point lies on an edge of a tirangle. These /// are degenerate and need to be terminated with extreme prejudice. fn is_triangle_degenerate( - triangle: &[Vector3; 3], + triangle: &[Point3; 3], epsilon_degrees: f64, epsilon_distance: f64, ) -> bool { @@ -619,9 +583,11 @@ fn is_triangle_degenerate( } let dir = dir.normalize(); - let proj = (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir + triangle[(i + 2) % 3]; + let proj = + (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir + triangle[(i + 2) % 3].coords; - worse_projection_distance = worse_projection_distance.min((proj - triangle[i]).norm()); + worse_projection_distance = + worse_projection_distance.min((proj - triangle[i].coords).norm()); } if worse_projection_distance < epsilon_distance { @@ -663,10 +629,9 @@ fn merge_triangle_sets( let p3 = points[verts[2].index()]; // Sometimes the triangulation is messed up due to numerical errors. If - // a triangle does not survive this test. You can bet it should be put out - // of its misery. + // a triangle does not survive this test it should be deleted. if is_triangle_degenerate( - &[p1.coords, p2.coords, p3.coords], + &[p1, p2, p3], metadata.angle_epsilon, metadata.global_insertion_epsilon, ) { @@ -687,9 +652,9 @@ fn merge_triangle_sets( if flip2 ^ (projection.is_inside_eps(¢er, epsilon)) { topology_indices.push([ - insert_into_set(p1.coords, &mut point_set, epsilon) as u32, - insert_into_set(p2.coords, &mut point_set, epsilon) as u32, - insert_into_set(p3.coords, &mut point_set, epsilon) as u32, + insert_into_set(p1, &mut point_set, epsilon) as u32, + insert_into_set(p2, &mut point_set, epsilon) as u32, + insert_into_set(p3, &mut point_set, epsilon) as u32, ]); if flip1 { @@ -715,10 +680,14 @@ fn merge_triangle_sets( #[cfg(test)] mod tests { use crate::shape::TriMeshFlags; + #[cfg(feature = "wavefront")] + use crate::transformation::wavefront::*; use super::*; + #[cfg(feature = "wavefront")] use obj::Obj; + #[cfg(feature = "wavefront")] #[test] fn test_same_mesh_intersection() { let Obj { @@ -753,9 +722,10 @@ mod tests { .unwrap() .unwrap(); - _mesh_to_obj(&res, &PathBuf::from("same_test.obj")) + mesh.to_obj_file(&PathBuf::from("same_test.obj")); } + #[cfg(feature = "wavefront")] #[test] fn test_offset_cylinder_intersection() { let Obj { @@ -811,9 +781,10 @@ mod tests { .unwrap() .unwrap(); - _mesh_to_obj(&res, &PathBuf::from("offset_test.obj")) + res.to_obj_file(&PathBuf::from("offset_test.obj")); } + #[cfg(feature = "wavefront")] #[test] fn test_stair_bar_intersection() { let Obj { @@ -867,9 +838,10 @@ mod tests { .unwrap() .unwrap(); - _mesh_to_obj(&res, &PathBuf::from("stair_test.obj")) + res.to_obj_file(&PathBuf::from("stair_test.obj")); } + #[cfg(feature = "wavefront")] #[test] fn test_complex_intersection() { let Obj { @@ -925,6 +897,6 @@ mod tests { .unwrap() .unwrap(); - _mesh_to_obj(&res, &PathBuf::from("complex_test.obj")) + res.to_obj_file(&PathBuf::from("complex_test.obj")); } } diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index a53ebe64..91034808 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -264,7 +264,7 @@ fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { /// Find the index of a vertex in a poly line, such that the two /// edges incident in that vertex form the angle closest to 90 /// degrees in the poly line. -pub fn angle_closest_to_90(points: &[na::Vector3]) -> usize { +pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { let n = points.len(); let mut best_cos = 2.0; diff --git a/src/transformation/mod.rs b/src/transformation/mod.rs index 93fd346e..605c0ba3 100644 --- a/src/transformation/mod.rs +++ b/src/transformation/mod.rs @@ -38,3 +38,6 @@ mod to_polyline; #[cfg(feature = "dim3")] mod to_trimesh; pub mod utils; + +#[cfg(feature = "wavefront")] +pub mod wavefront; From 9a7b87aff77462699615f487cc07fc19434e22c9 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Fri, 12 Jul 2024 12:27:07 -0400 Subject: [PATCH 06/45] Fix compilation errors --- src/shape/trimesh.rs | 4 ---- .../mesh_intersection/mesh_intersection.rs | 15 ++++----------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index e31227a9..2795f8bc 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -37,10 +37,6 @@ pub enum TopologyError { }, } -<<<<<<< HEAD -#[cfg(feature = "std")] -======= ->>>>>>> origin/master impl fmt::Display for TopologyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 508060e3..16569b80 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -2,22 +2,16 @@ use super::{MeshIntersectionError, TriangleTriangleIntersection}; use crate::math::{Isometry, Real}; use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; -use crate::shape::{FeatureId, TriMesh, Triangle}; -use crate::shape::{GenericTriMesh, TriMesh, Triangle}; +use crate::shape::{TriMesh, Triangle}; use crate::transformation::mesh_intersection::angle_closest_to_90; -use crate::utils::DefaultStorage; -use crate::utils::{self, WBasis}; use core::f64::consts::PI; -use na::{Point2, Vector2}; use na::{Point3, Vector3}; #[cfg(feature = "wavefront")] use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; use rstar::RTree; -use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _}; use spade::{ConstrainedDelaunayTriangulation, Triangulation as _}; use std::collections::BTreeMap; use std::collections::HashSet; -use std::collections::{HashMap, HashSet}; #[cfg(feature = "wavefront")] use std::path::PathBuf; @@ -603,8 +597,8 @@ fn is_triangle_degenerate( } fn merge_triangle_sets( - mesh1: &GenericTriMesh, - mesh2: &GenericTriMesh, + mesh1: &TriMesh, + mesh2: &TriMesh, triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, pos12: &Isometry, flip1: bool, @@ -670,8 +664,7 @@ fn merge_triangle_sets( let id2 = topology_indices.last().unwrap()[1]; let id3 = topology_indices.last().unwrap()[2]; - // If this triggers, yell at Camilo because his algorithm is - // disfunctional. + // This should *never* trigger. if id1 == id2 || id1 == id3 || id2 == id3 { return Err(MeshIntersectionError::DuplicateVertices); } From 130d5f25cc06a48cdee124bca442b8548a54e440 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Fri, 12 Jul 2024 18:12:57 -0400 Subject: [PATCH 07/45] Fix CI --- crates/parry3d/Cargo.toml | 1 + .../mesh_intersection/mesh_intersection.rs | 68 ++++++++++--------- .../triangle_triangle_intersection.rs | 8 +-- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/crates/parry3d/Cargo.toml b/crates/parry3d/Cargo.toml index 54402e1e..5603c710 100644 --- a/crates/parry3d/Cargo.toml +++ b/crates/parry3d/Cargo.toml @@ -63,6 +63,7 @@ bytemuck = { version = "1", features = ["derive"], optional = true } log = "0.4" ordered-float = { version = "4", default-features = false } thiserror = { version = "1", optional = true } +rstar = "0.12.0" [dev-dependencies] oorandom = "11" diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 16569b80..1bc63c57 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -20,23 +20,23 @@ use std::path::PathBuf; pub struct MeshIntersectionMetadata { /// The smallest angle (in degrees) that will be tolerated. A triangle with /// a smaller angle is considered degenerate and will be deleted. - pub angle_epsilon: f64, + pub angle_epsilon: Real, /// The maximum distance at which two points are considered to overlap in space /// if `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered /// to be the same point. - pub global_insertion_epsilon: f64, + pub global_insertion_epsilon: Real, /// A multiplier coefficient to scale `global_insertion_epsilon` when checking for /// point duplicatin within a single triangle. Inside of an individual triangle /// the distance at wich two points are considered to be the same is /// `global_insertion_epsilon * local_insertion_epsilon_mod`. - pub local_insertion_epsilon_mod: f64, + pub local_insertion_epsilon_mod: Real, } impl Default for MeshIntersectionMetadata { fn default() -> Self { Self { angle_epsilon: 0.005, // degrees - global_insertion_epsilon: f64::EPSILON * 100.0, + global_insertion_epsilon: Real::EPSILON * 100.0, local_insertion_epsilon_mod: 10., } } @@ -147,7 +147,7 @@ pub fn intersect_meshes_with_metadata( let mut point_set = RTree::::new(); let mut topology_indices = Vec::new(); { - let mut insert_point = |position: Point3| { + let mut insert_point = |position: Point3| { insert_into_set(position, &mut point_set, meta_data.global_insertion_epsilon) as u32 }; // Add the inside vertices and triangles from mesh1 @@ -350,11 +350,11 @@ fn extract_connected_components( fn syncretize_triangulation( tri: &Triangle, - constraints: &[[Point3; 2]], - epsilon: f64, + constraints: &[[Point3; 2]], + epsilon: Real, ) -> ( ConstrainedDelaunayTriangulation>, - Vec>, + Vec>, ) { let mut constraints = constraints.to_vec(); // Add the triangle points to the triangulation. @@ -405,7 +405,7 @@ fn syncretize_triangulation( let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; let (e1, e2) = planar_gram_schmidt(d1, d2); - let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); + let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); // Project points into 2D and triangulate the resulting set. let planar_points: Vec<_> = points @@ -417,7 +417,7 @@ fn syncretize_triangulation( }) .collect(); let cdt_triangulation = - ConstrainedDelaunayTriangulation::>::bulk_load_cdt_stable( + ConstrainedDelaunayTriangulation::>::bulk_load_cdt_stable( planar_points, edges, ) @@ -430,7 +430,7 @@ fn syncretize_triangulation( // We heavily recommend that this is left here in case one needs to debug the above code. #[cfg(feature = "wavefront")] -fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { +fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -441,7 +441,7 @@ fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { // We heavily recommend that this is left here in case one needs to debug the above code. #[cfg(feature = "wavefront")] -fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { +fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { use std::io::Write; let mut file = std::fs::File::create(path).unwrap(); @@ -456,12 +456,12 @@ fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &P #[derive(Copy, Clone, PartialEq, Debug, Default)] struct TreePoint { - point: Point3, + point: Point3, id: usize, } impl rstar::Point for TreePoint { - type Scalar = f64; + type Scalar = Real; const DIMENSIONS: usize = 3; fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { @@ -490,7 +490,11 @@ impl rstar::Point for TreePoint { } } -fn insert_into_set(position: Point3, point_set: &mut RTree, epsilon: f64) -> usize { +fn insert_into_set( + position: Point3, + point_set: &mut RTree, + epsilon: Real, +) -> usize { let point_count = point_set.size(); let point_to_insert = TreePoint { point: position, @@ -500,22 +504,22 @@ fn insert_into_set(position: Point3, point_set: &mut RTree, epsi match point_set.nearest_neighbor(&point_to_insert) { Some(tree_point) => { if (tree_point.point - position).norm_squared() <= epsilon { - return tree_point.id; + tree_point.id } else { point_set.insert(point_to_insert); debug_assert!(point_set.size() == point_count + 1); - return point_count; + point_count } } None => { point_set.insert(point_to_insert); debug_assert!(point_set.size() == point_count + 1); - return point_count; + point_count } } } -fn smallest_angle(points: &[Point3]) -> f64 { +fn smallest_angle(points: &[Point3]) -> Real { let n = points.len(); let mut worst_cos = 2.0; @@ -530,10 +534,10 @@ fn smallest_angle(points: &[Point3]) -> f64 { } } - worst_cos.acos() * 180. / PI + worst_cos.acos() * 180. / PI as Real } -fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) { +fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) { let u1 = v1; let u2 = v2 - (v2.dot(&u1) / u1.norm_squared()) * u1; @@ -543,7 +547,7 @@ fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vec (e1, e2) } -fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) -> Vector3 { +fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) -> Vector3 { let dir = segment[1] - segment[0]; let local = point - segment[0]; @@ -558,15 +562,15 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) - /// to create ultra thin triangles when a point lies on an edge of a tirangle. These /// are degenerate and need to be terminated with extreme prejudice. fn is_triangle_degenerate( - triangle: &[Point3; 3], - epsilon_degrees: f64, - epsilon_distance: f64, + triangle: &[Point3; 3], + epsilon_degrees: Real, + epsilon_distance: Real, ) -> bool { if smallest_angle(triangle) < epsilon_degrees { return true; } - let mut shortest_side = f64::MAX; + let mut shortest_side = Real::MAX; for i in 0..3 { let p1 = triangle[i]; let p2 = triangle[(i + 1) % 3]; @@ -574,7 +578,7 @@ fn is_triangle_degenerate( shortest_side = shortest_side.min((p1 - p2).norm()); } - let mut worse_projection_distance = f64::MAX; + let mut worse_projection_distance = Real::MAX; for i in 0..3 { let dir = triangle[(i + 1) % 3] - triangle[(i + 2) % 3]; if dir.norm() < epsilon_distance { @@ -599,7 +603,7 @@ fn is_triangle_degenerate( fn merge_triangle_sets( mesh1: &TriMesh, mesh2: &TriMesh, - triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, + triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, pos12: &Isometry, flip1: bool, flip2: bool, @@ -617,7 +621,7 @@ fn merge_triangle_sets( let (delaunay, points) = syncretize_triangulation( &tri, - &constraints, + constraints, metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, ); @@ -651,9 +655,9 @@ fn merge_triangle_sets( if flip2 ^ (projection.is_inside_eps(¢er, epsilon)) { topology_indices.push([ - insert_into_set(p1, &mut point_set, epsilon) as u32, - insert_into_set(p2, &mut point_set, epsilon) as u32, - insert_into_set(p3, &mut point_set, epsilon) as u32, + insert_into_set(p1, point_set, epsilon) as u32, + insert_into_set(p2, point_set, epsilon) as u32, + insert_into_set(p3, point_set, epsilon) as u32, ]); if flip1 { diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index 831f98a8..964ad9b7 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -35,8 +35,8 @@ pub fn triangle_triangle_intersection( tri1: &Triangle, tri2: &Triangle, ) -> Option { - let normal1 = robust_triangle_normal(&tri1); - let normal2 = robust_triangle_normal(&tri2); + let normal1 = robust_triangle_normal(tri1); + let normal2 = robust_triangle_normal(tri2); if let Some(intersection_dir) = normal1.cross(&normal2).try_normalize(1.0e-6) { let mut range1 = [ @@ -251,7 +251,7 @@ pub fn triangle_triangle_intersection( } /// Smarter, but more expensive, mechanism to find a triangle normal. -fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { +fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { let pts = [tri.a.coords, tri.b.coords, tri.c.coords]; let best_vertex = angle_closest_to_90(&pts); @@ -264,7 +264,7 @@ fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { /// Find the index of a vertex in a poly line, such that the two /// edges incident in that vertex form the angle closest to 90 /// degrees in the poly line. -pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { +pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { let n = points.len(); let mut best_cos = 2.0; From 20502c52fc8aa634b46a58586d5364ed71b49031 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Fri, 12 Jul 2024 18:16:45 -0400 Subject: [PATCH 08/45] Fix CI issues --- .../mesh_intersection/mesh_intersection.rs | 2 +- src/utils/mod.rs | 4 --- src/utils/spade.rs | 26 ------------------- 3 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 src/utils/spade.rs diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 1bc63c57..6a049b27 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -608,7 +608,7 @@ fn merge_triangle_sets( flip1: bool, flip2: bool, metadata: &MeshIntersectionMetadata, - mut point_set: &mut RTree, + point_set: &mut RTree, topology_indices: &mut Vec<[u32; 3]>, ) -> Result<(), MeshIntersectionError> { // For each triangle, and each constraint edge associated to that triangle, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b24de470..afed2c31 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -29,8 +29,6 @@ pub use self::segments_intersection::{segments_intersection2d, SegmentsIntersect pub(crate) use self::sort::sort2; pub(crate) use self::sort::sort3; pub use self::sorted_pair::SortedPair; -#[cfg(all(feature = "dim3", feature = "std"))] -pub(crate) use self::spade::sanitize_point; pub(crate) use self::weighted_value::WeightedValue; pub(crate) use self::wops::{simd_swap, WBasis, WCross, WSign}; @@ -61,7 +59,5 @@ mod sdp_matrix; mod segments_intersection; mod sort; mod sorted_pair; -#[cfg(all(feature = "dim3", feature = "std"))] -mod spade; mod weighted_value; mod wops; diff --git a/src/utils/spade.rs b/src/utils/spade.rs deleted file mode 100644 index a6ff93ce..00000000 --- a/src/utils/spade.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::math::Real; - -/// Ensures the given coordinate doesn’t go out of the bounds of spade’s acceptable values. -/// -/// Returns 0.0 if the coordinate is smaller than `spade::MIN_ALLOWED_VALUE`. -/// Returns `spade::MAX_ALLOWED_VALUE` the coordinate is larger than `spade::MAX_ALLOWED_VALUE`. -pub fn sanitize_coord(coord: Real) -> Real { - let abs = coord.abs(); - - #[allow(clippy::unnecessary_cast)] - if abs as f64 <= spade::MIN_ALLOWED_VALUE { - return 0.0; - } - - #[cfg(feature = "f64")] - if abs > spade::MAX_ALLOWED_VALUE { - // This cannot happen in f32 since the max is 3.40282347E+38. - return spade::MAX_ALLOWED_VALUE * coord.signum(); - } - - coord -} - -pub fn sanitize_point(point: spade::Point2) -> spade::Point2 { - spade::Point2::new(sanitize_coord(point.x), sanitize_coord(point.y)) -} From b061752c5dc02a5a263522d58069f22353e39052 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Fri, 12 Jul 2024 18:31:00 -0400 Subject: [PATCH 09/45] Delete unused code --- src/shape/trimesh.rs | 14 --- .../triangle_triangle_intersection.rs | 101 +++--------------- 2 files changed, 13 insertions(+), 102 deletions(-) diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index 2795f8bc..2080791b 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -193,20 +193,6 @@ impl TriMeshTopology { }) } } -impl TriMeshTopology { - #[cfg(feature = "dim3")] - pub(crate) fn face_half_edges_ids(&self, fid: u32) -> [u32; 3] { - let first_half_edge = self.faces[fid as usize].half_edge; - - let mut result = [first_half_edge; 3]; - for k in 1..3 { - let half_edge = self.half_edges[result[k - 1] as usize]; - result[k] = half_edge.next; - } - - result - } -} #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index 964ad9b7..fce3840a 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -8,9 +8,6 @@ use crate::utils::WBasis; #[derive(Copy, Clone, Debug, Default)] pub struct TriangleTriangleIntersectionPoint { pub p1: Point, - pub p2: Point, - pub f1: FeatureId, - pub f2: FeatureId, } #[derive(Clone, Debug)] @@ -99,82 +96,20 @@ pub fn triangle_triangle_intersection( return None; } - let edge_between = |a, b| match (a, b) { - (0, 1) | (1, 0) => FeatureId::Edge(0), - (1, 2) | (2, 1) => FeatureId::Edge(1), - (2, 0) | (0, 2) => FeatureId::Edge(2), - _ => FeatureId::Edge(a), - }; - - let inter_f1 = match (range1[0].2, range1[1].2) { - (FeatureId::Vertex(a), FeatureId::Vertex(b)) => edge_between(a, b), - (FeatureId::Vertex(v), FeatureId::Edge(e)) - | (FeatureId::Edge(e), FeatureId::Vertex(v)) => { - if e == (v + 1) % 3 { - FeatureId::Face(0) - } else { - FeatureId::Edge(e) - } - } - _ => FeatureId::Face(0), - }; - let inter_f2 = match (range2[0].2, range2[1].2) { - (FeatureId::Vertex(a), FeatureId::Vertex(b)) => edge_between(a, b), - (FeatureId::Vertex(v), FeatureId::Edge(e)) - | (FeatureId::Edge(e), FeatureId::Vertex(v)) => { - if e == (v + 1) % 3 { - FeatureId::Face(0) - } else { - FeatureId::Edge(e) - } - } - _ => FeatureId::Face(0), - }; - let a = if range2[0].0 > range1[0].0 + EPS { - TriangleTriangleIntersectionPoint { - p1: range2[0].1, - p2: range2[0].1, - f1: inter_f1, - f2: range2[0].2, - } + TriangleTriangleIntersectionPoint { p1: range2[0].1 } } else if range2[0].0 < range1[0].0 - EPS { - TriangleTriangleIntersectionPoint { - p1: range1[0].1, - p2: range1[0].1, - f1: range1[0].2, - f2: inter_f2, - } + TriangleTriangleIntersectionPoint { p1: range1[0].1 } } else { - TriangleTriangleIntersectionPoint { - p1: range1[0].1, - p2: range2[0].1, - f1: range1[0].2, - f2: range2[0].2, - } + TriangleTriangleIntersectionPoint { p1: range1[0].1 } }; let b = if range2[1].0 < range1[1].0 - EPS { - TriangleTriangleIntersectionPoint { - p1: range2[1].1, - p2: range2[1].1, - f1: inter_f1, - f2: range2[1].2, - } + TriangleTriangleIntersectionPoint { p1: range2[1].1 } } else if range2[1].0 > range1[1].0 + EPS { - TriangleTriangleIntersectionPoint { - p1: range1[1].1, - p2: range1[1].1, - f1: range1[1].2, - f2: inter_f2, - } + TriangleTriangleIntersectionPoint { p1: range1[1].1 } } else { - TriangleTriangleIntersectionPoint { - p1: range1[1].1, - p2: range2[1].1, - f1: range1[1].2, - f2: range2[1].2, - } + TriangleTriangleIntersectionPoint { p1: range1[1].1 } }; Some(TriangleTriangleIntersection::Segment { a, b }) @@ -216,27 +151,17 @@ pub fn triangle_triangle_intersection( crate::transformation::convex_polygons_intersection(&poly1, &poly2, |pt1, pt2| { let intersection = match (pt1, pt2) { (Some(loc1), Some(loc2)) => { - let (f1, p1) = convert_loc(loc1, pts1); - let (f2, p2) = convert_loc(loc2, pts2); - TriangleTriangleIntersectionPoint { p1, p2, f1, f2 } + let (_f1, p1) = convert_loc(loc1, pts1); + let (_f2, _p2) = convert_loc(loc2, pts2); + TriangleTriangleIntersectionPoint { p1 } } (Some(loc1), None) => { - let (f1, p1) = convert_loc(loc1, pts1); - TriangleTriangleIntersectionPoint { - p1, - p2: p1, - f1, - f2: FeatureId::Face(0), - } + let (_f1, p1) = convert_loc(loc1, pts1); + TriangleTriangleIntersectionPoint { p1 } } (None, Some(loc2)) => { - let (f2, p2) = convert_loc(loc2, pts2); - TriangleTriangleIntersectionPoint { - p1: p2, - p2, - f1: FeatureId::Face(0), - f2, - } + let (_f2, p2) = convert_loc(loc2, pts2); + TriangleTriangleIntersectionPoint { p1: p2 } } (None, None) => unreachable!(), }; From 592455da91439d7db39f9e4b9ee6110d2247d92d Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Fri, 12 Jul 2024 18:38:55 -0400 Subject: [PATCH 10/45] Simplift feature flag --- .../mesh_intersection/mesh_intersection.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 6a049b27..14a0ffba 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -679,17 +679,14 @@ fn merge_triangle_sets( Ok(()) } +#[cfg(feature = "wavefront")] #[cfg(test)] mod tests { - use crate::shape::TriMeshFlags; - #[cfg(feature = "wavefront")] - use crate::transformation::wavefront::*; use super::*; - #[cfg(feature = "wavefront")] + use crate::transformation::wavefront::*; use obj::Obj; - #[cfg(feature = "wavefront")] #[test] fn test_same_mesh_intersection() { let Obj { @@ -727,7 +724,6 @@ mod tests { mesh.to_obj_file(&PathBuf::from("same_test.obj")); } - #[cfg(feature = "wavefront")] #[test] fn test_offset_cylinder_intersection() { let Obj { @@ -786,7 +782,6 @@ mod tests { res.to_obj_file(&PathBuf::from("offset_test.obj")); } - #[cfg(feature = "wavefront")] #[test] fn test_stair_bar_intersection() { let Obj { @@ -843,7 +838,6 @@ mod tests { res.to_obj_file(&PathBuf::from("stair_test.obj")); } - #[cfg(feature = "wavefront")] #[test] fn test_complex_intersection() { let Obj { From 4c7e50be4c2b5ee0b397588e170b263b3affde15 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 14:19:13 -0400 Subject: [PATCH 11/45] Fix PR comments --- src/shape/shape.rs | 25 ++++++++ src/shape/triangle.rs | 27 +++++++- .../mesh_intersection/mesh_intersection.rs | 62 +++++++++---------- .../triangle_triangle_intersection.rs | 38 +----------- .../test_data => test_data}/bar.obj | 0 .../center_cylinder.obj | 0 .../low_poly_bunny.obj | 0 .../offset_cylinder.obj | 0 .../test_data => test_data}/poly_cylinder.obj | 0 .../test_data => test_data}/stairs.obj | 0 10 files changed, 80 insertions(+), 72 deletions(-) rename {src/transformation/mesh_intersection/test_data => test_data}/bar.obj (100%) rename {src/transformation/mesh_intersection/test_data => test_data}/center_cylinder.obj (100%) rename {src/transformation/mesh_intersection/test_data => test_data}/low_poly_bunny.obj (100%) rename {src/transformation/mesh_intersection/test_data => test_data}/offset_cylinder.obj (100%) rename {src/transformation/mesh_intersection/test_data => test_data}/poly_cylinder.obj (100%) rename {src/transformation/mesh_intersection/test_data => test_data}/stairs.obj (100%) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index 2c9aa64d..62aa8cab 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1400,3 +1400,28 @@ impl_shape_for_round_shape!( #[cfg(feature = "dim3")] #[cfg(feature = "std")] impl_shape_for_round_shape!(ConvexPolyhedron, RoundConvexPolyhedron); + +/// Find the index of a vertex in a poly line, such that the two +/// edges incident in that vertex form the angle closest to 90 +/// degrees in the poly line. +#[cfg(feature = "dim3")] +pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { + let n = points.len(); + + let mut best_cos = 2.0; + let mut selected_i = 0; + for i in 0..n { + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + + let cos = d1.dot(&d2); + + let cos_abs = cos.abs(); + if cos_abs < best_cos { + best_cos = cos_abs; + selected_i = i; + } + } + + selected_i +} diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index 0ca21ea9..bb7a98f1 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -5,6 +5,9 @@ use crate::shape::{FeatureId, SupportMap}; use crate::shape::{PolygonalFeature, Segment}; use crate::utils; +#[cfg(feature = "dim3")] +use crate::shape::shape::angle_closest_to_90; + use na::{self, ComplexField, Unit}; use num::Zero; #[cfg(feature = "dim3")] @@ -230,15 +233,35 @@ impl Triangle { /// /// The vector points such that it is collinear to `AB × AC` (where `×` denotes the cross /// product). + /// Note: on thin triangles this can cause numerical issues. A more robust + /// way to do this is to look for the incident angle closest to 90 degrees. #[inline] pub fn scaled_normal(&self) -> Vector { - // Note: on thin triangles this can cause numerical issues. A more robust - // way to do this is to look for the incident angle closest to 90 degrees. let ab = self.b - self.a; let ac = self.c - self.a; ab.cross(&ac) } + /// Smarter, but more expensive, mechanism to find a triangle normal. + #[inline] + #[cfg(feature = "dim3")] + pub fn robust_scaled_normal(&self) -> na::Vector3 { + let pts = [self.a.coords, self.b.coords, self.c.coords]; + let best_vertex = angle_closest_to_90(&pts); + + let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; + let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; + + d1.cross(&d2) + } + + /// Similar to `robust_scaled_normal`, but returns the unit length normal. + #[inline] + #[cfg(feature = "dim3")] + pub fn robust_normal(&self) -> na::Vector3 { + self.robust_scaled_normal().normalize() + } + /// Computes the extents of this triangle on the given direction. /// /// This computes the min and max values of the dot products between each diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 14a0ffba..4fa87953 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -2,8 +2,9 @@ use super::{MeshIntersectionError, TriangleTriangleIntersection}; use crate::math::{Isometry, Real}; use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; +use crate::shape::shape::angle_closest_to_90; +use crate::shape::TriMeshFlags; use crate::shape::{TriMesh, Triangle}; -use crate::transformation::mesh_intersection::angle_closest_to_90; use core::f64::consts::PI; use na::{Point3, Vector3}; #[cfg(feature = "wavefront")] @@ -17,12 +18,12 @@ use std::path::PathBuf; /// Metadata that specifies thresholds to use when making construction choices /// in mesh intersections. -pub struct MeshIntersectionMetadata { +pub struct MeshIntersectionTolerances { /// The smallest angle (in degrees) that will be tolerated. A triangle with /// a smaller angle is considered degenerate and will be deleted. pub angle_epsilon: Real, - /// The maximum distance at which two points are considered to overlap in space - /// if `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered + /// The maximum distance at which two points are considered to overlap in space. + /// If `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered /// to be the same point. pub global_insertion_epsilon: Real, /// A multiplier coefficient to scale `global_insertion_epsilon` when checking for @@ -32,7 +33,7 @@ pub struct MeshIntersectionMetadata { pub local_insertion_epsilon_mod: Real, } -impl Default for MeshIntersectionMetadata { +impl Default for MeshIntersectionTolerances { fn default() -> Self { Self { angle_epsilon: 0.005, // degrees @@ -54,29 +55,29 @@ pub fn intersect_meshes( mesh2: &TriMesh, flip2: bool, ) -> Result, MeshIntersectionError> { - intersect_meshes_with_metadata( + intersect_meshes_with_tolerances( pos1, mesh1, flip1, pos2, mesh2, flip2, - MeshIntersectionMetadata::default(), + MeshIntersectionTolerances::default(), ) } /// Similar to `intersect_meshes`. /// /// It allows to specify epsilons for how the algorithm will behave. -/// See `MeshIntersectionMetadata` for details. -pub fn intersect_meshes_with_metadata( +/// See `MeshIntersectionTolerances` for details. +pub fn intersect_meshes_with_tolerances( pos1: &Isometry, mesh1: &TriMesh, flip1: bool, pos2: &Isometry, mesh2: &TriMesh, flip2: bool, - meta_data: MeshIntersectionMetadata, + meta_data: MeshIntersectionTolerances, ) -> Result, MeshIntersectionError> { // NOTE: remove this, used for debugging only. if cfg!(debug_assertions) { @@ -177,18 +178,18 @@ pub fn intersect_meshes_with_metadata( // 5: Associate constraint edges generated by a tringle-triangle intersection // to each intersecting triangle where they occur. - let mut constraints1 = BTreeMap::new(); - let mut constraints2 = BTreeMap::new(); + let mut constraints1 = BTreeMap::<_, Vec<_>>::new(); + let mut constraints2 = BTreeMap::<_, Vec<_>>::new(); for (fid1, fid2) in &intersections { let tri1 = mesh1.triangle(*fid1); let tri2 = mesh2.triangle(*fid2).transformed(&pos12); - let list1 = constraints1.entry(fid1).or_insert(vec![]); - let list2 = constraints2.entry(fid2).or_insert(vec![]); + let list1 = constraints1.entry(fid1).or_default(); + let list2 = constraints2.entry(fid2).or_default(); let intersection = super::triangle_triangle_intersection(&tri1, &tri2); - if intersection.is_some() { - match intersection.unwrap() { + if let Some(intersection) = intersection { + match intersection { TriangleTriangleIntersection::Segment { a, b } => { // For both triangles, add the points in the intersection // and their associated edge to the set. @@ -348,7 +349,7 @@ fn extract_connected_components( } } -fn syncretize_triangulation( +fn triangulate_constraints_and_merge_duplicates( tri: &Triangle, constraints: &[[Point3; 2]], epsilon: Real, @@ -607,7 +608,7 @@ fn merge_triangle_sets( pos12: &Isometry, flip1: bool, flip2: bool, - metadata: &MeshIntersectionMetadata, + metadata: &MeshIntersectionTolerances, point_set: &mut RTree, topology_indices: &mut Vec<[u32; 3]>, ) -> Result<(), MeshIntersectionError> { @@ -619,7 +620,7 @@ fn merge_triangle_sets( for (triangle_id, constraints) in triangle_constraints.iter() { let tri = mesh1.triangle(**triangle_id); - let (delaunay, points) = syncretize_triangulation( + let (delaunay, points) = triangulate_constraints_and_merge_duplicates( &tri, constraints, metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, @@ -664,9 +665,7 @@ fn merge_triangle_sets( topology_indices.last_mut().unwrap().swap(0, 1) } - let id1 = topology_indices.last().unwrap()[0]; - let id2 = topology_indices.last().unwrap()[1]; - let id3 = topology_indices.last().unwrap()[2]; + let [id1, id2, id3] = topology_indices.last().unwrap(); // This should *never* trigger. if id1 == id2 || id1 == id3 || id2 == id3 { @@ -694,8 +693,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/low_poly_bunny.obj") - .unwrap(); + } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); let mesh = TriMesh::with_flags( position @@ -731,8 +729,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/offset_cylinder.obj") - .unwrap(); + } = Obj::load("../../test_data/offset_cylinder.obj").unwrap(); let offset_mesh = TriMesh::with_flags( position @@ -752,8 +749,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/center_cylinder.obj") - .unwrap(); + } = Obj::load("../../test_data/center_cylinder.obj").unwrap(); let center_mesh = TriMesh::with_flags( position @@ -789,7 +785,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/stairs.obj").unwrap(); + } = Obj::load("../../test_data/stairs.obj").unwrap(); let stair_mesh = TriMesh::with_flags( position @@ -809,7 +805,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/bar.obj").unwrap(); + } = Obj::load("../../test_data/bar.obj").unwrap(); let bar_mesh = TriMesh::with_flags( position @@ -845,8 +841,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/low_poly_bunny.obj") - .unwrap(); + } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); let bunny_mesh = TriMesh::with_flags( position @@ -866,8 +861,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../src/transformation/mesh_intersection/test_data/poly_cylinder.obj") - .unwrap(); + } = Obj::load("../../test_data/poly_cylinder.obj").unwrap(); let cylinder_mesh = TriMesh::with_flags( position diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index fce3840a..434a17df 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -32,8 +32,8 @@ pub fn triangle_triangle_intersection( tri1: &Triangle, tri2: &Triangle, ) -> Option { - let normal1 = robust_triangle_normal(tri1); - let normal2 = robust_triangle_normal(tri2); + let normal1 = tri1.robust_normal(); + let normal2 = tri2.robust_normal(); if let Some(intersection_dir) = normal1.cross(&normal2).try_normalize(1.0e-6) { let mut range1 = [ @@ -175,40 +175,6 @@ pub fn triangle_triangle_intersection( } } -/// Smarter, but more expensive, mechanism to find a triangle normal. -fn robust_triangle_normal(tri: &Triangle) -> na::Vector3 { - let pts = [tri.a.coords, tri.b.coords, tri.c.coords]; - let best_vertex = angle_closest_to_90(&pts); - - let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; - let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; - - d1.cross(&d2).normalize() -} - -/// Find the index of a vertex in a poly line, such that the two -/// edges incident in that vertex form the angle closest to 90 -/// degrees in the poly line. -pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { - let n = points.len(); - - let mut best_cos = 2.0; - let mut selected_i = 0; - for i in 0..n { - let d1 = (points[i] - points[(i + 1) % n]).normalize(); - let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); - - let cos = d1.dot(&d2); - - if cos.abs() < best_cos { - best_cos = cos.abs(); - selected_i = i; - } - } - - selected_i -} - fn segment_plane_intersection( plane_center: &Point, plane_normal: &Vector, diff --git a/src/transformation/mesh_intersection/test_data/bar.obj b/test_data/bar.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/bar.obj rename to test_data/bar.obj diff --git a/src/transformation/mesh_intersection/test_data/center_cylinder.obj b/test_data/center_cylinder.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/center_cylinder.obj rename to test_data/center_cylinder.obj diff --git a/src/transformation/mesh_intersection/test_data/low_poly_bunny.obj b/test_data/low_poly_bunny.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/low_poly_bunny.obj rename to test_data/low_poly_bunny.obj diff --git a/src/transformation/mesh_intersection/test_data/offset_cylinder.obj b/test_data/offset_cylinder.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/offset_cylinder.obj rename to test_data/offset_cylinder.obj diff --git a/src/transformation/mesh_intersection/test_data/poly_cylinder.obj b/test_data/poly_cylinder.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/poly_cylinder.obj rename to test_data/poly_cylinder.obj diff --git a/src/transformation/mesh_intersection/test_data/stairs.obj b/test_data/stairs.obj similarity index 100% rename from src/transformation/mesh_intersection/test_data/stairs.obj rename to test_data/stairs.obj From 00577c58c441691ec2f820646ffd138174b59046 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 14:30:36 -0400 Subject: [PATCH 12/45] Fix clippy issue --- src/transformation/mesh_intersection/mesh_intersection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 4fa87953..ac93641f 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -3,7 +3,6 @@ use crate::math::{Isometry, Real}; use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; use crate::shape::shape::angle_closest_to_90; -use crate::shape::TriMeshFlags; use crate::shape::{TriMesh, Triangle}; use core::f64::consts::PI; use na::{Point3, Vector3}; @@ -685,6 +684,7 @@ mod tests { use super::*; use crate::transformation::wavefront::*; use obj::Obj; + use crate::shape::TriMeshFlags; #[test] fn test_same_mesh_intersection() { @@ -703,7 +703,7 @@ mod tests { objects[0].groups[0] .polys .iter() - .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u3use crate::shape::TriMeshFlags;2]) .collect::>(), TriMeshFlags::all(), ); @@ -843,7 +843,7 @@ mod tests { .. } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); - let bunny_mesh = TriMesh::with_flags( + let bunny_mesh = TriMesh::with_flags(use crate::shape::TriMeshFlags; position .iter() .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) From fdd2919012c6afd41254b3ad3fc5913939dae10e Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 14:36:03 -0400 Subject: [PATCH 13/45] Remove accidental pastes --- src/transformation/mesh_intersection/mesh_intersection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index ac93641f..7ba4d66e 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -682,9 +682,9 @@ fn merge_triangle_sets( mod tests { use super::*; + use crate::shape::TriMeshFlags; use crate::transformation::wavefront::*; use obj::Obj; - use crate::shape::TriMeshFlags; #[test] fn test_same_mesh_intersection() { @@ -703,7 +703,7 @@ mod tests { objects[0].groups[0] .polys .iter() - .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u3use crate::shape::TriMeshFlags;2]) + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) .collect::>(), TriMeshFlags::all(), ); @@ -843,7 +843,7 @@ mod tests { .. } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); - let bunny_mesh = TriMesh::with_flags(use crate::shape::TriMeshFlags; + let bunny_mesh = TriMesh::with_flags( position .iter() .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) From 8416d1ffe6f563950775353e8e27b016000b5efc Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 14:46:11 -0400 Subject: [PATCH 14/45] Fix compilation in no-std --- src/shape/shape.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index 62aa8cab..735da59a 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1,6 +1,7 @@ use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume}; use crate::mass_properties::MassProperties; use crate::math::{Isometry, Point, Real, Vector}; +use crate::num::Float; use crate::query::{PointQuery, RayCast}; #[cfg(feature = "serde-serialize")] use crate::shape::SharedShape; From fe57b279f626a4148f035c689c5b34da4503f73c Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:00:00 -0400 Subject: [PATCH 15/45] Fix clippy --- src/shape/shape.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index 735da59a..cabc5436 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1,6 +1,7 @@ use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume}; use crate::mass_properties::MassProperties; use crate::math::{Isometry, Point, Real, Vector}; +#[cfg(feature = "std")] use crate::num::Float; use crate::query::{PointQuery, RayCast}; #[cfg(feature = "serde-serialize")] From b001cc51a7bc6e65825fce5e4b534c81ea5f9071 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:03:23 -0400 Subject: [PATCH 16/45] Fix Clippy --- src/shape/shape.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index cabc5436..83dfb469 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1,7 +1,7 @@ use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume}; use crate::mass_properties::MassProperties; use crate::math::{Isometry, Point, Real, Vector}; -#[cfg(feature = "std")] +#[cfg(not(feature = "std"))] use crate::num::Float; use crate::query::{PointQuery, RayCast}; #[cfg(feature = "serde-serialize")] From 97c2b6c0d5751b4ee691835b327e45717380ad6e Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:11:46 -0400 Subject: [PATCH 17/45] Fix clippy --- src/shape/shape.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index 83dfb469..e6f82adc 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1271,7 +1271,7 @@ impl Shape for Cone { } fn ccd_angular_thickness(&self) -> Real { - let apex_half_angle = self.radius.atan2(self.half_height); + let apex_half_angle = RealField::atan2(self.radius, self.half_height); assert!(apex_half_angle >= 0.0); let basis_angle = Real::frac_pi_2() - apex_half_angle; basis_angle.min(apex_half_angle * 2.0) From 998dfccda6b1337760b01906a577cf95cc4bd94b Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:27:23 -0400 Subject: [PATCH 18/45] Address new PR comments --- crates/parry3d-f64/Cargo.toml | 2 -- src/shape/trimesh.rs | 22 ------------------- .../mesh_intersection/mesh_intersection.rs | 10 ++++----- src/transformation/wavefront.rs | 4 ---- test_data/bar.obj | 13 ++--------- test_data/center_cylinder.obj | 5 ++--- test_data/low_poly_bunny.obj | 5 +++++ test_data/offset_cylinder.obj | 5 ++--- test_data/poly_cylinder.obj | 5 ++--- test_data/stairs.obj | 5 ++--- 10 files changed, 20 insertions(+), 56 deletions(-) diff --git a/crates/parry3d-f64/Cargo.toml b/crates/parry3d-f64/Cargo.toml index 8beb7249..edb6dd94 100644 --- a/crates/parry3d-f64/Cargo.toml +++ b/crates/parry3d-f64/Cargo.toml @@ -63,8 +63,6 @@ bytemuck = { version = "1", features = ["derive"], optional = true } rstar = "0.12.0" obj = { version = "0.10.2", optional = true } -[target.'cfg(not(target_os = "cuda"))'.dependencies] -cust = { version = "0.3", optional = true } log = "0.4" ordered-float = { version = "4", default-features = false } thiserror = { version = "1", optional = true } diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index 2080791b..270ffb53 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -172,28 +172,6 @@ pub struct TriMeshTopology { pub half_edges: Vec, } -#[cfg(all(feature = "std", feature = "cuda"))] -impl TriMeshTopology { - fn as_device_ptr(&self) -> TriMeshTopology { - TriMeshTopology { - vertices: self.vertices.as_device_ptr(), - faces: self.faces.as_device_ptr(), - half_edges: self.half_edges.as_device_ptr(), - } - } -} - -#[cfg(all(feature = "std", feature = "cuda"))] -impl TriMeshTopology { - fn to_cuda(&self) -> CudaResult> { - Ok(TriMeshTopology { - vertices: CudaArray1::new(&self.vertices)?, - faces: CudaArray1::new(&self.faces)?, - half_edges: CudaArray1::new(&self.half_edges)?, - }) - } -} - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( feature = "rkyv", diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 7ba4d66e..c2ac23a0 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; /// Metadata that specifies thresholds to use when making construction choices /// in mesh intersections. pub struct MeshIntersectionTolerances { - /// The smallest angle (in degrees) that will be tolerated. A triangle with + /// The smallest angle (in radians) that will be tolerated. A triangle with /// a smaller angle is considered degenerate and will be deleted. pub angle_epsilon: Real, /// The maximum distance at which two points are considered to overlap in space. @@ -35,7 +35,7 @@ pub struct MeshIntersectionTolerances { impl Default for MeshIntersectionTolerances { fn default() -> Self { Self { - angle_epsilon: 0.005, // degrees + angle_epsilon: 0.005 * PI as Real / 180., // 0.005 degrees global_insertion_epsilon: Real::EPSILON * 100.0, local_insertion_epsilon_mod: 10., } @@ -534,7 +534,7 @@ fn smallest_angle(points: &[Point3]) -> Real { } } - worst_cos.acos() * 180. / PI as Real + worst_cos.acos() } fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) { @@ -563,10 +563,10 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) /// are degenerate and need to be terminated with extreme prejudice. fn is_triangle_degenerate( triangle: &[Point3; 3], - epsilon_degrees: Real, + epsilon_angle: Real, epsilon_distance: Real, ) -> bool { - if smallest_angle(triangle) < epsilon_degrees { + if smallest_angle(triangle) < epsilon_angle { return true; } diff --git a/src/transformation/wavefront.rs b/src/transformation/wavefront.rs index 5e68f091..c7d6ad5c 100644 --- a/src/transformation/wavefront.rs +++ b/src/transformation/wavefront.rs @@ -1,11 +1,7 @@ use crate::shape::TriMesh; - -#[cfg(feature = "wavefront")] use obj::*; -#[cfg(feature = "wavefront")] use std::path::PathBuf; -#[cfg(feature = "wavefront")] impl TriMesh { pub fn to_obj_file(&self, path: &PathBuf) -> Result<(), ObjError> { let mut file = std::fs::File::create(path).unwrap(); diff --git a/test_data/bar.obj b/test_data/bar.obj index 57c8370a..a6748221 100644 --- a/test_data/bar.obj +++ b/test_data/bar.obj @@ -1,14 +1,5 @@ -#### -# -# OBJ File Generated by Meshlab -# -#### -# Object bar.obj -# -# Vertices: 64 -# Faces: 124 -# -#### +# Author: Camilo Talero +# License: CC0 v 0.467654 13.305400 -6.892637 v -0.550473 -3.651697 6.193181 v 0.451122 13.300038 -6.900873 diff --git a/test_data/center_cylinder.obj b/test_data/center_cylinder.obj index 565a3cdc..eaeecf53 100644 --- a/test_data/center_cylinder.obj +++ b/test_data/center_cylinder.obj @@ -1,6 +1,5 @@ -# Blender v3.0.1 OBJ File: '' -# www.blender.org -o Cylinder +# Author: Camilo Talero +# License: CC0 v 0.000000 -1.000000 -1.000000 v 0.000000 1.000000 -1.000000 v 0.195090 -1.000000 -0.980785 diff --git a/test_data/low_poly_bunny.obj b/test_data/low_poly_bunny.obj index 1cd8160a..6422f4df 100644 --- a/test_data/low_poly_bunny.obj +++ b/test_data/low_poly_bunny.obj @@ -1,3 +1,8 @@ +# Author: Camilo Talero +# License: CC0 + +# Decimated from https://graphics.stanford.edu/~mdfisher/Data/Meshes/bunny.obj + v 0.088770 1.286703 -0.402328 v -0.237358 1.595642 0.123569 v -1.125000 1.521824 0.796579 diff --git a/test_data/offset_cylinder.obj b/test_data/offset_cylinder.obj index 0824c435..88d7831e 100644 --- a/test_data/offset_cylinder.obj +++ b/test_data/offset_cylinder.obj @@ -1,6 +1,5 @@ -# Blender v3.0.1 OBJ File: '' -# www.blender.org -o Cylinder.001 +# Author: Camilo Talero +# License: CC0 v -0.010000 -1.010000 -1.780000 v -0.010000 0.990000 -1.780000 v 0.185090 -1.010000 -1.760785 diff --git a/test_data/poly_cylinder.obj b/test_data/poly_cylinder.obj index f99f2c3c..4937a5c7 100644 --- a/test_data/poly_cylinder.obj +++ b/test_data/poly_cylinder.obj @@ -1,6 +1,5 @@ -# Blender v3.0.1 OBJ File: '' -# www.blender.org -o Plane +# Author: Camilo Talero +# License: CC0 v -2.274960 1.634560 -0.654264 v -2.230466 1.592349 -0.700534 v -2.173552 1.441081 -0.682305 diff --git a/test_data/stairs.obj b/test_data/stairs.obj index 32ae616a..2cdad207 100644 --- a/test_data/stairs.obj +++ b/test_data/stairs.obj @@ -1,6 +1,5 @@ -# Blender v3.0.1 OBJ File: '' -# www.blender.org -o Cube_Cube.001 +# Author: Camilo Talero +# License: CC0 v -1.000000 -1.000000 1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 -1.000000 From f8d272d46c1f753c067a62280146ac3605c00f43 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:34:12 -0400 Subject: [PATCH 19/45] Make clippy happy --- .../mesh_intersection/triangle_triangle_intersection.rs | 4 ---- src/transformation/polygon_intersection.rs | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index 434a17df..b787fa7e 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -98,16 +98,12 @@ pub fn triangle_triangle_intersection( let a = if range2[0].0 > range1[0].0 + EPS { TriangleTriangleIntersectionPoint { p1: range2[0].1 } - } else if range2[0].0 < range1[0].0 - EPS { - TriangleTriangleIntersectionPoint { p1: range1[0].1 } } else { TriangleTriangleIntersectionPoint { p1: range1[0].1 } }; let b = if range2[1].0 < range1[1].0 - EPS { TriangleTriangleIntersectionPoint { p1: range2[1].1 } - } else if range2[1].0 > range1[1].0 + EPS { - TriangleTriangleIntersectionPoint { p1: range1[1].1 } } else { TriangleTriangleIntersectionPoint { p1: range1[1].1 } }; diff --git a/src/transformation/polygon_intersection.rs b/src/transformation/polygon_intersection.rs index 9f4f30e4..ebdd8376 100644 --- a/src/transformation/polygon_intersection.rs +++ b/src/transformation/polygon_intersection.rs @@ -437,7 +437,7 @@ pub fn polygons_intersection( out(None, Some(location)) }; - if let Some(first_intersection) = edge_inters.get(0) { + if let Some(first_intersection) = edge_inters.first() { // Jump on the first intersection and move on to the other polygon. to_traverse.poly = (to_traverse.poly + 1) % 2; to_traverse.edge = first_intersection.edges[to_traverse.poly]; @@ -455,12 +455,12 @@ pub fn polygons_intersection( // If there are no intersection, check if one polygon is inside the other. if intersections[0].is_empty() { - if utils::point_in_poly2d(&poly1[0], &poly2) { + if utils::point_in_poly2d(&poly1[0], poly2) { for pt_id in 0..poly1.len() { out(Some(PolylinePointLocation::OnVertex(pt_id)), None) } out(None, None); - } else if utils::point_in_poly2d(&poly2[0], &poly1) { + } else if utils::point_in_poly2d(&poly2[0], poly1) { for pt_id in 0..poly2.len() { out(None, Some(PolylinePointLocation::OnVertex(pt_id))) } From ff83f9d427c12891739c5745fd796ea7d9282545 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:39:08 -0400 Subject: [PATCH 20/45] Replace glob import with explicit items --- src/transformation/mesh_intersection/mesh_intersection.rs | 4 +++- src/transformation/wavefront.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index c2ac23a0..ee693047 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -666,7 +666,9 @@ fn merge_triangle_sets( let [id1, id2, id3] = topology_indices.last().unwrap(); - // This should *never* trigger. + // This should *never* trigger. If it does + // it means the code has created a triangle with duplicate vertices, + // which means we encountered an unaccounted for edge case. if id1 == id2 || id1 == id3 || id2 == id3 { return Err(MeshIntersectionError::DuplicateVertices); } diff --git a/src/transformation/wavefront.rs b/src/transformation/wavefront.rs index c7d6ad5c..829d6749 100644 --- a/src/transformation/wavefront.rs +++ b/src/transformation/wavefront.rs @@ -1,5 +1,5 @@ use crate::shape::TriMesh; -use obj::*; +use obj::{Group, IndexTuple, ObjData, ObjError, Object, SimplePolygon}; use std::path::PathBuf; impl TriMesh { From a79b65a2bccd72fb1bb84967840dddccde979cc4 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:45:42 -0400 Subject: [PATCH 21/45] Replace unwrap with error checking --- .../mesh_intersection/mesh_intersection.rs | 25 +++++++++++-------- .../mesh_intersection_error.rs | 1 + 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index ee693047..dad8d2ad 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -9,7 +9,7 @@ use na::{Point3, Vector3}; #[cfg(feature = "wavefront")] use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; use rstar::RTree; -use spade::{ConstrainedDelaunayTriangulation, Triangulation as _}; +use spade::{ConstrainedDelaunayTriangulation, InsertionError, Triangulation as _}; use std::collections::BTreeMap; use std::collections::HashSet; #[cfg(feature = "wavefront")] @@ -352,10 +352,13 @@ fn triangulate_constraints_and_merge_duplicates( tri: &Triangle, constraints: &[[Point3; 2]], epsilon: Real, -) -> ( - ConstrainedDelaunayTriangulation>, - Vec>, -) { +) -> Result< + ( + ConstrainedDelaunayTriangulation>, + Vec>, + ), + InsertionError, +> { let mut constraints = constraints.to_vec(); // Add the triangle points to the triangulation. let mut point_set = RTree::::new(); @@ -420,12 +423,11 @@ fn triangulate_constraints_and_merge_duplicates( ConstrainedDelaunayTriangulation::>::bulk_load_cdt_stable( planar_points, edges, - ) - .unwrap(); + )?; debug_assert!(cdt_triangulation.vertices().len() == points.len()); let points = points.into_iter().map(|p| Point3::from(p.point)).collect(); - (cdt_triangulation, points) + Ok((cdt_triangulation, points)) } // We heavily recommend that this is left here in case one needs to debug the above code. @@ -619,11 +621,14 @@ fn merge_triangle_sets( for (triangle_id, constraints) in triangle_constraints.iter() { let tri = mesh1.triangle(**triangle_id); - let (delaunay, points) = triangulate_constraints_and_merge_duplicates( + let (delaunay, points) = match triangulate_constraints_and_merge_duplicates( &tri, constraints, metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, - ); + ) { + Ok(v) => v, + Err(_) => return Err(MeshIntersectionError::TriangulationError), + }; for face in delaunay.inner_faces() { let verts = face.vertices(); diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index d9b65269..908d70a1 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -7,6 +7,7 @@ pub enum MeshIntersectionError { MissingPseudoNormals, TriTriError, DuplicateVertices, + TriangulationError, } impl fmt::Display for MeshIntersectionError { From 5a1a1877676d8ddd9c518173eb4e607abd487a0c Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 15:58:20 -0400 Subject: [PATCH 22/45] Fix bug when finding smallest triangle angle --- .../mesh_intersection/mesh_intersection.rs | 15 +++++---------- .../mesh_intersection/mesh_intersection_error.rs | 1 + 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index dad8d2ad..9900e93f 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -524,15 +524,14 @@ fn insert_into_set( fn smallest_angle(points: &[Point3]) -> Real { let n = points.len(); - let mut worst_cos = 2.0; + let mut worst_cos = -2.0; for i in 0..points.len() { let d1 = (points[i].coords - points[(i + 1) % n].coords).normalize(); let d2 = (points[(i + 2) % n].coords - points[(i + 1) % n].coords).normalize(); let cos = d1.dot(&d2); - - if cos < worst_cos { - worst_cos = cos.abs(); + if cos > worst_cos { + worst_cos = cos; } } @@ -554,7 +553,7 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) let local = point - segment[0]; let norm = dir.norm(); - // restrict the result to the segment portion of the line. + // Restrict the result to the segment portion of the line. let coeff = (dir.dot(&local) / norm).clamp(0., norm); segment[0] + coeff * dir.normalize() @@ -595,11 +594,7 @@ fn is_triangle_degenerate( worse_projection_distance.min((proj - triangle[i].coords).norm()); } - if worse_projection_distance < epsilon_distance { - return true; - } - - false + worse_projection_distance < epsilon_distance } fn merge_triangle_sets( diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index 908d70a1..bb4891a4 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -21,6 +21,7 @@ impl fmt::Display for MeshIntersectionError { } Self::TriTriError => f.pad("internal failure while intersecting two triangles"), Self::DuplicateVertices => f.pad("internal failure while merging faces resulting from intersections"), + Self::TriangulationError => f.pad("internal failure while triangulating an interseciton face"), } } } From b155fbf307be691cc53485d01ebd2cf95dd5830d Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Mon, 15 Jul 2024 16:24:37 -0400 Subject: [PATCH 23/45] Fix spelling --- src/transformation/mesh_intersection/mesh_intersection_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index bb4891a4..9132824e 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -21,7 +21,7 @@ impl fmt::Display for MeshIntersectionError { } Self::TriTriError => f.pad("internal failure while intersecting two triangles"), Self::DuplicateVertices => f.pad("internal failure while merging faces resulting from intersections"), - Self::TriangulationError => f.pad("internal failure while triangulating an interseciton face"), + Self::TriangulationError => f.pad("internal failure while triangulating an intersection face"), } } } From e93a2cd2eeffe3d6c24abd9212c319e11ee0cfe1 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:16:26 -0400 Subject: [PATCH 24/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- .../mesh_intersection/mesh_intersection.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 9900e93f..0ecb2319 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -25,10 +25,11 @@ pub struct MeshIntersectionTolerances { /// If `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered /// to be the same point. pub global_insertion_epsilon: Real, - /// A multiplier coefficient to scale `global_insertion_epsilon` when checking for - /// point duplicatin within a single triangle. Inside of an individual triangle - /// the distance at wich two points are considered to be the same is - /// `global_insertion_epsilon * local_insertion_epsilon_mod`. + /// A multiplier coefficient to scale [`Self::global_insertion_epsilon`] when checking for + /// point duplication within a single triangle. + /// + /// Inside of an individual triangle the distance at which two points are considered + /// to be the same is `global_insertion_epsilon * local_insertion_epsilon_mod`. pub local_insertion_epsilon_mod: Real, } From 9a227fb6cdc2b3b06214093b795c71e2f99d3b36 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:18:32 -0400 Subject: [PATCH 25/45] Update src/shape/triangle.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/shape/triangle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index bb7a98f1..e558a180 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -255,7 +255,7 @@ impl Triangle { d1.cross(&d2) } - /// Similar to `robust_scaled_normal`, but returns the unit length normal. + /// Similar to [`Triangle::robust_scaled_normal`], but returns the unit length normal. #[inline] #[cfg(feature = "dim3")] pub fn robust_normal(&self) -> na::Vector3 { From 0a18f4cac9ca3b0c9c816e7114104dfb557455cc Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:19:57 -0400 Subject: [PATCH 26/45] Update src/shape/triangle.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/shape/triangle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index e558a180..45160e7b 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -242,7 +242,11 @@ impl Triangle { ab.cross(&ac) } - /// Smarter, but more expensive, mechanism to find a triangle normal. + /// Find a triangle normal more robustly than with [`Triangle::scaled_normal`]. + /// + /// Thin triangles can cause numerical issues when computing its normal. This method accounts + /// for these numerical issues more robustly than [`Triangle::scaled_normal`], but is more + /// computationally expensive. #[inline] #[cfg(feature = "dim3")] pub fn robust_scaled_normal(&self) -> na::Vector3 { From 5b244763d4c1a0b1d62db0dad2eea10f0caf0666 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:20:12 -0400 Subject: [PATCH 27/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 0ecb2319..c93cae42 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -30,7 +30,7 @@ pub struct MeshIntersectionTolerances { /// /// Inside of an individual triangle the distance at which two points are considered /// to be the same is `global_insertion_epsilon * local_insertion_epsilon_mod`. - pub local_insertion_epsilon_mod: Real, + pub local_insertion_epsilon_scale: Real, } impl Default for MeshIntersectionTolerances { From 5d06a913d47f87197e703d116b8b5c22cb0280e8 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:20:30 -0400 Subject: [PATCH 28/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index c93cae42..75870e76 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -475,11 +475,7 @@ impl rstar::Point for TreePoint { } fn nth(&self, index: usize) -> Self::Scalar { - match index { - 0 => self.point.x, - 1 => self.point.y, - 2 => self.point.z, - _ => unreachable!(), + self.point[index] } } From d870f3888781b81c243a3131e21ce7101e4c7ca8 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:20:37 -0400 Subject: [PATCH 29/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 75870e76..fde52b4a 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -480,12 +480,7 @@ impl rstar::Point for TreePoint { } fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { - match index { - 0 => &mut self.point.x, - 1 => &mut self.point.y, - 2 => &mut self.point.z, - _ => unreachable!(), - } + &mut self.point[i] } } From cad8511d25d32d4dc43f993a794756b104a75911 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:20:52 -0400 Subject: [PATCH 30/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index fde52b4a..f11fd2e9 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -79,7 +79,6 @@ pub fn intersect_meshes_with_tolerances( flip2: bool, meta_data: MeshIntersectionTolerances, ) -> Result, MeshIntersectionError> { - // NOTE: remove this, used for debugging only. if cfg!(debug_assertions) { mesh1.assert_half_edge_topology_is_valid(); mesh2.assert_half_edge_topology_is_valid(); From 5b203450d85245f7464a81c66f2198da2980de41 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:21:08 -0400 Subject: [PATCH 31/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index f11fd2e9..20868fc2 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -517,8 +517,8 @@ fn smallest_angle(points: &[Point3]) -> Real { let mut worst_cos = -2.0; for i in 0..points.len() { - let d1 = (points[i].coords - points[(i + 1) % n].coords).normalize(); - let d2 = (points[(i + 2) % n].coords - points[(i + 1) % n].coords).normalize(); + let d1 = (points[i]- points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n]- points[(i + 1) % n]).normalize(); let cos = d1.dot(&d2); if cos > worst_cos { From 2dd4e03afbeb346dead01acb2d1c2b415858adaa Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:21:15 -0400 Subject: [PATCH 32/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 20868fc2..d5bcb39c 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -552,7 +552,7 @@ fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) /// No matter how smart we are about computing intersections. It is always possible /// to create ultra thin triangles when a point lies on an edge of a tirangle. These -/// are degenerate and need to be terminated with extreme prejudice. +/// are degenerate and need to be removed. fn is_triangle_degenerate( triangle: &[Point3; 3], epsilon_angle: Real, From a1e7f79a66de7e6f7bff2f20cf1262199a98f506 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:21:44 -0400 Subject: [PATCH 33/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index d5bcb39c..261bb859 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -539,7 +539,7 @@ fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, (e1, e2) } -fn project_point_to_segment(point: &Vector3, segment: &[Vector3; 2]) -> Vector3 { +fn project_point_to_segment(point: &Point3, segment: &[Point3; 2]) -> Point3 { let dir = segment[1] - segment[0]; let local = point - segment[0]; From 0a19454e2bb27a03e6553a1a05d6d9a508f7ceeb Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:21:54 -0400 Subject: [PATCH 34/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 261bb859..a5f5c500 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -582,7 +582,7 @@ fn is_triangle_degenerate( (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir + triangle[(i + 2) % 3].coords; worse_projection_distance = - worse_projection_distance.min((proj - triangle[i].coords).norm()); + worse_projection_distance.min((proj - triangle[i]).norm()); } worse_projection_distance < epsilon_distance From 46b1c75c0897476d46b29bcd2b19fd00b1f12442 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:22:05 -0400 Subject: [PATCH 35/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index a5f5c500..880a7229 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -579,7 +579,7 @@ fn is_triangle_degenerate( let dir = dir.normalize(); let proj = - (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir + triangle[(i + 2) % 3].coords; + triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir; worse_projection_distance = worse_projection_distance.min((proj - triangle[i]).norm()); From 08038a0c929b33dc2a78652c16a5aeab734824fd Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:23:39 -0400 Subject: [PATCH 36/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 880a7229..1162acf7 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -611,10 +611,7 @@ fn merge_triangle_sets( &tri, constraints, metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, - ) { - Ok(v) => v, - Err(_) => return Err(MeshIntersectionError::TriangulationError), - }; + ).ok_or(MeshIntersectionError::TriangulationError)?; for face in delaunay.inner_faces() { let verts = face.vertices(); From c5140278b4305aa883cf07c83f23b7126faad176 Mon Sep 17 00:00:00 2001 From: Makogan Date: Tue, 16 Jul 2024 12:23:50 -0400 Subject: [PATCH 37/45] Update src/transformation/mesh_intersection/mesh_intersection.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/transformation/mesh_intersection/mesh_intersection.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 1162acf7..6ac07d9e 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -670,7 +670,6 @@ fn merge_triangle_sets( #[cfg(feature = "wavefront")] #[cfg(test)] mod tests { - use super::*; use crate::shape::TriMeshFlags; use crate::transformation::wavefront::*; From 773b949cfa43ba0b07f977a25c240753ce621c2e Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Tue, 16 Jul 2024 12:24:39 -0400 Subject: [PATCH 38/45] Update comment --- src/shape/triangle.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index bb7a98f1..98375d2d 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -233,8 +233,9 @@ impl Triangle { /// /// The vector points such that it is collinear to `AB × AC` (where `×` denotes the cross /// product). - /// Note: on thin triangles this can cause numerical issues. A more robust - /// way to do this is to look for the incident angle closest to 90 degrees. + /// Note that on thin triangles the calculated normals can suffer from numerical issues. + /// For a more robust (but more computationally expensive) normal calculation, see + /// [`Triangle::robust_scaled_normal`]. #[inline] pub fn scaled_normal(&self) -> Vector { let ab = self.b - self.a; From 8193b7059b4736ee51f4597a34685be5031f06f9 Mon Sep 17 00:00:00 2001 From: "Makogan (Makogan)" Date: Tue, 16 Jul 2024 13:22:28 -0400 Subject: [PATCH 39/45] Fix formatting --- .../mesh_intersection/mesh_intersection.rs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 6ac07d9e..2780263e 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -27,7 +27,7 @@ pub struct MeshIntersectionTolerances { pub global_insertion_epsilon: Real, /// A multiplier coefficient to scale [`Self::global_insertion_epsilon`] when checking for /// point duplication within a single triangle. - /// + /// /// Inside of an individual triangle the distance at which two points are considered /// to be the same is `global_insertion_epsilon * local_insertion_epsilon_mod`. pub local_insertion_epsilon_scale: Real, @@ -38,7 +38,7 @@ impl Default for MeshIntersectionTolerances { Self { angle_epsilon: 0.005 * PI as Real / 180., // 0.005 degrees global_insertion_epsilon: Real::EPSILON * 100.0, - local_insertion_epsilon_mod: 10., + local_insertion_epsilon_scale: 10., } } } @@ -378,13 +378,13 @@ fn triangulate_constraints_and_merge_duplicates( let q1 = triangle[i]; let q2 = triangle[(i + 1) % 3]; - let proj1 = project_point_to_segment(&p1.coords, &[q1, q2]); - if (p1.coords - proj1).norm() < epsilon { + let proj1 = project_point_to_segment(&p1, &[q1.into(), q2.into()]); + if (p1 - proj1).norm() < epsilon { point_pair[0] = Point3::from(proj1); } - let proj2 = project_point_to_segment(&p2.coords, &[q1, q2]); - if (p2.coords - proj2).norm() < epsilon { + let proj2 = project_point_to_segment(&p2, &[q1.into(), q2.into()]); + if (p2 - proj2).norm() < epsilon { point_pair[1] = Point3::from(proj2); } } @@ -475,11 +475,10 @@ impl rstar::Point for TreePoint { fn nth(&self, index: usize) -> Self::Scalar { self.point[index] - } } fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { - &mut self.point[i] + &mut self.point[index] } } @@ -517,8 +516,8 @@ fn smallest_angle(points: &[Point3]) -> Real { let mut worst_cos = -2.0; for i in 0..points.len() { - let d1 = (points[i]- points[(i + 1) % n]).normalize(); - let d2 = (points[(i + 2) % n]- points[(i + 1) % n]).normalize(); + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); let cos = d1.dot(&d2); if cos > worst_cos { @@ -578,11 +577,9 @@ fn is_triangle_degenerate( } let dir = dir.normalize(); - let proj = - triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir; + let proj = triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir; - worse_projection_distance = - worse_projection_distance.min((proj - triangle[i]).norm()); + worse_projection_distance = worse_projection_distance.min((proj - triangle[i]).norm()); } worse_projection_distance < epsilon_distance @@ -607,11 +604,12 @@ fn merge_triangle_sets( for (triangle_id, constraints) in triangle_constraints.iter() { let tri = mesh1.triangle(**triangle_id); - let (delaunay, points) = match triangulate_constraints_and_merge_duplicates( + let (delaunay, points) = triangulate_constraints_and_merge_duplicates( &tri, constraints, - metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_mod, - ).ok_or(MeshIntersectionError::TriangulationError)?; + metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_scale, + ) + .or(Err(MeshIntersectionError::TriangulationError))?; for face in delaunay.inner_faces() { let verts = face.vertices(); From 56856d0c26de0b03e47e68ee526cded9983da5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 18:51:11 +0200 Subject: [PATCH 40/45] chore: move test assets to assets/tests --- {test_data => assets/tests}/bar.obj | 0 {test_data => assets/tests}/center_cylinder.obj | 0 {test_data => assets/tests}/low_poly_bunny.obj | 0 {test_data => assets/tests}/offset_cylinder.obj | 0 {test_data => assets/tests}/poly_cylinder.obj | 0 {test_data => assets/tests}/stairs.obj | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {test_data => assets/tests}/bar.obj (100%) rename {test_data => assets/tests}/center_cylinder.obj (100%) rename {test_data => assets/tests}/low_poly_bunny.obj (100%) rename {test_data => assets/tests}/offset_cylinder.obj (100%) rename {test_data => assets/tests}/poly_cylinder.obj (100%) rename {test_data => assets/tests}/stairs.obj (100%) diff --git a/test_data/bar.obj b/assets/tests/bar.obj similarity index 100% rename from test_data/bar.obj rename to assets/tests/bar.obj diff --git a/test_data/center_cylinder.obj b/assets/tests/center_cylinder.obj similarity index 100% rename from test_data/center_cylinder.obj rename to assets/tests/center_cylinder.obj diff --git a/test_data/low_poly_bunny.obj b/assets/tests/low_poly_bunny.obj similarity index 100% rename from test_data/low_poly_bunny.obj rename to assets/tests/low_poly_bunny.obj diff --git a/test_data/offset_cylinder.obj b/assets/tests/offset_cylinder.obj similarity index 100% rename from test_data/offset_cylinder.obj rename to assets/tests/offset_cylinder.obj diff --git a/test_data/poly_cylinder.obj b/assets/tests/poly_cylinder.obj similarity index 100% rename from test_data/poly_cylinder.obj rename to assets/tests/poly_cylinder.obj diff --git a/test_data/stairs.obj b/assets/tests/stairs.obj similarity index 100% rename from test_data/stairs.obj rename to assets/tests/stairs.obj From e888e0da9d01fc1c3732d9fd82704866d5ab702f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 18:58:45 +0200 Subject: [PATCH 41/45] chore: move angle_closest_to_90 as a method of Triangle --- src/shape/shape.rs | 25 --------------- src/shape/triangle.rs | 31 +++++++++++++++---- .../mesh_intersection/mesh_intersection.rs | 5 ++- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/shape/shape.rs b/src/shape/shape.rs index e6f82adc..3acadc5d 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -1402,28 +1402,3 @@ impl_shape_for_round_shape!( #[cfg(feature = "dim3")] #[cfg(feature = "std")] impl_shape_for_round_shape!(ConvexPolyhedron, RoundConvexPolyhedron); - -/// Find the index of a vertex in a poly line, such that the two -/// edges incident in that vertex form the angle closest to 90 -/// degrees in the poly line. -#[cfg(feature = "dim3")] -pub(crate) fn angle_closest_to_90(points: &[na::Vector3]) -> usize { - let n = points.len(); - - let mut best_cos = 2.0; - let mut selected_i = 0; - for i in 0..n { - let d1 = (points[i] - points[(i + 1) % n]).normalize(); - let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); - - let cos = d1.dot(&d2); - - let cos_abs = cos.abs(); - if cos_abs < best_cos { - best_cos = cos_abs; - selected_i = i; - } - } - - selected_i -} diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index ae8d6b89..af16b55e 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -5,9 +5,6 @@ use crate::shape::{FeatureId, SupportMap}; use crate::shape::{PolygonalFeature, Segment}; use crate::utils; -#[cfg(feature = "dim3")] -use crate::shape::shape::angle_closest_to_90; - use na::{self, ComplexField, Unit}; use num::Zero; #[cfg(feature = "dim3")] @@ -251,9 +248,8 @@ impl Triangle { #[inline] #[cfg(feature = "dim3")] pub fn robust_scaled_normal(&self) -> na::Vector3 { - let pts = [self.a.coords, self.b.coords, self.c.coords]; - let best_vertex = angle_closest_to_90(&pts); - + let pts = self.vertices(); + let best_vertex = self.angle_closest_to_90(); let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; @@ -581,6 +577,29 @@ impl Triangle { } } + /// Find the index of a vertex in this triangle, such that the two + /// edges incident in that vertex form the angle closest to 90 + /// degrees in the triangle. + pub fn angle_closest_to_90(&self) -> usize { + let points = self.vertices(); + let mut best_cos = 2.0; + let mut selected_i = 0; + + for i in 0..3 { + let d1 = (points[i] - points[(i + 1) % 3]).normalize(); + let d2 = (points[(i + 2) % 3] - points[(i + 1) % 3]).normalize(); + + let cos_abs = d1.dot(&d2).abs(); + + if cos_abs < best_cos { + best_cos = cos_abs; + selected_i = i; + } + } + + selected_i + } + /// Reverse the orientation of this triangle by swapping b and c. pub fn reverse(&mut self) { mem::swap(&mut self.b, &mut self.c); diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 2780263e..7a455710 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -2,7 +2,6 @@ use super::{MeshIntersectionError, TriangleTriangleIntersection}; use crate::math::{Isometry, Real}; use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; -use crate::shape::shape::angle_closest_to_90; use crate::shape::{TriMesh, Triangle}; use core::f64::consts::PI; use na::{Point3, Vector3}; @@ -402,8 +401,8 @@ fn triangulate_constraints_and_merge_duplicates( let mut points: Vec<_> = point_set.iter().cloned().collect(); points.sort_by(|a, b| a.id.cmp(&b.id)); - let tri_points = [tri.a.coords, tri.b.coords, tri.c.coords]; - let best_source = angle_closest_to_90(&tri_points); + let tri_points = tri.vertices(); + let best_source = tri.angle_closest_to_90(); let d1 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; let (e1, e2) = planar_gram_schmidt(d1, d2); From bb9447da71d2f1ce192b61315e6ad9eac797900b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 19:14:24 +0200 Subject: [PATCH 42/45] fix: re-add sanitization of spade point coordinates --- .../mesh_intersection/mesh_intersection.rs | 11 +++----- src/utils/mod.rs | 4 +++ src/utils/spade.rs | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 src/utils/spade.rs diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 7a455710..664926dd 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -407,22 +407,19 @@ fn triangulate_constraints_and_merge_duplicates( let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; let (e1, e2) = planar_gram_schmidt(d1, d2); - let project = |p: &Vector3| spade::Point2::new(e1.dot(p), e2.dot(p)); + let project = |p: &Point3| spade::Point2::new(e1.dot(&p.coords), e2.dot(&p.coords)); // Project points into 2D and triangulate the resulting set. let planar_points: Vec<_> = points .iter() .copied() .map(|point| { - let point_proj = project(&point.point.coords); - spade::Point2::new(point_proj.x, point_proj.y) + let point_proj = project(&point.point); + utils::sanitize_spade_point(point_proj) }) .collect(); let cdt_triangulation = - ConstrainedDelaunayTriangulation::>::bulk_load_cdt_stable( - planar_points, - edges, - )?; + ConstrainedDelaunayTriangulation::bulk_load_cdt_stable(planar_points, edges)?; debug_assert!(cdt_triangulation.vertices().len() == points.len()); let points = points.into_iter().map(|p| Point3::from(p.point)).collect(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index afed2c31..85e70f03 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -29,6 +29,8 @@ pub use self::segments_intersection::{segments_intersection2d, SegmentsIntersect pub(crate) use self::sort::sort2; pub(crate) use self::sort::sort3; pub use self::sorted_pair::SortedPair; +#[cfg(all(feature = "dim3", feature = "std"))] +pub(crate) use self::spade::sanitize_spade_point; pub(crate) use self::weighted_value::WeightedValue; pub(crate) use self::wops::{simd_swap, WBasis, WCross, WSign}; @@ -59,5 +61,7 @@ mod sdp_matrix; mod segments_intersection; mod sort; mod sorted_pair; +#[cfg(all(feature = "dim3", feature = "std"))] +mod spade; mod weighted_value; mod wops; diff --git a/src/utils/spade.rs b/src/utils/spade.rs new file mode 100644 index 00000000..f9efa214 --- /dev/null +++ b/src/utils/spade.rs @@ -0,0 +1,27 @@ +use crate::math::Real; + +/// Ensures the given coordinate doesn’t go out of the bounds of spade’s acceptable values. +/// +/// Returns 0.0 if the coordinate is smaller than `spade::MIN_ALLOWED_VALUE`. +/// Returns `spade::MAX_ALLOWED_VALUE` the coordinate is larger than `spade::MAX_ALLOWED_VALUE`. +pub fn sanitize_spade_coord(coord: Real) -> Real { + let abs = coord.abs(); + + #[allow(clippy::unnecessary_cast)] + if abs as f64 <= spade::MIN_ALLOWED_VALUE { + return 0.0; + } + + #[cfg(feature = "f64")] + if abs > spade::MAX_ALLOWED_VALUE { + // This cannot happen in f32 since the max is 3.40282347E+38. + return spade::MAX_ALLOWED_VALUE * coord.signum(); + } + + coord +} + +/// Ensures the coordinates of the given point don’t go out of the bounds of spade’s acceptable values. +pub fn sanitize_spade_point(point: spade::Point2) -> spade::Point2 { + spade::Point2::new(sanitize_spade_coord(point.x), sanitize_spade_coord(point.y)) +} From fce4c303028015bf00148ee82166c9bbb2df5cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 19:14:41 +0200 Subject: [PATCH 43/45] chore: minor coding style fixes --- .../mesh_intersection/mesh_intersection.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index 664926dd..fdc84685 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -3,7 +3,7 @@ use crate::math::{Isometry, Real}; use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; use crate::shape::{TriMesh, Triangle}; -use core::f64::consts::PI; +use crate::utils; use na::{Point3, Vector3}; #[cfg(feature = "wavefront")] use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; @@ -35,7 +35,7 @@ pub struct MeshIntersectionTolerances { impl Default for MeshIntersectionTolerances { fn default() -> Self { Self { - angle_epsilon: 0.005 * PI as Real / 180., // 0.005 degrees + angle_epsilon: (0.005 as Real).to_radians(), // 0.005 degrees global_insertion_epsilon: Real::EPSILON * 100.0, local_insertion_epsilon_scale: 10., } @@ -368,21 +368,21 @@ fn triangulate_constraints_and_merge_duplicates( // Sometimes, points on the edge of a triangle are slightly off, and this makes // spade think that there is a super thin triangle. Project points close to an edge // onto the edge to get better results. - let triangle = [tri.a.coords, tri.b.coords, tri.c.coords]; + let tri_vtx = tri.vertices(); for point_pair in constraints.iter_mut() { let p1 = point_pair[0]; let p2 = point_pair[1]; for i in 0..3 { - let q1 = triangle[i]; - let q2 = triangle[(i + 1) % 3]; + let q1 = tri_vtx[i]; + let q2 = tri_vtx[(i + 1) % 3]; - let proj1 = project_point_to_segment(&p1, &[q1.into(), q2.into()]); + let proj1 = project_point_to_segment(&p1, &[q1, q2]); if (p1 - proj1).norm() < epsilon { point_pair[0] = Point3::from(proj1); } - let proj2 = project_point_to_segment(&p2, &[q1.into(), q2.into()]); + let proj2 = project_point_to_segment(&p2, &[q1, q2]); if (p2 - proj2).norm() < epsilon { point_pair[1] = Point3::from(proj2); } @@ -676,7 +676,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); + } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap(); let mesh = TriMesh::with_flags( position @@ -712,7 +712,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/offset_cylinder.obj").unwrap(); + } = Obj::load("../../assets/tests/offset_cylinder.obj").unwrap(); let offset_mesh = TriMesh::with_flags( position @@ -732,7 +732,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/center_cylinder.obj").unwrap(); + } = Obj::load("../../assets/tests/center_cylinder.obj").unwrap(); let center_mesh = TriMesh::with_flags( position @@ -768,7 +768,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/stairs.obj").unwrap(); + } = Obj::load("../../assets/tests/stairs.obj").unwrap(); let stair_mesh = TriMesh::with_flags( position @@ -788,7 +788,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/bar.obj").unwrap(); + } = Obj::load("../../assets/tests/bar.obj").unwrap(); let bar_mesh = TriMesh::with_flags( position @@ -824,7 +824,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/low_poly_bunny.obj").unwrap(); + } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap(); let bunny_mesh = TriMesh::with_flags( position @@ -844,7 +844,7 @@ mod tests { position, objects, .. }, .. - } = Obj::load("../../test_data/poly_cylinder.obj").unwrap(); + } = Obj::load("../../assets/tests/poly_cylinder.obj").unwrap(); let cylinder_mesh = TriMesh::with_flags( position From 1f5217aa0891c6a4c0ff91afa91ec65109c6de91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 19:15:11 +0200 Subject: [PATCH 44/45] chore: update changelog --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d938c215..c9273b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Change Log -## v0.16.1 +## Unreleased + +## v0.16.2 + +### Added + +- Add `Triangle::robust_scaled_normal` and `Triangle::robust_normal` as a more robust way to compute the triangles + normal for thin triangles that generally cause numerical instabilities. +- Add `Triangle::angle_closest_to_90` to find the triangle’s vertex with an angle closest to 90 degree. + +### Modified + +- Improved the general stability of mesh/mesh intersection. ### Fix From 24b8b93cc1d92844f663ef0ff09d5224845f0628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 17 Jul 2024 19:19:25 +0200 Subject: [PATCH 45/45] chore: more changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b59801..e01125f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add `Triangle::robust_scaled_normal` and `Triangle::robust_normal` as a more robust way to compute the triangles normal for thin triangles that generally cause numerical instabilities. - Add `Triangle::angle_closest_to_90` to find the triangle’s vertex with an angle closest to 90 degree. +- Add the `wavefront` feature that enables `TriMesh::to_obj_file` for exporting a mesh as an obj file. ### Modified