3434namespace libMesh
3535{
3636// LaplaceMeshSmoother member functions
37- LaplaceMeshSmoother ::LaplaceMeshSmoother (UnstructuredMesh & mesh )
37+ LaplaceMeshSmoother ::LaplaceMeshSmoother (UnstructuredMesh & mesh , bool smooth_boundary )
3838 : MeshSmoother (mesh ),
39- _initialized (false)
39+ _initialized (false),
40+ _smooth_boundary (smooth_boundary )
4041{
4142}
4243
@@ -54,10 +55,7 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
5455 auto on_boundary = MeshTools ::find_boundary_nodes (_mesh );
5556
5657 // 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 );
6159
6260 // We can only update the nodes after all new positions were
6361 // determined. We store the new positions here
@@ -67,11 +65,11 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
6765 {
6866 new_positions .resize (_mesh .max_node_id ());
6967
70- auto calculate_new_position = [this , & on_boundary , & new_positions ](const Node * node ) {
68+ auto calculate_new_position = [this , & new_positions ](const Node * node ) {
7169 // leave the boundary intact
7270 // Only relocate the nodes which are vertices of an element
7371 // 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 )
7573 {
7674 Point avg_position (0. ,0. ,0. );
7775
@@ -100,16 +98,134 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
10098 _mesh .pid_nodes_end (DofObject ::invalid_processor_id )))
10199 calculate_new_position (node );
102100
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+ };
103221
104222 // now update the node positions (local and unpartitioned nodes only)
105223 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 );
108225
109226 for (auto & node : as_range (_mesh .pid_nodes_begin (DofObject ::invalid_processor_id ),
110227 _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 );
113229
114230 // Now the nodes which are ghosts on this processor may have been moved on
115231 // the processors which own them. So we need to synchronize with our neighbors
0 commit comments