From 8f5d3b421bafa7d4b3f28c8aeebe8efead13e48b Mon Sep 17 00:00:00 2001 From: Nicola Loi Date: Wed, 7 Aug 2024 14:48:02 +0200 Subject: [PATCH] Fix minimal oriented bounding box of MeshBase derived classes and add new unit tests (#6898) * Fix minimal oriented bounding box of MeshBase derived classes and add new unit tests * Update CHANGELOG --- CHANGELOG.md | 1 + cpp/open3d/geometry/MeshBase.cpp | 2 +- cpp/tests/geometry/TetraMesh.cpp | 224 +++++++++++++++++++++++++++ cpp/tests/geometry/TriangleMesh.cpp | 226 ++++++++++++++++++++++++++++ 4 files changed, 452 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9d0c88065..ed7a0c786cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix build with fmt v10.2.0 (#6783) - Fix segmentation fault (lambda reference capture) of VisualizerWithCustomAnimation::Play (PR #6804) - Add O3DVisualizer API to enable collapse control of verts in the side panel (PR #6865) +- Fix minimal oriented bounding box of MeshBase derived classes and add new unit tests (PR #6898) ## 0.13 diff --git a/cpp/open3d/geometry/MeshBase.cpp b/cpp/open3d/geometry/MeshBase.cpp index 1e403968900..b8986460e1b 100644 --- a/cpp/open3d/geometry/MeshBase.cpp +++ b/cpp/open3d/geometry/MeshBase.cpp @@ -51,7 +51,7 @@ OrientedBoundingBox MeshBase::GetOrientedBoundingBox(bool robust) const { } OrientedBoundingBox MeshBase::GetMinimalOrientedBoundingBox(bool robust) const { - return OrientedBoundingBox::CreateFromPoints(vertices_, robust); + return OrientedBoundingBox::CreateFromPointsMinimal(vertices_, robust); } MeshBase &MeshBase::Transform(const Eigen::Matrix4d &transformation) { diff --git a/cpp/tests/geometry/TetraMesh.cpp b/cpp/tests/geometry/TetraMesh.cpp index 5b5e29db246..6c26a6784f4 100644 --- a/cpp/tests/geometry/TetraMesh.cpp +++ b/cpp/tests/geometry/TetraMesh.cpp @@ -9,6 +9,7 @@ #include +#include "open3d/geometry/BoundingVolume.h" #include "open3d/geometry/PointCloud.h" #include "open3d/geometry/TriangleMesh.h" #include "tests/Tests.h" @@ -119,6 +120,229 @@ TEST(TetraMesh, GetMaxBound) { tm.GetMaxBound()); } +TEST(TetraMesh, GetCenter) { + int size = 100; + + Eigen::Vector3d dmin(0.0, 0.0, 0.0); + Eigen::Vector3d dmax(1000.0, 1000.0, 1000.0); + + geometry::TetraMesh tm; + + tm.vertices_.resize(size); + Rand(tm.vertices_, dmin, dmax, 0); + + ExpectEQ(tm.GetCenter(), + Eigen::Vector3d(531.137254, 535.176470, 501.882352)); + + geometry::TetraMesh tm_empty; + ExpectEQ(tm_empty.GetCenter(), Eigen::Vector3d(0, 0, 0)); +} + +TEST(TetraMesh, GetAxisAlignedBoundingBox) { + geometry::TetraMesh tm; + geometry::AxisAlignedBoundingBox aabb; + + tm = geometry::TetraMesh(); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); + + tm = geometry::TetraMesh({{0, 0, 0}}, {{0, 0, 0, 0}}); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); + + tm = geometry::TetraMesh({{0, 2, 0}, + {1, 1, 2}, + {1, 0, 3}, + {0, 1, 4}, + {1, 2, 5}, + {1, 3, 6}, + {0, 0, 7}, + {0, 3, 8}, + {1, 0, 9}, + {0, 2, 10}}, + {{0, 1, 2, 3}, + {4, 0, 1, 2}, + {4, 5, 2, 3}, + {4, 0, 2, 3}, + {4, 0, 5, 3}, + {6, 0, 5, 3}, + {6, 7, 8, 5}, + {9, 4, 7, 1}, + {9, 4, 0, 1}, + {9, 0, 1, 3}, + {9, 6, 7, 8}, + {9, 4, 7, 5}, + {9, 6, 7, 5}, + {9, 4, 0, 5}, + {9, 6, 0, 5}}); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(1, 3, 10)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); +} + +TEST(TetraMesh, GetOrientedBoundingBox) { + geometry::TetraMesh tm; + geometry::OrientedBoundingBox obb; + + // Empty (GetOrientedBoundingBox requires >=4 points) + tm = geometry::TetraMesh(); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + + // Point + tm = geometry::TetraMesh({{0, 0, 0}}, {{0, 0, 0, 0}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + {{0, 1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetOrientedBoundingBox(true)); + + // Plane + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}}, + {{0, 1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetOrientedBoundingBox(true)); + + // Valid 4 points + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 1, 1}}, + {{0, 1, 2, 3}}); + tm.GetOrientedBoundingBox(); + + // 8 points with known ground truth + tm = geometry::TetraMesh({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}, + {{0, 1, 2, 3}, + {4, 5, 6, 7}, + {0, 1, 4, 5}, + {2, 3, 6, 7}, + {0, 2, 4, 6}, + {1, 3, 5, 7}}); + obb = tm.GetOrientedBoundingBox(); + EXPECT_EQ(obb.center_, Eigen::Vector3d(1.5, 1, 0.5)); + EXPECT_EQ(obb.extent_, Eigen::Vector3d(3, 2, 1)); + EXPECT_EQ(obb.color_, Eigen::Vector3d(1, 1, 1)); + EXPECT_EQ(obb.R_, Eigen::Matrix3d::Identity()); + ExpectEQ(Sort(obb.GetBoxPoints()), + Sort(std::vector({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}))); + + // Check for a bug where the OBB rotation contained a reflection for this + // example. + tm = geometry::TetraMesh({{0, 2, 4}, {7, 9, 1}, {5, 2, 0}, {3, 8, 7}}, + {{0, 1, 2, 3}}); + obb = tm.GetOrientedBoundingBox(); + EXPECT_GT(obb.R_.determinant(), 0.999); +} + +TEST(TetraMesh, GetMinimalOrientedBoundingBox) { + geometry::TetraMesh tm; + geometry::OrientedBoundingBox obb; + + // Empty (GetOrientedBoundingBox requires >=4 points) + tm = geometry::TetraMesh(); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + + // Point + tm = geometry::TetraMesh({{0, 0, 0}}, {{0, 0, 0, 0}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + {{0, 1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetMinimalOrientedBoundingBox(true)); + + // Plane + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}}, + {{0, 1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetMinimalOrientedBoundingBox(true)); + + // Valid 4 points + tm = geometry::TetraMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 1, 1}}, + {{0, 1, 2, 3}}); + tm.GetMinimalOrientedBoundingBox(); + + // 8 points with known ground truth + tm = geometry::TetraMesh({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}, + {{0, 1, 2, 3}, + {4, 5, 6, 7}, + {0, 1, 4, 5}, + {2, 3, 6, 7}, + {0, 2, 4, 6}, + {1, 3, 5, 7}}); + obb = tm.GetMinimalOrientedBoundingBox(); + EXPECT_EQ(obb.center_, Eigen::Vector3d(1.5, 1, 0.5)); + EXPECT_EQ(obb.extent_, Eigen::Vector3d(3, 2, 1)); + EXPECT_EQ(obb.color_, Eigen::Vector3d(1, 1, 1)); + ExpectEQ(Sort(obb.GetBoxPoints()), + Sort(std::vector({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}))); + + // Check for a bug where the OBB rotation contained a reflection for this + // example. + tm = geometry::TetraMesh({{0, 2, 4}, {7, 9, 1}, {5, 2, 0}, {3, 8, 7}}, + {{0, 1, 2, 3}}); + obb = tm.GetMinimalOrientedBoundingBox(); + EXPECT_GT(obb.R_.determinant(), 0.999); + + // should always be equal/smaller than axis aligned- & oriented bounding box + tm = geometry::TetraMesh({{0.866, 0.474, 0.659}, + {0.943, 0.025, 0.789}, + {0.386, 0.264, 0.691}, + {0.938, 0.588, 0.496}, + {0.221, 0.116, 0.257}, + {0.744, 0.182, 0.052}, + {0.019, 0.525, 0.699}, + {0.722, 0.134, 0.668}}, + {{0, 1, 2, 3}, + {4, 0, 2, 3}, + {4, 0, 1, 2}, + {5, 4, 6, 3}, + {5, 4, 2, 3}, + {5, 4, 1, 2}, + {7, 1, 2, 6}, + {7, 5, 2, 6}, + {7, 5, 1, 2}, + {7, 5, 4, 6}, + {7, 5, 4, 1}, + {7, 0, 1, 6}, + {7, 4, 0, 6}, + {7, 4, 0, 1}}); + geometry::OrientedBoundingBox mobb = tm.GetMinimalOrientedBoundingBox(); + obb = tm.GetOrientedBoundingBox(); + geometry::AxisAlignedBoundingBox aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_GT(obb.Volume(), mobb.Volume()); + EXPECT_GT(aabb.Volume(), mobb.Volume()); +} + TEST(TetraMesh, Transform) { std::vector ref_vertices = { {1.411252, 4.274168, 3.130918}, {1.231757, 4.154505, 3.183678}, diff --git a/cpp/tests/geometry/TriangleMesh.cpp b/cpp/tests/geometry/TriangleMesh.cpp index ff6d75f6c8c..a562059cbc2 100644 --- a/cpp/tests/geometry/TriangleMesh.cpp +++ b/cpp/tests/geometry/TriangleMesh.cpp @@ -168,6 +168,232 @@ TEST(TriangleMesh, GetMaxBound) { tm.GetMaxBound()); } +TEST(TriangleMesh, GetCenter) { + int size = 100; + + Eigen::Vector3d dmin(0.0, 0.0, 0.0); + Eigen::Vector3d dmax(1000.0, 1000.0, 1000.0); + + geometry::TriangleMesh tm; + + tm.vertices_.resize(size); + Rand(tm.vertices_, dmin, dmax, 0); + + ExpectEQ(tm.GetCenter(), + Eigen::Vector3d(531.137254, 535.176470, 501.882352)); + + geometry::TriangleMesh tm_empty; + ExpectEQ(tm_empty.GetCenter(), Eigen::Vector3d(0, 0, 0)); +} + +TEST(TriangleMesh, GetAxisAlignedBoundingBox) { + geometry::TriangleMesh tm; + geometry::AxisAlignedBoundingBox aabb; + + tm = geometry::TriangleMesh(); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); + + tm = geometry::TriangleMesh({{0, 0, 0}}, {{0, 0, 0}}); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); + + tm = geometry::TriangleMesh({{0, 2, 0}, + {1, 1, 2}, + {1, 0, 3}, + {0, 1, 4}, + {1, 2, 5}, + {1, 3, 6}, + {0, 0, 7}, + {0, 3, 8}, + {1, 0, 9}, + {0, 2, 10}}, + {{3, 1, 0}, + {3, 2, 1}, + {1, 2, 4}, + {5, 4, 2}, + {2, 3, 5}, + {6, 3, 0}, + {6, 5, 3}, + {5, 6, 8}, + {8, 7, 5}, + {1, 7, 9}, + {1, 4, 7}, + {0, 1, 9}, + {9, 8, 6}, + {7, 8, 9}, + {4, 5, 7}, + {9, 6, 0}}); + aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_EQ(aabb.min_bound_, Eigen::Vector3d(0, 0, 0)); + EXPECT_EQ(aabb.max_bound_, Eigen::Vector3d(1, 3, 10)); + EXPECT_EQ(aabb.color_, Eigen::Vector3d(1, 1, 1)); +} + +TEST(TriangleMesh, GetOrientedBoundingBox) { + geometry::TriangleMesh tm; + geometry::OrientedBoundingBox obb; + + // Empty (GetOrientedBoundingBox requires >=4 points) + tm = geometry::TriangleMesh(); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + + // Point + tm = geometry::TriangleMesh({{0, 0, 0}}, {{0, 0, 0}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + {{0, 1, 2}, {1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetOrientedBoundingBox(true)); + + // Plane + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}}, + {{0, 1, 2}, {1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetOrientedBoundingBox(true)); + + // Valid 4 points + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 1, 1}}, + {{0, 1, 2}, {1, 2, 3}}); + tm.GetOrientedBoundingBox(); + + // 8 points with known ground truth + tm = geometry::TriangleMesh({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}, + {{0, 1, 2}, + {1, 2, 3}, + {4, 5, 6}, + {5, 6, 7}, + {0, 1, 4}, + {1, 4, 5}, + {2, 3, 6}, + {3, 6, 7}}); + obb = tm.GetOrientedBoundingBox(); + EXPECT_EQ(obb.center_, Eigen::Vector3d(1.5, 1, 0.5)); + EXPECT_EQ(obb.extent_, Eigen::Vector3d(3, 2, 1)); + EXPECT_EQ(obb.color_, Eigen::Vector3d(1, 1, 1)); + EXPECT_EQ(obb.R_, Eigen::Matrix3d::Identity()); + ExpectEQ(Sort(obb.GetBoxPoints()), + Sort(std::vector({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}))); + + // Check for a bug where the OBB rotation contained a reflection for this + // example. + tm = geometry::TriangleMesh({{0, 2, 4}, {7, 9, 1}, {5, 2, 0}, {3, 8, 7}}, + {{0, 1, 2}, {1, 2, 3}}); + obb = tm.GetOrientedBoundingBox(); + EXPECT_GT(obb.R_.determinant(), 0.999); +} + +TEST(TriangleMesh, GetMinimalOrientedBoundingBox) { + geometry::TriangleMesh tm; + geometry::OrientedBoundingBox obb; + + // Empty (GetOrientedBoundingBox requires >=4 points) + tm = geometry::TriangleMesh(); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + + // Point + tm = geometry::TriangleMesh({{0, 0, 0}}, {{0, 0, 0}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + {{0, 1, 2}, {1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetMinimalOrientedBoundingBox(true)); + + // Plane + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}}, + {{0, 1, 2}, {1, 2, 3}}); + EXPECT_ANY_THROW(tm.GetMinimalOrientedBoundingBox()); + EXPECT_NO_THROW(tm.GetMinimalOrientedBoundingBox(true)); + + // Valid 4 points + tm = geometry::TriangleMesh({{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 1, 1}}, + {{0, 1, 2}, {1, 2, 3}}); + tm.GetMinimalOrientedBoundingBox(); + + // 8 points with known ground truth + tm = geometry::TriangleMesh({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}, + {{0, 1, 2}, + {1, 2, 3}, + {4, 5, 6}, + {5, 6, 7}, + {0, 1, 4}, + {1, 4, 5}, + {2, 3, 6}, + {3, 6, 7}}); + obb = tm.GetMinimalOrientedBoundingBox(); + EXPECT_EQ(obb.center_, Eigen::Vector3d(1.5, 1, 0.5)); + EXPECT_EQ(obb.extent_, Eigen::Vector3d(3, 2, 1)); + EXPECT_EQ(obb.color_, Eigen::Vector3d(1, 1, 1)); + ExpectEQ(Sort(obb.GetBoxPoints()), + Sort(std::vector({{0, 0, 0}, + {0, 0, 1}, + {0, 2, 0}, + {0, 2, 1}, + {3, 0, 0}, + {3, 0, 1}, + {3, 2, 0}, + {3, 2, 1}}))); + + // Check for a bug where the OBB rotation contained a reflection for this + // example. + tm = geometry::TriangleMesh({{0, 2, 4}, {7, 9, 1}, {5, 2, 0}, {3, 8, 7}}, + {{0, 1, 2}, {1, 2, 3}}); + obb = tm.GetMinimalOrientedBoundingBox(); + EXPECT_GT(obb.R_.determinant(), 0.999); + + // should always be equal/smaller than axis aligned- & oriented bounding box + tm = geometry::TriangleMesh({{0.866, 0.474, 0.659}, + {0.943, 0.025, 0.789}, + {0.386, 0.264, 0.691}, + {0.938, 0.588, 0.496}, + {0.221, 0.116, 0.257}, + {0.744, 0.182, 0.052}, + {0.019, 0.525, 0.699}, + {0.722, 0.134, 0.668}}, + {{6, 5, 4}, + {6, 3, 5}, + {2, 6, 4}, + {6, 2, 0}, + {3, 6, 0}, + {5, 3, 1}, + {3, 0, 1}, + {7, 1, 2}, + {1, 0, 2}, + {4, 1, 7}, + {4, 5, 1}, + {7, 2, 4}}); + geometry::OrientedBoundingBox mobb = tm.GetMinimalOrientedBoundingBox(); + obb = tm.GetOrientedBoundingBox(); + geometry::AxisAlignedBoundingBox aabb = tm.GetAxisAlignedBoundingBox(); + EXPECT_GT(obb.Volume(), mobb.Volume()); + EXPECT_GT(aabb.Volume(), mobb.Volume()); +} + TEST(TriangleMesh, Transform) { std::vector ref_vertices = { {1.411252, 4.274168, 3.130918}, {1.231757, 4.154505, 3.183678},