34
34
namespace libMesh
35
35
{
36
36
// LaplaceMeshSmoother member functions
37
- LaplaceMeshSmoother ::LaplaceMeshSmoother (UnstructuredMesh & mesh )
37
+ LaplaceMeshSmoother ::LaplaceMeshSmoother (UnstructuredMesh & mesh , bool smooth_boundary )
38
38
: MeshSmoother (mesh ),
39
- _initialized (false)
39
+ _initialized (false),
40
+ _smooth_boundary (smooth_boundary )
40
41
{
41
42
}
42
43
@@ -54,10 +55,7 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
54
55
auto on_boundary = MeshTools ::find_boundary_nodes (_mesh );
55
56
56
57
// Also: don't smooth block boundary nodes
57
- auto on_block_boundary = MeshTools ::find_block_boundary_nodes (_mesh );
58
-
59
- // Merge them
60
- on_boundary .insert (on_block_boundary .begin (), on_block_boundary .end ());
58
+ auto subdomain_boundary_map = MeshTools ::build_subdomain_boundary_node_map (_mesh );
61
59
62
60
// We can only update the nodes after all new positions were
63
61
// determined. We store the new positions here
@@ -67,11 +65,11 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
67
65
{
68
66
new_positions .resize (_mesh .max_node_id ());
69
67
70
- auto calculate_new_position = [this , & on_boundary , & new_positions ](const Node * node ) {
68
+ auto calculate_new_position = [this , & new_positions ](const Node * node ) {
71
69
// leave the boundary intact
72
70
// Only relocate the nodes which are vertices of an element
73
71
// All other entries of _graph (the secondary nodes) are empty
74
- if (! on_boundary . count ( node -> id ()) && ( _graph [node -> id ()].size () > 0 ) )
72
+ if (_graph [node -> id ()].size () > 0 )
75
73
{
76
74
Point avg_position (0. ,0. ,0. );
77
75
@@ -100,16 +98,134 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
100
98
_mesh .pid_nodes_end (DofObject ::invalid_processor_id )))
101
99
calculate_new_position (node );
102
100
101
+ // update node position
102
+ auto update_node_position = [this , & new_positions , & subdomain_boundary_map , & on_boundary ](Node * node )
103
+ {
104
+ if (_graph [node -> id ()].size () == 0 )
105
+ return ;
106
+
107
+ // check if a node is on a given block boundary
108
+ auto is_node_on_block_boundary = [& subdomain_boundary_map ](dof_id_type node_id , const std ::pair < subdomain_id_type , subdomain_id_type > & block_boundary )
109
+ {
110
+ const auto it = subdomain_boundary_map .find (node_id );
111
+ if (it == subdomain_boundary_map .end ())
112
+ return false;
113
+ return it -> second .count (block_boundary ) > 0 ;
114
+ };
115
+
116
+ // check if a series of vectors is collinear/coplanar
117
+ RealVectorValue base ;
118
+ std ::size_t n_edges = 0 ;
119
+ auto has_zero_curvature = [this , & node , & base , & n_edges ](dof_id_type connected_id ) {
120
+ const auto connected_node = _mesh .point (connected_id );
121
+ RealVectorValue vec = connected_node - * node ;
122
+ const auto vec_norm_sq = vec .norm_sq ();
123
+ // if any connected edge has length zero we give up and don't move the node
124
+ if (vec_norm_sq < libMesh ::TOLERANCE * libMesh ::TOLERANCE )
125
+ return false;
126
+
127
+ // normalize
128
+ vec /= std ::sqrt (vec_norm_sq );
129
+
130
+ // count edges
131
+ n_edges ++ ;
132
+
133
+ // store first two edges for later projection
134
+ if (n_edges == 1 )
135
+ {
136
+ base = vec ;
137
+ return true;
138
+ }
139
+
140
+ // 2D - collinear - check cross product of first edge with all other edges
141
+ if (_mesh .mesh_dimension () == 2 )
142
+ return (base .cross (vec ).norm_sq () < libMesh ::TOLERANCE * libMesh ::TOLERANCE );
143
+
144
+ // 3D
145
+ if (n_edges == 2 )
146
+ {
147
+ const auto cross = base .cross (vec );
148
+ const auto cross_norm_sq = cross .norm_sq ();
149
+ if (cross_norm_sq < libMesh ::TOLERANCE * libMesh ::TOLERANCE )
150
+ n_edges -- ;
151
+ else
152
+ base = cross / std ::sqrt (cross_norm_sq );
153
+ return true;
154
+ }
155
+
156
+ return (base * vec < libMesh ::TOLERANCE );
157
+ };
158
+
159
+ const auto it = subdomain_boundary_map .find (node -> id ());
160
+ if (it != subdomain_boundary_map .end ())
161
+ {
162
+ if (!_smooth_boundary )
163
+ return ;
164
+
165
+ const auto num_boundaries = it -> second .size ();
166
+
167
+ // do not touch nodes at the intersection of two or more boundaries
168
+ if (num_boundaries > 1 || on_boundary .count (node -> id ()) > 0 )
169
+ return ;
170
+
171
+ if (num_boundaries == 1 )
172
+ {
173
+ // project to block boundary
174
+ const auto & boundary = * (it -> second .begin ());
175
+ // iterate over neighboring nodes that share the same boundary and check if all edges are coplanar/collinear
176
+ for (const auto & connected_id : _graph [node -> id ()])
177
+ if (is_node_on_block_boundary (connected_id , boundary ) && !has_zero_curvature (connected_id ))
178
+ return ;
179
+ // we have not failed the curvature check, but do we have enough edges?
180
+ if (n_edges < _mesh .mesh_dimension ())
181
+ return ;
182
+
183
+ // now project
184
+ auto delta = new_positions [node -> id ()] - * node ;
185
+ if (_mesh .mesh_dimension () == 2 )
186
+ delta = base * (delta * base );
187
+ else
188
+ delta -= base * (delta * base );
189
+ * node += delta ;
190
+ return ;
191
+ }
192
+ }
193
+
194
+ if (on_boundary .count (node -> id ()))
195
+ {
196
+ if (!_smooth_boundary )
197
+ return ;
198
+
199
+ // project to boundary
200
+ for (const auto & connected_id : _graph [node -> id ()])
201
+ if (on_boundary .count (connected_id ) && !has_zero_curvature (connected_id ))
202
+ return ;
203
+ // we have not failed the curvature check, but do we have enough edges?
204
+ if (n_edges < _mesh .mesh_dimension ())
205
+ return ;
206
+
207
+ // now project
208
+ auto delta = new_positions [node -> id ()] - * node ;
209
+ if (_mesh .mesh_dimension () == 2 )
210
+ delta = base * (delta * base );
211
+ else
212
+ delta -= base * (delta * base );
213
+ * node += delta ;
214
+ return ;
215
+ }
216
+
217
+
218
+ // otherwise just move the node freely
219
+ * node = new_positions [node -> id ()];
220
+ };
103
221
104
222
// now update the node positions (local and unpartitioned nodes only)
105
223
for (auto & node : _mesh .local_node_ptr_range ())
106
- if (!on_boundary .count (node -> id ()) && (_graph [node -> id ()].size () > 0 ))
107
- * node = new_positions [node -> id ()];
224
+ update_node_position (node );
108
225
109
226
for (auto & node : as_range (_mesh .pid_nodes_begin (DofObject ::invalid_processor_id ),
110
227
_mesh .pid_nodes_end (DofObject ::invalid_processor_id )))
111
- if (!on_boundary .count (node -> id ()) && (_graph [node -> id ()].size () > 0 ))
112
- * node = new_positions [node -> id ()];
228
+ update_node_position (node );
113
229
114
230
// Now the nodes which are ghosts on this processor may have been moved on
115
231
// the processors which own them. So we need to synchronize with our neighbors
0 commit comments