Skip to content

Commit 47e1ca5

Browse files
committed
feat(mesh-filters): add smooth-remesh
1 parent 0922728 commit 47e1ca5

File tree

4 files changed

+221
-15
lines changed

4 files changed

+221
-15
lines changed

packages/mesh-filters/CMakeLists.txt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ FetchContent_MakeAvailable(pmp geogram)
7070
include_directories(${pmp_SOURCE_DIR}/src)
7171
include_directories(${geogram_SOURCE_DIR}/src/lib)
7272

73-
foreach(pipeline geogram-conversion repair)
74-
# foreach(pipeline mesh-filters mesh-filters-sigma gaussian-kernel-radius mesh-filters-bin-shrink mesh-filters-label-image)
73+
foreach(pipeline geogram-conversion repair smooth-remesh)
7574
add_executable(${pipeline} ${pipeline}.cxx)
7675
target_link_libraries(${pipeline} PUBLIC ${ITK_LIBRARIES} geogram)
7776
target_include_directories(${pipeline} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
@@ -104,6 +103,27 @@ add_test(NAME repair-brain
104103
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/brain.off
105104
${CMAKE_CURRENT_BINARY_DIR}/brain-repair.off
106105
)
106+
107+
add_test(NAME smooth-remesh-help
108+
COMMAND smooth-remesh
109+
--help
110+
)
111+
add_test(NAME smooth-remesh-suzanne
112+
COMMAND smooth-remesh
113+
${CMAKE_CURRENT_SOURCE_DIR}/test/data/baseline/suzanne-repair.off
114+
${CMAKE_CURRENT_BINARY_DIR}/suzanne-smooth-remesh.off
115+
)
116+
add_test(NAME smooth-remesh-cow
117+
COMMAND smooth-remesh
118+
${CMAKE_CURRENT_SOURCE_DIR}/test/data/baseline/cow-repair.off
119+
${CMAKE_CURRENT_BINARY_DIR}/cow-smooth-remesh.off
120+
)
121+
add_test(NAME smooth-remesh-brain
122+
COMMAND smooth-remesh
123+
${CMAKE_CURRENT_SOURCE_DIR}/test/data/baseline/brain-repair.off
124+
${CMAKE_CURRENT_BINARY_DIR}/brain-smooth-remesh.off
125+
--number-points 30
126+
)
107127
# # Interesting backtrace on exit
108128
# if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
109129
# add_test(NAME mesh-filters

packages/mesh-filters/geogram-conversion.cxx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ int main( int argc, char * argv[] )
3232
constexpr unsigned int Dimension = 3;
3333
using MeshType = itk::QuadEdgeMesh< PixelType, Dimension >;
3434

35+
static constexpr bool SinglePrecision = std::is_same<typename MeshType::CoordRepType, float>::value;
36+
3537
using InputMeshType = itk::wasm::InputMesh<MeshType>;
3638
InputMeshType inputMesh;
3739
pipeline.add_option("input-mesh", inputMesh, "The input mesh")->required()->type_name("INPUT_MESH");
@@ -42,8 +44,11 @@ int main( int argc, char * argv[] )
4244

4345
ITK_WASM_PARSE(pipeline);
4446

45-
auto geoMesh = itk::meshToGeogramMesh<MeshType>(inputMesh.Get());
46-
auto itkMesh = itk::geogramMeshToMesh<MeshType>(geoMesh.get());
47+
GEO::Mesh geoMesh(Dimension, SinglePrecision);
48+
itk::meshToGeogramMesh<MeshType>(inputMesh.Get(), geoMesh);
49+
50+
MeshType::Pointer itkMesh = MeshType::New();
51+
itk::geogramMeshToMesh<MeshType>(geoMesh, itkMesh);
4752

4853
outputMesh.Set(itkMesh);
4954

packages/mesh-filters/repair.cxx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ int repairMesh(itk::wasm::Pipeline &pipeline, const TMesh *inputMesh)
4747
double pointMergeTolerance = 1e-6;
4848
pipeline.add_option("--merge-tolerance", pointMergeTolerance, "Point merging tolerance as a percent of the bounding box diagonal.");
4949

50-
double minimumComponentArea = 0.03;
50+
double minimumComponentArea = 3.0;
5151
pipeline.add_option("--minimum-component-area", minimumComponentArea, "Minimum component area as a percent of the total area. Components smaller than this are removed.");
5252

53-
double maximumHoleArea = 1e-3;
53+
double maximumHoleArea = 1e-1;
5454
pipeline.add_option("--maximum-hole-area", maximumHoleArea, "Maximum area of a hole as a percent of the total area. Holes smaller than this are filled.");
5555

5656
uint64_t maximumHoleEdges = 2000;
@@ -63,7 +63,10 @@ int repairMesh(itk::wasm::Pipeline &pipeline, const TMesh *inputMesh)
6363
pipeline.add_option("--remove-intersecting-triangles", removeIntersectingTriangles, "Remove intersecting triangles.");
6464

6565
bool noTriangulate = false;
66-
pipeline.add_option("--no-triangulate", noTriangulate, "Do not triangulate the mesh.");
66+
// TODO: See if we can add this option, disable auto-repair in fill_holes, and successfully
67+
// repair with mesh_repair afterwards without triangulation.
68+
// remove_degree3_vertices and mesh_remove_intersections also need to be checked for compatibility.
69+
// pipeline.add_option("--no-triangulate", noTriangulate, "Do not triangulate the mesh.");
6770

6871
itk::wasm::OutputMesh<MeshType> outputMesh;
6972
pipeline.add_option("output-mesh", outputMesh, "The output repaired mesh.")->type_name("OUTPUT_MESH");
@@ -85,8 +88,8 @@ int repairMesh(itk::wasm::Pipeline &pipeline, const TMesh *inputMesh)
8588
const double bboxDiagonal = GEO::bbox_diagonal(geoMesh);
8689
pointMergeTolerance *= (0.01 * bboxDiagonal);
8790
const double area = GEO::Geom::mesh_area(geoMesh, Dimension);
88-
minimumComponentArea *= area;
89-
maximumHoleArea *= area;
91+
minimumComponentArea *= (0.01 * area);
92+
maximumHoleArea *= (0.01 * area);
9093

9194
if (noTriangulate)
9295
{
@@ -126,9 +129,7 @@ int repairMesh(itk::wasm::Pipeline &pipeline, const TMesh *inputMesh)
126129

127130
if (removeIntersectingTriangles)
128131
{
129-
std::cout << "Removing intersections" << std::endl;
130132
GEO::mesh_remove_intersections(geoMesh);
131-
std::cout << "Removed intersections" << std::endl;
132133
}
133134

134135
typename MeshType::Pointer itkMesh = MeshType::New();
@@ -162,14 +163,13 @@ int main(int argc, char *argv[])
162163
itk::wasm::Pipeline pipeline("repair", "Repair a mesh so it is 2-manifold and optionally watertight.", argc, argv);
163164

164165
return itk::wasm::SupportInputMeshTypes<PipelineFunctor,
165-
// uint8_t,
166+
uint8_t,
166167
// int8_t,
167168
// uint16_t,
168169
// int16_t,
169170
// uint32_t,
170171
// int32_t,
171-
// float,
172-
// double>::Dimensions<
173-
float>::Dimensions<
172+
float,
173+
double>::Dimensions<
174174
3U>("input-mesh", pipeline);
175175
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*=========================================================================
2+
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
#include "itkPipeline.h"
20+
#include "itkInputMesh.h"
21+
#include "itkOutputMesh.h"
22+
#include "itkSupportInputMeshTypes.h"
23+
24+
#include "itkmeshToGeogramMesh.h"
25+
#include "itkgeogramMeshToMesh.h"
26+
27+
#include <geogram/mesh/mesh_remesh.h>
28+
#include <geogram/mesh/mesh_geometry.h>
29+
#include <geogram/basic/command_line.h>
30+
#include <geogram/basic/command_line_args.h>
31+
32+
#include <geogram/basic/common.h>
33+
#include <geogram/basic/process.h>
34+
#include <geogram/basic/logger.h>
35+
#include <geogram/basic/command_line.h>
36+
37+
template <typename TMesh>
38+
int repairMesh(itk::wasm::Pipeline &pipeline, const TMesh *inputMesh)
39+
{
40+
using MeshType = TMesh;
41+
constexpr unsigned int Dimension = MeshType::PointDimension;
42+
using PixelType = typename MeshType::PixelType;
43+
44+
pipeline.get_option("input-mesh")->required()->type_name("INPUT_MESH");
45+
46+
double numberPointsPercent = 75.0;
47+
pipeline.add_option("--number-points", numberPointsPercent, "Number of points as a percent of the bounding box diagonal. Output may have slightly more points.");
48+
49+
double triangleShapeAdaptation = 1.0;
50+
pipeline.add_option("--triangle-shape-adaptation", triangleShapeAdaptation, "Triangle shape adaptation factor. Use 0.0 to disable.");
51+
52+
double triangleSizeAdaptation = 0.0;
53+
pipeline.add_option("--triangle-size-adaptation", triangleSizeAdaptation, "Triangle size adaptation factor. Use 0.0 to disable.");
54+
55+
uint32_t normalIterations = 3;
56+
pipeline.add_option("--normal-iterations", normalIterations, "Number of normal smoothing iterations.");
57+
58+
uint32_t lloydIterations = 5;
59+
pipeline.add_option("--lloyd-iterations", lloydIterations, "Number of Lloyd relaxation iterations.");
60+
61+
uint32_t newtonIterations = 30;
62+
pipeline.add_option("--newton-iterations", newtonIterations, "Number of Newton iterations.");
63+
64+
uint32_t newtonM = 7;
65+
pipeline.add_option("--newton-m", newtonM, "Number of Newton evaluations per step for Hessian approximation.");
66+
67+
uint64_t lfsSamples = 10000;
68+
pipeline.add_option("--lfs-samples", lfsSamples, "Number of samples for size adaptation if triangle size adaptation is not 0.0.");
69+
70+
itk::wasm::OutputMesh<MeshType> outputMesh;
71+
pipeline.add_option("output-mesh", outputMesh, "The output repaired mesh.")->type_name("OUTPUT_MESH");
72+
73+
ITK_WASM_PARSE(pipeline);
74+
75+
// GEO::initialize(GEO::GEOGRAM_INSTALL_ALL);
76+
77+
GEO::Logger::initialize(); GEO::Logger::instance()->set_quiet(true);
78+
GEO::CmdLine::initialize();
79+
const auto flags = GEO::GEOGRAM_INSTALL_ALL;
80+
GEO::Process::initialize(flags);
81+
GEO::CmdLine::import_arg_group("algo");
82+
83+
GEO::Mesh geoMesh(Dimension, false);
84+
itk::meshToGeogramMesh<MeshType>(inputMesh, geoMesh);
85+
86+
if (geoMesh.facets.nb() == 0)
87+
{
88+
std::cerr << "The input mesh has no facets." << std::endl;
89+
return EXIT_FAILURE;
90+
}
91+
92+
if (!geoMesh.facets.are_simplices())
93+
{
94+
std::cerr << "The mesh needs to be simplicial. Use repair." << std::endl;
95+
return EXIT_FAILURE;
96+
}
97+
98+
uint64_t numberPoints = static_cast<uint64_t>(std::ceil(numberPointsPercent * 0.01 * geoMesh.vertices.nb()));
99+
100+
std::cout << "Remeshing with " << numberPoints << " points" << std::endl;
101+
102+
if(triangleShapeAdaptation != 0.0)
103+
{
104+
triangleShapeAdaptation *= 0.02;
105+
GEO::compute_normals(geoMesh);
106+
if(normalIterations != 0)
107+
{
108+
GEO::simple_Laplacian_smooth(geoMesh, normalIterations, true);
109+
}
110+
GEO::set_anisotropy(geoMesh, triangleShapeAdaptation);
111+
}
112+
else
113+
{
114+
geoMesh.vertices.set_dimension(3);
115+
}
116+
117+
if(triangleSizeAdaptation != 0.0)
118+
{
119+
GEO::compute_sizing_field(geoMesh, triangleSizeAdaptation, lfsSamples);
120+
}
121+
else
122+
{
123+
GEO::AttributesManager& attributes = geoMesh.vertices.attributes();
124+
if(attributes.is_defined("weight"))
125+
{
126+
attributes.delete_attribute_store("weight");
127+
}
128+
}
129+
130+
GEO::Mesh remesh;
131+
132+
GEO::remesh_smooth(geoMesh, remesh, numberPoints, 0, lloydIterations, newtonIterations, newtonM);
133+
134+
GEO::MeshElementsFlags what = GEO::MeshElementsFlags(
135+
GEO::MESH_VERTICES | GEO::MESH_EDGES | GEO::MESH_FACETS | GEO::MESH_CELLS
136+
);
137+
geoMesh.clear();
138+
geoMesh.copy(remesh, true, what);
139+
140+
typename MeshType::Pointer itkMesh = MeshType::New();
141+
itk::geogramMeshToMesh<MeshType>(geoMesh, itkMesh);
142+
143+
outputMesh.Set(itkMesh);
144+
145+
return EXIT_SUCCESS;
146+
}
147+
148+
template <typename TMesh>
149+
class PipelineFunctor
150+
{
151+
public:
152+
int operator()(itk::wasm::Pipeline &pipeline)
153+
{
154+
using MeshType = TMesh;
155+
156+
itk::wasm::InputMesh<MeshType> testMesh;
157+
pipeline.add_option("input-mesh", testMesh, "The input mesh")->type_name("INPUT_MESH");
158+
159+
ITK_WASM_PRE_PARSE(pipeline);
160+
161+
typename MeshType::ConstPointer inputMeshRef = testMesh.Get();
162+
return repairMesh<MeshType>(pipeline, inputMeshRef);
163+
}
164+
};
165+
166+
int main(int argc, char *argv[])
167+
{
168+
itk::wasm::Pipeline pipeline("remesh", "Smooth and remesh a mesh to improve quality.", argc, argv);
169+
170+
return itk::wasm::SupportInputMeshTypes<PipelineFunctor,
171+
// uint8_t,
172+
// int8_t,
173+
// uint16_t,
174+
// int16_t,
175+
// uint32_t,
176+
// int32_t,
177+
// float,
178+
// double>::Dimensions<
179+
float>::Dimensions<
180+
3U>("input-mesh", pipeline);
181+
}

0 commit comments

Comments
 (0)