libMesh
Loading...
Searching...
No Matches
Functions | Variables
libMesh::MeshTools::Subdivision Namespace Reference

Utility functions for subdivision surface operations on a Mesh. More...

Functions

void find_one_ring (const Tri3Subdivision *elem, std::vector< const Node * > &nodes)
 Determines the 1-ring of element elem, and writes it to the nodes vector.
 
void all_subdivision (MeshBase &mesh)
 Turns a triangulated mesh into a subdivision mesh.
 
void prepare_subdivision_mesh (MeshBase &mesh, bool ghosted=false)
 Prepares the mesh for use with subdivision elements.
 
void add_boundary_ghosts (MeshBase &mesh)
 Adds a new layer of "ghost" elements along the domain boundaries.
 
void tag_boundary_ghosts (MeshBase &mesh)
 Flags the outermost element layer along the domain boundaries as "ghost" elements.
 

Variables

static const unsigned int next [3] = {1,2,0}
 A lookup table for the increment modulo 3 operation, for iterating through the three nodes per element in positive direction.
 
static const unsigned int prev [3] = {2,0,1}
 A lookup table for the decrement modulo 3 operation, for iterating through the three nodes per element in negative direction.
 

Detailed Description

Utility functions for subdivision surface operations on a Mesh.

Author
Roman Vetter
Norbert Stoop
Date
2014

Support functions for subdivision surface elements.

Function Documentation

◆ add_boundary_ghosts()

void libMesh::MeshTools::Subdivision::add_boundary_ghosts ( MeshBase mesh)

Adds a new layer of "ghost" elements along the domain boundaries.

This function normally needn't be called by the user, because it is invoked by prepare_subdivision_mesh.

Definition at line 229 of file mesh_subdivision_support.C.

