From e0ac9d134fde497da46146b5eb13d9834db8c66a Mon Sep 17 00:00:00 2001 From: Alan Morris Date: Wed, 21 Oct 2026 14:10:48 -0600 Subject: [PATCH 1/2] Fix remeshing of open meshes (#2672) ACVD's ForceManifold treats clusters on an open boundary as non-manifold and keeps splitting them, piling extra vertices and sliver triangles along the boundary. With a cutting plane, geodesics and remesh percent < 100, the clipped mesh used for geodesics got these slivers, and particles clumped along the cut. Only force manifold output on closed meshes; regenerate the remeshPercent baselines. --- Libs/Mesh/Mesh.cpp | 14 +++++++++++++- Testing/MeshTests/MeshTests.cpp | 10 ++++++++++ Testing/data/remeshPercent1.vtk | 4 ++-- Testing/data/remeshPercent2.vtk | 4 ++-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Libs/Mesh/Mesh.cpp b/Libs/Mesh/Mesh.cpp index 57de5c7877b..fd81a03088d 100644 --- a/Libs/Mesh/Mesh.cpp +++ b/Libs/Mesh/Mesh.cpp @@ -490,7 +490,19 @@ Mesh& Mesh::remesh(int numVertices, double adaptivity) { numVertices = std::max(numVertices, 1); - remesh->SetForceManifold(true); + // ACVD's ForceManifold treats clusters on an open boundary as non-manifold and keeps splitting + // them, piling extra vertices and sliver triangles along the boundary (e.g. a mesh clipped by a + // cutting plane ends up with more vertices than requested). Only force manifold on closed meshes. + auto boundary_edges = vtkSmartPointer::New(); + boundary_edges->SetInputData(this->poly_data_); + boundary_edges->BoundaryEdgesOn(); + boundary_edges->FeatureEdgesOff(); + boundary_edges->NonManifoldEdgesOff(); + boundary_edges->ManifoldEdgesOff(); + boundary_edges->Update(); + bool is_closed = boundary_edges->GetOutput()->GetNumberOfCells() == 0; + + remesh->SetForceManifold(is_closed); remesh->SetInput(surf); remesh->SetFileLoadSaveOption(0); remesh->SetConsoleOutput(0); diff --git a/Testing/MeshTests/MeshTests.cpp b/Testing/MeshTests/MeshTests.cpp index 2ab85547e56..0b3b8b95a53 100644 --- a/Testing/MeshTests/MeshTests.cpp +++ b/Testing/MeshTests/MeshTests.cpp @@ -359,6 +359,16 @@ TEST(MeshTests, remeshPercentTest2) { ASSERT_TRUE(femur == ground_truth); } +// Remeshing an open (clipped) mesh should hit the requested vertex count instead of piling extra +// vertices along the open boundary (#2672) +TEST(MeshTests, remeshPercentOpenMeshTest) { + Mesh femur(std::string(TEST_DATA_DIR) + "/femur.vtk"); + femur.clip(makePlane(Point3({-91.0, 0.0, 1230.0}), makeVector({0.0, 0.0, 1.0}))); + auto num_points = femur.numPoints(); + femur.remeshPercent(0.40, 1.0); + ASSERT_NEAR(femur.numPoints(), num_points * 0.40, num_points * 0.02); +} + TEST(MeshTests, fieldGradientFloatTest) { // fields arrive on meshes as floats as often as doubles, and the gradient has to be computed for // either: a missing gradient array used to be read as a null pointer diff --git a/Testing/data/remeshPercent1.vtk b/Testing/data/remeshPercent1.vtk index e93b2c5d2c8..5a97443feb2 100644 --- a/Testing/data/remeshPercent1.vtk +++ b/Testing/data/remeshPercent1.vtk @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:780e4de73c8b478a83d6a443be5358b600378618fb811b7be791a89e7887e5ca -size 231379 +oid sha256:8fdd1416339b792e7e6e4c1d9b95f31d19108a9c7e8e56bb6097f8be3fad0ebc +size 253904 diff --git a/Testing/data/remeshPercent2.vtk b/Testing/data/remeshPercent2.vtk index 66328557279..b055bdaf0cd 100644 --- a/Testing/data/remeshPercent2.vtk +++ b/Testing/data/remeshPercent2.vtk @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f05141126a75d16e30c9281fdcc66d2dd6988fb9d4cede9b257681bdd285737 -size 95218 +oid sha256:4841cc2611eafbb6ec7445636b5f6725ef0d6b86a5208dabf372300b8fdf6780 +size 98795 From 721723e8ba4ade50ef82982b77f0c15152b71982 Mon Sep 17 00:00:00 2001 From: Alan Morris Date: Wed, 21 Oct 2026 21:33:11 -0600 Subject: [PATCH 2/2] Fix Windows crash in remesh boundary check on meshes with short point fields --- Libs/Mesh/Mesh.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Libs/Mesh/Mesh.cpp b/Libs/Mesh/Mesh.cpp index fd81a03088d..68398645a45 100644 --- a/Libs/Mesh/Mesh.cpp +++ b/Libs/Mesh/Mesh.cpp @@ -493,8 +493,13 @@ Mesh& Mesh::remesh(int numVertices, double adaptivity) { // ACVD's ForceManifold treats clusters on an open boundary as non-manifold and keeps splitting // them, piling extra vertices and sliver triangles along the boundary (e.g. a mesh clipped by a // cutting plane ends up with more vertices than requested). Only force manifold on closed meshes. + // geometry only: vtkFeatureEdges copies point data, which reads out of bounds on a field that + // doesn't have one value per point + auto geometry = vtkSmartPointer::New(); + geometry->SetPoints(this->poly_data_->GetPoints()); + geometry->SetPolys(this->poly_data_->GetPolys()); auto boundary_edges = vtkSmartPointer::New(); - boundary_edges->SetInputData(this->poly_data_); + boundary_edges->SetInputData(geometry); boundary_edges->BoundaryEdgesOn(); boundary_edges->FeatureEdgesOff(); boundary_edges->NonManifoldEdgesOff();