Skip to content

Commit 7507445

Browse files
committed
fix(Triangulate): change algorithm for ear clipping
1 parent b696ba2 commit 7507445

File tree

6 files changed

+135
-36
lines changed

6 files changed

+135
-36
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"${workspaceFolder}/build/opengeode",
66
"${workspaceFolder}/build/third_party/abseil/install/include",
77
"${workspaceFolder}/build/third_party/asyncplusplus/install/include",
8+
"${workspaceFolder}/build/third_party/earcut/install/include",
89
"${workspaceFolder}/build/third_party/bitsery/install/include",
910
"${workspaceFolder}/build/third_party/minizip/install/include",
1011
"${workspaceFolder}/build/third_party/nanoflann/install/include",

cmake/ConfigureOpenGeode.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ ExternalProject_Add(opengeode
4545
-DUSE_SUPERBUILD:BOOL=OFF
4646
-DASYNCPLUSPLUS_INSTALL_PREFIX:PATH=${ASYNCPLUSPLUS_INSTALL_PREFIX}
4747
-DBITSERY_INSTALL_PREFIX:PATH=${BITSERY_INSTALL_PREFIX}
48-
-DFILESYSTEM_INSTALL_PREFIX:PATH=${FILESYSTEM_INSTALL_PREFIX}
48+
-DEARCUT_INSTALL_PREFIX:PATH=${EARCUT_INSTALL_PREFIX}
4949
-DMINIZIP_INSTALL_PREFIX:PATH=${MINIZIP_INSTALL_PREFIX}
5050
-DNANOFLANN_INSTALL_PREFIX:PATH=${NANOFLANN_INSTALL_PREFIX}
5151
-DSPDLOG_INSTALL_PREFIX:PATH=${SPDLOG_INSTALL_PREFIX}
@@ -61,6 +61,7 @@ ExternalProject_Add(opengeode
6161
abseil
6262
asyncplusplus
6363
bitsery
64+
earcut
6465
gdal
6566
minizip
6667
nanoflann
@@ -80,6 +81,7 @@ add_custom_target(download
8081
abseil-download
8182
asyncplusplus-download
8283
bitsery-download
84+
earcut-download
8385
gdal-download
8486
minizip-download
8587
nanoflann-download

cmake/OpenGeode.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include(cmake/PythonTargets.cmake)
3131
find_package(absl REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${ABSEIL_INSTALL_PREFIX})
3232
find_package(Async++ REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${ASYNCPLUSPLUS_INSTALL_PREFIX})
3333
find_package(Bitsery REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${BITSERY_INSTALL_PREFIX})
34+
find_package(earcut_hpp REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${EARCUT_INSTALL_PREFIX})
3435
find_package(minizip-ng REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${MINIZIP_INSTALL_PREFIX})
3536
find_package(nanoflann REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${NANOFLANN_INSTALL_PREFIX})
3637
find_package(spdlog REQUIRED CONFIG NO_DEFAULT_PATH PATHS ${SPDLOG_INSTALL_PREFIX})
@@ -52,6 +53,7 @@ install(
5253
if(NOT BUILD_SHARED_LIBS)
5354
install(
5455
DIRECTORY
56+
${EARCUT_INSTALL_PREFIX}/
5557
${MINIZIP_INSTALL_PREFIX}/
5658
${NANOFLANN_INSTALL_PREFIX}/
5759
${SPDLOG_INSTALL_PREFIX}/

src/geode/mesh/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,5 +330,6 @@ add_geode_library(
330330
${PROJECT_NAME}::geometry
331331
PRIVATE_DEPENDENCIES
332332
Async++
333+
earcut_hpp::earcut_hpp
333334
${PROJECT_NAME}::image
334335
)

src/geode/mesh/helpers/convert_surface_mesh.cpp

Lines changed: 93 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
#include <geode/mesh/helpers/convert_surface_mesh.hpp>
2525

26+
#include <mapbox/earcut.hpp>
27+
2628
#include <geode/basic/attribute_manager.hpp>
2729
#include <geode/basic/logger.hpp>
2830

2931
#include <geode/geometry/point.hpp>
32+
#include <geode/geometry/vector.hpp>
3033

3134
#include <geode/mesh/builder/polygonal_surface_builder.hpp>
3235
#include <geode/mesh/builder/triangulated_surface_builder.hpp>
@@ -39,6 +42,21 @@
3942
#include <geode/mesh/helpers/detail/surface_merger.hpp>
4043
#include <geode/mesh/helpers/internal/copy.hpp>
4144

45+
namespace mapbox
46+
{
47+
namespace util
48+
{
49+
template < std::size_t coord, geode::index_t dimension >
50+
struct nth< coord, geode::Point< dimension > >
51+
{
52+
inline static auto get( const geode::Point< dimension >& point )
53+
{
54+
return point.value( coord );
55+
};
56+
};
57+
} // namespace util
58+
} // namespace mapbox
59+
4260
namespace
4361
{
4462
template < geode::index_t dimension >
@@ -273,35 +291,47 @@ namespace
273291
}
274292

275293
template < geode::index_t dimension >
276-
void transfer_adjacents( geode::SurfaceMeshBuilder< dimension >& builder,
277-
absl::Span< const std::optional< geode::PolygonEdge > > adjacents,
278-
absl::Span< const geode::index_t > new_polygons )
294+
std::array< absl::FixedArray< geode::Point2D >, 1 > polygon_points(
295+
const geode::SurfaceMesh< dimension >& surface,
296+
geode::index_t polygon_id,
297+
absl::Span< const geode::index_t > vertices );
298+
299+
template <>
300+
std::array< absl::FixedArray< geode::Point2D >, 1 > polygon_points(
301+
const geode::SurfaceMesh< 2 >& surface,
302+
geode::index_t /*unused*/,
303+
absl::Span< const geode::index_t > vertices )
279304
{
280-
if( adjacents.front() )
281-
{
282-
builder.set_polygon_adjacent(
283-
{ new_polygons.front(), 0 }, adjacents.front()->polygon_id );
284-
builder.set_polygon_adjacent(
285-
adjacents.front().value(), new_polygons.front() );
286-
}
287-
for( const auto v : geode::LRange{ 1, adjacents.size() - 1 } )
305+
std::array< absl::FixedArray< geode::Point2D >, 1 > polygons{
306+
absl::FixedArray< geode::Point2D >( vertices.size() )
307+
};
308+
auto& polygon = polygons[0];
309+
for( const auto v : geode::LIndices{ vertices } )
288310
{
289-
if( adjacents[v] )
290-
{
291-
builder.set_polygon_adjacent(
292-
{ new_polygons[v - 1], 1 }, adjacents[v]->polygon_id );
293-
builder.set_polygon_adjacent(
294-
adjacents[v].value(), new_polygons[v - 1] );
295-
}
311+
polygon[v] = surface.point( vertices[v] );
296312
}
297-
if( adjacents.back() )
313+
return polygons;
314+
}
315+
316+
template <>
317+
std::array< absl::FixedArray< geode::Point2D >, 1 > polygon_points(
318+
const geode::SurfaceMesh< 3 >& surface,
319+
geode::index_t polygon_id,
320+
absl::Span< const geode::index_t > vertices )
321+
{
322+
std::array< absl::FixedArray< geode::Point2D >, 1 > polygons{
323+
absl::FixedArray< geode::Point2D >( vertices.size() )
324+
};
325+
auto& polygon = polygons[0];
326+
const auto normal = surface.polygon_normal( polygon_id )
327+
.value_or( geode::Vector3D{ { 0, 0, 1 } } );
328+
const auto axis_to_remove = normal.most_meaningful_axis();
329+
for( const auto v : geode::LIndices{ vertices } )
298330
{
299-
builder.set_polygon_adjacent(
300-
{ new_polygons.back(), 2 }, adjacents.back()->polygon_id );
301-
builder.set_polygon_adjacent(
302-
adjacents.back().value(), new_polygons.back() );
331+
polygon[v] =
332+
surface.point( vertices[v] ).project_point( axis_to_remove );
303333
}
304-
builder.compute_polygon_adjacencies( new_polygons );
334+
return polygons;
305335
}
306336
} // namespace
307337

@@ -384,20 +414,50 @@ namespace geode
384414
to_delete[p] = nb_vertices != 3;
385415
if( nb_vertices > 3 )
386416
{
387-
absl::FixedArray< std::optional< PolygonEdge > > adjacents(
388-
nb_vertices, std::nullopt );
417+
using Edge = std::array< index_t, 2 >;
418+
absl::flat_hash_map< Edge, PolygonEdge > adjacents;
419+
const auto vertices = surface.polygon_vertices( p );
389420
for( const auto e : LRange{ nb_vertices } )
390421
{
391-
adjacents[e] = surface.polygon_adjacent_edge( { p, e } );
422+
if( const auto adj =
423+
surface.polygon_adjacent_edge( { p, e } ) )
424+
{
425+
adjacents.emplace(
426+
Edge{ vertices[e],
427+
vertices[e + 1 == nb_vertices ? 0 : e + 1] },
428+
adj.value() );
429+
}
392430
}
393-
absl::FixedArray< index_t > new_polygons( nb_vertices - 2 );
394-
const auto vertices = surface.polygon_vertices( p );
395-
for( const auto v : LRange{ 2, nb_vertices } )
431+
const auto polygons = ::polygon_points( surface, p, vertices );
432+
const auto new_triangles =
433+
mapbox::earcut< index_t >( polygons );
434+
absl::FixedArray< index_t > new_polygons(
435+
new_triangles.size() / 3 );
436+
for( const auto trgl : LIndices{ new_polygons } )
396437
{
397-
new_polygons[v - 2] = builder.create_polygon(
398-
{ vertices[0], vertices[v - 1], vertices[v] } );
438+
const std::array triangle{
439+
vertices[new_triangles[3 * trgl]],
440+
vertices[new_triangles[3 * trgl + 1]],
441+
vertices[new_triangles[3 * trgl + 2]]
442+
};
443+
new_polygons[trgl] = builder.create_polygon( triangle );
444+
for( const auto e : LRange{ 3 } )
445+
{
446+
const auto vertex0 = triangle[e];
447+
const auto vertex1 = triangle[e == 2 ? 0 : e + 1];
448+
const auto adj_it =
449+
adjacents.find( { vertex0, vertex1 } );
450+
if( adj_it == adjacents.end() )
451+
{
452+
continue;
453+
}
454+
builder.set_polygon_adjacent(
455+
adj_it->second, new_polygons[trgl] );
456+
builder.set_polygon_adjacent( { new_polygons[trgl], e },
457+
adj_it->second.polygon_id );
458+
}
399459
}
400-
::transfer_adjacents( builder, adjacents, new_polygons );
460+
builder.compute_polygon_adjacencies( new_polygons );
401461
}
402462
}
403463
to_delete.resize( surface.nb_polygons(), false );

tests/mesh/test-convert-surface.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@
2929
#include <geode/geometry/point.hpp>
3030
#include <geode/geometry/vector.hpp>
3131

32+
#include <geode/mesh/builder/surface_mesh_builder.hpp>
3233
#include <geode/mesh/core/light_regular_grid.hpp>
34+
#include <geode/mesh/core/polygonal_surface.hpp>
3335
#include <geode/mesh/core/triangulated_surface.hpp>
36+
#include <geode/mesh/io/polygonal_surface_output.hpp>
3437
#include <geode/mesh/io/triangulated_surface_input.hpp>
3538

3639
#include <geode/mesh/helpers/convert_surface_mesh.hpp>
3740

38-
void test()
41+
void convert_surface_dimension()
3942
{
40-
geode::OpenGeodeMeshLibrary::initialize();
4143
const auto surface2d = geode::load_triangulated_surface< 2 >(
4244
absl::StrCat( geode::DATA_PATH, "3patches.og_tsf2d" ) );
4345
const auto surface3d =
@@ -70,7 +72,10 @@ void test()
7072
surface2d->nb_polygons() == converted_surface2d->nb_polygons(),
7173
"[Test] Number of polygons in re-converted TriangulatedSurface2D "
7274
"is not correct" );
75+
}
7376

77+
void convert_grid_to_surface()
78+
{
7479
geode::LightRegularGrid2D grid{ geode::Point2D{ { 1, 2 } }, { 5, 6 },
7580
{ 1, 1 } };
7681
const std::array< geode::index_t, 4 > cells_to_densify{ 5, 11, 12, 13 };
@@ -89,4 +94,32 @@ void test()
8994
"correct." );
9095
}
9196

97+
void triangulate_surface()
98+
{
99+
auto surface = geode::PolygonalSurface3D::create();
100+
auto builder = geode::SurfaceMeshBuilder3D::create( *surface );
101+
102+
for( const auto i : geode::Range{ 10 } )
103+
{
104+
builder->create_point(
105+
geode::Point3D{ { i * 0.5, i * 0.5, i * 0.5 } } );
106+
}
107+
builder->create_point( geode::Point3D{ { 10, 0, 10 } } );
108+
std::vector< geode::index_t > polygon( surface->nb_vertices() );
109+
absl::c_iota( polygon, 0 );
110+
builder->create_polygon( polygon );
111+
geode::triangulate_surface_mesh( *surface, *builder );
112+
geode::save_polygonal_surface( *surface, "triangulated_surface.og_psf3d" );
113+
OPENGEODE_EXCEPTION( surface->nb_polygons() == 9,
114+
"[Test] Number of polygons in TriangulatedSurface3D is not correct" );
115+
}
116+
117+
void test()
118+
{
119+
geode::OpenGeodeMeshLibrary::initialize();
120+
convert_surface_dimension();
121+
convert_grid_to_surface();
122+
triangulate_surface();
123+
}
124+
92125
OPENGEODE_TEST( "convert-surface" )

0 commit comments

Comments
 (0)