230{
231 static const Real tol = 1e-5;
232
233 // add the mirrored ghost elements (without using iterators, because the mesh is modified in the course)
234 std::vector<Tri3Subdivision *> ghost_elems;
235 std::vector<Node *> ghost_nodes;
236 const unsigned int n_elem = mesh.n_elem();
237 for (unsigned int eid = 0; eid < n_elem; ++eid)
238 {
239 Elem * elem = mesh.elem_ptr(eid);
240 libmesh_assert_equal_to(elem->type(), TRI3SUBDIVISION);
241
242 // If the triangle happens to be in a corner (two boundary
243 // edges), we perform a counter-clockwise loop by mirroring the
244 // previous triangle until we come back to the original
245 // triangle. This prevents degenerated triangles in the mesh
246 // corners and guarantees that the node in the middle of the
247 // loop is of valence=6.
248 for (auto i : elem->side_index_range())
249 {
250 libmesh_assert_not_equal_to(elem->neighbor_ptr(i), elem);
251
252 if (elem->neighbor_ptr(i) == nullptr &&
253 elem->neighbor_ptr(next[i]) == nullptr)
254 {
255 Elem * nelem = elem;
256 unsigned int k = i;
257 for (unsigned int l=0;l<4;l++)
258 {
259 // this is the vertex to be mirrored
260 Point point = nelem->point(k) + nelem->point(next[k]) - nelem->point(prev[k]);
261
262 // Check if the proposed vertex doesn't coincide
263 // with one of the existing vertices. This is
264 // necessary because for some triangulations, it can
265 // happen that two mirrored ghost vertices coincide,
266 // which would then lead to a zero size ghost
267 // element below.
268 Node * node = nullptr;
269 for (auto & ghost_node : ghost_nodes)
270 if ((*ghost_node - point).norm() < tol * (elem->point(k) - point).norm())
271 {
272 node = ghost_node;
273 break;
274 }
275
276 // add the new vertex only if no other is nearby
277 if (node == nullptr)
278 {
279 node = mesh.add_point(point);
280 ghost_nodes.push_back(node);
281 }
282
283 auto uelem = Elem::build(TRI3SUBDIVISION);
284 auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
285
286 // add the first new ghost element to the list just as in the non-corner case
287 if (l == 0)
288 ghost_elems.push_back(newelem);
289
290 newelem->set_node(0, nelem->node_ptr(next[k]));
291 newelem->set_node(1, nelem->node_ptr(k));
292 newelem->set_node(2, node);
293 newelem->set_neighbor(0, nelem);
294 newelem->set_ghost(true);
295 if (l>0)
296 newelem->set_neighbor(2, nullptr);
297 nelem->set_neighbor(k, newelem);
298
299 Elem * added_elem = mesh.add_elem(std::move(uelem));
300 mesh.get_boundary_info().add_node(nelem->node_ptr(k), 1);
301 mesh.get_boundary_info().add_node(nelem->node_ptr(next[k]), 1);
302 mesh.get_boundary_info().add_node(nelem->node_ptr(prev[k]), 1);
303 mesh.get_boundary_info().add_node(node, 1);
304
305 nelem = added_elem;
306 k = 2 ;
307 }
308
309 auto uelem = Elem::build(TRI3SUBDIVISION);
310 auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
311
312 newelem->set_node(0, elem->node_ptr(next[i]));
313 newelem->set_node(1, nelem->node_ptr(2));
314 newelem->set_node(2, elem->node_ptr(prev[i]));
315 newelem->set_neighbor(0, nelem);
316 nelem->set_neighbor(2, newelem);
317 newelem->set_ghost(true);
318 newelem->set_neighbor(2, elem);
319 elem->set_neighbor(next[i],newelem);
320
321 mesh.add_elem(std::move(uelem));
322
323 break;
324 }
325 }
326
327 for (auto i : elem->side_index_range())
328 {
329 libmesh_assert_not_equal_to(elem->neighbor_ptr(i), elem);
330 if (elem->neighbor_ptr(i) == nullptr)
331 {
332 // this is the vertex to be mirrored
333 Point point = elem->point(i) + elem->point(next[i]) - elem->point(prev[i]);
334
335 // Check if the proposed vertex doesn't coincide with
336 // one of the existing vertices. This is necessary
337 // because for some triangulations, it can happen that
338 // two mirrored ghost vertices coincide, which would
339 // then lead to a zero size ghost element below.
340 Node * node = nullptr;
341 for (auto & ghost_node : ghost_nodes)
342 if ((*ghost_node - point).norm() < tol * (elem->point(i) - point).norm())
343 {
344 node = ghost_node;
345 break;
346 }
347
348 // add the new vertex only if no other is nearby
349 if (node == nullptr)
350 {
351 node = mesh.add_point(point);
352 ghost_nodes.push_back(node);
353 }
354
355 auto uelem = Elem::build(TRI3SUBDIVISION);
356 auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
357
358 ghost_elems.push_back(newelem);
359
360 newelem->set_node(0, elem->node_ptr(next[i]));
361 newelem->set_node(1, elem->node_ptr(i));
362 newelem->set_node(2, node);
363 newelem->set_neighbor(0, elem);
364 newelem->set_ghost(true);
365 elem->set_neighbor(i, newelem);
366
367 mesh.add_elem(std::move(uelem));
368 mesh.get_boundary_info().add_node(elem->node_ptr(i), 1);
369 mesh.get_boundary_info().add_node(elem->node_ptr(next[i]), 1);
370 mesh.get_boundary_info().add_node(elem->node_ptr(prev[i]), 1);
371 mesh.get_boundary_info().add_node(node, 1);
372 }
373 }
374 }
375
376 // add the missing ghost elements (connecting new ghost nodes)
377 std::vector<std::unique_ptr<Elem>> missing_ghost_elems;
378 for (auto & elem : ghost_elems)
379 {
380 libmesh_assert(elem->is_ghost());
381
382 for (auto i : elem->side_index_range())
383 {
384 if (elem->neighbor_ptr(i) == nullptr &&
385 elem->neighbor_ptr(prev[i]) != nullptr)
386 {
387 // go around counter-clockwise
388 Tri3Subdivision * nb1 = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(prev[i]));
389 Tri3Subdivision * nb2 = nb1;
390 unsigned int j = i;
391 unsigned int n_nb = 0;
392 while (nb1 != nullptr && nb1->id() != elem->id())
393 {
394 j = nb1->local_node_number(elem->node_id(i));
395 nb2 = nb1;
396 nb1 = static_cast<Tri3Subdivision *>(nb1->neighbor_ptr(prev[j]));
397 libmesh_assert(nb1 == nullptr || nb1->id() != nb2->id());
398 n_nb++;
399 }
400
401 libmesh_assert_not_equal_to(nb2->id(), elem->id());
402
403 // Above, we merged coinciding ghost vertices. Therefore, we need
404 // to exclude the case where there is no ghost element to add between
405 // these two (identical) ghost nodes.
406 if (elem->node_ptr(next[i])->id() == nb2->node_ptr(prev[j])->id())
407 break;
408
409 // If the number of already present neighbors is less than 4, we add another extra element
410 // so that the node in the middle of the loop ends up being of valence=6.
411 // This case usually happens when the middle node corresponds to a corner of the original mesh,
412 // and the extra element below prevents degenerated triangles in the mesh corners.
413 if (n_nb < 4)
414 {
415 // this is the vertex to be mirrored
416 Point point = nb2->point(j) + nb2->point(prev[j]) - nb2->point(next[j]);
417
418 // Check if the proposed vertex doesn't coincide with one of the existing vertices.
419 // This is necessary because for some triangulations, it can happen that two mirrored
420 // ghost vertices coincide, which would then lead to a zero size ghost element below.
421 Node * node = nullptr;
422 for (auto & ghost_node : ghost_nodes)
423 if ((*ghost_node - point).norm() < tol * (nb2->point(j) - point).norm())
424 {
425 node = ghost_node;
426 break;
427 }
428
429 // add the new vertex only if no other is nearby
430 if (node == nullptr)
431 {
432 node = mesh.add_point(point);
433 ghost_nodes.push_back(node);
434 }
435
436 auto uelem = Elem::build(TRI3SUBDIVISION);
437 auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
438
439 newelem->set_node(0, nb2->node_ptr(j));
440 newelem->set_node(1, nb2->node_ptr(prev[j]));
441 newelem->set_node(2, node);
442 newelem->set_neighbor(0, nb2);
443 newelem->set_neighbor(1, nullptr);
444 newelem->set_ghost(true);
445 nb2->set_neighbor(prev[j], newelem);
446
447 Elem * added_elem = mesh.add_elem(std::move(uelem));
448 mesh.get_boundary_info().add_node(nb2->node_ptr(j), 1);
449 mesh.get_boundary_info().add_node(nb2->node_ptr(prev[j]), 1);
450 mesh.get_boundary_info().add_node(node, 1);
451
452 nb2 = cast_ptr<Tri3Subdivision *>(added_elem);
453 j = nb2->local_node_number(elem->node_id(i));
454 }
455
456 auto uelem = Elem::build(TRI3SUBDIVISION);
457 auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
458
459 newelem->set_node(0, elem->node_ptr(next[i]));
460 newelem->set_node(1, elem->node_ptr(i));
461 newelem->set_node(2, nb2->node_ptr(prev[j]));
462 newelem->set_neighbor(0, elem);
463 newelem->set_neighbor(1, nb2);
464 newelem->set_neighbor(2, nullptr);
465 newelem->set_ghost(true);
466
467 elem->set_neighbor(i, newelem);
468 nb2->set_neighbor(prev[j], newelem);
469
470 missing_ghost_elems.push_back(std::move(uelem));
471 break;
472 }
473 } // end side loop
474 } // end ghost element loop
475
476 // add the missing ghost elements to the mesh
477 for (auto & elem : missing_ghost_elems)
478 mesh.add_elem(std::move(elem));
479}
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
const Point & point(const unsigned int i) const
Definition elem.h:2462
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition elem.h:2635
const Node * node_ptr(const unsigned int i) const
Definition elem.h:2516
virtual ElemType type() const =0
dof_id_type node_id(const unsigned int i) const
Definition elem.h:2484
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2615
A Node is like a Point, but with more information.
Definition node.h:55
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
MeshBase & mesh
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
auto norm(const T &a)
libmesh_assert(ctx)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

