@@ -54,10 +54,7 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
5454 auto on_boundary = MeshTools ::find_boundary_nodes (_mesh );
5555
5656 // 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 ());
57+ auto subdomain_boundary_map = MeshTools ::build_subdomain_boundary_node_map (_mesh );
6158
6259 // We can only update the nodes after all new positions were
6360 // determined. We store the new positions here
@@ -67,11 +64,11 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
6764 {
6865 new_positions .resize (_mesh .max_node_id ());
6966
70- auto calculate_new_position = [this , & on_boundary , & new_positions ](const Node * node ) {
67+ auto calculate_new_position = [this , & new_positions ](const Node * node ) {
7168 // leave the boundary intact
7269 // Only relocate the nodes which are vertices of an element
7370 // All other entries of _graph (the secondary nodes) are empty
74- if (! on_boundary . count ( node -> id ()) && ( _graph [node -> id ()].size () > 0 ) )
71+ if (_graph [node -> id ()].size () > 0 )
7572 {
7673 Point avg_position (0. ,0. ,0. );
7774
@@ -100,16 +97,119 @@ void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
10097 _mesh .pid_nodes_end (DofObject ::invalid_processor_id )))
10198 calculate_new_position (node );
10299
100+ // update node position
101+ auto update_node_position = [this , & new_positions , & subdomain_boundary_map , & on_boundary ](Node * node )
102+ {
103+ if (_graph [node -> id ()].size () == 0 )
104+ return ;
105+
106+ // check if a node is on a given block boundary
107+ 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 )
108+ {
109+ const auto it = subdomain_boundary_map .find (node_id );
110+ if (it == subdomain_boundary_map .end ())
111+ return false;
112+ return it -> second .count (block_boundary ) > 0 ;
113+ };
114+
115+ // check if a series of vectors is collinear/coplanar
116+ RealVectorValue base ;
117+ std ::size_t n_edges = 0 ;
118+ auto has_zero_curvature = [this , & node , & base , & n_edges ](dof_id_type connected_id ) {
119+ const auto connected_node = _mesh .point (connected_id );
120+ const RealVectorValue vec = connected_node - * node ;
121+ // if any connected edge has length zero we give up and don't move the node
122+ if (vec .norm_sq () < libMesh ::TOLERANCE )
123+ return false;
124+
125+ // count edges
126+ n_edges ++ ;
127+
128+ // store first two edges for later projection
129+ if (n_edges == 1 )
130+ {
131+ base = vec ;
132+ return true;
133+ }
134+
135+ // 2D - collinear - check cross product of first edge with all other edges
136+ if (_mesh .mesh_dimension () == 2 )
137+ return (base .cross (vec ).norm_sq () < libMesh ::TOLERANCE );
138+
139+ // 3D
140+ if (n_edges == 2 )
141+ {
142+ const auto cross = base .cross (vec );
143+ if (cross .norm_sq () < libMesh ::TOLERANCE )
144+ n_edges -- ;
145+ else
146+ base = cross ;
147+ return true;
148+ }
149+
150+ return (base * vec < libMesh ::TOLERANCE );
151+ };
152+
153+ if (on_boundary .count (node -> id ()))
154+ {
155+ // project to boundary
156+ for (const auto & connected_id : _graph [node -> id ()])
157+ if (on_boundary .count (connected_id ) && !has_zero_curvature (connected_id ))
158+ return ;
159+ // we have not failed the curvature check, but do we have enough edges?
160+ if (n_edges < _mesh .mesh_dimension ())
161+ return ;
162+
163+ // now project
164+ // base /= base.norm();
165+ // auto delta = new_positions[node->id()] - *node;
166+ // if (_mesh.mesh_dimension() == 2)
167+ // delta = base * (delta * base);
168+ // else
169+ // delta -= base * (delta * base);
170+ // *node += delta;
171+ return ;
172+ }
173+
174+ const auto it = subdomain_boundary_map .find (node -> id ());
175+ if (it != subdomain_boundary_map .end ())
176+ {
177+ const auto num_boundaries = it -> second .size ();
178+
179+ // do not touch nodes at the intersection of two or more boundaries
180+ if (num_boundaries > 1 )
181+ return ;
182+
183+ if (num_boundaries == 1 )
184+ {
185+ // project to block boundary
186+ const auto & boundary = * (it -> second .begin ());
187+ // iterate over neighboring nodes that share the same boundary and check if all edges are coplanar/collinear
188+ for (const auto & connected_id : _graph [node -> id ()])
189+ if (is_node_on_block_boundary (connected_id , boundary ) && !has_zero_curvature (connected_id ))
190+ return ;
191+ // we have not failed the curvature check, but do we have enough edges?
192+ if (n_edges < _mesh .mesh_dimension ())
193+ return ;
194+
195+ // now project
196+ // if (_mesh.mesh_dimension() == 2)
197+
198+ return ;
199+ }
200+ }
201+
202+ // otherwise just move the node freely
203+ * node = new_positions [node -> id ()];
204+ };
103205
104206 // now update the node positions (local and unpartitioned nodes only)
105207 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 ()];
208+ update_node_position (node );
108209
109210 for (auto & node : as_range (_mesh .pid_nodes_begin (DofObject ::invalid_processor_id ),
110211 _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 ()];
212+ update_node_position (node );
113213
114214 // Now the nodes which are ghosts on this processor may have been moved on
115215 // the processors which own them. So we need to synchronize with our neighbors
0 commit comments