Skip to content

Commit

Permalink
wip propagate errors from mesh creation / intersection
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrixyz committed Sep 4, 2024
1 parent 837291f commit 30e5d06
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 40 deletions.
4 changes: 2 additions & 2 deletions crates/parry2d/tests/query/point_composite_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion crates/parry3d/benches/common/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ pub fn generate_trimesh_around_origin<R: Rng>(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()
}
2 changes: 1 addition & 1 deletion crates/parry3d/examples/mesh3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion crates/parry3d/examples/plane_intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion crates/parry3d/tests/geometry/trimesh_intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn build_diamond(position: &Isometry<Real>) -> TriMesh {
[1, 4, 3],
];

TriMesh::new(points, indices)
TriMesh::new(points, indices).unwrap()
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/parry3d/tests/geometry/trimesh_trimesh_toi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Real> {
Expand Down
36 changes: 20 additions & 16 deletions src/query/split/split_trimesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -612,7 +615,7 @@ impl TriMesh {
aabb: &Aabb,
flip_cuboid: bool,
epsilon: Real,
) -> Option<Self> {
) -> Result<Option<Self>, MeshIntersectionError> {
let cuboid = Cuboid::new(aabb.half_extents());
let cuboid_pos = Isometry::from(aabb.center());
self.intersection_with_cuboid(
Expand All @@ -634,7 +637,7 @@ impl TriMesh {
cuboid_position: &Isometry<Real>,
flip_cuboid: bool,
epsilon: Real,
) -> Option<Self> {
) -> Result<Option<Self>, MeshIntersectionError> {
self.intersection_with_local_cuboid(
flip_mesh,
cuboid,
Expand All @@ -652,25 +655,26 @@ impl TriMesh {
cuboid_position: &Isometry<Real>,
flip_cuboid: bool,
_epsilon: Real,
) -> Option<Self> {
) -> Result<Option<Self>, 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);
Expand All @@ -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
Expand Down Expand Up @@ -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
}
})
}
}
13 changes: 10 additions & 3 deletions src/shape/shared_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Shape>);
Expand Down Expand Up @@ -194,8 +196,11 @@ impl SharedShape {
}

/// Initializes a triangle mesh shape defined by its vertex and index buffers.
pub fn trimesh(vertices: Vec<Point<Real>>, indices: Vec<[u32; 3]>) -> Self {
SharedShape(Arc::new(TriMesh::new(vertices, indices)))
pub fn trimesh(
vertices: Vec<Point<Real>>,
indices: Vec<[u32; 3]>,
) -> Result<Self, TriMeshBuilderError> {
Ok(SharedShape(Arc::new(TriMesh::new(vertices, indices)?)))
}

/// Initializes a triangle mesh shape defined by its vertex and index buffers and
Expand All @@ -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
Expand Down
59 changes: 48 additions & 11 deletions src/shape/trimesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Point<Real>>, indices: Vec<[u32; 3]>) -> Self {
pub fn new(
vertices: Vec<Point<Real>>,
indices: Vec<[u32; 3]>,
) -> Result<Self, TriMeshBuilderError> {
Self::with_flags(vertices, indices, TriMeshFlags::empty())
}

Expand All @@ -264,11 +289,10 @@ impl TriMesh {
vertices: Vec<Point<Real>>,
indices: Vec<[u32; 3]>,
flags: TriMeshFlags,
) -> Self {
assert!(
!indices.is_empty(),
"A triangle mesh must contain at least one triangle."
);
) -> Result<Self, TriMeshBuilderError> {
if indices.is_empty() {
return Err(TriMeshBuilderError::EmptyIndices);
}

let mut result = Self {
qbvh: Qbvh::new(),
Expand All @@ -288,7 +312,7 @@ impl TriMesh {
result.rebuild_qbvh();
}

result
Ok(result)
}

/// Sets the flags of this triangle mesh, controlling its optional associated data.
Expand Down Expand Up @@ -415,15 +439,15 @@ 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.
///
/// 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<Point<Real>>) -> Option<Self> {
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.
Expand Down Expand Up @@ -959,15 +983,15 @@ impl TriMesh {
impl From<crate::shape::HeightField> for TriMesh {
fn from(heightfield: crate::shape::HeightField) -> Self {
let (vtx, idx) = heightfield.to_trimesh();
TriMesh::new(vtx, idx)
TriMesh::new(vtx, idx).unwrap()
}
}

#[cfg(feature = "dim3")]
impl From<Cuboid> for TriMesh {
fn from(cuboid: Cuboid) -> Self {
let (vtx, idx) = cuboid.to_trimesh();
TriMesh::new(vtx, idx)
TriMesh::new(vtx, idx).unwrap()
}
}

Expand Down Expand Up @@ -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."
);
}
}
2 changes: 1 addition & 1 deletion src/transformation/mesh_intersection/mesh_intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
10 changes: 10 additions & 0 deletions src/transformation/mesh_intersection/mesh_intersection_error.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -8,6 +10,7 @@ pub enum MeshIntersectionError {
TriTriError,
DuplicateVertices,
TriangulationError,
TriMeshBuilderError(TriMeshBuilderError),
}

impl fmt::Display for MeshIntersectionError {
Expand All @@ -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<TriMeshBuilderError> for MeshIntersectionError {
fn from(value: TriMeshBuilderError) -> Self {
MeshIntersectionError::TriMeshBuilderError(value)
}
}
2 changes: 1 addition & 1 deletion src/transformation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 30e5d06

Please sign in to comment.