References libMesh::MeshBase::add_elem(), libMesh::BoundaryInfo::add_node(), libMesh::MeshBase::add_point(), libMesh::Elem::build(), libMesh::MeshBase::elem_ptr(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::libmesh_assert(), libMesh::Tri3Subdivision::local_node_number(), mesh, libMesh::MeshBase::n_elem(), libMesh::MeshTools::n_elem(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ptr(), libMesh::Elem::point(), libMesh::Real, libMesh::Elem::set_neighbor(), libMesh::Elem::side_index_range(), libMesh::TRI3SUBDIVISION, and libMesh::Elem::type().

Referenced by prepare_subdivision_mesh().

◆ all_subdivision()

void libMesh::MeshTools::Subdivision::all_subdivision ( MeshBase mesh)

Turns a triangulated mesh into a subdivision mesh.

This function normally needn't be called by the user, because it is invoked by prepare_subdivision_mesh.

Definition at line 90 of file mesh_subdivision_support.C.

91{
92 const bool mesh_has_boundary_data =
93 (mesh.get_boundary_info().n_boundary_ids() > 0);
94
95 std::vector<Elem *> new_boundary_elements;
96 std::vector<short int> new_boundary_sides;
97 std::vector<boundary_id_type> new_boundary_ids;
98
99 // Container to catch ids handed back from BoundaryInfo
100 std::vector<boundary_id_type> ids;
101
102 for (const auto & elem : mesh.element_ptr_range())
103 {
104 libmesh_assert_equal_to(elem->type(), TRI3);
105
106 auto tri = Elem::build_with_id(TRI3SUBDIVISION, elem->id());
107 tri->subdomain_id() = elem->subdomain_id();
108 tri->set_node(0, elem->node_ptr(0));
109 tri->set_node(1, elem->node_ptr(1));
110 tri->set_node(2, elem->node_ptr(2));
111
112 if (mesh_has_boundary_data)
113 {
114 for (auto side : elem->side_index_range())
115 {
116 mesh.get_boundary_info().boundary_ids(elem, side, ids);
117
118 for (const auto & id : ids)
119 {
120 // add the boundary id to the list of new boundary ids
121 new_boundary_ids.push_back(id);
122 new_boundary_elements.push_back(tri.get());
123 new_boundary_sides.push_back(side);
124 }
125 }
126
127 // remove the original element from the BoundaryInfo structure
128 mesh.get_boundary_info().remove(elem);
129 }
130
131 mesh.insert_elem(std::move(tri));
132 }
133 mesh.prepare_for_use();
134
135 if (mesh_has_boundary_data)
136 {
137 // If the old mesh had boundary data, the new mesh better have some too.
138 libmesh_assert_greater(new_boundary_elements.size(), 0);
139
140 // We should also be sure that the lengths of the new boundary data vectors
141 // are all the same.
142 libmesh_assert_equal_to(new_boundary_sides.size(), new_boundary_elements.size());
143 libmesh_assert_equal_to(new_boundary_sides.size(), new_boundary_ids.size());
144
145 // Add the new boundary info to the mesh.
146 for (auto s : index_range(new_boundary_elements))
147 mesh.get_boundary_info().add_side(new_boundary_elements[s],
148 new_boundary_sides[s],
149 new_boundary_ids[s]);
150 }
151
152 mesh.prepare_for_use();
153}
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153

References libMesh::BoundaryInfo::add_side(), libMesh::BoundaryInfo::boundary_ids(), libMesh::Elem::build_with_id(), libMesh::MeshBase::get_boundary_info(), libMesh::index_range(), libMesh::MeshBase::insert_elem(), mesh, libMesh::BoundaryInfo::n_boundary_ids(), libMesh::MeshBase::prepare_for_use(), libMesh::BoundaryInfo::remove(), libMesh::TRI3, and libMesh::TRI3SUBDIVISION.

Referenced by prepare_subdivision_mesh().

◆ find_one_ring()

void libMesh::MeshTools::Subdivision::find_one_ring ( const Tri3Subdivision elem,
std::vector< const Node * > &  nodes 
)

Determines the 1-ring of element elem, and writes it to the nodes vector.

This is necessary because subdivision elements have a larger local support than conventionally interpolated elements. The 1-ring may, for instance, look like this:

*    N+4 - N+1 - N+2
*    / \   / \   / \
*   /   \ /   \ /   \
* N+5 -- N --- 1 -- N+3
*   \   / \ e / \   /
*    \ /   \ /   \ /
*    N-1--- 0 --- 2
*      \   /|\   /
*       \ / | \ /
*        5--4--3
* 

Definition at line 31 of file mesh_subdivision_support.C.

33{
36
37 unsigned int valence = elem->get_ordered_valence(0);
38 nodes.resize(valence + 6);
39
40 // The first three vertices in the patch are the ones from the element triangle
41 nodes[0] = elem->get_ordered_node(0);
42 nodes[1] = elem->get_ordered_node(1);
43 nodes[valence] = elem->get_ordered_node(2);
44
45 const unsigned int nn0 = elem->local_node_number(nodes[0]->id());
46
47 const Tri3Subdivision * nb = dynamic_cast<const Tri3Subdivision *>(elem->neighbor_ptr(nn0));
49
50 unsigned int j, i = 1;
51
52 do
53 {
54 ++i;
55 j = nb->local_node_number(nodes[0]->id());
56 nodes[i] = nb->node_ptr(next[j]);
57 nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
58 } while (nb != elem);
59
60 /* for nodes connected with N (= valence[0]) */
61 nb = static_cast<const Tri3Subdivision *>(elem->neighbor_ptr(next[nn0]));
62 j = nb->local_node_number(nodes[1]->id());
63 nodes[valence+1] = nb->node_ptr(next[j]);
64
65 nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(next[j]));
66 j = nb->local_node_number(nodes[valence+1]->id());
67 nodes[valence+4] = nb->node_ptr(next[j]);
68
69 nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(next[j]));
70 j = nb->local_node_number(nodes[valence+4]->id());
71 nodes[valence+5] = nb->node_ptr(next[j]);
72
73 /* for nodes connected with 1 */
74 nb = static_cast<const Tri3Subdivision *>(elem->neighbor_ptr(next[nn0]));
75 j = nb->local_node_number(nodes[1]->id());
76 // nodes[valence+1] has been determined already
77
78 nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
79 j = nb->local_node_number(nodes[1]->id());
80 nodes[valence+2] = nb->node_ptr(next[j]);
81
82 nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
83 j = nb->local_node_number(nodes[1]->id());
84 nodes[valence+3] = nb->node_ptr(next[j]);
85
86 return;
87}
The Tri3Subdivision element is a three-noded subdivision surface shell element used in mechanics calc...
unsigned int get_ordered_valence(unsigned int node_id) const
Node * get_ordered_node(unsigned int node_id) const
unsigned int local_node_number(unsigned int node_id) const

