https://mooseframework.inl.gov
Loading...
Searching...
No Matches
XYIncrementalDelaunay.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
11
12#include "MooseError.h"
13#include "predicates.h"
14
15#include "libmesh/int_range.h"
16
17#include <algorithm>
18#include <map>
19
20namespace
21{
22static_assert(sizeof(XYIncrementalDelaunay::Point2D) == 2 * sizeof(double),
23 "The exact predicates read a point as two adjacent doubles.");
24
26const double *
27coords(const XYIncrementalDelaunay::Point2D & p)
28{
29 return &p.x;
30}
31}
32
33std::size_t
35{
36 return _vertices.empty() ? 0 : _vertices.size() - _num_bounding;
37}
38
39std::size_t
40XYIncrementalDelaunay::toInternal(const std::size_t id) const
41{
42 if (id >= numPoints())
43 mooseError("XYIncrementalDelaunay: vertex id ",
44 id,
45 " does not exist; the triangulation has ",
46 numPoints(),
47 " points.");
48 return id + _num_bounding;
49}
50
51std::size_t
52XYIncrementalDelaunay::toCaller(const std::size_t v) const
53{
54 mooseAssert(!isBounding(v), "A bounding triangle vertex has no caller vertex id");
55 return v - _num_bounding;
56}
57
59XYIncrementalDelaunay::point(const std::size_t id) const
60{
61 return _vertices[toInternal(id)];
62}
63
65XYIncrementalDelaunay::makeSegment(const std::size_t v0, const std::size_t v1)
66{
67 return {std::min(v0, v1), std::max(v0, v1)};
68}
69
70bool
71XYIncrementalDelaunay::isConstrainedSegment(const std::size_t v0, const std::size_t v1) const
72{
73 return _constraints.count(makeSegment(v0, v1)) > 0;
74}
75
76bool
77XYIncrementalDelaunay::isConstrainedEdge(const std::size_t v0, const std::size_t v1) const
78{
79 if (isBounding(v0) || isBounding(v1))
80 return false;
81 return _constraints.count(makeSegment(toCaller(v0), toCaller(v1))) > 0;
82}
83
84unsigned int
85XYIncrementalDelaunay::localVertexIndex(const std::size_t t, const std::size_t v) const
86{
87 for (const auto i : make_range(3u))
88 if (_triangles[t].vertices[i] == v)
89 return i;
90 mooseError("XYIncrementalDelaunay: triangle ", t, " does not have vertex ", v, ".");
91}
92
93unsigned int
94XYIncrementalDelaunay::localEdgeIndex(const std::size_t t, const Segment & edge) const
95{
96 for (const auto i : make_range(3u))
97 if (makeSegment(_triangles[t].vertices[(i + 1) % 3], _triangles[t].vertices[(i + 2) % 3]) ==
98 edge)
99 return i;
100 mooseError("XYIncrementalDelaunay: triangle ",
101 t,
102 " does not have an edge between vertices ",
103 edge.first,
104 " and ",
105 edge.second,
106 ".");
107}
108
109bool
110XYIncrementalDelaunay::containsPoint(const std::size_t t, const Point2D & p) const
111{
112 for (const auto i : make_range(3u))
113 if (moose_orient2d(xy(_triangles[t].vertices[(i + 1) % 3]),
114 xy(_triangles[t].vertices[(i + 2) % 3]),
115 coords(p)) < 0.0)
116 return false;
117 return true;
118}
119
120bool
122 const std::size_t v_mid,
123 const std::size_t v_last) const
124{
125 mooseAssert(moose_orient2d(xy(v_first), xy(v_mid), xy(v_last)) == 0.0,
126 "The three vertices have to be collinear for a coordinate comparison to order them");
127
128 // The three are collinear, so whichever coordinate separates the two ends orders all three.
129 const auto & first = _vertices[v_first];
130 const auto & mid = _vertices[v_mid];
131 const auto & last = _vertices[v_last];
132 if (first.x != last.x)
133 return (first.x < mid.x && mid.x < last.x) || (last.x < mid.x && mid.x < first.x);
134 return (first.y < mid.y && mid.y < last.y) || (last.y < mid.y && mid.y < first.y);
135}
136
137std::string
138XYIncrementalDelaunay::vertexName(const std::size_t v) const
139{
140 if (isBounding(v))
141 return "bounding vertex " + std::to_string(v);
142 return "point " + std::to_string(toCaller(v));
143}
144
145std::size_t
147{
148 // start from the last insertion if existing
149 auto current = _last_triangle < _triangles.size() ? _last_triangle : std::size_t(0);
150
151 // The walk always arrives in a Delaunay triangulation, but a constrained one can in principle
152 // send it round in a circle, so it gives up and scans rather than spin.
153 auto steps_left = 2 * _triangles.size() + 8;
154 while (steps_left > 0)
155 {
156 --steps_left;
157
158 auto next = invalid_index;
159 bool inside = true;
160 for (const auto i : make_range(3u))
161 if (moose_orient2d(xy(_triangles[current].vertices[(i + 1) % 3]),
162 xy(_triangles[current].vertices[(i + 2) % 3]),
163 coords(p)) < 0.0)
164 {
165 inside = false;
166 next = _triangles[current].neighbors[i];
167 break;
168 }
169
170 if (inside)
171 return current;
172 if (next == invalid_index)
173 break;
174 current = next;
175 }
176
177 // search all triangles if starting from the last inserted one did not succeed
178 for (const auto t : index_range(_triangles))
179 if (containsPoint(t, p))
180 return t;
181 return invalid_index;
182}
183
184void
186 const std::size_t v_new,
187 std::set<std::size_t> & cavity) const
188{
189 cavity.insert(seed);
190 std::vector<std::size_t> pending{seed};
191
192 while (!pending.empty())
193 {
194 const auto t = pending.back();
195 pending.pop_back();
196
197 for (const auto i : make_range(3u))
198 {
199 const auto v0 = _triangles[t].vertices[(i + 1) % 3];
200 const auto v1 = _triangles[t].vertices[(i + 2) % 3];
201 const auto side = moose_orient2d(xy(v0), xy(v1), xy(v_new));
202
203 if (isConstrainedEdge(v0, v1))
204 {
205 if (side <= 0.0)
206 mooseError("XYIncrementalDelaunay: the point (",
207 _vertices[v_new].x,
208 ", ",
209 _vertices[v_new].y,
210 ") is on or beyond constrained segment (",
211 toCaller(v0),
212 ", ",
213 toCaller(v1),
214 "), so it lies outside the region that segment bounds.");
215 continue;
216 }
217
218 const auto n = _triangles[t].neighbors[i];
219 if (n == invalid_index || cavity.count(n) > 0)
220 continue;
221
222 // Taking the neighbor in when the shared edge would give a triangle of zero or negative area
223 // is what keeps the cavity star shaped about the new vertex; with exact predicates that only
224 // happens when the new vertex falls exactly on the edge.
225 if (side <= 0.0 || moose_incircle(xy(_triangles[n].vertices[0]),
226 xy(_triangles[n].vertices[1]),
227 xy(_triangles[n].vertices[2]),
228 xy(v_new)) > 0.0)
229 {
230 cavity.insert(n);
231 pending.push_back(n);
232 }
233 }
234 }
235}
236
237std::vector<std::size_t>
238XYIncrementalDelaunay::retriangulate(const std::vector<std::size_t> & removed,
239 const std::vector<std::array<std::size_t, 3>> & added)
240{
241 mooseAssert(added.size() >= removed.size(),
242 "A region takes at least as many triangles to cover as it is emptied of");
243
244 const std::set<std::size_t> emptied(removed.begin(), removed.end());
245
246 // What lies outside the region being replaced, keyed on the edge it is joined along.
247 std::map<Segment, std::size_t> outside;
248 for (const auto t : emptied)
249 for (const auto i : make_range(3u))
250 {
251 const auto n = _triangles[t].neighbors[i];
252 if (n != invalid_index && emptied.count(n) == 0)
253 outside.emplace(
254 makeSegment(_triangles[t].vertices[(i + 1) % 3], _triangles[t].vertices[(i + 2) % 3]),
255 n);
256 }
257
258 // Filling the emptied slots in id order, rather than in the order the region happened to be
259 // walked, is what makes the triangle numbering repeatable from one run to the next.
260 std::vector<std::size_t> slots(emptied.begin(), emptied.end());
261 while (slots.size() < added.size())
262 {
263 slots.push_back(_triangles.size());
264 _triangles.emplace_back();
265 }
266
267 for (const auto i : index_range(added))
268 {
269 auto & triangle = _triangles[slots[i]];
270 triangle.vertices = added[i];
271 triangle.neighbors = {invalid_index, invalid_index, invalid_index};
272 for (const auto v : triangle.vertices)
273 _vertex_triangle[v] = slots[i];
274 }
275
276 // An edge two new triangles share joins them to each other; one only a single new triangle has is
277 // on the boundary of the region and joins it to what was already outside.
278 std::map<Segment, std::pair<std::size_t, unsigned int>> open_edges;
279 for (const auto i : index_range(added))
280 {
281 const auto t = slots[i];
282 for (const auto e : make_range(3u))
283 {
284 const auto edge =
285 makeSegment(_triangles[t].vertices[(e + 1) % 3], _triangles[t].vertices[(e + 2) % 3]);
286 const auto it = open_edges.find(edge);
287 if (it == open_edges.end())
288 open_edges.emplace(edge, std::make_pair(t, e));
289 else
290 {
291 const auto [other_t, other_e] = it->second;
292 _triangles[t].neighbors[e] = other_t;
293 _triangles[other_t].neighbors[other_e] = t;
294 open_edges.erase(it);
295 }
296 }
297 }
298
299 for (const auto & [edge, entry] : open_edges)
300 {
301 const auto [t, e] = entry;
302 const auto it = outside.find(edge);
303 // true outer boundary (for now)
304 if (it == outside.end())
305 continue;
306 // boundary with 'outside' the retriangulated area
307 _triangles[t].neighbors[e] = it->second;
308 _triangles[it->second].neighbors[localEdgeIndex(it->second, edge)] = t;
309 }
310
311 return slots;
312}
313
314void
316 const std::size_t v_start,
317 const std::size_t v_end,
318 const std::vector<std::size_t> & chain,
319 const std::size_t first,
320 const std::size_t last,
321 std::vector<std::array<std::size_t, 3>> & triangles) const
322{
323 // An empty chain leaves nothing but the edge from v_start to v_end itself, so there is no
324 // polygon to cover
325 if (first == last)
326 return;
327
328 // The triangle the edge (v_start, v_end) belongs to in a Delaunay triangulation of the polygon
329 // is the one whose apex leaves no other chain vertex inside its circumcircle. Every chain vertex
330 // sees the edge, so whenever a vertex lies inside the circumcircle of the current apex, that
331 // vertex takes over as the apex; the one left standing has an empty circumcircle
332 auto best = first;
333 for (const auto i : make_range(first + 1, last))
334 if (moose_incircle(xy(v_start), xy(v_end), xy(chain[best]), xy(chain[i])) > 0.0)
335 best = i;
336
337 // That triangle splits the polygon in two: the chain vertices before the apex form a polygon
338 // seen from the new edge (apex, v_end), and those after it a polygon seen from (v_start, apex),
339 // each of which is covered the same way
340 triangles.push_back({v_start, v_end, chain[best]});
341 triangulatePseudopolygon(chain[best], v_end, chain, first, best, triangles);
342 triangulatePseudopolygon(v_start, chain[best], chain, best + 1, last, triangles);
343}
344
345void
346XYIncrementalDelaunay::initialize(const std::vector<Point2D> & points,
347 const std::vector<Segment> & segments)
348{
349 Moose::initPredicates();
350
351 if (points.empty())
352 mooseError("XYIncrementalDelaunay: initialize() needs at least one point.");
353
354 _vertices.clear();
355 _triangles.clear();
356 _vertex_triangle.clear();
357 _constraints.clear();
358
359 auto x_min = points.front().x;
360 auto x_max = x_min;
361 auto y_min = points.front().y;
362 auto y_max = y_min;
363 for (const auto & p : points)
364 {
365 x_min = std::min(x_min, p.x);
366 x_max = std::max(x_max, p.x);
367 y_min = std::min(y_min, p.y);
368 y_max = std::max(y_max, p.y);
369 }
370
371 // A triangle far enough outside the points that all of them are strictly inside it. Inserting a
372 // point deletes the Delaunay cavity of that insertion, the triangles whose circumcircle contains
373 // the point (see growCavity()), and fans the hole they leave out from the point. With every
374 // point inside the bounding triangle that hole is always a closed polygon, so no insertion ever
375 // has to extend the convex hull of the triangulation.
376 const auto reach = _bounding_reach * std::max({x_max - x_min, y_max - y_min, 1.0});
377 const auto x_mid = 0.5 * (x_min + x_max);
378 const auto y_mid = 0.5 * (y_min + y_max);
379 _vertices.push_back({x_mid - reach, y_mid - reach});
380 _vertices.push_back({x_mid + reach, y_mid - reach});
381 _vertices.push_back({x_mid, y_mid + reach});
382 _vertex_triangle.assign(_num_bounding, std::size_t(0));
383
384 Triangle bounding;
385 bounding.vertices = {0, 1, 2};
387 _triangles.push_back(bounding);
388 _last_triangle = 0;
389
390 for (const auto i : index_range(points))
391 {
392 const auto id = insertPoint(points[i]);
393 if (id != i)
394 mooseError("XYIncrementalDelaunay: points ",
395 id,
396 " and ",
397 i,
398 " are the same point; initialize() needs the points to be distinct, because the "
399 "constrained segments refer to them by position.");
400 }
401
402 for (const auto & [v0, v1] : segments)
403 insertSegment(v0, v1);
404}
405
406std::size_t
408{
409 Moose::initPredicates();
410
411 if (_triangles.empty())
412 mooseError("XYIncrementalDelaunay: initialize() has to run before a point can be inserted.");
413
414 const auto seed = locate(p);
415 if (seed == invalid_index)
416 mooseError("XYIncrementalDelaunay: the point (",
417 p.x,
418 ", ",
419 p.y,
420 ") is outside the region initialize() enclosed, so it cannot be inserted.");
421
422 // Two points count as the same point only when their coordinates agree exactly, which is the
423 // criterion the exact predicates use as well.
424 for (const auto v : _triangles[seed].vertices)
425 if (_vertices[v].x == p.x && _vertices[v].y == p.y)
426 {
427 if (isBounding(v))
428 mooseError("XYIncrementalDelaunay: the point (",
429 p.x,
430 ", ",
431 p.y,
432 ") is a vertex of the bounding triangle, so it is far outside the region "
433 "initialize() enclosed.");
434 return toCaller(v);
435 }
436
437 // A point landing on a constrained segment divides it, because the Delaunay cavity may not cross
438 // a constrained segment and a segment with a vertex on it can no longer be a single edge.
440 for (const auto i : make_range(3u))
441 {
442 const auto v0 = _triangles[seed].vertices[(i + 1) % 3];
443 const auto v1 = _triangles[seed].vertices[(i + 2) % 3];
444 if (moose_orient2d(xy(v0), xy(v1), coords(p)) == 0.0 && isConstrainedEdge(v0, v1))
445 {
446 split = makeSegment(toCaller(v0), toCaller(v1));
447 _constraints.erase(split);
448 break;
449 }
450 }
451
452 _vertices.push_back(p);
454 const auto v_new = _vertices.size() - 1;
455
456 std::set<std::size_t> cavity;
457 growCavity(seed, v_new, cavity);
458
459 std::vector<std::array<std::size_t, 3>> added;
460 for (const auto t : cavity)
461 for (const auto i : make_range(3u))
462 {
463 const auto n = _triangles[t].neighbors[i];
464 if (n == invalid_index || cavity.count(n) == 0)
465 added.push_back(
466 {_triangles[t].vertices[(i + 1) % 3], _triangles[t].vertices[(i + 2) % 3], v_new});
467 }
468
469 mooseAssert(added.size() == cavity.size() + 2,
470 "The Delaunay cavity of an insertion is a disc, whose boundary has two more edges "
471 "than the disc has triangles");
472
473 const std::vector<std::size_t> removed(cavity.begin(), cavity.end());
474 _last_triangle = retriangulate(removed, added).front();
475
476 const auto id = toCaller(v_new);
477 if (split.first != invalid_index)
478 {
479 insertSegment(split.first, id);
480 insertSegment(id, split.second);
481 }
482 return id;
483}
484
485void
486XYIncrementalDelaunay::insertSegment(const std::size_t v0, const std::size_t v1)
487{
488 Moose::initPredicates();
489
490 if (v0 == v1)
492 "XYIncrementalDelaunay: a constrained segment needs two different vertices, but both "
493 "ends of this one are vertex ",
494 v0,
495 ".");
496
497 const auto v_from = toInternal(v0);
498 const auto v_to = toInternal(v1);
499
500 // Turn around v_from until the triangle the segment leaves through is found. The segment is
501 // already an edge if v_to is met on the way, and needs splitting if some other vertex is.
502 auto entered = invalid_index;
503 auto right = invalid_index;
504 auto left = invalid_index;
505 const auto start = _vertex_triangle[v_from];
506 mooseAssert(start < _triangles.size(), "Every vertex records a triangle it belongs to");
507
508 auto current = start;
509 do
510 {
511 const auto i = localVertexIndex(current, v_from);
512 const auto next_v = _triangles[current].vertices[(i + 1) % 3];
513 const auto far_v = _triangles[current].vertices[(i + 2) % 3];
514
515 if (next_v == v_to)
516 {
517 _constraints.insert(makeSegment(v0, v1));
518 return;
519 }
520
521 const auto next_side = moose_orient2d(xy(v_from), xy(next_v), xy(v_to));
522
523 // Nothing has been changed yet, so recovering the two halves from scratch is safe.
524 if (next_side == 0.0 && isStrictlyBetween(v_from, next_v, v_to))
525 {
526 insertSegment(v0, toCaller(next_v));
527 insertSegment(toCaller(next_v), v1);
528 return;
529 }
530
531 if (next_side > 0.0 && moose_orient2d(xy(v_from), xy(far_v), xy(v_to)) < 0.0)
532 {
533 entered = current;
534 right = next_v;
535 left = far_v;
536 break;
537 }
538
539 current = _triangles[current].neighbors[(i + 1) % 3];
540 } while (current != start && current != invalid_index);
541
542 if (entered == invalid_index)
543 mooseError("XYIncrementalDelaunay: no triangle around vertex ",
544 v0,
545 " is entered by the segment to vertex ",
546 v1,
547 ".");
548
549 // Follow the segment across the triangulation, keeping the vertices it passes on either side.
550 std::vector<std::size_t> crossed{entered};
551 std::vector<std::size_t> right_chain{right};
552 std::vector<std::size_t> left_chain{left};
553
554 while (true)
555 {
556 if (isConstrainedEdge(right, left))
557 mooseError("XYIncrementalDelaunay: the segment from vertex ",
558 v0,
559 " to vertex ",
560 v1,
561 " crosses constrained segment (",
562 toCaller(right),
563 ", ",
564 toCaller(left),
565 "); constrained segments may only meet at their ends.");
566
567 const auto edge = makeSegment(right, left);
568 const auto next_t = _triangles[crossed.back()].neighbors[localEdgeIndex(crossed.back(), edge)];
569 if (next_t == invalid_index)
570 mooseError("XYIncrementalDelaunay: the segment from vertex ",
571 v0,
572 " to vertex ",
573 v1,
574 " leaves the triangulation before it reaches its end.");
575
576 const auto apex = _triangles[next_t].vertices[localEdgeIndex(next_t, edge)];
577 if (apex == v_to)
578 {
579 crossed.push_back(next_t);
580 break;
581 }
582
583 const auto side = moose_orient2d(xy(v_from), xy(v_to), xy(apex));
584 if (side == 0.0)
585 {
586 insertSegment(v0, toCaller(apex));
587 insertSegment(toCaller(apex), v1);
588 return;
589 }
590
591 crossed.push_back(next_t);
592 if (side > 0.0)
593 {
594 left_chain.push_back(apex);
595 left = apex;
596 }
597 else
598 {
599 right_chain.push_back(apex);
600 right = apex;
601 }
602 }
603
604 // The segment leaves a polygon on either side of itself, every vertex of which the segment sees,
605 // so triangulating each on its own restores the Delaunay property everywhere.
606 std::vector<std::array<std::size_t, 3>> added;
607 const std::vector<std::size_t> left_polygon(left_chain.rbegin(), left_chain.rend());
608 triangulatePseudopolygon(v_from, v_to, left_polygon, 0, left_polygon.size(), added);
609 triangulatePseudopolygon(v_to, v_from, right_chain, 0, right_chain.size(), added);
610
611 retriangulate(crossed, added);
612 _constraints.insert(makeSegment(v0, v1));
613}
614
615std::vector<XYIncrementalDelaunay::Triangle>
617{
618 std::vector<std::size_t> position(_triangles.size(), invalid_index);
619 std::size_t count = 0;
620 for (const auto t : index_range(_triangles))
621 if (!isBounding(_triangles[t].vertices[0]) && !isBounding(_triangles[t].vertices[1]) &&
622 !isBounding(_triangles[t].vertices[2]))
623 position[t] = count++;
624
625 std::vector<Triangle> triangles(count);
626 for (const auto t : index_range(_triangles))
627 {
628 if (position[t] == invalid_index)
629 continue;
630
631 auto & out = triangles[position[t]];
632 for (const auto i : make_range(3u))
633 {
634 out.vertices[i] = toCaller(_triangles[t].vertices[i]);
635 const auto n = _triangles[t].neighbors[i];
636 out.neighbors[i] = n == invalid_index ? invalid_index : position[n];
637 }
638 }
639 return triangles;
640}
641
642std::vector<std::string>
644{
645 Moose::initPredicates();
646
647 std::vector<std::string> violations;
648 std::set<Segment> edges;
649
650 for (const auto t : index_range(_triangles))
651 {
652 const auto & triangle = _triangles[t];
653 if (moose_orient2d(
654 xy(triangle.vertices[0]), xy(triangle.vertices[1]), xy(triangle.vertices[2])) <= 0.0)
655 violations.push_back("triangle " + std::to_string(t) +
656 " is not counter-clockwise, so its area is zero or negative");
657
658 for (const auto i : make_range(3u))
659 {
660 const auto v0 = triangle.vertices[(i + 1) % 3];
661 const auto v1 = triangle.vertices[(i + 2) % 3];
662 edges.insert(makeSegment(v0, v1));
663
664 const auto n = triangle.neighbors[i];
665 if (n == invalid_index)
666 continue;
667 if (n >= _triangles.size())
668 {
669 violations.push_back("triangle " + std::to_string(t) + " has neighbor " +
670 std::to_string(n) + ", which is not a triangle");
671 continue;
672 }
673
674 unsigned int j = 3;
675 for (const auto k : make_range(3u))
676 if (makeSegment(_triangles[n].vertices[(k + 1) % 3], _triangles[n].vertices[(k + 2) % 3]) ==
677 makeSegment(v0, v1))
678 j = k;
679
680 if (j == 3)
681 {
682 violations.push_back("triangles " + std::to_string(t) + " and " + std::to_string(n) +
683 " are neighbors but share no edge");
684 continue;
685 }
686 if (_triangles[n].neighbors[j] != t)
687 violations.push_back("triangle " + std::to_string(t) + " has neighbor " +
688 std::to_string(n) + ", which does not have it back");
689
690 if (!isConstrainedEdge(v0, v1) && moose_incircle(xy(triangle.vertices[0]),
691 xy(triangle.vertices[1]),
692 xy(triangle.vertices[2]),
693 xy(_triangles[n].vertices[j])) > 0.0)
694 violations.push_back("the edge between " + vertexName(v0) + " and " + vertexName(v1) +
695 " is not constrained and is not locally Delaunay, because " +
696 vertexName(_triangles[n].vertices[j]) +
697 " is inside the circumcircle of triangle " + std::to_string(t));
698 }
699 }
700
701 for (const auto & [v0, v1] : _constraints)
702 if (edges.count(makeSegment(toInternal(v0), toInternal(v1))) == 0)
703 violations.push_back("constrained segment (" + std::to_string(v0) + ", " +
704 std::to_string(v1) + ") is not an edge of the triangulation");
705
706 return violations;
707}
708
709std::vector<std::string>
711{
712 Moose::initPredicates();
713
714 std::vector<std::string> violations;
715 for (const auto t : index_range(_triangles))
716 {
717 const auto & triangle = _triangles[t];
718 for (const auto v : index_range(_vertices))
719 {
720 if (v == triangle.vertices[0] || v == triangle.vertices[1] || v == triangle.vertices[2])
721 continue;
722 if (moose_incircle(
723 xy(triangle.vertices[0]), xy(triangle.vertices[1]), xy(triangle.vertices[2]), xy(v)) >
724 0.0)
725 violations.push_back(vertexName(v) + " is inside the circumcircle of triangle " +
726 std::to_string(t));
727 }
728 }
729 return violations;
730}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
unsigned int count
Definition MortarUtils.C:53
std::size_t _last_triangle
A triangle the last insertion produced, which is where the next point walk starts.
static constexpr std::size_t _num_bounding
The number of bounding triangle vertices padding the front of the vertex list.
void initialize(const std::vector< Point2D > &points, const std::vector< Segment > &segments)
Triangulates points and recovers every entry of segments as an edge of the result.
std::size_t numPoints() const
std::size_t insertPoint(const Point2D &p)
Inserts a point, restoring the constrained Delaunay property around it.
bool isConstrainedSegment(std::size_t v0, std::size_t v1) const
void triangulatePseudopolygon(std::size_t v_start, std::size_t v_end, const std::vector< std::size_t > &chain, std::size_t first, std::size_t last, std::vector< std::array< std::size_t, 3 > > &triangles) const
Triangulates a polygon whose vertices are all visible from one of its edges, which is the shape a rec...
static Segment makeSegment(std::size_t v0, std::size_t v1)
unsigned int localVertexIndex(std::size_t t, std::size_t v) const
std::pair< std::size_t, std::size_t > Segment
A constrained segment, held as a vertex id pair with the smaller id first.
static constexpr double _bounding_reach
How far the bounding triangle reaches beyond the points, as a multiple of their extent.
std::set< Segment > _constraints
The constrained segments, in caller vertex ids with the smaller id first.
std::vector< std::string > checkInvariants() const
Checks everything this class promises: that every triangle is counter-clockwise, that the neighbor en...
bool isStrictlyBetween(std::size_t v_first, std::size_t v_mid, std::size_t v_last) const
std::vector< Triangle > _triangles
The triangles, every one of them live and counter-clockwise.
std::vector< std::string > checkEmptyCircumcircle() const
Tests every vertex against the circumcircle of every triangle.
static bool isBounding(std::size_t v)
std::size_t locate(const Point2D &p) const
Finds the triangle a point falls in by walking from the triangle the last insertion produced,...
void growCavity(std::size_t seed, std::size_t v_new, std::set< std::size_t > &cavity) const
Collects the triangles of the triangulation built so far that have to make way for a new vertex,...
std::string vertexName(std::size_t v) const
std::vector< Point2D > _vertices
The bounding triangle vertices followed by the caller's points.
unsigned int localEdgeIndex(std::size_t t, const Segment &edge) const
std::size_t toInternal(std::size_t id) const
void insertSegment(std::size_t v0, std::size_t v1)
Makes the segment between two vertices an edge of the triangulation and records it as constrained,...
bool isConstrainedEdge(std::size_t v0, std::size_t v1) const
const Point2D & point(std::size_t id) const
const double * xy(std::size_t v) const
bool containsPoint(std::size_t t, const Point2D &p) const
std::vector< std::size_t > retriangulate(const std::vector< std::size_t > &removed, const std::vector< std::array< std::size_t, 3 > > &added)
Swaps one triangulation of a region for another, reusing the slots of the triangles it removes and re...
std::size_t toCaller(std::size_t v) const
static constexpr std::size_t invalid_index
Sentinel for a vertex, triangle or neighbor that does not exist.
std::vector< std::size_t > _vertex_triangle
One triangle touching each vertex of _vertices, which is where a walk around it starts.
std::vector< Triangle > getTriangles() const
A point of the triangulation, held as plain coordinates.
A triangle of the triangulation.
std::array< std::size_t, 3 > neighbors
std::array< std::size_t, 3 > vertices