Skip to content

Commit

Permalink
Improve error message for bad geometry in .obj file.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitiguy committed Sep 19, 2024
1 parent 9799cc0 commit 26a5b00
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 0 deletions.
3 changes: 3 additions & 0 deletions geometry/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,9 @@ filegroup(
name = "test_obj_files",
testonly = 1,
srcs = [
"test/bad_geometryA.obj",
"test/bad_geometryB.obj",
"test/bad_geometry_corrected.obj",
"test/convex.obj",
"test/cube_corners.obj",
"test/cube_with_hole.obj",
Expand Down
25 changes: 25 additions & 0 deletions geometry/test/bad_geometryA.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# File: bad_geometryA.obj
# The geometry in this file produces invalid mass and inertia properties
# due to the winding (order of the vertices) in some of the faces.
# Related files: bad_geometryB.obj and bad_geometry_corrected.obj
# ----------------------------------------------------------------------

g test

# 6 vertices.
v 0.0 0.0 0.5
v 0.0 0.0 -0.5
v 0.0 1.0 0.5
v 0.0 1.0 -0.5
v 1.0 0.0 0.5
v 1.0 0.0 -0.5

# 8 faces.
f 1 3 5
f 2 4 6
f 1 2 3
f 2 3 4
f 3 4 5
f 4 5 6
f 5 6 1
f 6 1 2
25 changes: 25 additions & 0 deletions geometry/test/bad_geometryB.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# File: bad_geometryB.obj
# The geometry in this file produces invalid mass and inertia properties
# due to the winding (order of the vertices) in some of the faces.
# Related files: bad_geometryA.obj and bad_geometry_corrected.obj
# -----------------------------------------------------------------------

g test

# 6 vertices.
v 0.0 0.0 0.5
v 0.0 0.0 -0.5
v 0.0 1.0 0.5
v 0.0 1.0 -0.5
v 1.0 0.0 0.5
v 1.0 0.0 -0.5

# 8 faces.
f 1 3 5
f 6 4 2
f 1 2 3
f 4 3 2
f 3 4 5
f 6 5 4
f 5 6 1
f 2 1 6
25 changes: 25 additions & 0 deletions geometry/test/bad_geometry_corrected.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# File: bad_geometry_corrected.obj
# The geometry in this file produces valid mass and inertia properties
# as contrasted with the invalid mass and inertia properties produced
# by the related files bad_geometryA.obj and bad_geometryB.obj.
# -----------------------------------------------------------------------

g test

# 6 vertices.
v 0.0 0.0 0.5
v 0.0 0.0 -0.5
v 0.0 1.0 0.5
v 0.0 1.0 -0.5
v 1.0 0.0 0.5
v 1.0 0.0 -0.5

# 8 faces.
f 1 5 3
f 2 4 6
f 1 3 2
f 2 3 4
f 3 5 4
f 4 5 6
f 5 1 6
f 6 1 2
13 changes: 13 additions & 0 deletions multibody/tree/geometry_spatial_inertia.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm>
#include <filesystem>
#include <limits>
#include <memory>
#include <string>

Expand Down Expand Up @@ -127,7 +128,19 @@ SpatialInertia<double> CalcSpatialInertia(
accum_com += (p + q + r) * tet_vol_times_six;
}

// Volume should be inherently positive.
const double volume = vol_times_six / 6.0;
constexpr double kEpsilon = std::numeric_limits<double>::epsilon();
if (volume <= kEpsilon) {
const std::string error_message = fmt::format(
"{}(): The calculated volume of a triangle surface mesh is {} whereas "
"a reasonable positive value was expected. The mesh may have bad "
"geometry, e.g., the winding (order of the vertices) of some faces do "
"not produce outward normals.", __func__, volume);
throw std::logic_error(error_message);
// Note: In a Wavefront .obj file, each face's vertices should be stored
// in a counter-clockwise order by default.
}
const double mass = density * volume;
const Vector3d p_GoGcm = accum_com / (vol_times_six * 4);
// We can compute I = C.trace * 1₃ - C. Two key points:
Expand Down
23 changes: 23 additions & 0 deletions multibody/tree/test/geometry_spatial_inertia_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,29 @@ GTEST_TEST(GeometrySpatialInertiaTest, Convex) {
/* tolerance = */ 1e-1));
}

// Check exception messages when CalcSpatialInertia(Shape) is called on a file
// with bad geometry (e.g. not watertight or bad outward normals, or ...).
GTEST_TEST(GeometrySpatialInertiaTest, ExceptionOnBadGeometry) {
std::string geometry_file_path = FindResourceOrThrow(
"drake/geometry/test/bad_geometryA.obj");
const geometry::Mesh bad_geometryA_obj(geometry_file_path, 1.0);
DRAKE_EXPECT_THROWS_MESSAGE(CalcSpatialInertia(bad_geometryA_obj, kDensity),
".*volume of a triangle surface mesh is.* whereas a reasonable "
"positive value was expected. The mesh may have bad geometry.*");

geometry_file_path = FindResourceOrThrow(
"drake/geometry/test/bad_geometryB.obj");
const geometry::Mesh bad_geometryB_obj(geometry_file_path, 1.0);
DRAKE_EXPECT_THROWS_MESSAGE(CalcSpatialInertia(bad_geometryB_obj, kDensity),
".*volume of a triangle surface mesh is.* whereas a reasonable "
"positive value was expected. The mesh may have bad geometry.*");

geometry_file_path = FindResourceOrThrow(
"drake/geometry/test/bad_geometry_corrected.obj");
const geometry::Mesh ok_geometry_obj(geometry_file_path, 1.0);
EXPECT_NO_THROW(CalcSpatialInertia(ok_geometry_obj, kDensity));
}

// Exercises the common code paths for Mesh and Convex (i.e., "MeshTypes").
template <typename MeshType>
class MeshTypeSpatialInertaTest : public testing::Test {};
Expand Down

0 comments on commit 26a5b00

Please sign in to comment.