References libMesh::Tri3Subdivision::get_ordered_node(), libMesh::Tri3Subdivision::get_ordered_valence(), libMesh::Tri3Subdivision::is_subdivision_updated(), libMesh::libmesh_assert(), libMesh::Tri3Subdivision::local_node_number(), libMesh::Elem::neighbor_ptr(), and libMesh::Elem::node_ptr().

Referenced by libMesh::FEMap::compute_map(), and libMesh::DofMap::dof_indices().

◆ prepare_subdivision_mesh()

void libMesh::MeshTools::Subdivision::prepare_subdivision_mesh ( MeshBase mesh,
bool  ghosted = false 
)

Prepares the mesh for use with subdivision elements.

The ghosted flag determines how boundaries are treated. If false, a new layer of "ghost" elements is appended along the domain boundaries. If true, the outermost element layer is taken as ghosts, i.e. no new elements are added.

Definition at line 156 of file mesh_subdivision_support.C.

157{
158 mesh.prepare_for_use();
159
160 // convert all mesh elements to subdivision elements
162
163 if (!ghosted)
164 {
165 // add the ghost elements for the boundaries
167 }
168 else
169 {
170 // This assumes that the mesh already has the ghosts. Only tagging them is required here.
171 tag_boundary_ghosts(mesh);
172 }
173
174 mesh.prepare_for_use();
175
176 std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
177 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
178
179 // compute the node valences
180 for (auto & node : mesh.node_ptr_range())
181 {
182 std::vector<const Node *> neighbors;
183 MeshTools::find_nodal_neighbors(mesh, *node, nodes_to_elem_map, neighbors);
184 const unsigned int valence =
185 cast_int<unsigned int>(neighbors.size());
186 libmesh_assert_greater(valence, 1);
187 node->set_valence(valence);
188 }
189
190 for (auto & elem : mesh.element_ptr_range())
191 {
192 Tri3Subdivision * tri3s = dynamic_cast<Tri3Subdivision *>(elem);
193 libmesh_assert(tri3s);
194 if (!tri3s->is_ghost())
195 tri3s->prepare_subdivision_properties();
196 }
197}
void add_boundary_ghosts(MeshBase &mesh)
Adds a new layer of "ghost" elements along the domain boundaries.
void all_subdivision(MeshBase &mesh)
Turns a triangulated mesh into a subdivision mesh.

