Skip to content

Commit 7f6d388

Browse files
Merge pull request #137 from MarkGillespie/remeshing
Remeshing
2 parents 30b0d0b + 5611715 commit 7f6d388

File tree

12 files changed

+750
-10
lines changed

12 files changed

+750
-10
lines changed
590 KB
Loading
508 KB
Loading
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
2+
| Original | Remeshed |
3+
| - | - |
4+
|![finely_triangulated_spot](/media/spot-triangulation-original.png){: style="max-width: 20em; display: block; margin-left: auto; margin-right: auto;"}|![coarsely_triangulated_spot](/media/spot-triangulation-remeshed.png){: style="max-width: 20em; display: block; margin-left: auto; margin-right: auto;"}
5+
6+
These routines try to improve mesh quality using repeated rounds of [vertex position smoothing](#tangential-vertex-smoothing), [edge flipping](#extrinsic-delaunay-flipping), and [edge splits/collapses](#edge-length-adjustment).
7+
8+
??? func "`#!cpp void remesh(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& geom, RemeshOptions options = defaultRemeshOptions);`"
9+
10+
Remesh a mesh using vertex smoothing along with edge splits, collapses, and flips. Options are passed as a [RemeshOptions](#options) object.
11+
12+
??? func "`#!cpp void remesh(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& geom, MutationManager& mm, RemeshOptions options = defaultRemeshOptions);`"
13+
14+
Remesh a mesh using vertex smoothing along with edge splits, collapses, and flips. Options are passed as a [RemeshOptions](#options) object.
15+
All mesh mutations are performed through a `MutationManager` object, which can e.g. keep special vertices fixed or run callback functions when certain mesh mutations occur.
16+
17+
## Tangential Vertex Smoothing
18+
Vertex smoothing moves each vertex towards the average of its neighborhood. This average can be computed in two ways: in *circumentric* smoothing, every vertex is moved towards the area-weighted average of the circumcenters of its neighboring faces (as in [[Chen & Holst 2011]](https://www.sciencedirect.com/science/article/abs/pii/S0045782510003117)), whereas in *Laplacian* smoothing, every vertex is moved towards the average of the positions of its neighboring vertices.
19+
20+
In either case we perform *tangential smoothing*, meaning that we only move vertices tangentially along the surface.
21+
22+
??? func "`#!cpp double smoothByCircumcenter(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);`"
23+
24+
Move each vertex tangentially towards the area-weighted average of its neighboring face circumcenters.
25+
Returns the average amount that each vertex was moved by.
26+
27+
??? func "`#!cpp double smoothByCircumcenter(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm, double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);`"
28+
29+
Move each vertex tangentially towards the area-weighted average of its neighboring face circumcenters
30+
, using the provided `MutationManager` to move the vertices
31+
Returns the average amount that each vertex was moved by.
32+
33+
??? func "`#!cpp double smoothByLaplacian(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);`"
34+
35+
Move each vertex tangentially towards the average of its neighbors
36+
Returns the average amount that each vertex was moved by.
37+
??? func "`#!cpp double smoothByLaplacian(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm, double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);`"
38+
39+
Move each vertex tangentially towards the average of its neighbors, using the provided `MutationManager` to move the vertices
40+
Returns the average amount that each vertex was moved by.
41+
42+
### Boundary Conditions
43+
The boundary conditions for smoothing can be set to `RemeshBoundaryCondition::Tangential`, `RemeshBoundaryCondition::Fixed`, or `RemeshBoundaryCondition::Free`. Tangential smoothing allows boundary vertices to move along the tangent direction to the boundary curve, fixed smoothing fixes the boundary vertices, and free smoothing allows boundary vertices to move along any direction in the surface's tangent plane. Leaving boundary vertices free allows the mesh to degenerate, so it is not recommended unless you impose additional constraints.
44+
45+
## Extrinsic Delaunay Flipping
46+
Delaunay flipping performs edge flips to improve triangle quality.
47+
48+
!!! warning "No guarantees"
49+
50+
Unlike the [intrinsic Delaunay flipping routines](/surface/intrinsic_triangulations/basics/#high-level-mutators), extrinsic flipping algorithms are not guaranteed to produce a Delaunay mesh. Nonetheless, these edge flips generally improve triangle quality in practice.
51+
52+
??? func "`#!cpp size_t fixDelaunay(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom);`"
53+
54+
Try to make all triangles Delaunay using extrinsic edge flips.
55+
Returns the number of flips performed.
56+
57+
??? func "`#!cpp size_t fixDelaunay(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm);`"
58+
Try to make all triangles Delaunay using extrinsic edge flips, using the provided `MutationManager` to flip edges.
59+
Returns the number of flips performed.
60+
61+
## Edge Length Adjustment
62+
These routines perform edge splits and collapses to drive mesh edge lengths towards a target value. If curvature adaptation is enabled, this target length is made shorter in high-curvature areas, leading to more resolution there.
63+
64+
??? func "`#!cpp bool adjustEdgeLengths(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, RemeshOptions options = defaultRemeshOptions);`"
65+
66+
Apply splits and collapses to adjust edge lengths.
67+
68+
Reads the following options from `options`, with the following default values and meanings:
69+
70+
* `#!cpp double options.targetEdgeLength = -1`: the target edge length in flat regions. If `targetEdgeLength` is negative, the target length is set relative to the input mesh's mean length.
71+
* `#!cpp double options.curvatureAdaptation = 0`: how much target edge length should vary due to curvature. Set `curvatureAdaptation` to zero if you want edge lengths to be approximately `targetEdgeLength` everywhere.
72+
* `#!cpp double options.minRelativeLength = 0.05`: the minimum possible edge length in the output mesh. Defined relative to `targetEdgeLength`.
73+
74+
??? func "`#!cpp bool adjustEdgeLengths(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm, RemeshOptions options = defaultRemeshOptions);`"
75+
76+
Apply splits and collapses to adjust edge lengths.
77+
All splits and collapses are performed using the provided `MutationManager`.
78+
79+
Reads the following options from `options`, with the following default values and meanings:
80+
81+
* `#!cpp double options.targetEdgeLength = -1`: the target edge length in flat regions. If `targetEdgeLength` is negative, the target length is set relative to the input mesh's mean length.
82+
* `#!cpp double options.curvatureAdaptation = 0`: how much target edge length should vary due to curvature. Set `curvatureAdaptation` to zero if you want edge lengths to be approximately `targetEdgeLength` everywhere.
83+
* `#!cpp double options.minRelativeLength = 0.05`: the minimum possible edge length in the output mesh. Defined relative to `targetEdgeLength`.
84+
85+
## Helper Types
86+
### Options
87+
Options are passed in to `remesh` via a `RemeshOptions` object.
88+
89+
| Field | Default value | Meaning |
90+
|----------------------------------------------------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
91+
| `#!cpp double targetEdgeLength` | `-1` | the target edge length in flat regions. If `targetEdgeLength` is negative, the target edge length is set to relative the input mesh's mean edge length |
92+
| `#!cpp size_t maxIterations` | `10` | the maximum number of iterations to run for |
93+
| `#!cpp double curvatureAdaptation` | `0` | how much target length should vary due to curvature. Set curvatureAdaptation to 0 if you want edge lengths to be approximately targetEdgeLength everywhere |
94+
| `#!cpp double minRelativeLength` | `0.05` | the minimum possible edge length allowed in the output mesh. Defined relative to targetEdgeLength |
95+
| `#!cpp RemeshSmoothStyle smoothStyle` | `RemeshSmoothStyle::Circumcentric` | the type of vertex smoothing to use (either `RemeshSmoothStyle::Circumcentric` or `RemeshSmoothStyle::Laplacian`) |
96+
| `#!cpp RemeshBoundaryCondition boundaryCondition` | `RemeshBoundaryCondition::Tangential` | the type of motions allowed for boundary vertices (either `RemeshBoundaryCondition::Fixed`, `RemeshBoundaryCondition::Tangential` or `RemeshBoundaryCondition::Free`) |
97+
98+
!!! warning "'Fixed' boundary may still move slightly"
99+
100+
Setting `boundaryCondition` to `RemeshBoundaryCondition::Fixed` only fixes boundary vertices during vertex smoothing. Boundary edges may still be split or collapsed during edge length adjustment, which can also cause the boundary to move slightly. To stop all motion of the boundary, you can pass in a `MutationManager` which marks all boundary edges as not splittable or collapsible.

docs/mkdocs.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ nav:
7676
- 'Barycentric Vector': 'surface/utilities/barycentric_vector.md'
7777
- Algorithms:
7878
- 'Direction Fields' : 'surface/algorithms/direction_fields.md'
79+
- 'Flip Geodesics' : 'surface/algorithms/flip_geodesics.md'
7980
- 'Geodesic Distance' : 'surface/algorithms/geodesic_distance.md'
80-
- 'Tracing Geodesic Paths' : 'surface/algorithms/geodesic_paths.md'
81-
- 'Vector Heat Method' : 'surface/algorithms/vector_heat_method.md'
82-
- 'Surface Centers' : 'surface/algorithms/surface_centers.md'
83-
- 'Surface Sampling' : 'surface/algorithms/surface_sampling.md'
8481
- 'Geodesic Centroidal Voronoi Tessellations' : 'surface/algorithms/geodesic_voronoi_tessellations.md'
85-
- 'Robust Geometry' : 'surface/algorithms/robust_geometry.md'
86-
- 'Flip Geodesics' : 'surface/algorithms/flip_geodesics.md'
8782
- 'Parameterization' : 'surface/algorithms/parameterization.md'
83+
- 'Remeshing' : 'surface/algorithms/remeshing.md'
84+
- 'Robust Geometry' : 'surface/algorithms/robust_geometry.md'
85+
- 'Surface Centers' : 'surface/algorithms/surface_centers.md'
86+
- 'Surface Sampling' : 'surface/algorithms/surface_sampling.md'
87+
- 'Tracing Geodesic Paths' : 'surface/algorithms/geodesic_paths.md'
88+
- 'Vector Heat Method' : 'surface/algorithms/vector_heat_method.md'
8889
- Intrinsic Triangulations:
8990
- 'Basics' : 'surface/intrinsic_triangulations/basics.md'
9091
- 'Common Subdivision' : 'surface/intrinsic_triangulations/common_subdivision.md'

include/geometrycentral/surface/mutation_manager.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,45 @@ class MutationManager {
106106
// Create a connectivity-only mutation manager, which does not touch any geometry by default
107107
MutationManager(ManifoldSurfaceMesh& mesh);
108108

109+
// ======================================================
110+
// ======== Constraints
111+
// ======================================================
112+
113+
// Set up callbacks to update edge constraint data following an edge split
114+
// TODO: work out sane policies for updating after other operations?
115+
void setConstraintCallbacks();
116+
117+
// Vertices which are allowed to be repositioned.
118+
// (set to an array which holds true if a vertex may be moved, and false if it should stay fixed)
119+
// Note that this array may be uninitialized, in which case all vertices are allowed to be repositioned
120+
// By default, any newly created vertices are repositionable, unless otherwise specified
121+
// Warning: this prevents vertices from being moved by calls to `repositionVertex()`, and also prevents incident edges
122+
// from being collapsed (since this might move the vertex)
123+
VertexData<bool> repositionableVertices;
124+
void setRepositionableVertices(const VertexData<bool>& repositionableVertex, bool defaultValue = true);
125+
void clearRepositionableVertices();
126+
bool mayRepositionVertex(Vertex v) const;
127+
128+
EdgeData<bool> splittableEdges;
129+
void setSplittableEdges(const EdgeData<bool>& splittableEdge, bool defaultValue = true);
130+
void clearSplittableEdges();
131+
bool maySplitEdge(Edge e) const;
132+
133+
EdgeData<bool> collapsibleEdges;
134+
void setCollapsibleEdges(const EdgeData<bool>& collapsibleEdge, bool defaultValue = true);
135+
void clearCollapsibleEdges();
136+
bool mayCollapseEdge(Edge e) const;
137+
138+
EdgeData<bool> flippableEdges;
139+
void setFlippableEdges(const EdgeData<bool>& flippableEdge, bool defaultValue = true);
140+
void clearFlippableEdges();
141+
bool mayFlipEdge(Edge e) const;
142+
143+
FaceData<bool> splittableFaces;
144+
void setSplittableFaces(const FaceData<bool>& splittableFace, bool defaultValue = true);
145+
void clearSplittableFaces();
146+
bool maySplitFace(Face f) const;
147+
109148

110149
// ======================================================
111150
// ======== Low-level mutations
@@ -117,7 +156,8 @@ class MutationManager {
117156
// many of these, since each adds the burden of a corresponding callback policy to update data.
118157

119158
// Move a vertex in 3D space
120-
void repositionVertex(Vertex vert, Vector3 offset);
159+
// Returns true if vertex was repositioned (May return false if, e.g., repositionalVertices[vert] is false)
160+
bool repositionVertex(Vertex vert, Vector3 offset);
121161

122162
// Flip an edge.
123163
bool flipEdge(Edge e);
@@ -131,6 +171,7 @@ class MutationManager {
131171
// Returns the new halfedge which points away from the new vertex (so he.vertex() is the new vertex), and is the same
132172
// direction as e.halfedge() on the original edge. The halfedge direction of the other part of the new split edge is
133173
// also preserved.
174+
// Returns Halfedge() if edge was not split (e.g. if splittableEdges[e] is false)
134175
Halfedge splitEdge(Edge e, double tSplit);
135176
Halfedge splitEdge(Edge e, Vector3 newVertexPosition);
136177
Halfedge splitEdge(Edge e, double tSplit, Vector3 newVertexPosition);
@@ -142,6 +183,7 @@ class MutationManager {
142183
Vertex collapseEdge(Edge e, double tCollapse, Vector3 newVertexPosition);
143184

144185
// Split a face (i.e. insert a vertex into the face), and return the new vertex
186+
// Returns Vertex() if face was not split (e.g. if splittableFaces[f] is false)
145187
Vertex splitFace(Face f, const std::vector<double>& bSplit);
146188
Vertex splitFace(Face f, Vector3 newVertexPosition);
147189
Vertex splitFace(Face f, const std::vector<double>& bSplit, Vector3 newVertexPosition);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include "geometrycentral/surface/barycentric_coordinate_helpers.h"
4+
#include "geometrycentral/surface/manifold_surface_mesh.h"
5+
#include "geometrycentral/surface/mutation_manager.h"
6+
#include "geometrycentral/surface/vertex_position_geometry.h"
7+
8+
#include <deque>
9+
10+
namespace geometrycentral {
11+
namespace surface {
12+
13+
enum class RemeshBoundaryCondition { Fixed, Tangential, Free };
14+
enum class RemeshSmoothStyle { Circumcentric, Laplacian };
15+
16+
struct RemeshOptions {
17+
double targetEdgeLength = -1; // the target edge length in flat regions. If `targetEdgeLength` is negative, the target
18+
// edge length is set to relative the input mesh's mean edge length
19+
size_t maxIterations = 10; // the maximum number of iterations to run for
20+
double curvatureAdaptation = 0; // how much target length should vary due to curvature. Set curvatureAdaptation
21+
// to 0 if you want lengths to be approximately targetEdgeLength everywhere
22+
double minRelativeLength = 0.05; // the minimum possible edge length allowed in the output mesh. Defined relative to
23+
// targetEdgeLength
24+
RemeshSmoothStyle smoothStyle = RemeshSmoothStyle::Circumcentric; // smoothing function to use
25+
RemeshBoundaryCondition boundaryCondition =
26+
RemeshBoundaryCondition::Tangential; // allowed movement of boundary vertices
27+
};
28+
extern const RemeshOptions defaultRemeshOptions;
29+
30+
// Improve mesh using repeated rounds of edge flipping, vertex position smoothing, and edge splits/collapses
31+
void remesh(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, RemeshOptions options = defaultRemeshOptions);
32+
void remesh(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm,
33+
RemeshOptions options = defaultRemeshOptions);
34+
35+
// Try to make all triangles Delaunay
36+
// Returns the number of flips performed
37+
size_t fixDelaunay(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom);
38+
size_t fixDelaunay(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm);
39+
40+
// Average positions of vertices based on surrounding vertex positions
41+
// Returns the average amount each vertex was moved by
42+
double smoothByLaplacian(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, double stepSize = 1,
43+
RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);
44+
double smoothByLaplacian(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm,
45+
double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);
46+
47+
// Average positions of vertices based on surrounding triangle circumenters as in [Chen & Holst 2011]
48+
// Returns the average amount each vertex was moved by
49+
double smoothByCircumcenter(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, double stepSize = 1,
50+
RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);
51+
double smoothByCircumcenter(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm,
52+
double stepSize = 1, RemeshBoundaryCondition bc = RemeshBoundaryCondition::Tangential);
53+
54+
// applies splits and collapses to adjust edge lengths based on the curvature
55+
bool adjustEdgeLengths(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom,
56+
RemeshOptions options = defaultRemeshOptions);
57+
bool adjustEdgeLengths(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geom, MutationManager& mm,
58+
RemeshOptions options = defaultRemeshOptions);
59+
60+
} // namespace surface
61+
} // namespace geometrycentral

include/geometrycentral/surface/surface_mesh.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class SurfaceMesh {
193193
std::list<std::function<void(const std::vector<size_t>&)>> edgePermuteCallbackList;
194194
std::list<std::function<void(const std::vector<size_t>&)>> halfedgePermuteCallbackList;
195195
std::list<std::function<void(const std::vector<size_t>&)>> boundaryLoopPermuteCallbackList;
196+
std::list<std::function<void(void)>> compressCallbackList;
196197

197198
// Mesh delete callbacks
198199
// (this unfortunately seems to be necessary; objects which have registered their callbacks above

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ SET(SRCS
4343
surface/fast_marching_method.cpp
4444
surface/uniformize.cpp
4545
surface/parameterize.cpp
46+
surface/remeshing.cpp
4647
surface/surgery.cpp
4748
surface/simple_idt.cpp
4849
surface/exact_polyhedral_geodesics.cpp
@@ -119,6 +120,7 @@ SET(HEADERS
119120
${INCLUDE_ROOT}/surface/parameterize.h
120121
${INCLUDE_ROOT}/surface/poisson_disk_sampler.h
121122
${INCLUDE_ROOT}/surface/quadric_error_simplification.h
123+
${INCLUDE_ROOT}/surface/remeshing.h
122124
${INCLUDE_ROOT}/surface/rich_surface_mesh_data.h
123125
${INCLUDE_ROOT}/surface/rich_surface_mesh_data.ipp
124126
${INCLUDE_ROOT}/surface/polygon_soup_mesh.h

0 commit comments

Comments
 (0)