OGS
MeshRevision.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "MeshRevision.h"
5
6#include <numeric>
7#include <range/v3/algorithm/copy.hpp>
8#include <range/v3/algorithm/transform.hpp>
9#include <range/v3/range/conversion.hpp>
10#include <range/v3/view/transform.hpp>
11
12#include "BaseLib/Algorithm.h"
13#include "BaseLib/Logging.h"
14#include "GeoLib/Grid.h"
17#include "MeshLib/Mesh.h"
18#include "MeshLib/Properties.h"
20
21namespace MeshToolsLib
22{
25unsigned lutPrismThirdNode(unsigned const id1, unsigned const id2)
26{
27 if ((id1 == 0 && id2 == 1) || (id1 == 1 && id2 == 0))
28 {
29 return 2;
30 }
31 if ((id1 == 1 && id2 == 2) || (id1 == 2 && id2 == 1))
32 {
33 return 0;
34 }
35 if ((id1 == 0 && id2 == 2) || (id1 == 2 && id2 == 0))
36 {
37 return 1;
38 }
39 if ((id1 == 3 && id2 == 4) || (id1 == 4 && id2 == 3))
40 {
41 return 5;
42 }
43 if ((id1 == 4 && id2 == 5) || (id1 == 5 && id2 == 4))
44 {
45 return 3;
46 }
47 if ((id1 == 3 && id2 == 5) || (id1 == 5 && id2 == 3))
48 {
49 return 4;
50 }
51 return std::numeric_limits<unsigned>::max();
52}
53} // namespace MeshToolsLib
54
55namespace
56{
57template <typename ElementType>
58std::unique_ptr<MeshLib::Element> createElement(
59 std::span<MeshLib::Node* const> const element_nodes,
60 std::vector<MeshLib::Node*> const& nodes,
61 std::array<std::size_t, ElementType::n_all_nodes> const local_ids)
62{
63 using namespace MeshLib::views;
64 auto lookup_in = [](auto const& values)
65 {
66 return ranges::views::transform([&values](std::size_t const n)
67 { return values[n]; });
68 };
69
70 std::array<MeshLib::Node*, ElementType::n_all_nodes> new_nodes;
71 ranges::copy(local_ids | lookup_in(element_nodes) | ids | lookup_in(nodes),
72 begin(new_nodes));
73
74 return std::make_unique<ElementType>(new_nodes);
75}
76
78unsigned subdivideQuad(MeshLib::Element const* const quad,
79 std::vector<MeshLib::Node*> const& nodes,
80 std::vector<MeshLib::Element*>& new_elements)
81{
82 std::array<std::size_t, 3> const tri1_node_ids{0, 1, 2};
83 new_elements.push_back(
84 createElement<MeshLib::Tri>(quad->nodes(), nodes, tri1_node_ids)
85 .release());
86
87 std::array<std::size_t, 3> const tri2_node_ids{0, 2, 3};
88 new_elements.push_back(
89 createElement<MeshLib::Tri>(quad->nodes(), nodes, tri2_node_ids)
90 .release());
91
92 return 2;
93}
94
96unsigned subdividePrism(MeshLib::Element const* const prism,
97 std::vector<MeshLib::Node*> const& nodes,
98 std::vector<MeshLib::Element*>& new_elements)
99{
100 auto addTetrahedron =
101 [&prism, &nodes, &new_elements](std::array<std::size_t, 4> const ids)
102 {
103 new_elements.push_back(
104 createElement<MeshLib::Tet>(prism->nodes(), nodes, ids).release());
105 };
106
107 addTetrahedron({0, 1, 2, 3});
108 addTetrahedron({3, 2, 4, 5});
109 addTetrahedron({2, 1, 3, 4});
110
111 return 3;
112}
113
115unsigned subdivideHex(MeshLib::Element const* const hex,
116 std::vector<MeshLib::Node*> const& nodes,
117 std::vector<MeshLib::Element*>& new_elements)
118{
119 auto prism1 =
120 createElement<MeshLib::Prism>(hex->nodes(), nodes, {0, 2, 1, 4, 6, 5});
121 subdividePrism(prism1.get(), nodes, new_elements);
122
123 auto prism2 =
124 createElement<MeshLib::Prism>(hex->nodes(), nodes, {4, 6, 7, 0, 2, 3});
125 subdividePrism(prism2.get(), nodes, new_elements);
126
127 return 6;
128}
129
131unsigned subdividePyramid(MeshLib::Element const* const pyramid,
132 std::vector<MeshLib::Node*> const& nodes,
133 std::vector<MeshLib::Element*>& new_elements)
134{
135 auto addTetrahedron =
136 [&pyramid, &nodes, &new_elements](std::array<std::size_t, 4> const ids)
137 {
138 new_elements.push_back(
139 createElement<MeshLib::Tet>(pyramid->nodes(), nodes, ids)
140 .release());
141 };
142
143 addTetrahedron({0, 1, 2, 4});
144 addTetrahedron({0, 2, 3, 4});
145
146 return 2;
147}
148
152 const std::vector<MeshLib::Node*>& nodes)
153{
154 std::array<std::size_t, 2> line_node_ids = {0, 0};
155 for (unsigned i = 1; i < element->getNumberOfBaseNodes(); ++i)
156 {
157 if (element->getNode(i)->getID() != element->getNode(0)->getID())
158 {
159 line_node_ids[1] = i;
160 break;
161 }
162 }
163 assert(line_node_ids[1] != 0);
164 return createElement<MeshLib::Line>(element->nodes(), nodes, line_node_ids)
165 .release();
166}
167
171 const std::vector<MeshLib::Node*>& nodes)
172{
173 // TODO?
174 // In theory three unique nodes could also be reduced to two lines e.g. with
175 // a quad where two diametral nodes collapse. This case is currently not
176 // implemented!
177 std::array<MeshLib::Node*, 3> tri_nodes;
178 tri_nodes[0] = nodes[element->getNode(0)->getID()];
179 tri_nodes[2] = nullptr;
180 for (unsigned i = 1; i < element->getNumberOfBaseNodes(); ++i)
181 {
182 if (element->getNode(i)->getID() != tri_nodes[0]->getID())
183 {
184 tri_nodes[1] = nodes[element->getNode(i)->getID()];
185 for (unsigned j = i + 1; j < element->getNumberOfBaseNodes(); ++j)
186 {
187 if (element->getNode(j)->getID() != tri_nodes[1]->getID())
188 {
189 tri_nodes[2] = nodes[element->getNode(j)->getID()];
190 break;
191 }
192 }
193 if (tri_nodes[2])
194 {
195 break;
196 }
197 }
198 }
199 assert(tri_nodes[2] != nullptr);
200 return new MeshLib::Tri(tri_nodes);
201}
202
206 MeshLib::Element const* const element,
207 std::vector<MeshLib::Node*> const& nodes,
208 unsigned const min_elem_dim = 1)
209{
210 std::array<MeshLib::Node*, 4> new_nodes;
211 unsigned count(0);
212 new_nodes[count++] = nodes[element->getNode(0)->getID()];
213 for (unsigned i = 1; i < element->getNumberOfBaseNodes(); ++i)
214 {
215 if (count > 3)
216 {
217 break;
218 }
219 bool unique_node(true);
220 for (unsigned j = 0; j < i; ++j)
221 {
222 if (element->getNode(i)->getID() == element->getNode(j)->getID())
223 {
224 unique_node = false;
225 break;
226 }
227 }
228 if (unique_node)
229 {
230 new_nodes[count++] = nodes[element->getNode(i)->getID()];
231 };
232 }
233
234 // test if quad or tet
235 const bool isQuad(MathLib::isCoplanar(*new_nodes[0], *new_nodes[1],
236 *new_nodes[2], *new_nodes[3]));
237 if (isQuad && min_elem_dim < 3)
238 {
239 MeshLib::Element* elem(new MeshLib::Quad(new_nodes));
240 for (unsigned i = 1; i < 3; ++i)
241 {
242 if (elem->validate().none())
243 {
244 return elem;
245 }
246
247 // change node order if not convex
248 MeshLib::Node* tmp = new_nodes[i + 1];
249 new_nodes[i + 1] = new_nodes[i];
250 new_nodes[i] = tmp;
251 }
252 return elem;
253 }
254 if (!isQuad)
255 {
256 return new MeshLib::Tet(new_nodes);
257 }
258 // is quad but min elem dim == 3
259
260 return nullptr;
261}
262
265void reducePyramid(MeshLib::Element const* const org_elem,
266 unsigned const n_unique_nodes,
267 std::vector<MeshLib::Node*> const& nodes,
268 std::vector<MeshLib::Element*>& new_elements,
269 unsigned const min_elem_dim)
270{
271 if (n_unique_nodes == 4)
272 {
273 MeshLib::Element* elem(
274 constructFourNodeElement(org_elem, nodes, min_elem_dim));
275 if (elem)
276 {
277 new_elements.push_back(elem);
278 }
279 }
280 else if (n_unique_nodes == 3 && min_elem_dim < 3)
281 {
282 new_elements.push_back(constructTri(org_elem, nodes));
283 }
284 else if (n_unique_nodes == 2 && min_elem_dim == 1)
285 {
286 new_elements.push_back(constructLine(org_elem, nodes));
287 }
288}
289
293unsigned reducePrism(MeshLib::Element const* const org_elem,
294 unsigned const n_unique_nodes,
295 std::vector<MeshLib::Node*> const& nodes,
296 std::vector<MeshLib::Element*>& new_elements,
297 unsigned const min_elem_dim)
298{
299 auto addTetrahedron =
300 [&org_elem, &nodes, &new_elements](std::array<std::size_t, 4> const ids)
301 {
302 new_elements.push_back(
303 createElement<MeshLib::Tet>(org_elem->nodes(), nodes, ids)
304 .release());
305 };
306
307 // TODO?
308 // In theory a node from the bottom triangle and a node from the top
309 // triangle that are not connected by an edge could collapse, resulting in a
310 // combination of tri and quad elements. This case is currently not tested.
311
312 // if one of the non-triangle edges collapsed, elem can be reduced to a
313 // pyramid, otherwise it will be two tets
314 if (n_unique_nodes == 5)
315 {
316 for (unsigned i = 0; i < 5; ++i)
317 {
318 for (unsigned j = i + 1; j < 6; ++j)
319 {
320 if (i != j && org_elem->getNode(i)->getID() ==
321 org_elem->getNode(j)->getID())
322 {
323 // non triangle edge collapsed
324 if (i % 3 == j % 3)
325 {
326 addTetrahedron(
327 {(i + 1) % 3, (i + 2) % 3, i, (i + 1) % 3 + 3});
328 addTetrahedron(
329 {(i + 1) % 3 + 3, (i + 2) % 3, i, (i + 2) % 3 + 3});
330 return 2;
331 }
332
333 // triangle edge collapsed
334 const unsigned i_offset = (i > 2) ? i - 3 : i + 3;
335 const unsigned j_offset = (i > 2) ? j - 3 : j + 3;
336 const unsigned k = MeshToolsLib::lutPrismThirdNode(i, j);
337 if (k == std::numeric_limits<unsigned>::max())
338 {
339 ERR("Unexpected error during prism reduction.");
340 return 0;
341 }
342 const unsigned k_offset = (i > 2) ? k - 3 : k + 3;
343
344 addTetrahedron({i_offset, j_offset, k_offset, i});
345
346 const unsigned l =
347 (MathLib::isCoplanar(*org_elem->getNode(i_offset),
348 *org_elem->getNode(k_offset),
349 *org_elem->getNode(i),
350 *org_elem->getNode(k)))
351 ? j
352 : i;
353 const unsigned l_offset = (i > 2) ? l - 3 : l + 3;
354 addTetrahedron({l_offset, k_offset, i, k});
355 return 2;
356 }
357 }
358 }
359 }
360 else if (n_unique_nodes == 4)
361 {
362 MeshLib::Element* const elem(
363 constructFourNodeElement(org_elem, nodes, min_elem_dim));
364 if (elem)
365 {
366 new_elements.push_back(elem);
367 }
368 }
369 else if (n_unique_nodes == 3 && min_elem_dim < 3)
370 {
371 new_elements.push_back(constructTri(org_elem, nodes));
372 }
373 else if (n_unique_nodes == 2 && min_elem_dim == 1)
374 {
375 new_elements.push_back(constructLine(org_elem, nodes));
376 }
377 return 1;
378}
379
382std::array<unsigned, 4> lutHexCuttingQuadNodes(unsigned id1, unsigned id2)
383{
384 if (id1 == 0 && id2 == 1)
385 {
386 return {3, 2, 5, 4};
387 }
388 if (id1 == 1 && id2 == 2)
389 {
390 return {0, 3, 6, 5};
391 }
392 if (id1 == 2 && id2 == 3)
393 {
394 return {1, 0, 7, 6};
395 }
396 if (id1 == 3 && id2 == 0)
397 {
398 return {2, 1, 4, 7};
399 }
400 if (id1 == 4 && id2 == 5)
401 {
402 return {0, 1, 6, 7};
403 }
404 if (id1 == 5 && id2 == 6)
405 {
406 return {1, 2, 7, 4};
407 }
408 if (id1 == 6 && id2 == 7)
409 {
410 return {2, 3, 4, 5};
411 }
412 if (id1 == 7 && id2 == 4)
413 {
414 return {3, 0, 5, 6};
415 }
416 if (id1 == 0 && id2 == 4)
417 {
418 return {3, 7, 5, 1};
419 }
420 if (id1 == 1 && id2 == 5)
421 {
422 return {0, 4, 6, 2};
423 }
424 if (id1 == 2 && id2 == 6)
425 {
426 return {1, 5, 7, 3};
427 }
428 if (id1 == 3 && id2 == 7)
429 {
430 return {2, 6, 4, 0};
431 }
432 if (id1 == 1 && id2 == 0)
433 {
434 return {2, 3, 4, 5};
435 }
436 if (id1 == 2 && id2 == 1)
437 {
438 return {3, 0, 5, 6};
439 }
440 if (id1 == 3 && id2 == 2)
441 {
442 return {0, 1, 6, 7};
443 }
444 if (id1 == 0 && id2 == 3)
445 {
446 return {1, 2, 7, 4};
447 }
448 if (id1 == 5 && id2 == 4)
449 {
450 return {1, 0, 7, 6};
451 }
452 if (id1 == 6 && id2 == 5)
453 {
454 return {2, 1, 4, 7};
455 }
456 if (id1 == 7 && id2 == 6)
457 {
458 return {3, 2, 5, 4};
459 }
460 if (id1 == 4 && id2 == 7)
461 {
462 return {0, 3, 6, 5};
463 }
464 if (id1 == 4 && id2 == 0)
465 {
466 return {7, 3, 1, 5};
467 }
468 if (id1 == 5 && id2 == 1)
469 {
470 return {4, 0, 2, 6};
471 }
472 if (id1 == 6 && id2 == 2)
473 {
474 return {5, 1, 3, 7};
475 }
476 if (id1 == 7 && id2 == 3)
477 {
478 return {6, 2, 0, 4};
479 }
480
481 OGS_FATAL(
482 "lutHexCuttingQuadNodes() for nodes {} and {} does not have a valid "
483 "return value.",
484 id1, id2);
485}
486
489unsigned lutHexDiametralNode(unsigned const id)
490{
491 constexpr std::array<unsigned, 8> hex_diametral_node_ids = {
492 {6, 7, 4, 5, 2, 3, 0, 1}};
493
494 return hex_diametral_node_ids[id];
495}
496
499std::pair<unsigned, unsigned> lutHexBackNodes(unsigned const i,
500 unsigned const j,
501 unsigned const k,
502 unsigned const l)
503{
504 // collapsed edges are *not* connected
505 if (lutHexDiametralNode(i) == k)
506 {
507 return {i, lutHexDiametralNode(l)};
508 }
509 if (lutHexDiametralNode(i) == l)
510 {
511 return {i, lutHexDiametralNode(k)};
512 }
513 if (lutHexDiametralNode(j) == k)
514 {
515 return {j, lutHexDiametralNode(l)};
516 }
517 if (lutHexDiametralNode(j) == l)
518 {
519 return {j, lutHexDiametralNode(k)};
520 }
521
522 // collapsed edges *are* connected
523 if (i == k)
524 {
525 return {lutHexDiametralNode(l), j};
526 }
527 if (i == l)
528 {
529 return {lutHexDiametralNode(k), j};
530 }
531 if (j == k)
532 {
533 return {lutHexDiametralNode(l), i};
534 }
535 if (j == l)
536 {
537 return {lutHexDiametralNode(k), i};
538 }
539
540 return {std::numeric_limits<unsigned>::max(),
541 std::numeric_limits<unsigned>::max()};
542}
543
544// In an element with 5 unique nodes, return the node that will be the top of
545// the resulting pyramid.
546unsigned findPyramidTopNode(MeshLib::Element const& element,
547 std::array<std::size_t, 4> const& base_node_ids)
548{
549 const std::size_t nNodes(element.getNumberOfBaseNodes());
550 for (std::size_t i = 0; i < nNodes; ++i)
551 {
552 bool top_node = true;
553 for (unsigned j = 0; j < 4; ++j)
554 {
555 if (element.getNode(i)->getID() == base_node_ids[j])
556 {
557 top_node = false;
558 }
559 }
560 if (top_node)
561 {
562 return i;
563 }
564 }
565 return std::numeric_limits<unsigned>::max(); // should never be reached if
566 // called correctly
567}
568
572unsigned reduceHex(MeshLib::Element const* const org_elem,
573 unsigned const n_unique_nodes,
574 std::vector<MeshLib::Node*> const& nodes,
575 std::vector<MeshLib::Element*>& new_elements,
576 unsigned const min_elem_dim)
577{
578 // TODO?
579 // if two diametral nodes collapse, all kinds of bizarre (2D-)element
580 // combinations could be the result. this case is currently not implemented!
581
582 if (n_unique_nodes == 7)
583 {
584 // reduce to prism + pyramid
585 for (unsigned i = 0; i < 7; ++i)
586 {
587 for (unsigned j = i + 1; j < 8; ++j)
588 {
589 if (org_elem->getNode(i)->getID() ==
590 org_elem->getNode(j)->getID())
591 {
592 const std::array<unsigned, 4> base_node_ids(
594 std::array<std::size_t, 5> const pyr_node_ids = {
595 base_node_ids[0], base_node_ids[1], base_node_ids[2],
596 base_node_ids[3], i};
597 new_elements.push_back(
599 nodes, pyr_node_ids)
600 .release());
601
602 if (i < 4 && j >= 4)
603 {
604 std::swap(i, j);
605 }
606 std::array<std::size_t, 6> const prism_node_ids{
607 base_node_ids[0], base_node_ids[3],
608 lutHexDiametralNode(j), base_node_ids[1],
609 base_node_ids[2], lutHexDiametralNode(i)};
610 new_elements.push_back(
611 createElement<MeshLib::Prism>(org_elem->nodes(), nodes,
612 prism_node_ids)
613 .release());
614 return 2;
615 }
616 }
617 }
618 }
619 else if (n_unique_nodes == 6)
620 {
621 // reduce to prism
622 for (unsigned i = 0; i < 6; ++i)
623 {
624 const MeshLib::Element* face(org_elem->getFace(i));
625 if (face->getNode(0)->getID() == face->getNode(1)->getID() &&
626 face->getNode(2)->getID() == face->getNode(3)->getID())
627 {
628 std::array<std::size_t, 6> const prism_node_ids{
630 getNodeIDinElement(*org_elem, face->getNode(0))),
632 getNodeIDinElement(*org_elem, face->getNode(1))),
633 getNodeIDinElement(*org_elem, face->getNode(2)),
635 getNodeIDinElement(*org_elem, face->getNode(2))),
637 getNodeIDinElement(*org_elem, face->getNode(3))),
638 getNodeIDinElement(*org_elem, face->getNode(0))};
639
640 new_elements.push_back(
641 createElement<MeshLib::Prism>(org_elem->nodes(), nodes,
642 prism_node_ids)
643 .release());
644 delete face;
645 return 1;
646 }
647 if (face->getNode(0)->getID() == face->getNode(3)->getID() &&
648 face->getNode(1)->getID() == face->getNode(2)->getID())
649 {
650 std::array<std::size_t, 6> const prism_node_ids{
652 getNodeIDinElement(*org_elem, face->getNode(0))),
654 getNodeIDinElement(*org_elem, face->getNode(3))),
655 getNodeIDinElement(*org_elem, face->getNode(2)),
657 getNodeIDinElement(*org_elem, face->getNode(1))),
659 getNodeIDinElement(*org_elem, face->getNode(2))),
660 getNodeIDinElement(*org_elem, face->getNode(0))};
661 new_elements.push_back(
662 createElement<MeshLib::Prism>(org_elem->nodes(), nodes,
663 prism_node_ids)
664 .release());
665 delete face;
666 return 1;
667 }
668 delete face;
669 }
670 // reduce to four tets -> divide into 2 prisms such that each has one
671 // collapsed node
672 for (unsigned i = 0; i < 7; ++i)
673 {
674 for (unsigned j = i + 1; j < 8; ++j)
675 {
676 if (org_elem->getNode(i)->getID() ==
677 org_elem->getNode(j)->getID())
678 {
679 for (unsigned k = i; k < 7; ++k)
680 {
681 for (unsigned l = k + 1; l < 8; ++l)
682 {
683 if (!(i == k && j == l) && org_elem->isEdge(i, j) &&
684 org_elem->isEdge(k, l) &&
685 org_elem->getNode(k)->getID() ==
686 org_elem->getNode(l)->getID())
687 {
688 const std::pair<unsigned, unsigned> back(
689 lutHexBackNodes(i, j, k, l));
690 if (back.first ==
691 std::numeric_limits<unsigned>::max() ||
692 back.second ==
693 std::numeric_limits<unsigned>::max())
694 {
695 ERR("Unexpected error during Hex "
696 "reduction");
697 return 0;
698 }
699
700 std::array<unsigned, 4> const cutting_plane(
701 lutHexCuttingQuadNodes(back.first,
702 back.second));
703 std::array<std::size_t, 6> const pris1_node_ids{
704 back.first, cutting_plane[0],
705 cutting_plane[3], back.second,
706 cutting_plane[1], cutting_plane[2]};
707 auto prism1 = createElement<MeshLib::Prism>(
708 org_elem->nodes(), nodes, pris1_node_ids);
709 unsigned nNewElements =
710 reducePrism(prism1.get(), 5, nodes,
711 new_elements, min_elem_dim);
712
713 std::array<std::size_t, 6> const pris2_node_ids{
714 lutHexDiametralNode(back.first),
715 cutting_plane[0],
716 cutting_plane[3],
717 lutHexDiametralNode(back.second),
718 cutting_plane[1],
719 cutting_plane[2]};
720 auto prism2 = createElement<MeshLib::Prism>(
721 org_elem->nodes(), nodes, pris2_node_ids);
722 nNewElements +=
723 reducePrism(prism2.get(), 5, nodes,
724 new_elements, min_elem_dim);
725 return nNewElements;
726 }
727 }
728 }
729 }
730 }
731 }
732 }
733 else if (n_unique_nodes == 5)
734 {
735 MeshLib::Element* tet1(constructFourNodeElement(org_elem, nodes));
736 std::array<std::size_t, 4> const first_four_node_ids = {
737 {tet1->getNode(0)->getID(), tet1->getNode(1)->getID(),
738 tet1->getNode(2)->getID(), tet1->getNode(3)->getID()}};
739 unsigned const fifth_node =
740 findPyramidTopNode(*org_elem, first_four_node_ids);
741
742 bool tet_changed(false);
744 {
745 delete tet1;
746 tet_changed = true;
747 std::array const tet1_nodes = {
748 nodes[first_four_node_ids[0]], nodes[first_four_node_ids[1]],
749 nodes[first_four_node_ids[2]],
750 nodes[org_elem->getNode(fifth_node)->getID()]};
751 new_elements.push_back(new MeshLib::Tet(tet1_nodes));
752 }
753 else
754 {
755 new_elements.push_back(tet1);
756 }
757
758 std::array const tet2_nodes = {
759 (tet_changed) ? nodes[first_four_node_ids[0]]
760 : nodes[first_four_node_ids[1]],
761 nodes[first_four_node_ids[2]], nodes[first_four_node_ids[3]],
762 nodes[org_elem->getNode(fifth_node)->getID()]};
763 new_elements.push_back(new MeshLib::Tet(tet2_nodes));
764 return 2;
765 }
766 else if (n_unique_nodes == 4)
767 {
768 MeshLib::Element* elem(
769 constructFourNodeElement(org_elem, nodes, min_elem_dim));
770 if (elem)
771 {
772 new_elements.push_back(elem);
773 return 1;
774 }
775 }
776 else if (n_unique_nodes == 3 && min_elem_dim < 3)
777 {
778 new_elements.push_back(constructTri(org_elem, nodes));
779 return 1;
780 }
781 else if (min_elem_dim == 1)
782 {
783 new_elements.push_back(constructLine(org_elem, nodes));
784 return 1;
785 }
786 return 0;
787}
788
796std::size_t subdivideElement(MeshLib::Element const* const element,
797 std::vector<MeshLib::Node*> const& nodes,
798 std::vector<MeshLib::Element*>& elements)
799{
801 {
802 return subdivideQuad(element, nodes, elements);
803 }
805 {
806 return subdivideHex(element, nodes, elements);
807 }
809 {
810 return subdividePyramid(element, nodes, elements);
811 }
813 {
814 return subdividePrism(element, nodes, elements);
815 }
816 return 0;
817}
818
819// Revises an element by removing collapsed nodes, using the nodes vector from
820// the result mesh.
821std::size_t reduceElement(MeshLib::Element const* const element,
822 unsigned const n_unique_nodes,
823 std::vector<MeshLib::Node*> const& nodes,
824 std::vector<MeshLib::Element*>& elements,
825 unsigned const min_elem_dim)
826{
827 /***************
828 * TODO: modify neighbouring elements if one elements has been subdivided
829 ***************/
831 min_elem_dim == 1)
832 {
833 elements.push_back(constructLine(element, nodes));
834 return 1;
835 }
836 if ((element->getGeomType() == MeshLib::MeshElemType::QUAD) ||
838 {
839 if (n_unique_nodes == 3 && min_elem_dim < 3)
840 {
841 elements.push_back(constructTri(element, nodes));
842 }
843 else if (min_elem_dim == 1)
844 {
845 elements.push_back(constructLine(element, nodes));
846 }
847 return 1;
848 }
850 {
851 return reduceHex(element, n_unique_nodes, nodes, elements,
852 min_elem_dim);
853 }
855 {
856 reducePyramid(element, n_unique_nodes, nodes, elements, min_elem_dim);
857 return 1;
858 }
860 {
861 return reducePrism(element, n_unique_nodes, nodes, elements,
862 min_elem_dim);
863 }
864
865 ERR("Unknown element type.");
866 return 0;
867}
868
870unsigned getNumberOfUniqueNodes(MeshLib::Element const* const element)
871{
872 unsigned const nNodes(element->getNumberOfBaseNodes());
873 unsigned count(nNodes);
874
875 for (unsigned i = 0; i < nNodes - 1; ++i)
876 {
877 for (unsigned j = i + 1; j < nNodes; ++j)
878 {
879 if (element->getNode(i)->getID() == element->getNode(j)->getID())
880 {
881 count--;
882 break;
883 }
884 }
885 }
886 return count;
887}
888
889template <typename T>
891 MeshLib::PropertyVector<T> const& old_prop,
892 std::vector<size_t> const& node_ids)
893{
894 std::size_t const n_nodes = node_ids.size();
895 for (std::size_t i = 0; i < n_nodes; ++i)
896 {
897 if (node_ids[i] != i)
898 {
899 continue;
900 }
901 new_prop.push_back(old_prop[i]);
902 }
903}
904
905template <typename T>
907 MeshLib::PropertyVector<T> const& old_prop,
908 std::vector<size_t> const& elem_ids)
909{
910 assert(new_prop.size() == old_prop.size());
911 assert(new_prop.size() == elem_ids.size());
912 ranges::transform(elem_ids, new_prop.begin(),
913 [&](std::size_t const i) { return old_prop[i]; });
914}
915
920 std::vector<std::size_t> const& node_ids,
921 std::vector<std::size_t> const& elem_ids)
922{
923 auto const prop_names = props.getPropertyVectorNames();
924 MeshLib::Properties new_properties;
925
926 for (auto name : prop_names)
927 {
929 1))
930 {
931 auto const* p = props.getPropertyVector<int>(
933 auto new_node_vec = new_properties.createNewPropertyVector<int>(
935 fillNodeProperty(*new_node_vec, *p, node_ids);
936 continue;
937 }
939 1))
940 {
941 auto const* p = props.getPropertyVector<float>(
943 auto new_node_vec = new_properties.createNewPropertyVector<float>(
945 fillNodeProperty(*new_node_vec, *p, node_ids);
946 continue;
947 }
948 if (props.existsPropertyVector<double>(name,
950 {
951 auto const* p = props.getPropertyVector<double>(
953 auto new_node_vec = new_properties.createNewPropertyVector<double>(
955 fillNodeProperty(*new_node_vec, *p, node_ids);
956 continue;
957 }
959 1))
960 {
961 auto const* p = props.getPropertyVector<int>(
963 auto new_cell_vec = new_properties.createNewPropertyVector<int>(
964 name, MeshLib::MeshItemType::Cell, p->size(), 1);
965 fillElemProperty(*new_cell_vec, *p, elem_ids);
966 continue;
967 }
969 1))
970 {
971 auto const* p = props.getPropertyVector<float>(
973 auto new_cell_vec = new_properties.createNewPropertyVector<float>(
974 name, MeshLib::MeshItemType::Cell, p->size(), 1);
975 fillElemProperty(*new_cell_vec, *p, elem_ids);
976 continue;
977 }
978 if (props.existsPropertyVector<double>(name,
980 {
981 auto const* p = props.getPropertyVector<double>(
983 auto new_cell_vec = new_properties.createNewPropertyVector<double>(
984 name, MeshLib::MeshItemType::Cell, p->size(), 1);
985 fillElemProperty(*new_cell_vec, *p, elem_ids);
986 continue;
987 }
988 WARN("PropertyVector {:s} not being converted.", name);
989 }
990 return new_properties;
991}
992} // namespace
993
994namespace MeshToolsLib
995{
997
998unsigned MeshRevision::getNumberOfCollapsibleNodes(double const eps) const
999{
1000 std::vector<std::size_t> const id_map = collapseNodeIndices(eps);
1001 std::size_t const nNodes = id_map.size();
1002 unsigned count(0);
1003 for (std::size_t i = 0; i < nNodes; ++i)
1004 {
1005 if (i != id_map[i])
1006 {
1007 count++;
1008 }
1009 }
1010 return count;
1011}
1012
1013MeshLib::Mesh* MeshRevision::simplifyMesh(const std::string& new_mesh_name,
1014 double const eps,
1015 unsigned const min_elem_dim) const
1016{
1017 if (this->_mesh.getNumberOfElements() == 0)
1018 {
1019 return nullptr;
1020 }
1021
1022 std::vector<MeshLib::Element*> const& elements(this->_mesh.getElements());
1023 auto const node_ids = collapseNodeIndices(eps);
1024 std::vector<MeshLib::Node*> new_nodes =
1025 this->constructNewNodesArray(node_ids);
1026 std::vector<MeshLib::Element*> new_elements;
1027 std::vector<std::size_t> element_ids;
1028
1029 for (std::size_t k(0); k < elements.size(); ++k)
1030 {
1031 MeshLib::Element const* const elem(elements[k]);
1032 unsigned const n_unique_nodes(getNumberOfUniqueNodes(elem));
1033 if (n_unique_nodes == elem->getNumberOfBaseNodes() &&
1034 elem->getDimension() >= min_elem_dim)
1035 {
1036 ElementErrorCode const e = elem->validate();
1038 {
1039 std::size_t const n_new_elements(
1040 subdivideElement(elem, new_nodes, new_elements));
1041 if (n_new_elements == 0)
1042 {
1043 ERR("Element {:d} has unknown element type.", k);
1044 _mesh.resetNodeIDs();
1045 BaseLib::cleanupVectorElements(new_nodes, new_elements);
1046 return nullptr;
1047 }
1048 element_ids.insert(element_ids.end(), n_new_elements, k);
1049 }
1050 else
1051 {
1052 new_elements.push_back(MeshLib::copyElement(elem, new_nodes));
1053 element_ids.push_back(k);
1054 }
1055 }
1056 else if (n_unique_nodes < elem->getNumberOfBaseNodes() &&
1057 n_unique_nodes > 1)
1058 {
1059 std::size_t const n_new_elements(reduceElement(
1060 elem, n_unique_nodes, new_nodes, new_elements, min_elem_dim));
1061 element_ids.insert(element_ids.end(), n_new_elements, k);
1062 }
1063 else
1064 {
1065 ERR("Something is wrong, more unique nodes than actual nodes");
1066 }
1067 }
1068
1069 auto const& props = _mesh.getProperties();
1070 MeshLib::Properties const new_properties =
1071 copyProperties(props, node_ids, element_ids);
1072
1073 _mesh.resetNodeIDs();
1074 if (!new_elements.empty())
1075 {
1076 return new MeshLib::Mesh(new_mesh_name, new_nodes, new_elements,
1077 true /* compute_element_neighbors */,
1078 new_properties);
1079 }
1080
1081 BaseLib::cleanupVectorElements(new_nodes, new_elements);
1082 return nullptr;
1083}
1084
1085std::vector<std::size_t> MeshRevision::collapseNodeIndices(
1086 double const eps) const
1087{
1088 const std::vector<MeshLib::Node*>& nodes(_mesh.getNodes());
1089 const std::size_t nNodes(_mesh.getNumberOfNodes());
1090 std::vector<std::size_t> id_map(nNodes);
1091 const double half_eps(eps / 2.0);
1092 const double sqr_eps(eps * eps);
1093 std::iota(id_map.begin(), id_map.end(), 0);
1094
1095 GeoLib::Grid<MeshLib::Node> const grid(nodes.begin(), nodes.end(), 64);
1096
1097 for (std::size_t k = 0; k < nNodes; ++k)
1098 {
1099 MeshLib::Node const* const node(nodes[k]);
1100 if (node->getID() != k)
1101 {
1102 continue;
1103 }
1104 std::vector<std::vector<MeshLib::Node*> const*> const node_vectors(
1105 grid.getPntVecsOfGridCellsIntersectingCube(*node, half_eps));
1106
1107 const std::size_t nVectors(node_vectors.size());
1108 for (std::size_t i = 0; i < nVectors; ++i)
1109 {
1110 const std::vector<MeshLib::Node*>& cell_vector(*node_vectors[i]);
1111 const std::size_t nGridCellNodes(cell_vector.size());
1112 for (std::size_t j = 0; j < nGridCellNodes; ++j)
1113 {
1114 MeshLib::Node const* const test_node(cell_vector[j]);
1115 // are node indices already identical (i.e. nodes will be
1116 // collapsed)
1117 if (id_map[node->getID()] == id_map[test_node->getID()])
1118 {
1119 continue;
1120 }
1121
1122 // if test_node has already been collapsed to another node x,
1123 // ignore it (if the current node would need to be collapsed
1124 // with x it would already have happened when x was tested)
1125 if (test_node->getID() != id_map[test_node->getID()])
1126 {
1127 continue;
1128 }
1129
1130 // calc distance
1131 if (MathLib::sqrDist(*node, *test_node) < sqr_eps)
1132 {
1133 id_map[test_node->getID()] = node->getID();
1134 }
1135 }
1136 }
1137 }
1138 return id_map;
1139}
1140
1141std::vector<MeshLib::Node*> MeshRevision::constructNewNodesArray(
1142 const std::vector<std::size_t>& id_map) const
1143{
1144 const std::vector<MeshLib::Node*>& nodes(_mesh.getNodes());
1145 const std::size_t nNodes(nodes.size());
1146 std::vector<MeshLib::Node*> new_nodes;
1147 new_nodes.reserve(nNodes);
1148 for (std::size_t k = 0; k < nNodes; ++k)
1149 {
1150 // all nodes that have not been collapsed with other nodes are copied
1151 // into new array
1152 if (nodes[k]->getID() == id_map[k])
1153 {
1154 std::size_t const id(new_nodes.size());
1155 new_nodes.push_back(new MeshLib::Node(
1156 (*nodes[k])[0], (*nodes[k])[1], (*nodes[k])[2], id));
1157 nodes[k]->setID(id); // the node in the old array gets the index of
1158 // the same node in the new array
1159 }
1160 // the other nodes are not copied and get the index of the nodes they
1161 // will have been collapsed with
1162 else
1163 {
1164 nodes[k]->setID(nodes[id_map[k]]->getID());
1165 }
1166 }
1167 return new_nodes;
1168}
1169
1170} // namespace MeshToolsLib
#define OGS_FATAL(...)
Definition Error.h:19
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
Collects error flags for mesh elements.
std::vector< std::vector< POINT * > const * > getPntVecsOfGridCellsIntersectingCube(P const &center, double half_len) const
Definition Grid.h:243
std::size_t getID() const
virtual MeshElemType getGeomType() const =0
virtual ElementErrorCode validate() const =0
virtual const Element * getFace(unsigned i) const =0
Returns the i-th face of the element.
virtual unsigned getNumberOfBaseNodes() const =0
virtual const Node * getNode(unsigned idx) const =0
virtual bool isEdge(unsigned i, unsigned j) const =0
Returns true if these two indices form an edge and false otherwise.
virtual constexpr unsigned getDimension() const =0
Get dimension of the mesh element.
constexpr std::span< Node *const > nodes() const
Span of element's nodes, their pointers actually.
Definition Element.h:63
Property manager on mesh items. Class Properties manages scalar, vector or matrix properties....
std::vector< std::string > getPropertyVectorNames() const
bool existsPropertyVector(std::string_view name) const
PropertyVector< T > * createNewPropertyVector(std::string_view name, MeshItemType mesh_item_type, std::size_t n_components=1)
PropertyVector< T > const * getPropertyVector(std::string_view name) const
constexpr PROP_VAL_TYPE * begin()
constexpr std::size_t size() const
constexpr void push_back(const PROP_VAL_TYPE &value)
MeshRevision(MeshLib::Mesh &mesh)
unsigned getNumberOfCollapsibleNodes(double eps=std::numeric_limits< double >::epsilon()) const
Returns the number of potentially collapsible nodes.
MeshLib::Mesh & _mesh
The original mesh used for constructing the class.
MeshLib::Mesh * simplifyMesh(const std::string &new_mesh_name, double eps, unsigned min_elem_dim=1) const
std::vector< MeshLib::Node * > constructNewNodesArray(const std::vector< std::size_t > &id_map) const
std::vector< std::size_t > collapseNodeIndices(double eps) const
void cleanupVectorElements(std::vector< T * > &items)
Definition Algorithm.h:249
bool isCoplanar(const MathLib::Point3d &a, const MathLib::Point3d &b, const MathLib::Point3d &c, const MathLib::Point3d &d)
Checks if the four given points are located on a plane.
double sqrDist(MathLib::Point3d const &p0, MathLib::Point3d const &p1)
Definition Point3d.cpp:19
MeshLib specific, lazy, non-owning, non-mutating, composable range views.
Definition Mesh.h:214
constexpr ranges::views::view_closure ids
For an element of a range view return its id.
Definition Mesh.h:216
TemplateElement< MeshLib::TetRule4 > Tet
Definition Tet.h:14
TemplateElement< MeshLib::QuadRule4 > Quad
Definition Quad.h:17
TemplateElement< MeshLib::TriRule3 > Tri
Definition Tri.h:15
unsigned getNodeIDinElement(Element const &element, const MeshLib::Node *node)
Returns the position of the given node in the node array of this element.
Definition Element.cpp:213
Element * copyElement(Element const *const element, const std::vector< Node * > &nodes, std::vector< std::size_t > const *const id_map)
unsigned lutPrismThirdNode(unsigned const id1, unsigned const id2)
std::size_t reduceElement(MeshLib::Element const *const element, unsigned const n_unique_nodes, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &elements, unsigned const min_elem_dim)
unsigned lutHexDiametralNode(unsigned const id)
MeshLib::Element * constructTri(MeshLib::Element const *const element, const std::vector< MeshLib::Node * > &nodes)
MeshLib::Element * constructLine(MeshLib::Element const *const element, const std::vector< MeshLib::Node * > &nodes)
unsigned reduceHex(MeshLib::Element const *const org_elem, unsigned const n_unique_nodes, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements, unsigned const min_elem_dim)
unsigned reducePrism(MeshLib::Element const *const org_elem, unsigned const n_unique_nodes, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements, unsigned const min_elem_dim)
void fillNodeProperty(MeshLib::PropertyVector< T > &new_prop, MeshLib::PropertyVector< T > const &old_prop, std::vector< size_t > const &node_ids)
unsigned getNumberOfUniqueNodes(MeshLib::Element const *const element)
unsigned subdivideHex(MeshLib::Element const *const hex, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements)
Subdivides a Hex with nonplanar faces into tets.
std::unique_ptr< MeshLib::Element > createElement(std::span< MeshLib::Node *const > const element_nodes, std::vector< MeshLib::Node * > const &nodes, std::array< std::size_t, ElementType::n_all_nodes > const local_ids)
std::size_t subdivideElement(MeshLib::Element const *const element, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &elements)
void reducePyramid(MeshLib::Element const *const org_elem, unsigned const n_unique_nodes, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements, unsigned const min_elem_dim)
MeshLib::Properties copyProperties(MeshLib::Properties const &props, std::vector< std::size_t > const &node_ids, std::vector< std::size_t > const &elem_ids)
unsigned subdividePrism(MeshLib::Element const *const prism, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements)
Subdivides a prism with nonplanar quad faces into two tets.
std::array< unsigned, 4 > lutHexCuttingQuadNodes(unsigned id1, unsigned id2)
std::pair< unsigned, unsigned > lutHexBackNodes(unsigned const i, unsigned const j, unsigned const k, unsigned const l)
unsigned subdividePyramid(MeshLib::Element const *const pyramid, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements)
Subdivides a pyramid with a nonplanar base into two tets.
void fillElemProperty(MeshLib::PropertyVector< T > &new_prop, MeshLib::PropertyVector< T > const &old_prop, std::vector< size_t > const &elem_ids)
unsigned findPyramidTopNode(MeshLib::Element const &element, std::array< std::size_t, 4 > const &base_node_ids)
MeshLib::Element * constructFourNodeElement(MeshLib::Element const *const element, std::vector< MeshLib::Node * > const &nodes, unsigned const min_elem_dim=1)
unsigned subdivideQuad(MeshLib::Element const *const quad, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &new_elements)
Subdivides a nonplanar quad into two triangles.