Skip to content

Commit

Permalink
Propagate errors from mesh creation (#262)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrixyz authored Sep 13, 2024
1 parent 217a567 commit 9f0270e
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 76 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
- Implement `::to_trimesh` in 2d for `Cuboid` and `Aabb`.
- Implement `Shape::feature_normal_at_point` for `TriMesh` to retrieve the normal of a face, when passing a `FeatureId::Face`.

### Modified

- Propagate error information while creating a mesh and using functions making use of it (See #262):
- `TriMesh::new`
- `TriMesh::intersection_with_aabb`
- `SharedShape::trimesh`
- `SharedShape::trimesh_with_flags`

## v0.17.1

### Modified
Expand Down
2 changes: 1 addition & 1 deletion crates/parry2d/examples/project_point2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async fn main() {
let scale = 200f32;
let (points, indices) = Cuboid::new(Vector2::new(0.2 * scale, 0.5 * scale)).to_trimesh();

let trimesh = TriMesh::with_flags(points, indices, TriMeshFlags::ORIENTED);
let trimesh = TriMesh::with_flags(points, indices, TriMeshFlags::ORIENTED).unwrap();
for _i in 1.. {
clear_background(BLACK);

Expand Down
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 @@ -14,7 +14,7 @@ async fn main() {
let camera_pos = Vec3::new(-1.5f32, 2.5f32, -3f32);

let mesh = mquad_mesh_from_points(&trimesh, light_pos, DARKGRAY);
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
2 changes: 1 addition & 1 deletion crates/parry3d/examples/project_point3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async fn main() {
Color::from_rgba(200, 200, 200, 150),
);
let (points, indices) = trimesh;
let trimesh = TriMesh::with_flags(points, indices, TriMeshFlags::ORIENTED);
let trimesh = TriMesh::with_flags(points, indices, TriMeshFlags::ORIENTED).unwrap();
for _i 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
31 changes: 16 additions & 15 deletions src/query/split/split_trimesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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::transformation::{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 +339,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 +612,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 +634,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 +652,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 +683,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 +731,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
}
})
}
}
15 changes: 11 additions & 4 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 @@ -204,8 +209,10 @@ impl SharedShape {
vertices: Vec<Point<Real>>,
indices: Vec<[u32; 3]>,
flags: TriMeshFlags,
) -> Self {
SharedShape(Arc::new(TriMesh::with_flags(vertices, indices, flags)))
) -> Result<Self, TriMeshBuilderError> {
Ok(SharedShape(Arc::new(TriMesh::with_flags(
vertices, indices, flags,
)?)))
}

/// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or
Expand Down
65 changes: 38 additions & 27 deletions src/shape/trimesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ use crate::query::details::NormalConstraints;
use rkyv::{bytecheck, CheckBytes};

/// Indicated an inconsistency in the topology of a triangle mesh.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq)]
pub enum TopologyError {
/// Found a triangle with two or three identical vertices.
#[error("the triangle {0} has at least two identical vertices.")]
BadTriangle(u32),
/// At least two adjacent triangles have opposite orientations.
#[error("the triangles {triangle1} and {triangle2} sharing the edge {edge:?} have opposite orientations.")]
BadAdjacentTrianglesOrientation {
/// The first triangle, with an orientation opposite to the second triangle.
triangle1: u32,
Expand All @@ -37,23 +39,17 @@ pub enum TopologyError {
},
}

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."))
}
Self::BadAdjacentTrianglesOrientation {
triangle1,
triangle2,
edge,
} => f.pad(&format!("the triangles {triangle1} and {triangle2} sharing the edge {:?} have opposite orientations.", edge)),
}
}
/// Indicated an inconsistency while building a triangle mesh.
#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq)]
pub enum TriMeshBuilderError {
/// A triangle mesh must contain at least one triangle.
#[error("A triangle mesh must contain at least one triangle.")]
EmptyIndices,
/// Indicated an inconsistency in the topology of a triangle mesh.
#[error("Topology Error: {0}")]
TopologyError(TopologyError),
}

impl std::error::Error for TopologyError {}

/// 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 +251,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 +263,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 +286,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 +413,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 @@ -970,15 +968,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 @@ -1041,3 +1039,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 @@ -244,7 +244,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
Loading

0 comments on commit 9f0270e

Please sign in to comment.