Skip to content

Commit

Permalink
Make the convex-hull calculation itself failible.
Browse files Browse the repository at this point in the history
  • Loading branch information
sebcrozet committed Jan 15, 2023
1 parent 2e181d1 commit 7d44932
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 66 deletions.
36 changes: 21 additions & 15 deletions src/transformation/convex_hull3/convex_hull.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::InitialMesh;
use super::TriangleFacet;
use super::{ConvexHullError, TriangleFacet};
use crate::math::Real;
use crate::transformation::convex_hull_utils::indexed_support_point_nth;
use crate::transformation::convex_hull_utils::{indexed_support_point_id, normalize};
Expand All @@ -8,8 +8,15 @@ use na::{self, Point3};

/// Computes the convex hull of a set of 3d points.
pub fn convex_hull(points: &[Point3<Real>]) -> (Vec<Point3<Real>>, Vec<[u32; 3]>) {
try_convex_hull(points).unwrap()
}

/// Computes the convex hull of a set of 3d points.
pub fn try_convex_hull(
points: &[Point3<Real>],
) -> Result<(Vec<Point3<Real>>, Vec<[u32; 3]>), ConvexHullError> {
if points.is_empty() {
return (Vec::new(), Vec::new());
return Ok((Vec::new(), Vec::new()));
}

// print_buildable_vec("input", points);
Expand All @@ -23,12 +30,13 @@ pub fn convex_hull(points: &[Point3<Real>]) -> (Vec<Point3<Real>>, Vec<[u32; 3]>

let mut triangles;

match super::get_initial_mesh(points, &mut normalized_points[..], &mut undecidable_points) {
match super::try_get_initial_mesh(points, &mut normalized_points[..], &mut undecidable_points)?
{
InitialMesh::Facets(facets) => {
triangles = facets;
}
InitialMesh::ResultMesh(vertices, indices) => {
return (vertices, indices);
return Ok((vertices, indices));
}
}

Expand Down Expand Up @@ -75,7 +83,7 @@ pub fn convex_hull(points: &[Point3<Real>]) -> (Vec<Point3<Real>>, Vec<[u32; 3]>
&mut silhouette_loop_facets_and_idx,
&mut removed_facets,
&mut triangles[..],
);
)?;

// Check that the silhouette is valid.
// FIXME: remove this debug code.
Expand All @@ -97,11 +105,9 @@ pub fn convex_hull(points: &[Point3<Real>]) -> (Vec<Point3<Real>>, Vec<[u32; 3]>
}

if any_valid {
panic!(
"Warning: exitting an unfinished work: {}, {}",
normalized_points[point],
triangles.len()
);
return Err(ConvexHullError::InternalError(
"Internal error: exiting an unfinished work.",
));
}

// FIXME: this is very harsh.
Expand Down Expand Up @@ -143,11 +149,9 @@ pub fn convex_hull(points: &[Point3<Real>]) -> (Vec<Point3<Real>>, Vec<[u32; 3]>

let mut points = points.to_vec();
utils::remove_unused_points(&mut points, &mut idx[..]);

assert!(points.len() != 0, "Internal error: empty output mesh.");
// super::check_convex_hull(&points, &idx);

(points, idx)
Ok((points, idx))
}

fn compute_silhouette(
Expand Down Expand Up @@ -203,7 +207,7 @@ fn fix_silhouette_topology(
out_facets_and_idx: &mut Vec<(usize, usize)>,
removed_facets: &mut Vec<usize>,
triangles: &mut [TriangleFacet],
) {
) -> Result<(), ConvexHullError> {
// FIXME: don't allocate this everytime.
let mut workspace = vec![0; points.len()];
let mut needs_fixing = false;
Expand Down Expand Up @@ -236,7 +240,7 @@ fn fix_silhouette_topology(
.iter()
.map(|(f, ai)| triangles[*f].second_point_from_edge(*ai)),
)
.unwrap();
.ok_or(ConvexHullError::MissingSupportPoint)?;
let selected = &out_facets_and_idx[supp];
if workspace[triangles[selected.0].second_point_from_edge(selected.1)] == 1 {
// This is a valid point to start with.
Expand Down Expand Up @@ -290,6 +294,8 @@ fn fix_silhouette_topology(

// println!("");
}

Ok(())
}

fn attach_and_push_facets(
Expand Down
25 changes: 25 additions & 0 deletions src/transformation/convex_hull3/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[derive(Debug, PartialEq)]
/// Errors generated by the convex-hull calculation.
pub enum ConvexHullError {
/// Reached an impossible configuration in the convex-hull calculation,
/// likely because of a bug.
InternalError(&'static str),
/// The convex hull calculation was unable to find a support point.
/// This generally happens if the input point set contains invalid points (with NaN coordinates)
/// or if they are almost coplanar.
MissingSupportPoint,
/// Reached a piece of code we shouldn’t (internal error).
Unreachable,
}

impl std::fmt::Display for ConvexHullError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConvexHullError::InternalError(reason) => write!(f, "InternalError({})", reason),
ConvexHullError::MissingSupportPoint => write!(f, "MissingSupportPoint"),
ConvexHullError::Unreachable => write!(f, "Unreachable"),
}
}
}

impl std::error::Error for ConvexHullError {}
58 changes: 14 additions & 44 deletions src/transformation/convex_hull3/initial_mesh.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::TriangleFacet;
use super::{ConvexHullError, TriangleFacet};
use crate::math::Real;
use crate::shape::Triangle;
use crate::transformation;
Expand All @@ -7,25 +7,6 @@ use crate::utils;
use na::{Point2, Point3, Vector3};
use std::cmp::Ordering;

#[derive(Debug, PartialEq)]
pub enum InitialMeshError {
AssertionFailed(&'static str),
MissingSupportPoint,
Unreachable,
}

impl std::fmt::Display for InitialMeshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InitialMeshError::AssertionFailed(reason) => write!(f, "AssertionFailed({})", reason),
InitialMeshError::MissingSupportPoint => write!(f, "MissingSupportPoint"),
InitialMeshError::Unreachable => write!(f, "Unreachable"),
}
}
}

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

#[derive(Debug)]
pub enum InitialMesh {
Facets(Vec<TriangleFacet>),
Expand All @@ -52,19 +33,11 @@ fn build_degenerate_mesh_segment(
(vec![a, b], vec![ta, tb])
}

pub fn get_initial_mesh(
original_points: &[Point3<Real>],
normalized_points: &mut [Point3<Real>],
undecidable: &mut Vec<usize>,
) -> InitialMesh {
try_get_initial_mesh(original_points, normalized_points, undecidable).unwrap()
}

pub fn try_get_initial_mesh(
original_points: &[Point3<Real>],
normalized_points: &mut [Point3<Real>],
undecidable: &mut Vec<usize>,
) -> Result<InitialMesh, InitialMeshError> {
) -> Result<InitialMesh, ConvexHullError> {
/*
* Compute the eigenvectors to see if the input data live on a subspace.
*/
Expand Down Expand Up @@ -177,9 +150,9 @@ pub fn try_get_initial_mesh(
}

let p1 = support_point_id(&eigpairs[0].0, normalized_points)
.ok_or(InitialMeshError::MissingSupportPoint)?;
.ok_or(ConvexHullError::MissingSupportPoint)?;
let p2 = support_point_id(&-eigpairs[0].0, normalized_points)
.ok_or(InitialMeshError::MissingSupportPoint)?;
.ok_or(ConvexHullError::MissingSupportPoint)?;

let mut max_area = 0.0;
let mut p3 = usize::max_value();
Expand All @@ -195,7 +168,7 @@ pub fn try_get_initial_mesh(
}

if p3 == usize::max_value() {
Err(InitialMeshError::AssertionFailed("Internal convex hull error: no triangle found."))
Err(ConvexHullError::InternalError("no triangle found."))
} else {
// Build two facets with opposite normals
let mut f1 = TriangleFacet::new(p1, p2, p3, normalized_points);
Expand Down Expand Up @@ -248,18 +221,22 @@ pub fn try_get_initial_mesh(
Ok(InitialMesh::Facets(facets))
}
}
_ => Err(InitialMeshError::Unreachable),
_ => Err(ConvexHullError::Unreachable),
}
}

#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "f32")]
// TODO: ideally we would want this test to actually fail (i.e. we want the
// convex hull calculation to succeed in this case). Though right now almost-coplanar
// points can result in a failure of the algorithm. So we are testing here that the
// error is correctly reported (instead of panicking internally).
fn try_get_initial_mesh_should_fail_for_missing_support_points() {
use na::Point3;
use super::*;
use crate::transformation::convex_hull_utils::normalize;
use crate::transformation::try_convex_hull;
use na::Point3;

let point_cloud = vec![
Point3::new(103.05024, 303.44974, 106.125),
Expand All @@ -268,14 +245,7 @@ mod tests {
Point3::new(106.55025, 303.44974, 106.125),
Point3::new(106.55043, 303.44974, 106.125),
];

let mut normalized_points = point_cloud.to_vec();
let _ = normalize(&mut normalized_points[..]);

let mut undecidable_points = vec![];

let result = try_get_initial_mesh(&point_cloud, &mut normalized_points, &mut undecidable_points);

assert_eq!(InitialMeshError::MissingSupportPoint, result.unwrap_err());
let result = try_convex_hull(&point_cloud);
assert_eq!(ConvexHullError::MissingSupportPoint, result.unwrap_err());
}
}
8 changes: 5 additions & 3 deletions src/transformation/convex_hull3/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub(self) use self::initial_mesh::{get_initial_mesh, InitialMesh};
pub use self::error::ConvexHullError;
pub(self) use self::initial_mesh::{try_get_initial_mesh, InitialMesh};
pub(self) use self::triangle_facet::TriangleFacet;
pub(self) use self::validation::check_facet_links;
pub use convex_hull::convex_hull;
pub use convex_hull::{convex_hull, try_convex_hull};
pub use validation::check_convex_hull;

mod convex_hull;
pub mod initial_mesh;
mod error;
mod initial_mesh;
mod triangle_facet;
mod validation;
8 changes: 4 additions & 4 deletions src/transformation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ pub(crate) use self::convex_hull2::convex_hull2_idx;
#[cfg(feature = "dim2")]
pub use self::convex_hull2::{convex_hull2 as convex_hull, convex_hull2_idx as convex_hull_idx};
#[cfg(feature = "dim3")]
pub use self::convex_hull3::{check_convex_hull, convex_hull};
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::polygon_intersection::{
convex_polygons_intersection, convex_polygons_intersection_points,
};

pub mod convex_hull2;
mod convex_hull2;
#[cfg(feature = "dim3")]
pub mod convex_hull3;
pub mod convex_hull_utils;
mod convex_hull3;
pub(crate) mod convex_hull_utils;

mod polygon_intersection;
/// Approximate convex decomposition using the VHACD algorithm.
Expand Down

0 comments on commit 7d44932

Please sign in to comment.