References add_boundary_ghosts(), all_subdivision(), libMesh::MeshTools::build_nodes_to_elem_map(), libMesh::MeshTools::find_nodal_neighbors(), libMesh::Tri3Subdivision::is_ghost(), libMesh::libmesh_assert(), mesh, libMesh::MeshBase::prepare_for_use(), libMesh::Tri3Subdivision::prepare_subdivision_properties(), and tag_boundary_ghosts().

Referenced by main().

◆ tag_boundary_ghosts()

void libMesh::MeshTools::Subdivision::tag_boundary_ghosts ( MeshBase mesh)

Flags the outermost element layer along the domain boundaries as "ghost" elements.

This function normally needn't be called by the user, because it is invoked by prepare_subdivision_mesh.

Definition at line 200 of file mesh_subdivision_support.C.

201{
202 for (auto & elem : mesh.element_ptr_range())
203 {
204 libmesh_assert_equal_to(elem->type(), TRI3SUBDIVISION);
205
206 Tri3Subdivision * sd_elem = static_cast<Tri3Subdivision *>(elem);
207 for (auto i : elem->side_index_range())
208 {
209 if (elem->neighbor_ptr(i) == nullptr)
210 {
211 sd_elem->set_ghost(true);
212 // set all other neighbors to ghosts as well
213 if (elem->neighbor_ptr(next[i]))
214 {
215 Tri3Subdivision * nb = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(next[i]));
216 nb->set_ghost(true);
217 }
218 if (elem->neighbor_ptr(prev[i]))
219 {
220 Tri3Subdivision * nb = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(prev[i]));
221 nb->set_ghost(true);
222 }
223 }
224 }
225 }
226}
void set_ghost(bool ghosted)
Sets the boolean flag identifying ghost elements.

References mesh, libMesh::Tri3Subdivision::set_ghost(), and libMesh::TRI3SUBDIVISION.

Referenced by prepare_subdivision_mesh().

Variable Documentation

◆ next

const unsigned int libMesh::MeshTools::Subdivision::next[3] = {1,2,0}
static

A lookup table for the increment modulo 3 operation, for iterating through the three nodes per element in positive direction.

Definition at line 102 of file mesh_subdivision_support.h.

102{1,2,0};

Referenced by assemble_shell(), and libMesh::Tri3Subdivision::prepare_subdivision_properties().

◆ prev

const unsigned int libMesh::MeshTools::Subdivision::prev[3] = {2,0,1}
static

A lookup table for the decrement modulo 3 operation, for iterating through the three nodes per element in negative direction.

Definition at line 108 of file mesh_subdivision_support.h.

108{2,0,1};

Referenced by assemble_shell(), and libMesh::Tri3Subdivision::prepare_subdivision_properties().