From 30e5d069308cce2824139a88db604c368ac0da52 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 4 Sep 2024 17:45:50 +0200 Subject: [PATCH] wip propagate errors from mesh creation / intersection --- .../tests/query/point_composite_shape.rs | 4 +- crates/parry3d/benches/common/generators.rs | 2 +- crates/parry3d/examples/mesh3d.rs | 2 +- crates/parry3d/examples/plane_intersection.rs | 2 +- .../geometry/trimesh_connected_components.rs | 2 +- .../tests/geometry/trimesh_intersection.rs | 2 +- .../tests/geometry/trimesh_trimesh_toi.rs | 2 +- src/query/split/split_trimesh.rs | 36 ++++++----- src/shape/shared_shape.rs | 13 +++- src/shape/trimesh.rs | 59 +++++++++++++++---- .../mesh_intersection/mesh_intersection.rs | 2 +- .../mesh_intersection_error.rs | 10 ++++ src/transformation/mod.rs | 2 +- 13 files changed, 98 insertions(+), 40 deletions(-) diff --git a/crates/parry2d/tests/query/point_composite_shape.rs b/crates/parry2d/tests/query/point_composite_shape.rs index 0f50f140..dd113714 100644 --- a/crates/parry2d/tests/query/point_composite_shape.rs +++ b/crates/parry2d/tests/query/point_composite_shape.rs @@ -10,7 +10,7 @@ fn project_local_point_and_get_feature_gets_the_enclosing_triangle() { Point2::new(1.0, 1.0), ]; - let mesh = TriMesh::new(vertices, vec![[0, 1, 2], [3, 0, 2]]); + let mesh = TriMesh::new(vertices, vec![[0, 1, 2], [3, 0, 2]]).unwrap(); let query_pt = Point2::new(0.6, 0.6); // Inside the top-right triangle (index 1) let (proj, feat) = mesh.project_local_point_and_get_feature(&query_pt); @@ -34,7 +34,7 @@ fn project_local_point_and_get_feature_projects_correctly_from_outside() { Point2::new(1.0, 1.0), ]; - let mesh = TriMesh::new(vertices, vec![[0, 1, 2], [3, 0, 2]]); + let mesh = TriMesh::new(vertices, vec![[0, 1, 2], [3, 0, 2]]).unwrap(); { let query_pt = Point2::new(-1.0, 0.0); // Left from the bottom-left triangle (index 0) diff --git a/crates/parry3d/benches/common/generators.rs b/crates/parry3d/benches/common/generators.rs index a1a5f61c..4ff7dc43 100644 --- a/crates/parry3d/benches/common/generators.rs +++ b/crates/parry3d/benches/common/generators.rs @@ -9,5 +9,5 @@ pub fn generate_trimesh_around_origin(rng: &mut R) -> TriMesh { .map(|i| Point3::new(i * 3, i * 3 + 1, i * 3 + 2)) .collect(); - TriMesh::new(pts, indices, Some(uvs)) + TriMesh::new(pts, indices, Some(uvs)).unwrap() } diff --git a/crates/parry3d/examples/mesh3d.rs b/crates/parry3d/examples/mesh3d.rs index e32e1424..aeb26eb0 100644 --- a/crates/parry3d/examples/mesh3d.rs +++ b/crates/parry3d/examples/mesh3d.rs @@ -14,7 +14,7 @@ fn main() { let indices = vec![[0u32, 1, 2], [0, 2, 3], [0, 3, 1]]; // Build the mesh. - let mesh = TriMesh::new(points, indices); + let mesh = TriMesh::new(points, indices).unwrap(); assert!(mesh.vertices().len() == 4); } diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs index c80fc0e5..3579c716 100644 --- a/crates/parry3d/examples/plane_intersection.rs +++ b/crates/parry3d/examples/plane_intersection.rs @@ -12,7 +12,7 @@ async fn main() { let camera_pos = Vec3::new(-1.5f32, 2.5f32, -3f32); let mesh = mquad_mesh_from_points(&trimesh, camera_pos); - let trimesh = TriMesh::new(trimesh.0, trimesh.1); + let trimesh = TriMesh::new(trimesh.0, trimesh.1).unwrap(); for _ in 1.. { clear_background(BLACK); diff --git a/crates/parry3d/tests/geometry/trimesh_connected_components.rs b/crates/parry3d/tests/geometry/trimesh_connected_components.rs index bfaf5789..03216020 100644 --- a/crates/parry3d/tests/geometry/trimesh_connected_components.rs +++ b/crates/parry3d/tests/geometry/trimesh_connected_components.rs @@ -15,7 +15,7 @@ fn mesh_connected_components_grouped_faces() { Point::new(15.82, 6.4, 0.0), ]; - let mut roof = TriMesh::new(verts, vec![[0, 1, 2], [3, 4, 5]]); + let mut roof = TriMesh::new(verts, vec![[0, 1, 2], [3, 4, 5]]).unwrap(); if let Err(e) = roof.set_flags(TriMeshFlags::MERGE_DUPLICATE_VERTICES | TriMeshFlags::CONNECTED_COMPONENTS) diff --git a/crates/parry3d/tests/geometry/trimesh_intersection.rs b/crates/parry3d/tests/geometry/trimesh_intersection.rs index 78cf096a..94df835c 100644 --- a/crates/parry3d/tests/geometry/trimesh_intersection.rs +++ b/crates/parry3d/tests/geometry/trimesh_intersection.rs @@ -22,7 +22,7 @@ fn build_diamond(position: &Isometry) -> TriMesh { [1, 4, 3], ]; - TriMesh::new(points, indices) + TriMesh::new(points, indices).unwrap() } #[test] diff --git a/crates/parry3d/tests/geometry/trimesh_trimesh_toi.rs b/crates/parry3d/tests/geometry/trimesh_trimesh_toi.rs index 22493edb..ae08c636 100644 --- a/crates/parry3d/tests/geometry/trimesh_trimesh_toi.rs +++ b/crates/parry3d/tests/geometry/trimesh_trimesh_toi.rs @@ -15,7 +15,7 @@ fn build_pyramid() -> TriMesh { let indices = vec![[0u32, 1, 2], [0, 2, 3], [0, 3, 1]]; - TriMesh::new(points, indices) + TriMesh::new(points, indices).unwrap() } fn do_toi_test() -> Option { diff --git a/src/query/split/split_trimesh.rs b/src/query/split/split_trimesh.rs index b4c3b166..88d8a1d7 100644 --- a/src/query/split/split_trimesh.rs +++ b/src/query/split/split_trimesh.rs @@ -2,8 +2,11 @@ use crate::bounding_volume::Aabb; use crate::math::{Isometry, Point, Real, UnitVector, Vector}; use crate::query::visitors::BoundingVolumeIntersectionsVisitor; use crate::query::{IntersectResult, PointQuery, SplitResult}; -use crate::shape::{Cuboid, FeatureId, Polyline, Segment, Shape, TriMesh, TriMeshFlags, Triangle}; -use crate::transformation; +use crate::shape::{ + Cuboid, FeatureId, Polyline, Segment, Shape, TriMesh, TriMeshBuilderError, TriMeshFlags, + Triangle, +}; +use crate::transformation::{self, intersect_meshes, MeshIntersectionError}; use crate::utils::{hashmap::HashMap, SortedPair, WBasis}; use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _}; use std::cmp::Ordering; @@ -339,8 +342,8 @@ impl TriMesh { } else if indices_lhs.is_empty() { SplitResult::Positive } else { - let mesh_lhs = TriMesh::new(vertices_lhs, indices_lhs); - let mesh_rhs = TriMesh::new(vertices_rhs, indices_rhs); + let mesh_lhs = TriMesh::new(vertices_lhs, indices_lhs).unwrap(); + let mesh_rhs = TriMesh::new(vertices_rhs, indices_rhs).unwrap(); SplitResult::Pair(mesh_lhs, mesh_rhs) } } @@ -612,7 +615,7 @@ impl TriMesh { aabb: &Aabb, flip_cuboid: bool, epsilon: Real, - ) -> Option { + ) -> Result, MeshIntersectionError> { let cuboid = Cuboid::new(aabb.half_extents()); let cuboid_pos = Isometry::from(aabb.center()); self.intersection_with_cuboid( @@ -634,7 +637,7 @@ impl TriMesh { cuboid_position: &Isometry, flip_cuboid: bool, epsilon: Real, - ) -> Option { + ) -> Result, MeshIntersectionError> { self.intersection_with_local_cuboid( flip_mesh, cuboid, @@ -652,25 +655,26 @@ impl TriMesh { cuboid_position: &Isometry, flip_cuboid: bool, _epsilon: Real, - ) -> Option { + ) -> Result, MeshIntersectionError> { if self.topology().is_some() && self.pseudo_normals().is_some() { let (cuboid_vtx, cuboid_idx) = cuboid.to_trimesh(); let cuboid_trimesh = TriMesh::with_flags( cuboid_vtx, cuboid_idx, TriMeshFlags::HALF_EDGE_TOPOLOGY | TriMeshFlags::ORIENTED, - ); + ) + // `TriMesh::with_flags` can't fail for `cuboid.to_trimesh()`. + .unwrap(); - return transformation::intersect_meshes( + let intersect_meshes = intersect_meshes( &Isometry::identity(), self, flip_mesh, cuboid_position, &cuboid_trimesh, flip_cuboid, - ) - .ok() - .flatten(); + ); + return intersect_meshes; } let cuboid_aabb = cuboid.compute_aabb(cuboid_position); @@ -682,7 +686,7 @@ impl TriMesh { let _ = self.qbvh().traverse_depth_first(&mut visitor); if intersecting_tris.is_empty() { - return None; + return Ok(None); } // First, very naive version that outputs a triangle soup without @@ -730,10 +734,10 @@ impl TriMesh { *pt = cuboid_position * *pt; } - if new_vertices.len() >= 3 { - Some(TriMesh::new(new_vertices, new_indices)) + Ok(if new_vertices.len() >= 3 { + Some(TriMesh::new(new_vertices, new_indices).unwrap()) } else { None - } + }) } } diff --git a/src/shape/shared_shape.rs b/src/shape/shared_shape.rs index 7add13b7..f9f928cc 100644 --- a/src/shape/shared_shape.rs +++ b/src/shape/shared_shape.rs @@ -17,6 +17,8 @@ use std::fmt; use std::ops::Deref; use std::sync::Arc; +use super::TriMeshBuilderError; + /// The shape of a collider. #[derive(Clone)] pub struct SharedShape(pub Arc); @@ -194,8 +196,11 @@ impl SharedShape { } /// Initializes a triangle mesh shape defined by its vertex and index buffers. - pub fn trimesh(vertices: Vec>, indices: Vec<[u32; 3]>) -> Self { - SharedShape(Arc::new(TriMesh::new(vertices, indices))) + pub fn trimesh( + vertices: Vec>, + indices: Vec<[u32; 3]>, + ) -> Result { + Ok(SharedShape(Arc::new(TriMesh::new(vertices, indices)?))) } /// Initializes a triangle mesh shape defined by its vertex and index buffers and @@ -205,7 +210,9 @@ impl SharedShape { indices: Vec<[u32; 3]>, flags: TriMeshFlags, ) -> Self { - SharedShape(Arc::new(TriMesh::with_flags(vertices, indices, flags))) + SharedShape(Arc::new( + TriMesh::with_flags(vertices, indices, flags).unwrap(), + )) } /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index f263f9fc..e6d0738c 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -54,6 +54,28 @@ impl fmt::Display for TopologyError { impl std::error::Error for TopologyError {} +/// Indicated an inconsistency while building a triangle mesh. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TriMeshBuilderError { + /// A triangle mesh must contain at least one triangle. + EmptyIndices, + /// Indicated an inconsistency in the topology of a triangle mesh. + TopologyError(TopologyError), +} + +impl fmt::Display for TriMeshBuilderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EmptyIndices => f.pad(&format!( + "A triangle mesh must contain at least one triangle." + )), + Self::TopologyError(topology_error) => topology_error.fmt(f), + } + } +} + +impl std::error::Error for TriMeshBuilderError {} + /// The set of pseudo-normals of a triangle mesh. /// /// These pseudo-normals are used for the inside-outside test of a @@ -255,7 +277,10 @@ impl fmt::Debug for TriMesh { impl TriMesh { /// Creates a new triangle mesh from a vertex buffer and an index buffer. - pub fn new(vertices: Vec>, indices: Vec<[u32; 3]>) -> Self { + pub fn new( + vertices: Vec>, + indices: Vec<[u32; 3]>, + ) -> Result { Self::with_flags(vertices, indices, TriMeshFlags::empty()) } @@ -264,11 +289,10 @@ impl TriMesh { vertices: Vec>, indices: Vec<[u32; 3]>, flags: TriMeshFlags, - ) -> Self { - assert!( - !indices.is_empty(), - "A triangle mesh must contain at least one triangle." - ); + ) -> Result { + if indices.is_empty() { + return Err(TriMeshBuilderError::EmptyIndices); + } let mut result = Self { qbvh: Qbvh::new(), @@ -288,7 +312,7 @@ impl TriMesh { result.rebuild_qbvh(); } - result + Ok(result) } /// Sets the flags of this triangle mesh, controlling its optional associated data. @@ -415,7 +439,7 @@ impl TriMesh { let vertices = std::mem::take(&mut self.vertices); let indices = std::mem::take(&mut self.indices); - *self = TriMesh::with_flags(vertices, indices, self.flags); + *self = TriMesh::with_flags(vertices, indices, self.flags).unwrap(); } /// Create a `TriMesh` from a set of points assumed to describe a counter-clockwise non-convex polygon. @@ -423,7 +447,7 @@ impl TriMesh { /// This operation may fail if the input polygon is invalid, e.g. it is non-simple or has zero surface area. #[cfg(feature = "dim2")] pub fn from_polygon(vertices: Vec>) -> Option { - triangulate_ear_clipping(&vertices).map(|indices| Self::new(vertices, indices)) + triangulate_ear_clipping(&vertices).map(|indices| Self::new(vertices, indices).unwrap()) } /// A flat view of the index buffer of this mesh. @@ -959,7 +983,7 @@ impl TriMesh { impl From for TriMesh { fn from(heightfield: crate::shape::HeightField) -> Self { let (vtx, idx) = heightfield.to_trimesh(); - TriMesh::new(vtx, idx) + TriMesh::new(vtx, idx).unwrap() } } @@ -967,7 +991,7 @@ impl From for TriMesh { impl From for TriMesh { fn from(cuboid: Cuboid) -> Self { let (vtx, idx) = cuboid.to_trimesh(); - TriMesh::new(vtx, idx) + TriMesh::new(vtx, idx).unwrap() } } @@ -1030,3 +1054,16 @@ impl TypedSimdCompositeShape for TriMesh { &self.qbvh } } + +#[cfg(test)] +mod test { + use crate::shape::{TriMesh, TriMeshFlags}; + + #[test] + fn trimesh_error_empty_indices() { + assert!( + TriMesh::with_flags(vec![], vec![], TriMeshFlags::empty()).is_err(), + "A triangle mesh with no triangles is invalid." + ); + } +} diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index bb181c44..df35f747 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -242,7 +242,7 @@ pub fn intersect_meshes_with_tolerances( let vertices: Vec<_> = vertices.iter().map(|p| Point3::from(p.point)).collect(); if !topology_indices.is_empty() { - Ok(Some(TriMesh::new(vertices, topology_indices))) + Ok(Some(TriMesh::new(vertices, topology_indices)?)) } else { Ok(None) } diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index 9132824e..d067fcc2 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -1,5 +1,7 @@ use core::fmt; +use crate::shape::TriMeshBuilderError; + /// Error indicating that a query is not supported between certain shapes #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum MeshIntersectionError { @@ -8,6 +10,7 @@ pub enum MeshIntersectionError { TriTriError, DuplicateVertices, TriangulationError, + TriMeshBuilderError(TriMeshBuilderError), } impl fmt::Display for MeshIntersectionError { @@ -22,9 +25,16 @@ 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 intersection face"), + Self::TriMeshBuilderError(error) => error.fmt(f), } } } #[cfg(feature = "std")] impl std::error::Error for MeshIntersectionError {} + +impl From for MeshIntersectionError { + fn from(value: TriMeshBuilderError) -> Self { + MeshIntersectionError::TriMeshBuilderError(value) + } +} diff --git a/src/transformation/mod.rs b/src/transformation/mod.rs index be7e8348..d6c5a0f2 100644 --- a/src/transformation/mod.rs +++ b/src/transformation/mod.rs @@ -7,7 +7,7 @@ pub use self::convex_hull2::{convex_hull2 as convex_hull, convex_hull2_idx as co #[cfg(feature = "dim3")] pub use self::convex_hull3::{check_convex_hull, convex_hull, try_convex_hull, ConvexHullError}; #[cfg(feature = "dim3")] -pub use self::mesh_intersection::intersect_meshes; +pub use self::mesh_intersection::{intersect_meshes, MeshIntersectionError}; pub use self::polygon_intersection::{ convex_polygons_intersection, convex_polygons_intersection_points, polygons_intersection, polygons_intersection_points,