SWMMVis  6.0.0-alpha.4
Qt6/C++ GIS-based graphical user interface for the SWMMVis engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
cdt_impl.hpp
Go to the documentation of this file.
1
13#pragma once
14#include "meshkit/cdt.hpp"
16#include "predicates.h"
17#include <array>
18#include <algorithm>
19#include <cmath>
20#include <queue>
21#include <cstring>
22#include <limits>
23#include <mutex>
24
25namespace meshkit
26{
27
28namespace cdt_detail
29{
30
31inline std::once_flag gPredicatesInit;
32
40inline std::uint64_t mortonKey(std::uint32_t x, std::uint32_t y)
41{
42 auto spread = [](std::uint64_t v) {
43 v &= 0xffffffffull;
44 v = (v | (v << 16)) & 0x0000ffff0000ffffull;
45 v = (v | (v << 8)) & 0x00ff00ff00ff00ffull;
46 v = (v | (v << 4)) & 0x0f0f0f0f0f0f0f0full;
47 v = (v | (v << 2)) & 0x3333333333333333ull;
48 v = (v | (v << 1)) & 0x5555555555555555ull;
49 return v;
50 };
51 return spread(x) | (spread(y) << 1);
52}
53
63inline std::uint64_t edgeKey64(int a, int b)
64{
65 if (a > b)
66 std::swap(a, b);
67 return (std::uint64_t(std::uint32_t(a)) << 32) | std::uint64_t(std::uint32_t(b));
68}
75inline int keyLo(std::uint64_t k)
76{
77 return int(std::uint32_t(k >> 32));
78}
84inline int keyHi(std::uint64_t k)
85{
86 return int(std::uint32_t(k & 0xffffffffull));
87}
88
99template <class Point> double orientPts(const Point &a, const Point &b, const Point &c)
100{
101 const double pa[2] = {a.x(), a.y()}, pb[2] = {b.x(), b.y()}, pc[2] = {c.x(), c.y()};
102 return orient2d(pa, pb, pc);
103}
104
112template <class Point> inline double dist2(const Point &a, const Point &b)
113{
114 const double dx = a.x() - b.x(), dy = a.y() - b.y();
115 return dx * dx + dy * dy;
116}
117
118// Preserve the existing refinement coincidence rule during extraction.
119// Input build/dedup remains exact, with signed-zero normalization.
120template <class Point> bool refinementCoincident(const Point &a, const Point &b)
121{
122 auto close = [](double x, double y) {
123 return (x == 0 || y == 0) ? std::abs(x - y) <= 1e-12
124 : std::abs(x - y) * 1e12 <= std::min(std::abs(x), std::abs(y));
125 };
126 return close(a.x(), b.x()) && close(a.y(), b.y());
127}
128
129constexpr double kEquilateralArea = 0.4330127018922193; // √3/4
130
131} // namespace cdt_detail
132using namespace cdt_detail;
133
134// ── Predicates ───────────────────────────────────────────────────────────
135
136template <class Storage>
138 const Point &c)
139{
140 std::call_once(gPredicatesInit, [] { exactinit(); });
141 return orientPts(a, b, c);
142}
143
144template <class Storage> double BasicConstrainedDelaunay<Storage>::orient(int a, int b, int c) const
145{
146 const double pa[2] = {m_pts[a].x(), m_pts[a].y()};
147 const double pb[2] = {m_pts[b].x(), m_pts[b].y()};
148 const double pc[2] = {m_pts[c].x(), m_pts[c].y()};
149 return orient2d(pa, pb, pc);
150}
151
152template <class Storage>
153double BasicConstrainedDelaunay<Storage>::orient(int a, int b, const Point &p) const
154{
155 const double pa[2] = {m_pts[a].x(), m_pts[a].y()};
156 const double pb[2] = {m_pts[b].x(), m_pts[b].y()};
157 const double pc[2] = {p.x(), p.y()};
158 return orient2d(pa, pb, pc);
159}
160
161template <class Storage>
162bool BasicConstrainedDelaunay<Storage>::inCircle(int a, int b, int c, int d) const
163{
164 const double pa[2] = {m_pts[a].x(), m_pts[a].y()};
165 const double pb[2] = {m_pts[b].x(), m_pts[b].y()};
166 const double pc[2] = {m_pts[c].x(), m_pts[c].y()};
167 const double pd[2] = {m_pts[d].x(), m_pts[d].y()};
168 return incircle(pa, pb, pc, pd) > 0.0;
169}
170
171// ── Topology helpers ─────────────────────────────────────────────────────
172
173template <class Storage> int BasicConstrainedDelaunay<Storage>::edgeIndex(int t, int v) const
174{
175 const Triangle &T = m_tris[t];
176 for (int i = 0; i < 3; ++i)
177 if (T.v[i] == v)
178 return i;
179 return -1;
180}
181
182template <class Storage>
183void BasicConstrainedDelaunay<Storage>::trianglesAround(int v, Vector<int> *out) const
184{
185 const int start = m_vertexTri[v];
186 if (start < 0)
187 return;
188 for (int dir = 0; dir < 2; ++dir) {
189 int t = start;
190 for (int guard = 0; guard < 100000; ++guard) {
191 const int i = edgeIndex(t, v);
192 if (i < 0)
193 break;
194 if (!(dir == 1 && t == start))
195 out->append(t);
196 const int next = m_tris[t].adj[dir == 0 ? (i + 1) % 3 : (i + 2) % 3];
197 if (next < 0)
198 break;
199 if (next == start)
200 return; // closed fan: done after one direction
201 t = next;
202 }
203 }
204}
205
206template <class Storage> void BasicConstrainedDelaunay<Storage>::setAdj(int t, int e, int n)
207{
208 if (t >= 0)
209 m_tris[t].adj[e] = n;
210}
211
214template <class Storage>
215int BasicConstrainedDelaunay<Storage>::triangleWithEdge(int a, int b, int *edgeIndexOut) const
216{
217 const int start = m_vertexTri[a];
218 if (start < 0)
219 return -1;
220 int t = start;
221 for (int guard = 0; guard < 100000; ++guard) {
222 const Triangle &T = m_tris[t];
223 const int i = edgeIndex(t, a);
224 if (i < 0)
225 return -1;
226 if (T.v[(i + 1) % 3] == b) {
227 if (edgeIndexOut)
228 *edgeIndexOut = (i + 2) % 3;
229 return t;
230 }
231 // Rotate counter-clockwise around a: next triangle across the edge
232 // (a, v[i+2]) which is opposite v[i+1].
233 const int next = T.adj[(i + 1) % 3];
234 if (next < 0 || next == start)
235 break;
236 t = next;
237 }
238 // Rotate the other way in case the fan is open on one side.
239 t = start;
240 for (int guard = 0; guard < 100000; ++guard) {
241 const Triangle &T = m_tris[t];
242 const int i = edgeIndex(t, a);
243 if (i < 0)
244 return -1;
245 if (T.v[(i + 1) % 3] == b) {
246 if (edgeIndexOut)
247 *edgeIndexOut = (i + 2) % 3;
248 return t;
249 }
250 const int next = T.adj[(i + 2) % 3];
251 if (next < 0 || next == start)
252 break;
253 t = next;
254 }
255 return -1;
256}
257
258template <class Storage> bool BasicConstrainedDelaunay<Storage>::isConstrained(int a, int b) const
259{
260 int e = -1;
261 int t = triangleWithEdge(a, b, &e);
262 if (t < 0)
263 t = triangleWithEdge(b, a, &e);
264 return t >= 0 && m_tris[t].constrained[e];
265}
266
267template <class Storage>
269{
270 // Follow constrained edges from a: first the edge to b itself, then the
271 // subsegments refinement or insertion split off (a, b) — its origin — and
272 // failing both an exactly collinear constrained edge towards b.
273 const std::uint64_t origin = edgeKey64(a, b);
275 int cur = a, prev = -1;
276 for (int guard = 0; guard < 1000000 && cur != b; ++guard) {
277 Vector<int> fan;
278 trianglesAround(cur, &fan);
279 bool reachesB = false;
280 int byOrigin = -1, byLine = -1;
281 double bestD2 = std::numeric_limits<double>::infinity();
282 const Point &P = m_pts[cur], &B = m_pts[b];
283 for (int t : fan) {
284 const Triangle &T = m_tris[t];
285 const int i = edgeIndex(t, cur);
286 for (int k = 1; k <= 2; ++k) {
287 const int v = T.v[(i + k) % 3];
288 const int e =
289 (i + (k == 1 ? 2 : 1)) % 3; // edge (cur, v) is opposite the third vertex
290 if (!T.constrained[e] || v == cur || v == prev)
291 continue;
292 if (v == b) {
293 reachesB = true;
294 continue;
295 }
296 const std::uint64_t ek = edgeKey64(cur, v);
297 const auto it = m_segOrigin.constFind(ek);
298 if (it != m_segOrigin.constEnd() &&
299 (it.value() == origin || m_segOriginExtra.contains(ek, origin))) {
300 byOrigin = v;
301 continue;
302 }
303 if (orient(a, b, v) != 0.0)
304 continue;
305 const Point &V = m_pts[v];
306 const double dot =
307 (V.x() - P.x()) * (B.x() - P.x()) + (V.y() - P.y()) * (B.y() - P.y());
308 if (dot <= 0.0)
309 continue;
310 const double d2 = dist2(V, P);
311 if (d2 < bestD2) {
312 bestD2 = d2;
313 byLine = v;
314 }
315 }
316 }
317 const int next = reachesB ? b : (byOrigin >= 0 ? byOrigin : byLine);
318 if (next < 0)
319 return {};
320 chain.append(next);
321 prev = cur;
322 cur = next;
323 }
324 return cur == b ? chain : Vector<int>();
325}
326
327template <class Storage> void BasicConstrainedDelaunay<Storage>::markConstrained(int a, int b)
328{
329 for (int dir = 0; dir < 2; ++dir) {
330 int e = -1;
331 const int t = dir == 0 ? triangleWithEdge(a, b, &e) : triangleWithEdge(b, a, &e);
332 if (t < 0)
333 continue;
334 m_tris[t].constrained[e] = true;
335 const int n = m_tris[t].adj[e];
336 if (n >= 0)
337 for (int i = 0; i < 3; ++i)
338 if (m_tris[n].adj[i] == t)
339 m_tris[n].constrained[i] = true;
340 }
341}
342
343template <class Storage>
344int BasicConstrainedDelaunay<Storage>::findTriangle(const Point &p, int start) const
345{
346 if (m_tris.isEmpty())
347 return -1;
348 int t = (start >= 0 && start < m_tris.size()) ? start : 0;
349 const int cap = m_tris.size() * 2 + 16;
350 for (int step = 0; step < cap; ++step) {
351 const Triangle &T = m_tris[t];
352 int next = -1;
353 for (int i = 0; i < 3; ++i) {
354 if (orient(T.v[(i + 1) % 3], T.v[(i + 2) % 3], p) < 0.0) {
355 next = T.adj[i];
356 if (next < 0)
357 return -1;
358 break;
359 }
360 }
361 if (next < 0)
362 return t;
363 t = next;
364 }
365 // Cycle guard: brute force.
366 for (int i = 0; i < m_tris.size(); ++i) {
367 const Triangle &T = m_tris[i];
368 if (orient(T.v[1], T.v[2], p) >= 0.0 && orient(T.v[2], T.v[0], p) >= 0.0 &&
369 orient(T.v[0], T.v[1], p) >= 0.0)
370 return i;
371 }
372 return -1;
373}
374
375template <class Storage> int BasicConstrainedDelaunay<Storage>::locate(const Point &p) const
376{
377 const int t = findTriangle(p, m_lastLocate);
378 return (t >= 0 && m_tris[t].alive) ? t : -1;
379}
380
382{
383 return m_liveTriangles;
384}
385
386// ── Flips and splits ─────────────────────────────────────────────────────
387
390template <class Storage> void BasicConstrainedDelaunay<Storage>::flip(int t, int e)
391{
392 Triangle &T = m_tris[t];
393 const int n = T.adj[e];
394 Triangle &N = m_tris[n];
395 const int a = T.v[e], b = T.v[(e + 1) % 3], c = T.v[(e + 2) % 3];
396 int k = 0;
397 for (int i = 0; i < 3; ++i)
398 if (N.adj[i] == t) {
399 k = i;
400 break;
401 }
402 const int d = N.v[k];
403 const int Tca = T.adj[(e + 1) % 3], Tab = T.adj[(e + 2) % 3];
404 const int Nbd = N.adj[(k + 1) % 3], Ndc = N.adj[(k + 2) % 3];
405 const bool cCA = T.constrained[(e + 1) % 3], cAB = T.constrained[(e + 2) % 3];
406 const bool cBD = N.constrained[(k + 1) % 3], cDC = N.constrained[(k + 2) % 3];
407
408 T.v[0] = a;
409 T.v[1] = b;
410 T.v[2] = d;
411 T.adj[0] = Nbd;
412 T.adj[1] = n;
413 T.adj[2] = Tab;
414 T.constrained[0] = cBD;
415 T.constrained[1] = false;
416 T.constrained[2] = cAB;
417 N.v[0] = a;
418 N.v[1] = d;
419 N.v[2] = c;
420 N.adj[0] = Ndc;
421 N.adj[1] = Tca;
422 N.adj[2] = t;
423 N.constrained[0] = cDC;
424 N.constrained[1] = cCA;
425 N.constrained[2] = false;
426 if (Nbd >= 0)
427 for (int i = 0; i < 3; ++i)
428 if (m_tris[Nbd].adj[i] == n)
429 m_tris[Nbd].adj[i] = t;
430 if (Tca >= 0)
431 for (int i = 0; i < 3; ++i)
432 if (m_tris[Tca].adj[i] == t)
433 m_tris[Tca].adj[i] = n;
434 m_vertexTri[a] = t;
435 m_vertexTri[b] = t;
436 m_vertexTri[d] = t;
437 m_vertexTri[c] = n;
438 if (m_triangleChanged) {
439 m_triangleChanged(t);
440 m_triangleChanged(n);
441 }
442}
443
444template <class Storage> void BasicConstrainedDelaunay<Storage>::legalize(int t0, int e0)
445{
446 Vector<std::pair<int, int>> stack;
447 stack.append(std::make_pair(t0, e0));
448 while (!stack.isEmpty()) {
449 const std::pair<int, int> te = stack.takeLast();
450 const int t = te.first, e = te.second;
451 const Triangle &T = m_tris[t];
452 const int n = T.adj[e];
453 if (n < 0 || T.constrained[e])
454 continue;
455 const Triangle &N = m_tris[n];
456 int k = 0;
457 for (int i = 0; i < 3; ++i)
458 if (N.adj[i] == t) {
459 k = i;
460 break;
461 }
462 const int d = N.v[k];
463 if (!inCircle(T.v[0], T.v[1], T.v[2], d))
464 continue;
465 flip(t, e);
466 // t = (a, b, d), n = (a, d, c): recheck the quad's four outer edges.
467 stack.append(std::make_pair(t, 0));
468 stack.append(std::make_pair(t, 2));
469 stack.append(std::make_pair(n, 0));
470 stack.append(std::make_pair(n, 1));
471 }
472}
473
474template <class Storage> void BasicConstrainedDelaunay<Storage>::splitTriangle(int t, int v)
475{
476 const Triangle old = m_tris[t];
477 const int a = old.v[0], b = old.v[1], c = old.v[2];
478 const int t1 = t, t2 = m_tris.size(), t3 = m_tris.size() + 1;
479 Triangle T1, T2, T3;
480 T1.v[0] = v;
481 T1.v[1] = a;
482 T1.v[2] = b;
483 T2.v[0] = v;
484 T2.v[1] = b;
485 T2.v[2] = c;
486 T3.v[0] = v;
487 T3.v[1] = c;
488 T3.v[2] = a;
489 T1.adj[0] = old.adj[2];
490 T1.adj[1] = t2;
491 T1.adj[2] = t3;
492 T2.adj[0] = old.adj[0];
493 T2.adj[1] = t3;
494 T2.adj[2] = t1;
495 T3.adj[0] = old.adj[1];
496 T3.adj[1] = t1;
497 T3.adj[2] = t2;
498 T1.constrained[0] = old.constrained[2];
499 T1.constrained[1] = T1.constrained[2] = false;
500 T2.constrained[0] = old.constrained[0];
501 T2.constrained[1] = T2.constrained[2] = false;
502 T3.constrained[0] = old.constrained[1];
503 T3.constrained[1] = T3.constrained[2] = false;
504 T1.alive = T2.alive = T3.alive = old.alive;
505 m_tris[t1] = T1;
506 m_tris.append(T2);
507 m_tris.append(T3);
508 m_liveTriangles += old.alive ? 2 : 0;
509 if (old.adj[2] >= 0)
510 for (int i = 0; i < 3; ++i)
511 if (m_tris[old.adj[2]].adj[i] == t)
512 m_tris[old.adj[2]].adj[i] = t1;
513 if (old.adj[0] >= 0)
514 for (int i = 0; i < 3; ++i)
515 if (m_tris[old.adj[0]].adj[i] == t)
516 m_tris[old.adj[0]].adj[i] = t2;
517 if (old.adj[1] >= 0)
518 for (int i = 0; i < 3; ++i)
519 if (m_tris[old.adj[1]].adj[i] == t)
520 m_tris[old.adj[1]].adj[i] = t3;
521 m_vertexTri[v] = t1;
522 m_vertexTri[a] = t1;
523 m_vertexTri[b] = t2;
524 m_vertexTri[c] = t3;
525 legalize(t1, 0);
526 legalize(t2, 0);
527 legalize(t3, 0);
528 if (m_triangleChanged) {
529 m_triangleChanged(t1);
530 m_triangleChanged(t2);
531 m_triangleChanged(t3);
532 }
533}
534
535template <class Storage> void BasicConstrainedDelaunay<Storage>::splitEdge(int t, int e, int v)
536{
537 const Triangle old = m_tris[t];
538 const int a = old.v[e], b = old.v[(e + 1) % 3], c = old.v[(e + 2) % 3];
539 const int n = old.adj[e];
540 const bool f = old.constrained[e];
541 const int t1 = t, t2 = m_tris.size();
542 Triangle T1, T2;
543 T1.v[0] = v;
544 T1.v[1] = a;
545 T1.v[2] = b;
546 T2.v[0] = v;
547 T2.v[1] = c;
548 T2.v[2] = a;
549 T1.alive = T2.alive = old.alive;
550 int n1 = -1, n2 = -1;
551 if (n >= 0) {
552 n1 = n;
553 n2 = m_tris.size() + 1;
554 }
555 // Old edges: (a, b) is opposite c = index e+2; (c, a) is opposite b = index e+1.
556 const int Tab = old.adj[(e + 2) % 3], Tca = old.adj[(e + 1) % 3];
557 T1.adj[0] = Tab;
558 T1.adj[1] = n1;
559 T1.adj[2] = t2;
560 T2.adj[0] = Tca;
561 T2.adj[1] = t1;
562 T2.adj[2] = n2;
563 T1.constrained[0] = old.constrained[(e + 2) % 3];
564 T1.constrained[1] = f;
565 T1.constrained[2] = false;
566 T2.constrained[0] = old.constrained[(e + 1) % 3];
567 T2.constrained[1] = false;
568 T2.constrained[2] = f;
569 m_tris[t1] = T1;
570 m_tris.append(T2);
571 m_liveTriangles += old.alive ? 1 : 0;
572 if (Tab >= 0)
573 for (int i = 0; i < 3; ++i)
574 if (m_tris[Tab].adj[i] == t)
575 m_tris[Tab].adj[i] = t1;
576 if (Tca >= 0)
577 for (int i = 0; i < 3; ++i)
578 if (m_tris[Tca].adj[i] == t)
579 m_tris[Tca].adj[i] = t2;
580 m_vertexTri[v] = t1;
581 m_vertexTri[a] = t1;
582 m_vertexTri[b] = t1;
583 m_vertexTri[c] = t2;
584 if (n >= 0) {
585 const Triangle oldN = m_tris[n];
586 int k = 0;
587 for (int i = 0; i < 3; ++i)
588 if (oldN.adj[i] == t) {
589 k = i;
590 break;
591 }
592 const int d = oldN.v[k];
593 Triangle N1, N2;
594 N1.v[0] = v;
595 N1.v[1] = b;
596 N1.v[2] = d;
597 N2.v[0] = v;
598 N2.v[1] = d;
599 N2.v[2] = c;
600 N1.alive = N2.alive = oldN.alive;
601 N1.adj[0] = oldN.adj[(k + 1) % 3];
602 N1.adj[1] = n2;
603 N1.adj[2] = t1;
604 N2.adj[0] = oldN.adj[(k + 2) % 3];
605 N2.adj[1] = t2;
606 N2.adj[2] = n1;
607 N1.constrained[0] = oldN.constrained[(k + 1) % 3];
608 N1.constrained[1] = false;
609 N1.constrained[2] = f;
610 N2.constrained[0] = oldN.constrained[(k + 2) % 3];
611 N2.constrained[1] = f;
612 N2.constrained[2] = false;
613 m_tris[n1] = N1;
614 m_tris.append(N2);
615 m_liveTriangles += oldN.alive ? 1 : 0;
616 if (N1.adj[0] >= 0)
617 for (int i = 0; i < 3; ++i)
618 if (m_tris[N1.adj[0]].adj[i] == n)
619 m_tris[N1.adj[0]].adj[i] = n1;
620 if (N2.adj[0] >= 0)
621 for (int i = 0; i < 3; ++i)
622 if (m_tris[N2.adj[0]].adj[i] == n)
623 m_tris[N2.adj[0]].adj[i] = n2;
624 m_vertexTri[d] = n1;
625 }
626 legalize(t1, 0);
627 legalize(t2, 0);
628 if (n >= 0) {
629 legalize(n1, 0);
630 legalize(n2, 0);
631 }
632 if (m_triangleChanged) {
633 m_triangleChanged(t1);
634 m_triangleChanged(t2);
635 if (n >= 0) {
636 m_triangleChanged(n1);
637 m_triangleChanged(n2);
638 }
639 }
640}
641
642// ── Construction ─────────────────────────────────────────────────────────
643
644template <class Storage>
646 const std::function<bool()> &cancelled,
647 const std::function<void(std::uint64_t)> &progress)
648{
649 std::call_once(gPredicatesInit, [] { exactinit(); });
650 m_origins.clear();
651 m_pts.clear();
652 m_tris.clear();
653 m_liveTriangles = 0;
654 m_vertexTri.clear();
655 m_errorMsg.clear();
656 m_terrainElevations.clear();
657 m_segOrigin.clear();
658 m_segOriginExtra.clear();
659 m_segPiece.clear();
660 m_vertexSeg.clear();
661 m_fixedSub.clear();
662 m_lastLocate = 0;
663 auto stopped = [&] {
664 if (cancelled && cancelled()) {
665 m_errorMsg = Storage::message("cdt: cancelled");
666 return true;
667 }
668 return false;
669 };
670 if (stopped())
671 return false;
672
673 // Dedup on the exact bit pattern.
675 seen.reserve(points.size());
676 if (vertexOfPoint)
677 vertexOfPoint->resize(points.size());
678 double minX = std::numeric_limits<double>::infinity(), minY = minX, maxX = -minX, maxY = -minX;
679 for (int i = 0; i < points.size(); ++i) {
680 if ((i & 4095) == 0 && stopped())
681 return false;
682 const Point &p = points[i];
683 if (!std::isfinite(p.x()) || !std::isfinite(p.y())) {
684 m_errorMsg = Storage::invalidPointMessage(i);
685 return false;
686 }
687 std::int64_t kx, ky;
688 double x = p.x(), y = p.y();
689 if (x == 0.0)
690 x = 0.0;
691 if (y == 0.0)
692 y = 0.0; // fold -0
693 std::memcpy(&kx, &x, 8);
694 std::memcpy(&ky, &y, 8);
695 const std::pair<std::int64_t, std::int64_t> key(kx, ky);
696 auto it = seen.constFind(key);
697 int id;
698 if (it == seen.constEnd()) {
699 id = m_pts.size();
700 m_pts.append(p);
701 seen.insert(key, id);
702 minX = std::min(minX, p.x());
703 maxX = std::max(maxX, p.x());
704 minY = std::min(minY, p.y());
705 maxY = std::max(maxY, p.y());
706 } else
707 id = it.value();
708 if (vertexOfPoint)
709 (*vertexOfPoint)[i] = id;
710 }
711 if (m_pts.size() < 3) {
712 m_errorMsg = Storage::message("cdt: fewer than 3 distinct points");
713 return false;
714 }
715
716 // Super triangle far outside the data.
717 const Box bbox(Point(minX, minY), Point(maxX, maxY));
718 const double ext = std::max({bbox.width(), bbox.height(), 1e-9});
719 const double cx = bbox.center().x(), cy = bbox.center().y();
720 const double R = ext * 1e5;
721 m_superBase = m_pts.size();
722 m_pts.append(Point(cx - 2.0 * R, cy - R));
723 m_pts.append(Point(cx + 2.0 * R, cy - R));
724 m_pts.append(Point(cx, cy + 2.0 * R));
725 m_vertexTri.fill(-1, m_pts.size());
726 Triangle sup;
727 sup.v[0] = m_superBase;
728 sup.v[1] = m_superBase + 1;
729 sup.v[2] = m_superBase + 2;
730 sup.adj[0] = sup.adj[1] = sup.adj[2] = -1;
731 sup.constrained[0] = sup.constrained[1] = sup.constrained[2] = false;
732 sup.alive = true;
733 m_tris.append(sup);
734 m_liveTriangles = 1;
735 m_vertexTri[m_superBase] = m_vertexTri[m_superBase + 1] = m_vertexTri[m_superBase + 2] = 0;
736
737 // Insertion order: Morton on a 2^20 quantisation (deterministic, local).
739 const double sx = bbox.width() > 0 ? 1048575.0 / bbox.width() : 0.0;
740 const double sy = bbox.height() > 0 ? 1048575.0 / bbox.height() : 0.0;
741 for (int i = 0; i < m_superBase; ++i) {
742 const std::uint32_t qx = std::uint32_t((m_pts[i].x() - bbox.left()) * sx);
743 const std::uint32_t qy = std::uint32_t((m_pts[i].y() - bbox.top()) * sy);
744 order[i] = std::make_pair(mortonKey(qx, qy), i);
745 }
746 if (!cancelled)
747 std::sort(order.begin(), order.end());
748 else {
749 struct CancelBuild {
750 };
751 std::uint64_t comparisons = 0;
752 try {
753 std::sort(order.begin(), order.end(), [&](const auto &a, const auto &b) {
754 if ((++comparisons & 16383) == 0 && stopped())
755 throw CancelBuild{};
756 return a < b;
757 });
758 } catch (const CancelBuild &) {
759 return false;
760 }
761 }
762
763 m_tris.reserve(2 * m_superBase + 8);
764 std::uint64_t completed = 0;
765 for (const std::pair<std::uint64_t, int> &o : order) {
766 if ((completed++ & 4095) == 0) {
767 if (stopped())
768 return false;
769 if (progress)
770 progress(completed - 1);
771 }
772 const int v = o.second;
773 const int t = findTriangle(m_pts[v], m_lastLocate);
774 if (t < 0) {
775 m_errorMsg = Storage::message("cdt: point location failed");
776 return false;
777 }
778 const Triangle &T = m_tris[t];
779 int onEdge = -1;
780 for (int i = 0; i < 3; ++i)
781 if (orient(T.v[(i + 1) % 3], T.v[(i + 2) % 3], v) == 0.0) {
782 onEdge = i;
783 break;
784 }
785 if (onEdge >= 0)
786 splitEdge(t, onEdge, v);
787 else
788 splitTriangle(t, v);
789 m_lastLocate = m_vertexTri[v];
790 }
791 return true;
792}
793
794template <class Storage> int BasicConstrainedDelaunay<Storage>::insertPoint(const Point &p)
795{
796 const int t = findTriangle(p, m_lastLocate);
797 if (t < 0 || !m_tris[t].alive)
798 return -1;
799 const int v = m_pts.size();
800 m_pts.append(p);
801 if (!m_origins.isEmpty())
802 m_origins.append(0);
803 m_vertexTri.append(-1);
804 const Triangle &T = m_tris[t];
805 int onEdge = -1;
806 for (int i = 0; i < 3; ++i)
807 if (orient(T.v[(i + 1) % 3], T.v[(i + 2) % 3], v) == 0.0) {
808 onEdge = i;
809 break;
810 }
811 if (onEdge >= 0) {
812 // Coincident with a vertex? Then reuse it.
813 for (int i = 0; i < 3; ++i)
814 if (refinementCoincident(m_pts[T.v[i]], p)) {
815 m_pts.removeLast();
816 m_vertexTri.removeLast();
817 return T.v[i];
818 }
819 splitEdge(t, onEdge, v);
820 } else
821 splitTriangle(t, v);
822 m_lastLocate = m_vertexTri[v];
823 return v;
824}
825
826template <class Storage> bool BasicConstrainedDelaunay<Storage>::insertConstraint(int a, int b)
827{
828 return insertConstraintImpl(a, b, edgeKey64(a, b));
829}
830
831template <class Storage>
832bool BasicConstrainedDelaunay<Storage>::insertConstraintImpl(int a, int b, std::uint64_t origin)
833{
834 if (a == b)
835 return true;
836 if (a < 0 || b < 0 || a >= m_pts.size() || b >= m_pts.size() || isSuperVertex(a) ||
837 isSuperVertex(b))
838 return false;
839 int e = -1;
840 // Record the input segment and the piece an edge realises. A second
841 // constraint over the same edge (an overlap) adds its origin.
842 auto record = [&](int x, int y) {
843 const std::uint64_t k = edgeKey64(x, y);
844 const auto it = m_segOrigin.constFind(k);
845 if (it == m_segOrigin.constEnd())
846 m_segOrigin.insert(k, origin);
847 else if (it.value() != origin && !m_segOriginExtra.contains(k, origin))
848 m_segOriginExtra.insert(k, origin);
849 if (!m_segPiece.contains(k))
850 m_segPiece.insert(k, k);
851 };
852 if (triangleWithEdge(a, b, &e) >= 0 || triangleWithEdge(b, a, &e) >= 0) {
853 markConstrained(a, b);
854 record(a, b);
855 return true;
856 }
857
858 // Find the triangle at a whose opposite edge the segment a→b crosses.
859 // A vertex lying exactly on the segment splits the constraint there.
860 auto onSegment = [&](int p) {
861 if (p == a || p == b || orient(a, b, p) != 0.0)
862 return false;
863 const Point &A = m_pts[a], &B = m_pts[b], &P = m_pts[p];
864 return (P.x() - A.x()) * (B.x() - A.x()) + (P.y() - A.y()) * (B.y() - A.y()) > 0.0 &&
865 (P.x() - B.x()) * (A.x() - B.x()) + (P.y() - B.y()) * (A.y() - B.y()) > 0.0;
866 };
867 int start = -1, startEdge = -1;
868 for (int dir = 0; dir < 2 && start < 0; ++dir) {
869 const int t0 = m_vertexTri[a];
870 int t = t0;
871 for (int guard = 0; guard < 1000000 && t >= 0; ++guard) {
872 const int i = edgeIndex(t, a);
873 if (i < 0)
874 break;
875 const int p = m_tris[t].v[(i + 1) % 3], q = m_tris[t].v[(i + 2) % 3];
876 if (onSegment(p))
877 return insertConstraintImpl(a, p, origin) && insertConstraintImpl(p, b, origin);
878 if (onSegment(q))
879 return insertConstraintImpl(a, q, origin) && insertConstraintImpl(q, b, origin);
880 if (orient(a, b, p) < 0.0 && orient(a, b, q) > 0.0) {
881 start = t;
882 startEdge = i;
883 break;
884 }
885 const int next = m_tris[t].adj[dir == 0 ? (i + 1) % 3 : (i + 2) % 3];
886 if (next < 0 || next == t0)
887 break;
888 t = next;
889 }
890 }
891 if (start < 0) {
892 m_errorMsg = Storage::message("cdt: constraint start not found");
893 return false;
894 }
895
896 // Collect the crossed edges by walking from a to b.
897 Vector<std::pair<int, int>> crossed;
898 {
899 int t = start, e = startEdge;
900 for (int guard = 0; guard < 10000000; ++guard) {
901 const int p = m_tris[t].v[(e + 1) % 3], q = m_tris[t].v[(e + 2) % 3];
902 if (m_tris[t].constrained[e]) {
903 m_errorMsg = Storage::message("cdt: constraint crosses another constraint");
904 return false;
905 }
906 crossed.append(std::make_pair(p, q));
907 const int n = m_tris[t].adj[e];
908 if (n < 0) {
909 m_errorMsg = Storage::message("cdt: walked off the triangulation");
910 return false;
911 }
912 int k = 0;
913 for (int i = 0; i < 3; ++i)
914 if (m_tris[n].adj[i] == t) {
915 k = i;
916 break;
917 }
918 const int r = m_tris[n].v[k];
919 if (r == b)
920 break;
921 const double orr = orient(a, b, r);
922 if (orr == 0.0)
923 return insertConstraintImpl(a, r, origin) && insertConstraintImpl(r, b, origin);
924 // Next crossed edge: (p, r) if r is left of a→b (q side), else (r, q).
925 // In n (CCW): v[k]=r, v[k+1]=q, v[k+2]=p. Edge opposite v[k+1] is (p, r)... indices:
926 // edge opposite index j is (v[j+1], v[j+2]).
927 // r left of a→b: exit through (p, r) = (v[k+2], v[k]), opposite k+1;
928 // r right: exit through (r, q) = (v[k], v[k+1]), opposite k+2.
929 e = (orr > 0.0) ? (k + 1) % 3 : (k + 2) % 3;
930 t = n;
931 }
932 }
933
934 // Sloan: flip crossed edges until none crosses a→b.
935 Vector<std::pair<int, int>> newEdges;
936 int spins = 0;
937 while (!crossed.isEmpty()) {
938 const std::pair<int, int> pq = crossed.takeFirst();
939 int te = -1;
940 int t = triangleWithEdge(pq.first, pq.second, &te);
941 if (t < 0)
942 t = triangleWithEdge(pq.second, pq.first, &te);
943 if (t < 0) {
944 m_errorMsg = Storage::message("cdt: lost a crossed edge");
945 return false;
946 }
947 const int n = m_tris[t].adj[te];
948 const int apex = m_tris[t].v[te];
949 int k = 0;
950 for (int i = 0; i < 3; ++i)
951 if (m_tris[n].adj[i] == t) {
952 k = i;
953 break;
954 }
955 const int d = m_tris[n].v[k];
956 // Convex quad test: p and q on opposite sides of apex→d.
957 const int p = m_tris[t].v[(te + 1) % 3], q = m_tris[t].v[(te + 2) % 3];
958 const double s1 = orient(apex, d, p), s2 = orient(apex, d, q);
959 if (!((s1 > 0.0 && s2 < 0.0) || (s1 < 0.0 && s2 > 0.0))) {
960 crossed.append(pq);
961 if (++spins > 100 * (crossed.size() + 1)) {
962 m_errorMsg = Storage::message("cdt: constraint recovery stalled");
963 return false;
964 }
965 continue;
966 }
967 spins = 0;
968 flip(t, te);
969 // New edge (apex, d): does it still cross a→b?
970 const double o1 = orient(a, b, apex), o2 = orient(a, b, d);
971 const bool isTarget = (apex == a && d == b) || (apex == b && d == a);
972 if (!isTarget && ((o1 > 0.0 && o2 < 0.0) || (o1 < 0.0 && o2 > 0.0)))
973 crossed.append(std::make_pair(apex, d));
974 else if (!isTarget)
975 newEdges.append(std::make_pair(apex, d));
976 }
977 markConstrained(a, b);
978 record(a, b);
979 // Restore the Delaunay property on the edges created by the flips.
980 for (const std::pair<int, int> &ed : newEdges) {
981 int e2 = -1;
982 int t = triangleWithEdge(ed.first, ed.second, &e2);
983 if (t < 0)
984 t = triangleWithEdge(ed.second, ed.first, &e2);
985 if (t >= 0)
986 legalize(t, e2);
987 }
988 return true;
989}
990
991// ── Regions ─────────────────────────────────────────────────────────────
992
994{
995 Vector<int> stack;
996 for (int i = 0; i < m_tris.size(); ++i) {
997 const Triangle &T = m_tris[i];
998 if (!T.alive)
999 continue;
1000 if (isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) || isSuperVertex(T.v[2])) {
1001 m_tris[i].alive = false;
1002 --m_liveTriangles;
1003 stack.append(i);
1004 }
1005 }
1006 while (!stack.isEmpty()) {
1007 const int t = stack.takeLast();
1008 for (int i = 0; i < 3; ++i) {
1009 const int n = m_tris[t].adj[i];
1010 if (n < 0 || !m_tris[n].alive || m_tris[t].constrained[i])
1011 continue;
1012 m_tris[n].alive = false;
1013 --m_liveTriangles;
1014 stack.append(n);
1015 }
1016 }
1017}
1018
1020{
1021 for (Triangle &T : m_tris)
1022 if (T.alive && (isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) || isSuperVertex(T.v[2]))) {
1023 T.alive = false;
1024 --m_liveTriangles;
1025 }
1026}
1027
1029{
1030 const int t0 = locate(p);
1031 if (t0 < 0)
1032 return 0;
1033 Vector<int> stack{t0};
1034 m_tris[t0].alive = false;
1035 --m_liveTriangles;
1036 int removed = 1;
1037 while (!stack.isEmpty()) {
1038 const int t = stack.takeLast();
1039 for (int i = 0; i < 3; ++i) {
1040 const int n = m_tris[t].adj[i];
1041 if (n < 0 || !m_tris[n].alive || m_tris[t].constrained[i])
1042 continue;
1043 m_tris[n].alive = false;
1044 --m_liveTriangles;
1045 ++removed;
1046 stack.append(n);
1047 }
1048 }
1049 return removed;
1050}
1051
1052// Seed by topology, not an epsilon-offset point (which can cross a thin region).
1053template <class Storage> int BasicConstrainedDelaunay<Storage>::removeRegionLeftOf(int a, int b)
1054{
1055 if (!isConstrained(a, b))
1056 return 0;
1057 Vector<int> stack;
1058 const auto chain = constrainedChain(a, b);
1059 for (int k = 0; k + 1 < chain.size(); ++k) {
1060 Vector<int> around;
1061 trianglesAround(chain[k], &around);
1062 for (int t : around) {
1063 const auto &tri = m_tris[t];
1064 if (!tri.alive)
1065 continue;
1066 bool hasB = false;
1067 int apex = -1;
1068 for (int v : tri.v) {
1069 if (v == chain[k + 1])
1070 hasB = true;
1071 else if (v != chain[k])
1072 apex = v;
1073 }
1074 if (hasB && apex >= 0 && orient(a, b, apex) > 0)
1075 stack.append(t);
1076 }
1077 }
1078 int removed = 0;
1079 while (!stack.isEmpty()) {
1080 const int t = stack.takeLast();
1081 if (!m_tris[t].alive)
1082 continue;
1083 m_tris[t].alive = false;
1084 --m_liveTriangles;
1085 ++removed;
1086 for (int e = 0; e < 3; ++e) {
1087 const int n = m_tris[t].adj[e];
1088 if (n >= 0 && m_tris[n].alive && !m_tris[t].constrained[e])
1089 stack.append(n);
1090 }
1091 }
1092 return removed;
1093}
1094
1095// ── Quality refinement ──────────────────────────────────────────────────
1096// Ruppert's algorithm with Shewchuk's refinements, as Triangle implements
1097// them (workplans/MESH_TRIANGLE_ENGINE_PLAN_2026-09-30.md §4).
1098
1099template <class Storage> void BasicConstrainedDelaunay<Storage>::setFixedConstraint(int a, int b)
1100{
1101 const Vector<int> chain = constrainedChain(a, b);
1102 for (int i = 0; i + 1 < chain.size(); ++i)
1103 m_fixedSub.insert(edgeKey64(chain[i], chain[i + 1]));
1104}
1105
1106template <class Storage> bool BasicConstrainedDelaunay<Storage>::isFixedSub(int a, int b) const
1107{
1108 return !m_fixedSub.isEmpty() && m_fixedSub.contains(edgeKey64(a, b));
1109}
1110
1113template <class Storage>
1114bool BasicConstrainedDelaunay<Storage>::subsegmentEncroached(int a, int b) const
1115{
1116 const Point &A = m_pts[a], &B = m_pts[b];
1117 for (int dir = 0; dir < 2; ++dir) {
1118 int e = -1;
1119 const int t = dir == 0 ? triangleWithEdge(a, b, &e) : triangleWithEdge(b, a, &e);
1120 if (t < 0 || !m_tris[t].alive)
1121 continue;
1122 const int c = m_tris[t].v[e];
1123 if (isSuperVertex(c))
1124 continue;
1125 const Point &C = m_pts[c];
1126 if ((A.x() - C.x()) * (B.x() - C.x()) + (A.y() - C.y()) * (B.y() - C.y()) < 0.0)
1127 return true;
1128 }
1129 return false;
1130}
1131
1137template <class Storage> int BasicConstrainedDelaunay<Storage>::splitSubsegment(int a, int b)
1138{
1139 int e = -1;
1140 int t = triangleWithEdge(a, b, &e);
1141 if (t < 0)
1142 t = triangleWithEdge(b, a, &e);
1143 if (t < 0)
1144 return -1;
1145 const std::uint64_t key = edgeKey64(a, b);
1146 const std::uint64_t origin = m_segOrigin.value(key, key);
1147 const std::uint64_t piece = m_segPiece.value(key, key);
1148 const Vector<std::uint64_t> extra = m_segOriginExtra.values(key);
1149
1150 auto acuteAt = [&](int x, int other) {
1151 if (x >= m_superBase)
1152 return false; // refinement vertex, not an input corner
1153 const Point &X
1154 = m_pts[x], &O = m_pts[other];
1155 Vector<int> fan;
1156 trianglesAround(x, &fan);
1157 for (int ft : fan) {
1158 const Triangle &T = m_tris[ft];
1159 const int i = edgeIndex(ft, x);
1160 if (i < 0)
1161 continue;
1162 for (int k = 1; k <= 2; ++k) {
1163 const int w = T.v[(i + k) % 3];
1164 const int ei = (i + (k == 1 ? 2 : 1)) % 3;
1165 if (!T.constrained[ei] || w == other)
1166 continue;
1167 const Point &W = m_pts[w];
1168 if ((W.x() - X.x()) * (O.x() - X.x()) + (W.y() - X.y()) * (O.y() - X.y()) > 0.0)
1169 return true;
1170 }
1171 }
1172 return false;
1173 };
1174 const Point A = m_pts[a], B = m_pts[b];
1175 const double len = std::sqrt(dist2(A, B));
1176 if (!(len > 0.0) || !std::isfinite(len))
1177 return -1;
1178 const bool acA = acuteAt(a, b), acB = acuteAt(b, a);
1179 double split = 0.5;
1180 if (acA != acB) {
1181 double pw = 1.0;
1182 while (len > 3.0 * pw)
1183 pw *= 2.0;
1184 while (len < 1.5 * pw)
1185 pw *= 0.5;
1186 split = pw / len;
1187 if (acB)
1188 split = 1.0 - split;
1189 }
1190 const Point p = A + (B - A) * split;
1191 if (p == A || p == B)
1192 return -1; // below floating-point resolution
1193 const int v = m_pts.size();
1194 m_pts.append(p);
1195 if (!m_origins.isEmpty())
1196 m_origins.append(0);
1197 m_vertexTri.append(-1);
1198 if (!m_origins.isEmpty())
1199 m_origins[v] = ((m_origins[a] | m_origins[b]) & 127u) | SegmentSplit;
1200 splitEdge(t, e, v); // topological split: both halves stay constrained
1201 m_segOrigin.remove(key);
1202 m_segPiece.remove(key);
1203 m_segOriginExtra.remove(key);
1204 for (const std::uint64_t half : {edgeKey64(a, v), edgeKey64(v, b)}) {
1205 m_segOrigin.insert(half, origin);
1206 m_segPiece.insert(half, piece);
1207 for (std::uint64_t o : extra)
1208 m_segOriginExtra.insert(half, o);
1209 }
1210 m_vertexSeg.insert(v, piece);
1211 return v;
1212}
1213
1220template <class Storage>
1221bool BasicConstrainedDelaunay<Storage>::exemptShortestEdge(int u, int v) const
1222{
1223 const auto iu = m_vertexSeg.constFind(u), iv = m_vertexSeg.constFind(v);
1224 if (iu == m_vertexSeg.constEnd() || iv == m_vertexSeg.constEnd() || iu.value() == iv.value())
1225 return false;
1226 const int a1 = keyLo(iu.value()), b1 = keyHi(iu.value());
1227 const int a2 = keyLo(iv.value()), b2 = keyHi(iv.value());
1228 int j = -1;
1229 if (a1 == a2 || a1 == b2)
1230 j = a1;
1231 else if (b1 == a2 || b1 == b2)
1232 j = b1;
1233 if (j < 0)
1234 return false;
1235 const double d1 = dist2(m_pts[u], m_pts[j]), d2 = dist2(m_pts[v], m_pts[j]);
1236 return d1 < 1.001 * d2 && d1 > 0.999 * d2;
1237}
1238
1239template <class Storage> bool BasicConstrainedDelaunay<Storage>::smallAngleExempt(int t) const
1240{
1241 const Triangle &T = m_tris[t];
1242 double best = std::numeric_limits<double>::infinity();
1243 int s = 0;
1244 for (int k = 0; k < 3; ++k) {
1245 const double l = dist2(m_pts[T.v[(k + 1) % 3]], m_pts[T.v[(k + 2) % 3]]);
1246 if (l < best) {
1247 best = l;
1248 s = k;
1249 }
1250 }
1251 return exemptShortestEdge(T.v[(s + 1) % 3], T.v[(s + 2) % 3]);
1252}
1253
1254template <class Storage> bool BasicConstrainedDelaunay<Storage>::touchesFixed(int t) const
1255{
1256 if (m_fixedSub.isEmpty())
1257 return false;
1258 for (int k = 0; k < 3; ++k) {
1259 const int x = m_tris[t].v[k];
1260 Vector<int> fan;
1261 trianglesAround(x, &fan);
1262 for (int ft : fan) {
1263 const Triangle &T = m_tris[ft];
1264 const int i = edgeIndex(ft, x);
1265 if (i < 0)
1266 continue;
1267 for (int kk = 1; kk <= 2; ++kk) {
1268 const int ei = (i + (kk == 1 ? 2 : 1)) % 3;
1269 if (T.constrained[ei] && m_fixedSub.contains(edgeKey64(x, T.v[(i + kk) % 3])))
1270 return true;
1271 }
1272 }
1273 }
1274 return false;
1275}
1276
1277// Build and legalize a cavity before touching shared topology. The ring is
1278// ordered by the closed fan, so boundary edges retain their direction and flags.
1279template <class Storage>
1280bool BasicConstrainedDelaunay<Storage>::prepareRemoval(int v, Removal *out) const
1281{
1282 if (v < m_superBase + 3 || !isVertexActive(v) || m_vertexSeg.contains(v))
1283 return false;
1284 Removal r;
1285 trianglesAround(v, &r.star);
1286 if (r.star.size() < 3 || r.star.size() > 128)
1287 return false;
1288 for (int t : r.star) {
1289 const auto &T = m_tris[t];
1290 const int i = edgeIndex(t, v);
1291 if (!T.alive || i < 0 || T.constrained[(i + 1) % 3] || T.constrained[(i + 2) % 3] ||
1292 T.adj[(i + 1) % 3] < 0 || T.adj[(i + 2) % 3] < 0)
1293 return false;
1294 for (int k = 0; k < 3; ++k)
1295 if (isSuperVertex(T.v[k]))
1296 return false;
1297 r.ring.append(T.v[(i + 1) % 3]);
1298 }
1299 const int n = r.ring.size();
1300 for (int j = 0; j < n; ++j) {
1301 const auto &T = m_tris[r.star[j]];
1302 if (T.v[(edgeIndex(r.star[j], v) + 2) % 3] != r.ring[(j + 1) % n])
1303 return false;
1304 }
1305 auto triangle = [](int a, int b, int c) {
1306 return Triangle{{a, b, c}, {-1, -1, -1}, {false, false, false}, true};
1307 };
1308 Vector<int> polygon = r.ring;
1309 while (polygon.size() > 3) {
1310 bool clipped = false;
1311 for (int j = 0; j < polygon.size(); ++j) {
1312 const int a = polygon[(j + polygon.size() - 1) % polygon.size()], b = polygon[j],
1313 c = polygon[(j + 1) % polygon.size()];
1314 if (!(orient(a, b, c) > 0))
1315 continue;
1316 bool occupied = false;
1317 for (int q : polygon)
1318 if (q != a && q != b && q != c && orient(a, b, q) >= 0 && orient(b, c, q) >= 0 &&
1319 orient(c, a, q) >= 0) {
1320 occupied = true;
1321 break;
1322 }
1323 if (occupied)
1324 continue;
1325 r.replacement.append(triangle(a, b, c));
1326 polygon.removeAt(j);
1327 clipped = true;
1328 break;
1329 }
1330 if (!clipped)
1331 return false;
1332 }
1333 if (!(orient(polygon[0], polygon[1], polygon[2]) > 0))
1334 return false;
1335 r.replacement.append(triangle(polygon[0], polygon[1], polygon[2]));
1336 // Lawson flips confined to scratch triangles. Never call the global
1337 // legalizer after quality gates: it may change triangles outside the star.
1338 bool settled = false;
1339 for (int guard = 0; guard < 4 * n * n; ++guard) {
1340 bool flipped = false;
1341 for (int i = 0; i < r.replacement.size() && !flipped; ++i)
1342 for (int j = i + 1; j < r.replacement.size() && !flipped; ++j)
1343 for (int e = 0; e < 3 && !flipped; ++e)
1344 for (int f = 0; f < 3 && !flipped; ++f) {
1345 auto &T = r.replacement[i];
1346 auto &N = r.replacement[j];
1347 const int a = T.v[e], b = T.v[(e + 1) % 3], c = T.v[(e + 2) % 3],
1348 d = N.v[f];
1349 if (b != N.v[(f + 2) % 3] || c != N.v[(f + 1) % 3])
1350 continue;
1351 if (orient(a, b, d) <= 0 || orient(a, d, c) <= 0 ||
1352 !inCircle(T.v[0], T.v[1], T.v[2], d))
1353 continue;
1354 T = triangle(a, b, d);
1355 N = triangle(a, d, c);
1356 flipped = true;
1357 }
1358 if (!flipped) {
1359 settled = true;
1360 break;
1361 }
1362 }
1363 if (!settled)
1364 return false;
1365 for (int i = 0; i < r.replacement.size(); ++i)
1366 for (int e = 0; e < 3; ++e) {
1367 auto &T = r.replacement[i];
1368 const int a = T.v[(e + 1) % 3], b = T.v[(e + 2) % 3];
1369 bool found = false;
1370 for (int j = 0; j < n; ++j)
1371 if (a == r.ring[j] && b == r.ring[(j + 1) % n]) {
1372 const auto &old = m_tris[r.star[j]];
1373 const int k = edgeIndex(r.star[j], v), neighbour = old.adj[k];
1374 T.adj[e] = neighbour;
1375 T.constrained[e] = old.constrained[k];
1376 found = true;
1377 // Conservatively reject a cavity whose outer edge would need a
1378 // flip. Outside triangles are never modified by legalization.
1379 if (neighbour >= 0 && !T.constrained[e]) {
1380 const auto &N = m_tris[neighbour];
1381 if (!N.alive)
1382 return false;
1383 for (int f = 0; f < 3; ++f)
1384 if (N.adj[f] == r.star[j] && inCircle(T.v[0], T.v[1], T.v[2], N.v[f]))
1385 return false;
1386 }
1387 break;
1388 }
1389 if (found)
1390 continue;
1391 for (int j = 0; j < r.replacement.size() && !found; ++j)
1392 if (i != j)
1393 for (int f = 0; f < 3; ++f)
1394 if (r.replacement[j].v[(f + 1) % 3] == b &&
1395 r.replacement[j].v[(f + 2) % 3] == a) {
1396 T.adj[e] = r.star[j];
1397 found = true;
1398 break;
1399 }
1400 if (!found)
1401 return false;
1402 }
1403 *out = std::move(r);
1404 return true;
1405}
1406
1407template <class Storage>
1408void BasicConstrainedDelaunay<Storage>::commitRemoval(int v, const Removal &r)
1409{
1410 // Repair external backlinks before reusing any old slot.
1411 for (int j = 0; j < r.ring.size(); ++j) {
1412 const int old = r.star[j], k = edgeIndex(old, v), neighbour = m_tris[old].adj[k];
1413 if (neighbour < 0)
1414 continue;
1415 int replacement = -1;
1416 for (int i = 0; i < r.replacement.size(); ++i)
1417 for (int e = 0; e < 3; ++e)
1418 if (r.replacement[i].v[(e + 1) % 3] == r.ring[j] &&
1419 r.replacement[i].v[(e + 2) % 3] == r.ring[(j + 1) % r.ring.size()])
1420 replacement = r.star[i];
1421 for (int e = 0; e < 3; ++e)
1422 if (m_tris[neighbour].v[(e + 1) % 3] == r.ring[(j + 1) % r.ring.size()] &&
1423 m_tris[neighbour].v[(e + 2) % 3] == r.ring[j])
1424 m_tris[neighbour].adj[e] = replacement;
1425 }
1426 for (int t : r.star) {
1427 m_liveTriangles -= m_tris[t].alive ? 1 : 0;
1428 m_tris[t].alive = false;
1429 for (int &a : m_tris[t].adj)
1430 a = -1;
1431 }
1432 for (int i = 0; i < r.replacement.size(); ++i) {
1433 const int t = r.star[i];
1434 m_tris[t] = r.replacement[i];
1435 m_liveTriangles += m_tris[t].alive ? 1 : 0;
1436 for (int q : m_tris[t].v)
1437 m_vertexTri[q] = t;
1438 if (m_triangleChanged)
1439 m_triangleChanged(t);
1440 }
1441 m_vertexTri[v] = -1;
1442 m_lastLocate = r.star[0];
1443}
1444
1445template <class Storage> bool BasicConstrainedDelaunay<Storage>::removeVertex(int v)
1446{
1447 Removal r;
1448 if (!prepareRemoval(v, &r))
1449 return false;
1450 commitRemoval(v, r);
1451 return true;
1452}
1453
1454template <class Storage>
1456{
1457 QualityReport rep;
1458 const bool resolutionOrder = opt.coarseningOrder == CoarseningOrder::ResolutionAware;
1459 if ((opt.coarseningOrder != CoarseningOrder::LegacyFlatness && !resolutionOrder) ||
1460 (resolutionOrder &&
1461 (!opt.coarseningTargetSize || !(opt.coarseningReferenceSize > 0) ||
1462 !std::isfinite(opt.coarseningReferenceSize) || !(opt.normalVerticalToXY > 0) ||
1463 !std::isfinite(opt.normalVerticalToXY))) ||
1464 (opt.terrainToleranceForFootprint &&
1465 (!(opt.terrainTolerance > 0) || !opt.terrainError || !opt.terrainElevationAt))) {
1466 rep.invalidPolicy = true;
1467 return rep;
1468 }
1469 auto triangleBox = [&](const Triangle &cell) {
1470 double x0 = m_pts[cell.v[0]].x(), x1 = x0, y0 = m_pts[cell.v[0]].y(), y1 = y0;
1471 for (int k = 1; k < 3; ++k) {
1472 const auto p = m_pts[cell.v[k]];
1473 x0 = std::min(x0, p.x());
1474 x1 = std::max(x1, p.x());
1475 y0 = std::min(y0, p.y());
1476 y1 = std::max(y1, p.y());
1477 }
1478 return Box(Point(x0, y0), Point(x1, y1));
1479 };
1480 auto toleranceFor = [&](const Triangle &cell) {
1481 const double value = opt.terrainToleranceForFootprint
1482 ? opt.terrainToleranceForFootprint(triangleBox(cell))
1483 : opt.terrainTolerance;
1484 if (value > 0 && std::isfinite(value))
1485 return value;
1486 if (opt.terrainToleranceForFootprint)
1487 rep.invalidPolicy = true;
1488 return std::numeric_limits<double>::quiet_NaN();
1489 };
1490 const double theta = std::clamp(opt.minAngleDeg, 0.0, 60.0) * M_PI / 180.0;
1491 const double sinMin = std::sin(theta);
1492 // Üngör's off-centre at Triangle's 0.475 factor: the new triangle on the
1493 // shortest edge gets an apex angle just over the bound.
1494 const double offK =
1495 theta > 0.0 ? 0.475 * std::sqrt((1.0 + std::cos(theta)) / (1.0 - std::cos(theta))) : 0.0;
1496 bool stop = false;
1497 const double minEdge2 = opt.minEdge > 0.0 ? opt.minEdge * opt.minEdge : 0.0;
1498 auto tooShortToSplit = [&](int a, int b) {
1499 return minEdge2 > 0.0 && dist2(m_pts[a], m_pts[b]) < 4.0 * minEdge2;
1500 };
1501 const bool terrain = opt.terrainError && opt.terrainElevationAt && opt.terrainTolerance > 0;
1502 // Terrain insertion must respect nearby vertices beyond the source
1503 // triangle too. Quality/constraint repairs retain their separate floor.
1504 const double terrainSpacing = std::max(opt.terrainMinSpacing, opt.minEdge);
1506 auto terrainKey = [&](const Point &p) {
1507 return std::make_pair(std::int64_t(std::floor(p.x() / terrainSpacing)),
1508 std::int64_t(std::floor(p.y() / terrainSpacing)));
1509 };
1510 auto indexTerrainVertex = [&](int v) {
1511 if (terrain && terrainSpacing > 0 && !isSuperVertex(v))
1512 terrainGrid[terrainKey(m_pts[v])].append(v);
1513 };
1514 auto rebuildTerrainGrid = [&] {
1515 terrainGrid.clear();
1516 for (int v = 0; v < m_pts.size(); ++v)
1517 indexTerrainVertex(v);
1518 };
1519 auto terrainClear = [&](const Point &p) {
1520 if (!(terrainSpacing > 0))
1521 return true;
1522 const auto key = terrainKey(p);
1523 for (int dy = -1; dy <= 1; ++dy)
1524 for (int dx = -1; dx <= 1; ++dx) {
1525 const auto it =
1526 terrainGrid.constFind(std::make_pair(key.first + dx, key.second + dy));
1527 if (it != terrainGrid.cend())
1528 for (int v : *it)
1529 if (dist2(p, m_pts[v]) < terrainSpacing * terrainSpacing * (1.0 - 1e-12))
1530 return false;
1531 }
1532 return true;
1533 };
1534 if (terrain)
1535 rebuildTerrainGrid();
1536 m_terrainElevations.clear();
1537 auto &heights = m_terrainElevations;
1538 auto terrainError = [&](int t, Point *p) {
1539 const auto &T = m_tris[t];
1540 while (heights.size() < m_pts.size())
1541 heights.append(std::numeric_limits<double>::infinity());
1542 Point xy[3];
1543 double z[3];
1544 for (int k = 0; k < 3; ++k) {
1545 const int v = T.v[k];
1546 xy[k] = m_pts[v];
1547 if (std::isinf(heights[v])) {
1548 const double h = opt.terrainElevationAt(xy[k].x(), xy[k].y());
1549 heights[v] = std::isfinite(h) ? h : std::numeric_limits<double>::quiet_NaN();
1550 }
1551 z[k] = heights[v];
1552 }
1553 return opt.terrainError(xy, z, p);
1554 };
1555 // The terrain error is a pure function of a triangle's ordered vertices
1556 // (heights never change once set), so the final pass may reuse the last
1557 // finite value measured for the same triple in the same slot.
1558 struct Measured {
1559 int v[3] = {-1, -1, -1};
1560 double e = 0;
1561 };
1562 Vector<Measured> measured;
1563 auto measure = [&](int t, Point *p) {
1564 const double e = terrainError(t, p);
1565 if (std::isfinite(e)) {
1566 if (measured.size() <= t)
1567 measured.resize(std::max<std::ptrdiff_t>(t + 1, measured.size() * 3 / 2));
1568 Measured &m = measured[t];
1569 for (int k = 0; k < 3; ++k)
1570 m.v[k] = m_tris[t].v[k];
1571 m.e = e;
1572 }
1573 return e;
1574 };
1575 int reason = 0; // 1 size, 2 angle, 3 terrain; count successful inserts only.
1576 bool repairing = false;
1577 int repairBegin = 0;
1578 auto footprintCap = [&](const Triangle &t) {
1579 if (!opt.maximumAreaForTriangle)
1580 return 0.;
1581 const Point xy[3] = {m_pts[t.v[0]], m_pts[t.v[1]], m_pts[t.v[2]]};
1582 return opt.maximumAreaForTriangle(xy);
1583 };
1584
1585 // Point for a bad triangle; false when it meets both bounds.
1586 auto badPoint = [&](int t, Point *out) {
1587 reason = 0;
1588 const Triangle &T = m_tris[t];
1589 if (!T.alive || isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) || isSuperVertex(T.v[2]))
1590 return false;
1591 const Point &A = m_pts[T.v[0]], &B = m_pts[T.v[1]], &C = m_pts[T.v[2]];
1592 const double cross = (B.x() - A.x()) * (C.y() - A.y()) - (B.y() - A.y()) * (C.x() - A.x());
1593 if (!(cross > 0.0))
1594 return false;
1595 const double l[3] = {dist2(B, C), dist2(C, A), dist2(A, B)}; // edge opposite v[i]
1596 const int s = (l[0] <= l[1] && l[0] <= l[2]) ? 0 : (l[1] <= l[2] ? 1 : 2);
1597 bool bad = false;
1598 if (opt.hAt) {
1599 const Point g = (A + B + C) / 3.0;
1600 const double h = opt.hAt(g.x(), g.y());
1601 if (h > 0.0 && 0.5 * cross > kEquilateralArea * h * h) {
1602 bad = true;
1603 reason = 1;
1604 }
1605 }
1606 if (!bad && opt.maximumAreaForTriangle) {
1607 const double cap = footprintCap(T);
1608 if (cap > 0 && .5 * cross > cap * (1 + 1e-10)) {
1609 bad = true;
1610 reason = 1;
1611 }
1612 }
1613 if (!bad && repairing && opt.maximumAdjacentRatio >= 1) {
1614 for (int neighbour : T.adj) {
1615 if (neighbour < 0 || !m_tris[neighbour].alive)
1616 continue;
1617 const auto &other = m_tris[neighbour];
1618 if (isSuperVertex(other.v[0]) || isSuperVertex(other.v[1]) ||
1619 isSuperVertex(other.v[2]))
1620 continue;
1621 const double otherArea =
1622 std::abs(orientPts(m_pts[other.v[0]], m_pts[other.v[1]], m_pts[other.v[2]])) *
1623 .5;
1624 if (std::sqrt(.5 * cross / otherArea) > opt.maximumAdjacentRatio * (1 + 1e-10)) {
1625 bad = true;
1626 reason = 1;
1627 break;
1628 }
1629 }
1630 if (!bad && opt.fixedNeighbourSize)
1631 for (int k = 0; k < 3; ++k) {
1632 const double neighbour =
1633 opt.fixedNeighbourSize(T.v[(k + 1) % 3], T.v[(k + 2) % 3]);
1634 if (neighbour > 0 && std::sqrt(.5 * cross / kEquilateralArea) / neighbour >
1635 opt.maximumAdjacentRatio * (1 + 1e-10)) {
1636 bad = true;
1637 reason = 1;
1638 break;
1639 }
1640 }
1641 }
1642 if (!bad && sinMin > 0.0 && l[s] >= minEdge2) {
1643 const double sinA =
1644 cross /
1645 std::sqrt(l[(s + 1) % 3] * l[(s + 2) % 3]); // angle opposite the shortest edge
1646 if (sinA < sinMin && !exemptShortestEdge(T.v[(s + 1) % 3], T.v[(s + 2) % 3])) {
1647 bad = true;
1648 reason = 2;
1649 }
1650 }
1651 if (!bad && terrain && (!opt.terrainWorstFirst || repairing)) {
1652 const double e = measure(t, out);
1653 if (std::isfinite(e) && e > toleranceFor(T)) {
1654 const double clearance = std::max(opt.terrainMinSpacing, opt.minEdge);
1655 // A protected elevation or minimum spacing may make the
1656 // tolerance impossible. Leave it for the explicit final report.
1657 if (std::min({dist2(*out, A), dist2(*out, B), dist2(*out, C)}) <=
1658 clearance * clearance)
1659 return false;
1660 reason = 3;
1661 return true;
1662 }
1663 }
1664 if (!bad)
1665 return false;
1666 // Shortest edge P→Q with R on its left (counter-clockwise order).
1667 const Point &P = m_pts[T.v[(s + 1) % 3]], &Q = m_pts[T.v[(s + 2) % 3]], &R = m_pts[T.v[s]];
1668 const double bx = Q.x() - P.x(), by = Q.y() - P.y(), cx = R.x() - P.x(), cy = R.y() - P.y();
1669 const double d = 2.0 * (bx * cy - by * cx);
1670 if (!(d > 0.0))
1671 return false;
1672 const double b2 = bx * bx + by * by, c2 = cx * cx + cy * cy;
1673 double ux = (cy * b2 - by * c2) / d, uy = (bx * c2 - cx * b2) / d;
1674 if (offK > 0.0) {
1675 const double mx = 0.5 * bx, my = 0.5 * by;
1676 const double ox = mx - offK * by, oy = my + offK * bx;
1677 if ((ox - mx) * (ox - mx) + (oy - my) * (oy - my) <
1678 (ux - mx) * (ux - mx) + (uy - my) * (uy - my)) {
1679 ux = ox;
1680 uy = oy;
1681 }
1682 }
1683 *out = Point(P.x() + ux, P.y() + uy);
1684 return std::isfinite(out->x()) && std::isfinite(out->y());
1685 };
1686
1687 // Straight walk from triangle t's centroid to p. Returns the live
1688 // triangle containing p, or -1 with *blockT/*blockE set to the first
1689 // constrained edge the walk would cross (-1 when lost).
1690 auto walkTo = [&](int t, const Point &p, int *blockT, int *blockE) {
1691 *blockT = *blockE = -1;
1692 const Triangle &T0 = m_tris[t];
1693 const Point g = (m_pts[T0.v[0]] + m_pts[T0.v[1]] + m_pts[T0.v[2]]) / 3.0;
1694 const int cap = 4 * m_tris.size() + 16;
1695 for (int step = 0; step < cap; ++step) {
1696 const Triangle &T = m_tris[t];
1697 double o[3];
1698 for (int i = 0; i < 3; ++i)
1699 o[i] = orient(T.v[(i + 1) % 3], T.v[(i + 2) % 3], p);
1700 if (o[0] >= 0.0 && o[1] >= 0.0 && o[2] >= 0.0)
1701 return t;
1702 int exit = -1;
1703 for (int i = 0; i < 3 && exit < 0; ++i) {
1704 if (!(o[i] < 0.0))
1705 continue;
1706 const double s1 = orientPts(g, p, m_pts[T.v[(i + 1) % 3]]);
1707 const double s2 = orientPts(g, p, m_pts[T.v[(i + 2) % 3]]);
1708 if ((s1 <= 0.0 && s2 >= 0.0) || (s1 >= 0.0 && s2 <= 0.0))
1709 exit = i;
1710 }
1711 if (exit < 0)
1712 for (int i = 0; i < 3 && exit < 0; ++i)
1713 if (o[i] < 0.0)
1714 exit = i;
1715 if (T.constrained[exit]) {
1716 *blockT = t;
1717 *blockE = exit;
1718 return -1;
1719 }
1720 const int n = T.adj[exit];
1721 if (n < 0 || !m_tris[n].alive)
1722 return -1;
1723 t = n;
1724 }
1725 return -1;
1726 };
1727
1728 // Constrained edges of the insertion cavity of p (triangles whose
1729 // circumcircle holds p, reached without crossing a constraint) whose
1730 // diametral circle holds p.
1731 auto cavityEncroached = [&](int t0, const Point &p, Vector<std::uint64_t> *enc) {
1732 const double pd[2] = {p.x(), p.y()};
1733 Vector<int> stack{t0}, seen{t0};
1734 while (!stack.isEmpty()) {
1735 const int t = stack.takeLast();
1736 const Triangle &T = m_tris[t];
1737 for (int k = 0; k < 3; ++k) {
1738 const int a = T.v[(k + 1) % 3], b = T.v[(k + 2) % 3];
1739 if (T.constrained[k]) {
1740 const Point &A = m_pts[a], &B = m_pts[b];
1741 if ((A.x() - p.x()) * (B.x() - p.x()) + (A.y() - p.y()) * (B.y() - p.y()) < 0.0)
1742 enc->append(edgeKey64(a, b));
1743 continue;
1744 }
1745 const int n = T.adj[k];
1746 if (n < 0 || !m_tris[n].alive || seen.contains(n))
1747 continue;
1748 const Triangle &N = m_tris[n];
1749 if (isSuperVertex(N.v[0]) || isSuperVertex(N.v[1]) || isSuperVertex(N.v[2]))
1750 continue;
1751 const double pa[2] = {m_pts[N.v[0]].x(), m_pts[N.v[0]].y()};
1752 const double pb[2] = {m_pts[N.v[1]].x(), m_pts[N.v[1]].y()};
1753 const double pc[2] = {m_pts[N.v[2]].x(), m_pts[N.v[2]].y()};
1754 if (!(incircle(pa, pb, pc, pd) > 0.0))
1755 continue;
1756 seen.append(n);
1757 stack.append(n);
1758 }
1759 }
1760 };
1761
1762 Vector<std::uint64_t> segQueue;
1763 // Intrusive FIFO angle buckets: one pending entry per triangle, no stale
1764 // history proportional to all insertions, and no O(log N) heap operations.
1765 std::array<int, 32> heads, tails;
1766 heads.fill(-1);
1767 tails.fill(-1);
1768 Vector<int> next(m_tris.size(), -2); // -2 means not queued, -1 end of list
1769 auto enqueue = [&](int t) {
1770 while (next.size() < m_tris.size())
1771 next.append(-2);
1772 const auto &T = m_tris[t];
1773 if (!T.alive || next[t] != -2)
1774 return;
1775 int bucket = 31;
1776 if (opt.prioritizeQuality && sinMin > 0) {
1777 const auto &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
1778 const double cross =
1779 std::abs((b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x()));
1780 const double l0 = dist2(a, b), l1 = dist2(b, c), l2 = dist2(c, a);
1781 const double denom = std::sqrt(std::max({l0 * l1, l1 * l2, l2 * l0}));
1782 if (denom > 0)
1783 bucket = std::clamp(int(31 * cross / (denom * sinMin)), 0, 31);
1784 }
1785 next[t] = -1;
1786 if (tails[bucket] >= 0)
1787 next[tails[bucket]] = t;
1788 else
1789 heads[bucket] = t;
1790 tails[bucket] = t;
1791 };
1792 auto dequeue = [&]() {
1793 for (int k = 0; k < 32; ++k)
1794 if (heads[k] >= 0) {
1795 const int t = heads[k];
1796 heads[k] = next[t];
1797 if (heads[k] < 0)
1798 tails[k] = -1;
1799 next[t] = -2;
1800 return t;
1801 }
1802 return -1;
1803 };
1804 m_triangleChanged = enqueue;
1805 // The cell budget counts live triangles: removed regions (exterior,
1806 // holes, patch interiors) leave dead slots that are never refilled.
1807 GenerationProgress::Stage progressStage = GenerationProgress::Stage::Refining;
1808 auto publishProgress = [&] {
1809 if (opt.onProgress) {
1810 GenerationProgress state;
1811 state.stage = progressStage;
1812 state.workingVertices = std::max<std::ptrdiff_t>(0, m_pts.size() - 3);
1813 state.triangles = liveTriangleCount();
1814 state.inserted = rep.inserted;
1815 state.removed = rep.coarsenRemoved;
1816 opt.onProgress(state);
1817 }
1818 };
1819 publishProgress();
1820 const auto clearObserver = detail::scopeExit([&] { m_triangleChanged = {}; });
1821 auto queueEncroachedOf = [&](int t) {
1822 const Triangle &T = m_tris[t];
1823 for (int k = 0; k < 3; ++k) {
1824 if (!T.constrained[k])
1825 continue;
1826 const int a = T.v[(k + 1) % 3], b = T.v[(k + 2) % 3];
1827 if (isSuperVertex(a) || isSuperVertex(b) || isFixedSub(a, b))
1828 continue;
1829 if (subsegmentEncroached(a, b))
1830 segQueue.append(edgeKey64(a, b));
1831 }
1832 };
1833 auto afterInsert = [&](int v) {
1834 indexTerrainVertex(v);
1835 ++rep.inserted;
1836 Vector<int> star;
1837 trianglesAround(v, &star);
1838 for (int t : std::as_const(star))
1839 if (m_tris[t].alive) {
1840 enqueue(t);
1841 queueEncroachedOf(t);
1842 }
1843 if (rep.inserted >= opt.maxInsertions || liveTriangleCount() >= opt.maxTriangles) {
1844 rep.capped = true;
1845 stop = true;
1846 }
1847 if (repairing && rep.inserted - repairBegin >= opt.maximumSizingRepairInsertions)
1848 rep.sizingRepairLimited = true;
1849 if ((rep.inserted & 4095) == 0) {
1850 publishProgress();
1851 if (opt.cancelled && opt.cancelled()) {
1852 rep.cancelled = true;
1853 stop = true;
1854 }
1855 }
1856 };
1857 auto drainSegments = [&]() {
1858 while (!segQueue.isEmpty() && !stop && !(repairing && rep.sizingRepairLimited)) {
1859 const std::uint64_t k = segQueue.takeLast();
1860 const int a = keyLo(k), b = keyHi(k);
1861 if (isFixedSub(a, b) || tooShortToSplit(a, b) || !isConstrained(a, b) ||
1862 !subsegmentEncroached(a, b))
1863 continue;
1864 const int v = splitSubsegment(a, b);
1865 if (v < 0)
1866 continue;
1867 ++rep.segmentSplits;
1868 afterInsert(v);
1869 }
1870 };
1871
1872 for (int i = 0; i < m_tris.size(); ++i)
1873 if (m_tris[i].alive) {
1874 enqueue(i);
1875 queueEncroachedOf(i);
1876 }
1877 if (liveTriangleCount() >= opt.maxTriangles) {
1878 rep.capped = true;
1879 stop = true;
1880 }
1881 drainSegments();
1882
1884 // Insert p for bad triangle t (reason already set), or split the
1885 // subsegments p would encroach. Returns false when nothing changed.
1886 auto tryInsert = [&](int t, Point p) {
1887 if (reason == 3 && !terrainClear(p))
1888 return false;
1889 enc.clear();
1890 int blockT = -1, blockE = -1;
1891 int c = walkTo(t, p, &blockT, &blockE);
1892 if (c < 0) {
1893 if (blockT < 0)
1894 return false; // lost the point: leave the triangle
1895 enc.append(
1896 edgeKey64(m_tris[blockT].v[(blockE + 1) % 3], m_tris[blockT].v[(blockE + 2) % 3]));
1897 } else
1898 cavityEncroached(c, p, &enc);
1899 if (!enc.isEmpty()) {
1900 // Split what the point would encroach instead of inserting it
1901 // (the subsegments need not be encroached by an existing vertex).
1902 bool split = false;
1903 for (std::uint64_t k : std::as_const(enc)) {
1904 const int a = keyLo(k), b = keyHi(k);
1905 if (stop || isFixedSub(a, b) || tooShortToSplit(a, b) || !isConstrained(a, b))
1906 continue;
1907 if (reason == 3 && !terrainClear((m_pts[a] + m_pts[b]) * 0.5))
1908 continue;
1909 const int v = splitSubsegment(a, b);
1910 if (v < 0)
1911 continue;
1912 ++rep.segmentSplits;
1913 split = true;
1914 afterInsert(v);
1915 }
1916 if (!split) {
1917 ++rep.blockedByFixed;
1918 // Size repair may need a skinny triangle against an immutable
1919 // quad edge. An interior insertion preserves that edge exactly;
1920 // ordinary constrained edges must still use segment recovery.
1921 if (!repairing || reason != 1 || !opt.maximumAreaForTriangle)
1922 return false;
1923 const auto &cell = m_tris[t];
1924 const auto a = m_pts[cell.v[0]], b = m_pts[cell.v[1]], cxy = m_pts[cell.v[2]];
1925 const double cap = footprintCap(cell);
1926 if (!(cap > 0) || orientPts(a, b, cxy) * .5 <= cap * (1 + 1e-10))
1927 return false;
1928 const Point center = a + ((b - a) + (cxy - a)) / 3.;
1929 if (!std::isfinite(center.x()) || !std::isfinite(center.y()) ||
1930 std::min({dist2(center, a), dist2(center, b), dist2(center, cxy)}) < minEdge2 ||
1931 orientPts(a, b, center) <= 0 || orientPts(b, cxy, center) <= 0 ||
1932 orientPts(cxy, a, center) <= 0)
1933 return false;
1934 enc.clear();
1935 cavityEncroached(t, center, &enc);
1936 for (std::uint64_t key : enc)
1937 if (!isFixedSub(keyLo(key), keyHi(key)))
1938 return false;
1939 p = center;
1940 c = t;
1941 } else {
1942 enqueue(t); // retried with the subsegments split
1943 drainSegments();
1944 return true;
1945 }
1946 }
1947 m_lastLocate = c;
1948 const int before = m_pts.size();
1949 const int v = insertPoint(p);
1950 if (v < 0 || v < before)
1951 return false; // outside or coincident
1952 addVertexOrigin(v, reason == 1 ? Size : reason == 2 ? Angle : Terrain);
1953 if (reason == 1)
1954 ++rep.sizeInsertions;
1955 else if (reason == 2)
1956 ++rep.qualityInsertions;
1957 else if (reason == 3)
1958 ++rep.terrainInsertions;
1959 afterInsert(v);
1960 drainSegments();
1961 return true;
1962 };
1963 int examined = 0;
1964 auto drainQueue = [&]() {
1965 while (!stop && !rep.invalidPolicy && !(repairing && rep.sizingRepairLimited)) {
1966 const int t = dequeue();
1967 if (t < 0)
1968 break;
1969 if ((++examined & 4095) == 0) {
1970 publishProgress();
1971 if (opt.cancelled && opt.cancelled()) {
1972 rep.cancelled = true;
1973 break;
1974 }
1975 }
1976 Point p;
1977 if (!badPoint(t, &p))
1978 continue;
1979 tryInsert(t, p);
1980 }
1981 };
1982 drainQueue();
1983
1984 // Non-degrading smoothing (MESH_REGIONAL_TRIQUAD_PLAN D-R6): before any
1985 // terrain point is placed, so no sampled height is moved off its spot.
1986 if (opt.smoothingPasses > 0 && (!terrain || opt.terrainWorstFirst) && !stop && !rep.cancelled) {
1987 progressStage = GenerationProgress::Stage::Smoothing;
1988 publishProgress();
1989 auto minAngleOf = [&](int t) {
1990 const auto &T = m_tris[t];
1991 const Point &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
1992 const double l[3] = {dist2(b, c), dist2(c, a), dist2(a, b)};
1993 const double cross =
1994 std::abs((b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x()));
1995 // sin of the angle opposite the shortest edge
1996 const int s = (l[0] <= l[1] && l[0] <= l[2]) ? 0 : (l[1] <= l[2] ? 1 : 2);
1997 return cross / std::sqrt(l[(s + 1) % 3] * l[(s + 2) % 3]);
1998 };
1999 auto positive = [&](int t) {
2000 const auto &T = m_tris[t];
2001 const Point &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
2002 return (b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x()) > 0.0;
2003 };
2004 const int firstFree = m_superBase + 3;
2005 Vector<int> star;
2006 for (int pass = 0; pass < opt.smoothingPasses; ++pass) {
2007 for (int v = firstFree; v < m_pts.size(); ++v) {
2008 star.clear();
2009 trianglesAround(v, &star);
2010 if (star.size() < 3)
2011 continue;
2012 bool fixed = false;
2013 double wsum = 0, cx = 0, cy = 0, before = 1e300;
2014 for (int t : std::as_const(star)) {
2015 const auto &T = m_tris[t];
2016 const int i = edgeIndex(t, v);
2017 if (!T.alive || i < 0 || isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) ||
2018 isSuperVertex(T.v[2]) || T.constrained[(i + 1) % 3] ||
2019 T.constrained[(i + 2) % 3] || T.adj[(i + 1) % 3] < 0 ||
2020 T.adj[(i + 2) % 3] < 0) {
2021 fixed = true;
2022 break;
2023 }
2024 const Point &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
2025 const double area = 0.5 * std::abs((b.x() - a.x()) * (c.y() - a.y()) -
2026 (b.y() - a.y()) * (c.x() - a.x()));
2027 cx += area * (a.x() + b.x() + c.x()) / 3.0;
2028 cy += area * (a.y() + b.y() + c.y()) / 3.0;
2029 wsum += area;
2030 before = std::min(before, minAngleOf(t));
2031 }
2032 if (fixed || !(wsum > 0.0))
2033 continue;
2034 const Point old = m_pts[v];
2035 m_pts[v] = Point(cx / wsum, cy / wsum);
2036 double after = 1e300;
2037 bool valid = true;
2038 for (int t : std::as_const(star)) {
2039 if (!positive(t)) {
2040 valid = false;
2041 break;
2042 }
2043 after = std::min(after, minAngleOf(t));
2044 }
2045 if (!valid || !(after > before * (1.0 + 1e-9))) {
2046 m_pts[v] = old;
2047 continue;
2048 }
2049 for (int t : std::as_const(star))
2050 if (m_tris[t].alive) {
2051 const int i = edgeIndex(t, v);
2052 if (i >= 0)
2053 legalize(t, i);
2054 }
2055 }
2056 }
2057 }
2058
2059 // Worst-first terrain refinement: with size and angle met, spend the
2060 // remaining cell budget where the surface error is largest, so a capped
2061 // run still has even fidelity. Each insertion restores quality locally,
2062 // then only the triangles it changed are measured again.
2063 if (terrain && opt.terrainWorstFirst && !stop && !rep.cancelled) {
2064 progressStage = GenerationProgress::Stage::Terrain;
2065 publishProgress();
2066 // Smoothing may have moved vertices since the initial grid build.
2067 rebuildTerrainGrid();
2068 struct Candidate {
2069 double e;
2070 int t;
2071 int v[3];
2072 Point p;
2073 };
2074 auto worse = [](const Candidate &a, const Candidate &b) { return a.e < b.e; };
2075 std::priority_queue<Candidate, std::vector<Candidate>, decltype(worse)> heap(worse);
2076 auto consider = [&](int t) {
2077 const Triangle &T = m_tris[t];
2078 if (!T.alive || isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) || isSuperVertex(T.v[2]))
2079 return;
2080 const Point &A = m_pts[T.v[0]], &B = m_pts[T.v[1]], &C = m_pts[T.v[2]];
2081 if (!((B.x() - A.x()) * (C.y() - A.y()) - (B.y() - A.y()) * (C.x() - A.x()) > 0.0))
2082 return;
2083 Point p;
2084 const double e = measure(t, &p);
2085 const double localTolerance = toleranceFor(T);
2086 if (!std::isfinite(e) || !std::isfinite(localTolerance) || e <= localTolerance)
2087 return;
2088 const double clearance = std::max(opt.terrainMinSpacing, opt.minEdge);
2089 if (std::min({dist2(p, A), dist2(p, B), dist2(p, C)}) <= clearance * clearance)
2090 return;
2091 heap.push({opt.terrainToleranceForFootprint ? e / localTolerance : e,
2092 t,
2093 {T.v[0], T.v[1], T.v[2]},
2094 p});
2095 };
2096 for (int t = 0; t < m_tris.size(); ++t)
2097 consider(t);
2099 m_triangleChanged = [&](int t) {
2100 enqueue(t);
2101 changed.append(t);
2102 };
2103 while (!heap.empty() && !stop) {
2104 const Candidate c = heap.top();
2105 heap.pop();
2106 const Triangle &T = m_tris[c.t];
2107 if (!T.alive || T.v[0] != c.v[0] || T.v[1] != c.v[1] || T.v[2] != c.v[2])
2108 continue;
2109 if ((++examined & 4095) == 0 && opt.cancelled && opt.cancelled()) {
2110 rep.cancelled = true;
2111 break;
2112 }
2113 changed.clear();
2114 reason = 3;
2115 if (!tryInsert(c.t, c.p))
2116 continue;
2117 drainQueue();
2118 for (int t : std::as_const(changed))
2119 consider(t);
2120 }
2121 m_triangleChanged = enqueue;
2122 }
2123 // Coarsening cannot use refinement witnesses as upper bounds. Only the
2124 // explicit coverage-aware callback (and bounds derived from it) certifies
2125 // replacement triangles. Removed slots and vertex ids remain stable.
2126 m_triangleChanged = {};
2127 if (opt.coarsenPasses > 0 && !rep.cancelled) {
2128 progressStage = GenerationProgress::Stage::Coarsening;
2129 publishProgress();
2130 detail::Timer clock;
2131 clock.start();
2132 rep.coarsenSkippedTerrain = terrain && !opt.terrainErrorBound;
2133 if (!rep.coarsenSkippedTerrain) {
2134 const double unknown = std::numeric_limits<double>::quiet_NaN();
2135 Vector<double> bounds(terrain ? m_tris.size() : 0, unknown);
2136 Vector<unsigned char> attempts(m_pts.size(), 0);
2137 Vector<unsigned> versions(resolutionOrder ? 0 : m_pts.size(), 0);
2138 const auto freeSlots = std::max<std::ptrdiff_t>(0, m_pts.size() - m_superBase - 3);
2139 CoarseningQueue resolutionHeap(resolutionOrder && freeSlots ? m_pts.size() : 0,
2140 resolutionOrder ? freeSlots : 0);
2141 // Serial Morton tiles improve DEM locality; boundary stars are
2142 // allowed because no concurrent topology mutation takes place.
2143 double x0 = std::numeric_limits<double>::infinity(), y0 = x0, x1 = -x0, y1 = -x0;
2144 Vector<char> used(m_pts.size(), 0);
2145 for (const auto &T : m_tris)
2146 if (T.alive)
2147 for (int v : T.v)
2148 if (!isSuperVertex(v)) {
2149 used[v] = 1;
2150 x0 = std::min(x0, m_pts[v].x());
2151 y0 = std::min(y0, m_pts[v].y());
2152 x1 = std::max(x1, m_pts[v].x());
2153 y1 = std::max(y1, m_pts[v].y());
2154 }
2155 auto key = [&](int v, unsigned scale) {
2156 const unsigned x =
2157 x1 > x0
2158 ? unsigned(std::clamp((m_pts[v].x() - x0) / (x1 - x0), 0.0, 1.0) * scale)
2159 : 0;
2160 const unsigned y =
2161 y1 > y0
2162 ? unsigned(std::clamp((m_pts[v].y() - y0) / (y1 - y0), 0.0, 1.0) * scale)
2163 : 0;
2164 return mortonKey(x, y);
2165 };
2166 if (terrain) {
2167 // Capped refinement may not have reached terrain at all.
2168 // Sample each needed elevation once, spatially ordered; the
2169 // assembly stage reuses these same values afterwards.
2170 while (heights.size() < m_pts.size())
2171 heights.append(std::numeric_limits<double>::infinity());
2173 for (int v = 0; v < m_pts.size(); ++v)
2174 if (used[v] && std::isinf(heights[v]))
2175 pending.append({key(v, 65535), v});
2176 std::sort(pending.begin(), pending.end());
2177 for (int i = 0; i < pending.size(); ++i) {
2178 if ((i & 4095) == 0 && opt.cancelled && opt.cancelled()) {
2179 rep.cancelled = true;
2180 break;
2181 }
2182 const int v = pending[i].second;
2183 heights[v] = opt.terrainElevationAt(m_pts[v].x(), m_pts[v].y());
2184 }
2185 }
2186 struct Candidate {
2187 float score;
2188 int v;
2189 unsigned version, tile;
2190 };
2191 auto less = [](const Candidate &a, const Candidate &b) {
2192 if (a.tile != b.tile)
2193 return a.tile > b.tile;
2194 return a.score != b.score ? a.score < b.score : a.v > b.v;
2195 };
2196 std::priority_queue<Candidate, std::vector<Candidate>, decltype(less)> heap(less);
2197 auto height = [&](int v) {
2198 while (heights.size() < m_pts.size())
2199 heights.append(std::numeric_limits<double>::infinity());
2200 if (std::isinf(heights[v]))
2201 heights[v] = opt.terrainElevationAt(m_pts[v].x(), m_pts[v].y());
2202 return heights[v];
2203 };
2204 auto score = [&](int v, const Vector<int> &star) {
2205 if (!terrain)
2206 return 1.0;
2207 std::array<double, 3> sum{0, 0, 0};
2209 for (int t : star) {
2210 const auto &T = m_tris[t];
2211 const Point b = m_pts[T.v[1]] - m_pts[T.v[0]],
2212 c = m_pts[T.v[2]] - m_pts[T.v[0]];
2213 const double scale = resolutionOrder ? opt.normalVerticalToXY : 1.;
2214 const double z = height(T.v[0]), bz = (height(T.v[1]) - z) * scale,
2215 cz = (height(T.v[2]) - z) * scale;
2216 if (!std::isfinite(z) || !std::isfinite(bz) || !std::isfinite(cz))
2217 return -1.0;
2218 std::array<double, 3> normal{b.y() * cz - bz * c.y(), bz * c.x() - b.x() * cz,
2219 b.x() * c.y() - b.y() * c.x()};
2220 const double length = std::hypot(normal[0], normal[1], normal[2]);
2221 if (!(length > 0))
2222 return -1.0;
2223 for (int k = 0; k < 3; ++k) {
2224 sum[k] += normal[k];
2225 normal[k] /= length;
2226 }
2227 normals.append(normal);
2228 }
2229 const double length = std::hypot(sum[0], sum[1], sum[2]);
2230 if (!(length > 0))
2231 return -1.0;
2232 double result = 1;
2233 for (const auto &n : normals)
2234 result =
2235 std::min(result, (n[0] * sum[0] + n[1] * sum[1] + n[2] * sum[2]) / length);
2236 return result;
2237 };
2238 const int limit = std::clamp(opt.coarsenPasses, 1, 10);
2239 auto queue = [&](int v) {
2240 if (v < m_superBase + 3 || !isVertexActive(v) || m_vertexSeg.contains(v) ||
2241 opt.protectedVertices.contains(v) ||
2242 (opt.protectedLocation && opt.protectedLocation(m_pts[v])) ||
2243 attempts[v] >= limit)
2244 return;
2245 Vector<int> star;
2246 if (terrain || resolutionOrder)
2247 trianglesAround(v, &star);
2248 const double flatness = score(v, star);
2249 if (!resolutionOrder) {
2250 heap.push({float(flatness), v, ++versions[v], unsigned(key(v, 31))});
2251 rep.coarsenQueuePeak =
2252 std::max<std::uint64_t>(rep.coarsenQueuePeak, heap.size());
2253 return;
2254 }
2255 Box box;
2256 double totalArea = 0;
2257 bool first = true;
2258 for (int t : star) {
2259 const auto &cell = m_tris[t];
2260 const auto bound = triangleBox(cell);
2261 box = first ? bound : box.united(bound);
2262 first = false;
2263 totalArea +=
2264 .5 * orientPts(m_pts[cell.v[0]], m_pts[cell.v[1]], m_pts[cell.v[2]]);
2265 }
2267 double target = opt.coarseningTargetSize(box);
2268 if (!std::isfinite(target) || target < 0) {
2269 rep.invalidPolicy = true;
2270 return;
2271 }
2272 if (target == 0)
2273 target = opt.coarseningReferenceSize;
2274 const double actual =
2275 star.isEmpty() ? 0 : std::sqrt(totalArea / (star.size() * kEquilateralArea));
2276 const double density = actual / target;
2277 const double importance =
2278 flatness >= -1 ? resolutionRemovalImportance(flatness, actual, target)
2279 : std::numeric_limits<double>::infinity();
2280 const auto compact = [](double value) {
2281 return float(std::min(value, double(std::numeric_limits<float>::max())));
2282 };
2283 resolutionHeap.update(
2284 {compact(importance), compact(density), v, unsigned(key(v, 31))});
2285 rep.coarsenQueuePeak =
2286 std::max<std::uint64_t>(rep.coarsenQueuePeak, resolutionHeap.size());
2287 };
2288 for (int v = m_superBase + 3; v < m_pts.size() && !rep.cancelled && !rep.invalidPolicy;
2289 ++v) {
2290 if ((v & 4095) == 0 && opt.cancelled && opt.cancelled()) {
2291 rep.cancelled = true;
2292 break;
2293 }
2294 queue(v);
2295 }
2296 auto plane = [&](const Triangle &T, const Point &p) {
2297 const auto &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
2298 const double z = height(T.v[0]);
2299 const double cross = orientPts(a, b, c);
2300 const double u =
2301 ((p.x() - a.x()) * (c.y() - a.y()) - (p.y() - a.y()) * (c.x() - a.x())) / cross;
2302 const double w =
2303 ((b.x() - a.x()) * (p.y() - a.y()) - (b.y() - a.y()) * (p.x() - a.x())) / cross;
2304 return z + u * (height(T.v[1]) - z) + w * (height(T.v[2]) - z);
2305 };
2306 while (!(resolutionOrder ? resolutionHeap.empty() : heap.empty()) && !rep.cancelled &&
2307 !rep.invalidPolicy) {
2308 Candidate c{};
2309 if (resolutionOrder) {
2310 const auto next = resolutionHeap.pop();
2311 c.v = next.vertex;
2312 } else {
2313 c = heap.top();
2314 heap.pop();
2315 }
2316 const int v = c.v;
2317 if ((!resolutionOrder && c.version != versions[v]) || !isVertexActive(v))
2318 continue;
2319 if ((rep.coarsenAttempts & 255) == 0) {
2320 publishProgress();
2321 if (opt.cancelled && opt.cancelled()) {
2322 rep.cancelled = true;
2323 break;
2324 }
2325 }
2326 ++attempts[v];
2327 ++rep.coarsenAttempts;
2328 Removal r;
2329 if (!prepareRemoval(v, &r))
2330 continue;
2331 double shortest = std::numeric_limits<double>::infinity();
2332 for (int t : r.star)
2333 for (int k = 0; k < 3; ++k)
2334 shortest = std::min(shortest, dist2(m_pts[m_tris[t].v[k]],
2335 m_pts[m_tris[t].v[(k + 1) % 3]]));
2336 bool accepted = true;
2337 for (const auto &T : r.replacement) {
2338 const Point &a = m_pts[T.v[0]], &b = m_pts[T.v[1]], &c = m_pts[T.v[2]];
2339 const double cross = orientPts(a, b, c);
2340 const double l[3] = {dist2(b, c), dist2(c, a), dist2(a, b)};
2341 const int k = l[0] <= l[1] && l[0] <= l[2] ? 0 : l[1] <= l[2] ? 1 : 2;
2342 if (l[k] < shortest * (1 - 1e-12)) {
2343 ++rep.coarsenRejectedEdge;
2344 accepted = false;
2345 break;
2346 }
2347 if (l[k] >= minEdge2 &&
2348 cross / std::sqrt(l[(k + 1) % 3] * l[(k + 2) % 3]) < sinMin &&
2349 !exemptShortestEdge(T.v[(k + 1) % 3], T.v[(k + 2) % 3])) {
2351 accepted = false;
2352 break;
2353 }
2354 const Point g = (a + b + c) / 3;
2355 const double h = opt.hAt ? opt.hAt(g.x(), g.y()) : 0;
2356 if (h > 0 && cross * .5 > kEquilateralArea * h * h) {
2357 ++rep.coarsenRejectedSize;
2358 accepted = false;
2359 break;
2360 }
2361 if (resolutionOrder) {
2362 const double target = opt.coarseningTargetSize(triangleBox(T));
2363 if (!std::isfinite(target) || target < 0) {
2364 rep.invalidPolicy = true;
2365 accepted = false;
2366 break;
2367 }
2368 if (target > 0 && static_cast<long double>(cross) * .5 >
2369 kEquilateralArea * static_cast<long double>(target) *
2370 target * (1 + 1e-10)) {
2371 ++rep.coarsenRejectedSize;
2372 accepted = false;
2373 break;
2374 }
2375 }
2376 const double cap = footprintCap(T);
2377 if (cap > 0 && cross * .5 > cap * (1 + 1e-10)) {
2378 ++rep.coarsenRejectedSize;
2379 accepted = false;
2380 break;
2381 }
2382 }
2383 if (!accepted)
2384 continue;
2385 Vector<double> newBounds;
2386 if (terrain) {
2387 // A necessary condition only: the removed reference height
2388 // must still fit the new plane covering its location.
2389 for (const auto &T : r.replacement)
2390 if (orient(T.v[0], T.v[1], m_pts[v]) >= 0 &&
2391 orient(T.v[1], T.v[2], m_pts[v]) >= 0 &&
2392 orient(T.v[2], T.v[0], m_pts[v]) >= 0) {
2393 const double error = std::abs(plane(T, m_pts[v]) - height(v));
2394 if (!std::isfinite(error) || !std::isfinite(toleranceFor(T)) ||
2395 error > toleranceFor(T)) {
2397 accepted = false;
2398 }
2399 break;
2400 }
2401 for (const auto &T : r.replacement) {
2402 if (!accepted)
2403 break;
2404 double bound = 0;
2405 // The plane difference is affine. Bounding it at all
2406 // three vertices of EVERY old triangle covers every
2407 // overlap, without assuming the polygons are convex.
2408 for (int t : r.star) {
2409 if (!std::isfinite(bounds[t])) {
2410 bound = unknown;
2411 break;
2412 }
2413 double difference = 0;
2414 for (int q : m_tris[t].v) {
2415 const double d = std::abs(plane(T, m_pts[q]) - height(q));
2416 if (!std::isfinite(d)) {
2417 difference = unknown;
2418 break;
2419 }
2420 difference = std::max(difference, d);
2421 }
2422 if (!std::isfinite(difference)) {
2423 bound = unknown;
2424 break;
2425 }
2426 bound = std::max(bound, bounds[t] + difference);
2427 }
2428 if (std::isfinite(bound) && bound <= toleranceFor(T) * (1 - 1e-10))
2429 ++rep.coarsenBoundAccepts;
2430 else {
2431 Point xy[3];
2432 double z[3];
2433 for (int k = 0; k < 3; ++k) {
2434 xy[k] = m_pts[T.v[k]];
2435 z[k] = height(T.v[k]);
2436 }
2437 bound = opt.terrainErrorBound(xy, z);
2438 ++rep.coarsenExactQueries;
2439 }
2440 if (!std::isfinite(bound) || bound < 0 || !std::isfinite(toleranceFor(T)) ||
2441 bound > toleranceFor(T))
2442 accepted = false;
2443 newBounds.append(bound);
2444 }
2445 if (!accepted) {
2447 continue;
2448 }
2449 }
2450 commitRemoval(v, r);
2451 ++rep.coarsenRemoved;
2452 for (int i = 0; i < r.star.size(); ++i) {
2453 const int t = r.star[i];
2454 if (t < measured.size())
2455 measured[t] = Measured{};
2456 if (terrain)
2457 bounds[t] = i < newBounds.size() ? newBounds[i] : unknown;
2458 }
2459 for (int q : r.ring)
2460 queue(q);
2461 }
2462 }
2463 rep.coarsenSeconds = clock.nsecsElapsed() / 1e9;
2464 }
2465 // Bounded repair after smoothing, terrain insertion and coarsening. Ordinary
2466 // constraints may be subdivided but never moved/deleted; fixed patch edges
2467 // retain their stations. Terrain is rechecked inline for every changed cell.
2468 if (opt.sizingRepairPasses > 0 && !stop && !rep.cancelled) {
2469 repairing = true;
2470 repairBegin = rep.inserted;
2471 if (opt.maximumSizingRepairInsertions <= 0)
2472 rep.sizingRepairLimited = true;
2473 progressStage = GenerationProgress::Stage::Refining;
2474 m_triangleChanged = [&](int t) {
2475 enqueue(t);
2476 for (int n : m_tris[t].adj)
2477 if (n >= 0)
2478 enqueue(n);
2479 };
2480 for (int pass = 0; pass < std::clamp(opt.sizingRepairPasses, 0, 3) && !stop &&
2481 !rep.cancelled && !rep.sizingRepairLimited;
2482 ++pass) {
2483 heads.fill(-1);
2484 tails.fill(-1);
2485 next.fill(-2, m_tris.size());
2486 const int before = rep.inserted;
2487 ++rep.sizingRepairPasses;
2488 for (int t = 0; t < m_tris.size(); ++t) {
2489 if ((t & 4095) == 0 && opt.cancelled && opt.cancelled()) {
2490 rep.cancelled = true;
2491 break;
2492 }
2493 if (m_tris[t].alive)
2494 enqueue(t);
2495 }
2496 if (rep.cancelled)
2497 break;
2498 drainQueue();
2499 if (rep.inserted == before)
2500 break;
2501 }
2502 rep.sizingRepairInserted = rep.inserted - repairBegin;
2503 m_triangleChanged = {};
2504 }
2505 // Independent pass over the final geometry: every live triangle is checked,
2506 // so a missed edge flip or a blocked terrain candidate cannot go unnoticed.
2507 // A value is reused only for the identical ordered vertex triple.
2508 if (terrain && opt.terrainFinalCheck && !rep.cancelled) {
2509 progressStage = GenerationProgress::Stage::Verifying;
2510 publishProgress();
2511 for (int t = 0; t < m_tris.size(); ++t) {
2512 const auto &T = m_tris[t];
2513 if (!T.alive || isSuperVertex(T.v[0]) || isSuperVertex(T.v[1]) || isSuperVertex(T.v[2]))
2514 continue;
2515 if ((t & 4095) == 0 && opt.cancelled && opt.cancelled()) {
2516 rep.cancelled = true;
2517 break;
2518 }
2519 double e;
2520 if (t < measured.size() && measured[t].v[0] == T.v[0] && measured[t].v[1] == T.v[1] &&
2521 measured[t].v[2] == T.v[2])
2522 e = measured[t].e;
2523 else {
2524 Point p;
2525 e = terrainError(t, &p);
2526 }
2527 if (!std::isfinite(e) || !std::isfinite(toleranceFor(T)))
2528 ++rep.terrainUnknown;
2529 else {
2530 rep.maxTerrainError = std::max(rep.maxTerrainError, e);
2531 if (e > toleranceFor(T))
2532 ++rep.terrainUnresolved;
2533 }
2534 }
2535 }
2536 publishProgress();
2537 return rep;
2538}
2539
2540} // namespace mesh
double step
Definition aquifereditordialog.cpp:62
typename Storage::PointInput PointInput
Definition cdt.hpp:53
typename Storage::template Vector< T > Vector
Definition cdt.hpp:54
int liveTriangleCount() const
Count slots whose alive flag is true, including super faces if still alive.
Definition cdt_impl.hpp:381
static double orientExact(const Point &a, const Point &b, const Point &c)
Return adaptive-exact orientation sign after thread-safe one-time predicate initialization.
Definition cdt_impl.hpp:137
bool build(const PointInput &points, Vector< int > *vertexOfPoint=nullptr, const std::function< bool()> &cancelled={}, const std::function< void(std::uint64_t)> &progress={})
Reset and incrementally triangulate distinct finite points inside a constructed super triangle.
Definition cdt_impl.hpp:645
int locate(const Point &p) const
Locate a finite point and require its found triangle to be alive.
Definition cdt_impl.hpp:375
typename Storage::template Hash< K, V > Hash
Definition cdt.hpp:55
bool isConstrained(int a, int b) const
Check whether a direct edge between two stored vertices is marked constrained.
Definition cdt_impl.hpp:258
Vector< int > constrainedChain(int a, int b) const
Follow direct, origin-associated or collinear constrained edges from one endpoint to another.
Definition cdt_impl.hpp:268
typename Storage::Box Box
Definition cdt.hpp:51
typename Storage::Point Point
Definition cdt.hpp:50
Definition coarseningqueue.hpp:13
Definition storage.hpp:196
std::int64_t nsecsElapsed() const
Definition storage.hpp:201
void start()
Definition storage.hpp:200
bool stopped
Definition constraints.cpp:63
size_t i
Definition contourjob.cpp:30
DiagramType t
Definition diagramspec.cpp:23
std::function< void(bool)> changed
Guarded layer refresh callback.
Definition extensionattributes.cpp:36
QString target
Definition initialbedqualitydialog.cpp:51
double s
Scene px per model length unit.
Definition inletdrawingview.cpp:95
std::string reason
Definition job.cpp:20
int density
Plants across the cell.
Definition lidlayerdiagram.cpp:101
ArrowPlacement p
Definition linesymbollayer.cpp:29
MaskMode m
Definition maskspec.cpp:21
hid_t id
Definition mesh2dh5reader.cpp:54
int h
Definition mesh2dresultsexport.cpp:477
int w
Definition mesh2dresultsexport.cpp:477
CellAdjacency adj
Definition mesh2dresultsexport.cpp:693
std::vector< double > cx
Definition mesh2dresultsexport.cpp:131
std::vector< double > cy
area centroid per cell
Definition mesh2dresultsexport.cpp:131
std::vector< int > start
size = pixels + 1
Definition mesh2dresultsexport.cpp:762
std::vector< double > len
Definition mesh2dresultsexport.cpp:571
std::vector< float > dx
Definition mesh2dresultsexport.cpp:765
std::size_t n
Definition mesh2dresultsexport.cpp:538
int k
Definition mesh2dresultsexport.cpp:694
std::vector< int > cell
Definition mesh2dresultsexport.cpp:763
std::vector< float > dy
Green–Gauss only, parallel to cell
Definition mesh2dresultsexport.cpp:765
double sum
Σ weight · value.
Definition meshassignmentsampling.cpp:285
double wsum
Σ weight.
Definition meshassignmentsampling.cpp:286
void triangle(QImage &image, std::array< Vertex, 3 > v)
Composite one Gouraud-shaded triangle using a shared-edge ownership rule.
Definition smoothfillraster.h:40
std::uint64_t mortonKey(std::uint32_t x, std::uint32_t y)
Interleave two unsigned 32-bit coordinates into a Morton ordering key.
Definition cdt_impl.hpp:40
constexpr double kEquilateralArea
Definition cdt_impl.hpp:129
std::once_flag gPredicatesInit
Definition cdt_impl.hpp:31
double dist2(const Point &a, const Point &b)
Compute ordinary squared Cartesian distance without overflow/finite checking.
Definition cdt_impl.hpp:112
int keyHi(std::uint64_t k)
Recover the larger endpoint word from an edgeKey64 key.
Definition cdt_impl.hpp:84
std::uint64_t edgeKey64(int a, int b)
Pack a normalized integer vertex pair into a 64-bit undirected edge identity.
Definition cdt_impl.hpp:63
bool refinementCoincident(const Point &a, const Point &b)
Definition cdt_impl.hpp:120
int keyLo(std::uint64_t k)
Recover the smaller endpoint word from an edgeKey64 key.
Definition cdt_impl.hpp:75
double orientPts(const Point &a, const Point &b, const Point &c)
Evaluate adaptive orientation on three borrowed Cartesian points after predicate initialization.
Definition cdt_impl.hpp:99
Definition blocks.hpp:25
Definition qtcdtstorage.h:9
double resolutionRemovalImportance(double flatness, double actualSize, double targetSize)
Definition terrainpolicy.hpp:51
Box box
Definition constraintaudit.cpp:161
int chain
Definition constraintshared.cpp:85
double length
Definition patch.cpp:102
Vector< int > order
Definition patch.cpp:166
double area
Definition raingageassignment.cpp:302
double value
Definition rasterfluxintegration.cpp:27
double height
Full depth of the section (0 → thin stub).
Definition sectionmodelbuilders.cpp:717
std::uint64_t limit
Definition smsio.cpp:24
Options for refineQuality().
Definition cdt.hpp:231
bool coarsenSkippedTerrain
Definition cdt.hpp:314
bool capped
Stopped at maxInsertions.
Definition cdt.hpp:309
std::uint64_t coarsenQueuePeak
Definition cdt.hpp:316
int inserted
Vertices added, segment splits included.
Definition cdt.hpp:302
bool sizingRepairLimited
Definition cdt.hpp:301
int coarsenRejectedTerrain
Definition cdt.hpp:312
double maxTerrainError
Definition cdt.hpp:308
std::uint64_t coarsenPriorityQueries
Definition cdt.hpp:316
int blockedByFixed
Bad triangles left because their point would encroach a fixed edge.
Definition cdt.hpp:304
double coarsenSeconds
Definition cdt.hpp:315
int adj[3]
Neighbour across the edge opposite v[i] (v[i+1], v[i+2]); -1 = none.
Definition cdt.hpp:60
int v[3]
Counter-clockwise.
Definition cdt.hpp:59
bool constrained[3]
Edge opposite v[i] is a constraint.
Definition cdt.hpp:61
Definition progress.hpp:7
Stage stage
Definition progress.hpp:9
std::int64_t triangles
Definition progress.hpp:12
std::int64_t inserted
Definition progress.hpp:14
std::int64_t removed
Definition progress.hpp:14
Stage
Definition progress.hpp:8
std::int64_t workingVertices
Definition progress.hpp:11
int y
Definition terrainerrorfield.cpp:60
double a
Definition terrainerrorfield.cpp:42
double b
Definition terrainerrorfield.cpp:42
double error
Definition terrainerrorfield.cpp:42
double c
Definition terrainerrorfield.cpp:42
int x
Definition terrainerrorfield.cpp:60
double y0
Definition terrainerrorfield.cpp:41
double y1
Definition terrainerrorfield.cpp:41
double x1
Definition terrainerrorfield.cpp:41
double x0
Definition terrainerrorfield.cpp:41
double scale
Definition timeseriesassignment.cpp:21
Sample first
Definition timeseriesrunlayer.cpp:51
bool used
Definition timeseriesrunlayer.cpp:50
QRectF bounds
Definition traceanalysislayer.cpp:165
const ExportProgress * progress
Definition vectorexport.cpp:211
bool cancelled
Definition vectorexport.cpp:214