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