Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
visibilitygraph.hpp
Go to the documentation of this file.
1#pragma once
2
3// Included from pgl.hpp after algorithm/triangulation.hpp, whose Triangulation,
4// Polygon and PolygonWithHoles members this defines out of line.
5#include "algorithm/graph.hpp"
6
7#include <compare>
8#include <cstddef>
9#include <cstdint>
10#include <vector>
11
29
30namespace pgl {
31
32namespace detail {
33
34// Integer form of an orientation sign. `unordered` — reachable only for
35// floating-point coordinates carrying a NaN — reads as collinear, as elsewhere
36// in the library.
37constexpr int signValue(std::partial_ordering order) {
38 return order > 0 ? 1 : (order < 0 ? -1 : 0);
39}
40
41} // namespace detail
42
43// -----------------------------------------------------------------------------
44// Triangulation
45// -----------------------------------------------------------------------------
46
47template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
48template <class OnVertex, class OnBlocked>
49void Triangulation<TriangleType_, SegmentType_>::expandVisibility(
50 const PointType& origin, VisibilityCone start, std::vector<VisibilityCone>& scratch,
51 OnVertex onVertex, OnBlocked onBlocked) const {
52 scratch.clear();
53 scratch.push_back(start);
54 while (!scratch.empty()) {
55 const VisibilityCone cone = scratch.back();
56 scratch.pop_back();
57 if (blocksVisibility(cone.tri, cone.side)) {
58 onBlocked(cone); // the cone runs into a wall: a leaf of the traversal
59 continue;
60 }
61 const TriIndex entered = triangles_[static_cast<std::size_t>(cone.tri)].nbr[cone.side];
62 const int back = findSide(entered, cone.tri);
63 const auto& v = triangles_[static_cast<std::size_t>(entered)].v;
64 // `entered` winds the shared edge the other way round, so its clockwise
65 // end as seen from the origin is v[(back+2)%3] and its counterclockwise
66 // end v[(back+1)%3]. The apex is the third vertex.
67 const VertexIndex apex = v[back];
68 const int fromRight = detail::signValue(orientationSign(
69 origin, vertices_[static_cast<std::size_t>(cone.right)],
70 vertices_[static_cast<std::size_t>(apex)]));
71 const int fromLeft = detail::signValue(orientationSign(
72 origin, vertices_[static_cast<std::size_t>(cone.left)],
73 vertices_[static_cast<std::size_t>(apex)]));
74
75 VertexIndex rightChildLeft = cone.left;
76 VertexIndex leftChildRight = cone.right;
77 bool crossRight = true;
78 bool crossLeft = true;
79 if (fromRight > 0 && fromLeft < 0) {
80 // Strictly inside the cone: nothing stands between the origin and the
81 // apex, which therefore is clearly visible and splits the cone in
82 // two. An apex merely *on* a bound is not — the vertex that set that
83 // bound is in the way — and the two comparisons being strict is the
84 // whole of what separates clear visibility from the grazing kind.
85 onVertex(apex);
86 rightChildLeft = apex;
87 leftChildRight = apex;
88 } else if (fromRight <= 0) {
89 crossRight = false; // the clockwise sub-cone came out empty
90 } else {
91 crossLeft = false; // the counterclockwise sub-cone came out empty
92 }
93
94 // Side (back+1)%3 is the clockwise half of the entry edge, side
95 // (back+2)%3 the counterclockwise half. The counterclockwise child goes
96 // on the stack first so the clockwise one comes off it first: leaves then
97 // arrive in counterclockwise order, which is what lets
98 // regularizedVisiblePolygon lay out its ring as it goes.
99 if (crossLeft) {
100 scratch.push_back({entered, static_cast<std::int8_t>((back + 2) % 3),
101 leftChildRight, cone.left});
102 }
103 if (crossRight) {
104 scratch.push_back({entered, static_cast<std::int8_t>((back + 1) % 3),
105 cone.right, rightChildLeft});
106 }
107 }
108}
109
110template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
111std::vector<std::vector<typename Triangulation<TriangleType_, SegmentType_>::VertexIndex>>
112Triangulation<TriangleType_, SegmentType_>::clearVisibleAdjacency() const {
113 std::vector<std::vector<VertexIndex>> adjacency(vertices_.size());
114 if (domainTriangleCount_ == 0) {
115 return adjacency;
116 }
117 const VertexIndex vertexCount = static_cast<VertexIndex>(vertices_.size());
118 std::vector<VisibilityCone> scratch;
119 const auto ignore = [](const VisibilityCone&) {};
120
121 for (VertexIndex source = GHOST + 1; source < vertexCount; ++source) {
122 const TriIndex seed = incidentTriangleOf(source);
123 if (seed == NO_TRI) {
124 continue;
125 }
126 const PointType& origin = vertices_[static_cast<std::size_t>(source)];
127 auto& visible = adjacency[static_cast<std::size_t>(source)];
128 const auto report = [&](VertexIndex w) { visible.push_back(w); };
129
130 // One expansion per in-domain triangle of the source's fan. Such a
131 // triangle (source, p, q) is counterclockwise, so p bounds its wedge
132 // clockwise and q counterclockwise, and the fan wedges together tile
133 // exactly the part of the plane the source can see into.
134 visitVertexFan(seed, source, [&](TriIndex t) {
135 if (!inDomain(t)) {
136 return;
137 }
138 const auto& v = triangles_[static_cast<std::size_t>(t)].v;
139 const int i = v[0] == source ? 0 : (v[1] == source ? 1 : 2);
140 const VertexIndex clockwise = v[(i + 1) % 3];
141 // Edge (source, clockwise) is the side opposite v[(i+2)%3]. Taking
142 // only the clockwise neighbour visits every fan edge exactly once,
143 // since an edge with no second in-domain triangle blocks anyway.
144 if (!blocksVisibility(t, (i + 2) % 3)) {
145 visible.push_back(clockwise);
146 }
147 expandVisibility(origin,
148 {t, static_cast<std::int8_t>(i), clockwise, v[(i + 2) % 3]},
149 scratch, report, ignore);
150 });
151 }
152 return adjacency;
153}
154
155template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
156typename Triangulation<TriangleType_, SegmentType_>::TriIndex
157Triangulation<TriangleType_, SegmentType_>::inDomainTriangleAt(const PointType& query,
158 TriIndex start) const {
159 TriIndex real = start;
160 if (real == NO_TRI) {
161 return NO_TRI;
162 }
163 // A ghost triangle tiles the exterior beyond one real hull edge, so a query
164 // that landed on one is outside the domain unless it sits on that very edge.
165 if (isGhost(real)) {
166 const auto& v = triangles_[static_cast<std::size_t>(real)].v;
167 const int atGhost = v[0] == GHOST ? 0 : (v[1] == GHOST ? 1 : 2);
168 const PointType& a = vertices_[static_cast<std::size_t>(v[(atGhost + 1) % 3])];
169 const PointType& b = vertices_[static_cast<std::size_t>(v[(atGhost + 2) % 3])];
170 if (!Segment<PointType>(a, b).contains(query)) {
171 return NO_TRI;
172 }
173 real = triangles_[static_cast<std::size_t>(real)].nbr[atGhost];
174 if (real == NO_TRI || isGhost(real)) {
175 return NO_TRI;
176 }
177 }
178 if (inDomain(real)) {
179 return real;
180 }
181 // A hull-fill triangle outside the domain. The query can still be on the
182 // domain boundary, which it shares: at one of the triangle's vertices, or in
183 // the relative interior of one of its sides.
184 const auto& v = triangles_[static_cast<std::size_t>(real)].v;
185 for (int i = 0; i < 3; ++i) {
186 if (v[i] == GHOST || !(vertices_[static_cast<std::size_t>(v[i])] == query)) {
187 continue;
188 }
189 TriIndex answer = NO_TRI;
190 visitVertexFan(real, v[i], [&](TriIndex t) {
191 if (answer == NO_TRI && inDomain(t)) {
192 answer = t;
193 }
194 });
195 return answer;
196 }
197 for (int s = 0; s < 3; ++s) {
198 const VertexIndex a = v[(s + 1) % 3];
199 const VertexIndex b = v[(s + 2) % 3];
200 if (a == GHOST || b == GHOST) {
201 continue;
202 }
203 const PointType& pa = vertices_[static_cast<std::size_t>(a)];
204 const PointType& pb = vertices_[static_cast<std::size_t>(b)];
205 if (!Segment<PointType>(pa, pb).contains(query)) {
206 continue;
207 }
208 const TriIndex across = triangles_[static_cast<std::size_t>(real)].nbr[s];
209 if (inDomain(across)) {
210 return across;
211 }
212 }
213 return NO_TRI;
214}
215
216template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
217typename Triangulation<TriangleType_, SegmentType_>::VisibilitySeeds
218Triangulation<TriangleType_, SegmentType_>::visibilitySeeds(const PointType& query) const {
219 VisibilitySeeds seeds;
220 if (domainTriangleCount_ == 0) {
221 return seeds;
222 }
223 TriIndex found = locateIndex(query);
224 if (!inDomain(found)) {
225 found = inDomainTriangleAt(query, found);
226 }
227 if (found == NO_TRI) {
228 return seeds;
229 }
230 seeds.located = true;
231
232 // `found` holds the query in its closure, so the query is one of its
233 // vertices, inside one of its sides, or strictly within it.
234 const auto& fv = triangles_[static_cast<std::size_t>(found)].v;
235 int atVertex = -1;
236 int onSide = -1;
237 for (int i = 0; i < 3; ++i) {
238 if (vertices_[static_cast<std::size_t>(fv[i])] == query) {
239 atVertex = i;
240 }
241 }
242 if (atVertex < 0) {
243 for (int s = 0; s < 3; ++s) {
244 if (orientationSign(vertices_[static_cast<std::size_t>(fv[(s + 1) % 3])],
245 vertices_[static_cast<std::size_t>(fv[(s + 2) % 3])],
246 query) == 0) {
247 onSide = s;
248 }
249 }
250 }
251
252 // Crossing side `side` of `t` spans the wedge from v[(side+1)%3] clockwise to
253 // v[(side+2)%3] counterclockwise, whichever of the three cases seeds it.
254 const auto addCone = [&](TriIndex t, int side) {
255 const auto& v = triangles_[static_cast<std::size_t>(t)].v;
256 seeds.cones.push_back({t, static_cast<std::int8_t>(side), v[(side + 1) % 3],
257 v[(side + 2) % 3]});
258 };
259
260 if (atVertex >= 0) {
261 const VertexIndex self = fv[atVertex];
262 visitVertexFan(found, self, [&](TriIndex t) {
263 if (!inDomain(t)) {
264 return;
265 }
266 const auto& v = triangles_[static_cast<std::size_t>(t)].v;
267 const int i = v[0] == self ? 0 : (v[1] == self ? 1 : 2);
268 // Both bounds, not just the clockwise one: where the fan runs out of
269 // domain — at every boundary vertex — the last edge is no other
270 // in-domain triangle's clockwise bound and would go unlisted. The
271 // duplicates this leaves are dropped below, and an edge's two records
272 // agree on whether sight along it grazes, that being a property of
273 // the edge.
274 seeds.direct.emplace_back(v[(i + 1) % 3], blocksVisibility(t, (i + 2) % 3));
275 seeds.direct.emplace_back(v[(i + 2) % 3], blocksVisibility(t, (i + 1) % 3));
276 addCone(t, i);
277 });
278 } else if (onSide >= 0) {
279 // The query splits a mesh edge. Sight along that edge only grazes when the
280 // edge bounds the domain or walls it off; the wedges to either side of it
281 // are seeded independently, so a wall is never crossed.
282 const bool grazes = blocksVisibility(found, onSide);
283 seeds.direct.emplace_back(fv[(onSide + 1) % 3], grazes);
284 seeds.direct.emplace_back(fv[(onSide + 2) % 3], grazes);
285 const TriIndex across = triangles_[static_cast<std::size_t>(found)].nbr[onSide];
286 for (const TriIndex t : {found, across}) {
287 if (!inDomain(t)) {
288 continue;
289 }
290 const int s = t == found ? onSide : static_cast<int>(findSide(t, found));
291 seeds.direct.emplace_back(triangles_[static_cast<std::size_t>(t)].v[s], false);
292 addCone(t, (s + 1) % 3);
293 addCone(t, (s + 2) % 3);
294 }
295 } else {
296 for (int i = 0; i < 3; ++i) {
297 seeds.direct.emplace_back(fv[i], false);
298 }
299 for (int s = 0; s < 3; ++s) {
300 addCone(found, s);
301 }
302 }
303
304 // Counterclockwise by each cone's clockwise bound. Two cones never share that
305 // bound's direction — that would put one bound inside the edge to the other —
306 // so the order is total.
307 const auto upperHalf = [&](VertexIndex w) {
308 const PointType& p = vertices_[static_cast<std::size_t>(w)];
309 return p.y() > query.y() || (p.y() == query.y() && p.x() > query.x()) ? 0 : 1;
310 };
311 std::sort(seeds.cones.begin(), seeds.cones.end(),
312 [&](const VisibilityCone& a, const VisibilityCone& b) {
313 const int half = upperHalf(a.right);
314 const int other = upperHalf(b.right);
315 if (half != other) {
316 return half < other;
317 }
318 return orientationSign(query, vertices_[static_cast<std::size_t>(a.right)],
319 vertices_[static_cast<std::size_t>(b.right)]) > 0;
320 });
321
322 // A cone continues the previous one when it opens where that one closed;
323 // anywhere else the visible directions break, and the region reaches the
324 // query along a separate lobe. Rotating a break to the front leaves the arcs
325 // as ascending ranges.
326 const std::size_t count = seeds.cones.size();
327 std::size_t firstBreak = count;
328 for (std::size_t k = 0; k < count && firstBreak == count; ++k) {
329 if (seeds.cones[(k + count - 1) % count].left != seeds.cones[k].right) {
330 firstBreak = k;
331 }
332 }
333 seeds.fullTurn = firstBreak == count;
334 if (seeds.fullTurn) {
335 seeds.arcs.push_back(0);
336 } else {
337 std::rotate(seeds.cones.begin(),
338 seeds.cones.begin() + static_cast<std::ptrdiff_t>(firstBreak),
339 seeds.cones.end());
340 for (std::size_t k = 0; k < count; ++k) {
341 if (k == 0 || seeds.cones[k - 1].left != seeds.cones[k].right) {
342 seeds.arcs.push_back(k);
343 }
344 }
345 }
346
347 std::sort(seeds.direct.begin(), seeds.direct.end());
348 seeds.direct.erase(std::unique(seeds.direct.begin(), seeds.direct.end(),
349 [](const auto& a, const auto& b) {
350 return a.first == b.first;
351 }),
352 seeds.direct.end());
353 return seeds;
354}
355
356template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
357std::vector<typename Triangulation<TriangleType_, SegmentType_>::VertexIndex>
358Triangulation<TriangleType_, SegmentType_>::visibleIds(const PointType& query,
359 const VisibilitySeeds& seeds,
360 bool grazing) const {
361 std::vector<VertexIndex> found;
362 if (!seeds.located) {
363 return found;
364 }
365 for (const auto& [vertex, grazes] : seeds.direct) {
366 if (grazing || !grazes) {
367 found.push_back(vertex);
368 }
369 }
370 std::vector<VisibilityCone> scratch;
371 const auto report = [&](VertexIndex w) { found.push_back(w); };
372 const auto ignore = [](const VisibilityCone&) {};
373 for (const VisibilityCone& cone : seeds.cones) {
374 expandVisibility(query, cone, scratch, report, ignore);
375 }
376 std::sort(found.begin(), found.end());
377 found.erase(std::unique(found.begin(), found.end()), found.end());
378 return found;
379}
380
381template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
382std::vector<typename Triangulation<TriangleType_, SegmentType_>::PointType>
384 const PointType& query) const {
385 const VisibilitySeeds seeds = visibilitySeeds(query);
386 std::vector<PointType> result;
387 for (const VertexIndex v : visibleIds(query, seeds, false)) {
388 result.push_back(vertices_[static_cast<std::size_t>(v)]);
389 }
390 sortAround(result, query);
391 return result;
392}
393
394template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
395std::vector<typename Triangulation<TriangleType_, SegmentType_>::PointType>
397 const VisibilitySeeds seeds = visibilitySeeds(query);
398 std::vector<VertexIndex> reached = visibleIds(query, seeds, true);
399 if (reached.empty()) {
400 return {};
401 }
402 // Grazing sight through a vertex, exactly as in visibleAdjacency: a segment
403 // that passes straight through vertices is in the domain when each of its
404 // pieces is, so walking each ray on from the first vertex it meets picks up
405 // the rest. Only a vertex a line can cross starts a chain.
406 const std::size_t direct = reached.size();
407 for (std::size_t k = 0; k < direct; ++k) {
408 VertexIndex current = reached[k];
409 if (!passesThrough(current)) {
410 continue;
411 }
412 VertexIndex next = nextVertexAlongRay(query, current);
413 while (next != GHOST) {
414 reached.push_back(next);
415 if (!passesThrough(next)) {
416 break;
417 }
418 const VertexIndex previous = current;
419 current = next;
420 next = nextVertexAlongRay(vertices_[static_cast<std::size_t>(previous)], current);
421 }
422 }
423 std::sort(reached.begin(), reached.end());
424 reached.erase(std::unique(reached.begin(), reached.end()), reached.end());
425 std::vector<PointType> result;
426 result.reserve(reached.size());
427 for (const VertexIndex v : reached) {
428 result.push_back(vertices_[static_cast<std::size_t>(v)]);
429 }
430 sortAround(result, query);
431 return result;
432}
433
434template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
435bool Triangulation<TriangleType_, SegmentType_>::passesThrough(VertexIndex m) const {
436 const TriIndex seed = incidentTriangleOf(m);
437 if (seed == NO_TRI) {
438 return false;
439 }
440 // Extremes of the domain wedges meeting at m: the ends of a wedge are the
441 // fan edges whose other triangle is out of domain.
442 VertexIndex clockwise = GHOST;
443 VertexIndex counter = GHOST;
444 int extremes = 0;
445 bool any = false;
446 visitVertexFan(seed, m, [&](TriIndex t) {
447 if (!inDomain(t)) {
448 return;
449 }
450 any = true;
451 const auto& tri = triangles_[static_cast<std::size_t>(t)];
452 const int i = tri.v[0] == m ? 0 : (tri.v[1] == m ? 1 : 2);
453 if (!inDomain(tri.nbr[(i + 2) % 3])) {
454 clockwise = tri.v[(i + 1) % 3];
455 ++extremes;
456 }
457 if (!inDomain(tri.nbr[(i + 1) % 3])) {
458 counter = tri.v[(i + 2) % 3];
459 ++extremes;
460 }
461 });
462 if (!any) {
463 return false;
464 }
465 if (extremes == 0) {
466 return true; // the domain surrounds m
467 }
468 if (extremes != 2 || clockwise == GHOST || counter == GHOST) {
469 return true; // several wedges meet at m; assume a line fits through one
470 }
471 // A single wedge, running counterclockwise from `clockwise` to `counter`,
472 // holds a pair of opposite directions exactly when it spans half a turn or
473 // more — that is, when its ends do not make a left turn at m.
474 return detail::signValue(orientationSign(vertices_[static_cast<std::size_t>(m)],
475 vertices_[static_cast<std::size_t>(clockwise)],
476 vertices_[static_cast<std::size_t>(counter)])) <= 0;
477}
478
479template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
480typename Triangulation<TriangleType_, SegmentType_>::VertexIndex
481Triangulation<TriangleType_, SegmentType_>::nextVertexAlongRay(const PointType& tail,
482 VertexIndex current) const {
483 const TriIndex seed = incidentTriangleOf(current);
484 if (seed == NO_TRI) {
485 return GHOST;
486 }
487 const PointType& head = vertices_[static_cast<std::size_t>(current)];
488 // The ray direction is head - tail, and the cross product of that direction
489 // with (w - head) is exactly orientationSign(tail, head, w). So the whole
490 // walk speaks the shared predicate on stored points, and the direction is
491 // never subtracted out. Its half-plane is read off the coordinates directly,
492 // for the same reason.
493 const int forwardHalf =
494 (head.y() > tail.y() || (head.y() == tail.y() && head.x() > tail.x())) ? 0 : 1;
495 const auto sameRay = [&](VertexIndex w) {
496 const PointType& p = vertices_[static_cast<std::size_t>(w)];
497 const int half = (p.y() > head.y() || (p.y() == head.y() && p.x() > head.x())) ? 0 : 1;
498 return half == forwardHalf;
499 };
500 const auto side = [&](VertexIndex w) {
501 return detail::signValue(
502 orientationSign(tail, head, vertices_[static_cast<std::size_t>(w)]));
503 };
504
505 // Where the ray leaves `current`: along a fan edge, or into the interior of
506 // one fan triangle. Anywhere else it leaves the domain at once.
507 VertexIndex onEdge = GHOST;
508 TriIndex entry = NO_TRI;
509 int entrySide = 0;
510 visitVertexFan(seed, current, [&](TriIndex t) {
511 if (onEdge != GHOST || entry != NO_TRI || !inDomain(t)) {
512 return;
513 }
514 const auto& v = triangles_[static_cast<std::size_t>(t)].v;
515 const int i = v[0] == current ? 0 : (v[1] == current ? 1 : 2);
516 const VertexIndex clockwise = v[(i + 1) % 3];
517 const VertexIndex counter = v[(i + 2) % 3];
518 const int fromClockwise = side(clockwise);
519 const int fromCounter = side(counter);
520 if (fromClockwise == 0 && sameRay(clockwise)) {
521 onEdge = clockwise;
522 } else if (fromCounter == 0 && sameRay(counter)) {
523 onEdge = counter;
524 } else if (fromClockwise < 0 && fromCounter > 0) {
525 entry = t;
526 entrySide = i; // the side opposite `current`
527 }
528 });
529 if (onEdge != GHOST) {
530 // A mesh edge of an in-domain triangle: always both in the domain and
531 // free of any vertex in between.
532 return onEdge;
533 }
534 if (entry == NO_TRI || blocksVisibility(entry, entrySide)) {
535 return GHOST;
536 }
537
538 // Straight walk from triangle to triangle. The ray entered through the
539 // relative interior of the shared edge, so the two ends of that edge lie
540 // strictly on opposite sides of it and the apex settles which side the ray
541 // leaves by.
542 TriIndex tri = triangles_[static_cast<std::size_t>(entry)].nbr[entrySide];
543 int back = findSide(tri, entry);
544 for (;;) {
545 const auto& v = triangles_[static_cast<std::size_t>(tri)].v;
546 const VertexIndex apex = v[back];
547 const int fromApex = side(apex);
548 if (fromApex == 0) {
549 return apex; // the ray runs straight into it
550 }
551 // The apex and the entry end on its own side of the ray stay together;
552 // the ray leaves through the edge joining that group to the other end.
553 const int leaving = fromApex == side(v[(back + 1) % 3]) ? (back + 1) % 3 : (back + 2) % 3;
554 if (blocksVisibility(tri, leaving)) {
555 return GHOST;
556 }
557 const TriIndex next = triangles_[static_cast<std::size_t>(tri)].nbr[leaving];
558 back = findSide(next, tri);
559 tri = next;
560 }
561}
562
563template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
564std::vector<std::vector<typename Triangulation<TriangleType_, SegmentType_>::VertexIndex>>
565Triangulation<TriangleType_, SegmentType_>::visibleAdjacency() const {
566 auto adjacency = clearVisibleAdjacency();
567 if (domainTriangleCount_ == 0) {
568 return adjacency;
569 }
570 const VertexIndex vertexCount = static_cast<VertexIndex>(vertices_.size());
571
572 // Every in-domain mesh edge joins two mutually visible vertices. The
573 // unblocked ones are already clearly visible; only the walls and the
574 // boundary are missing. A constrained edge with domain on both sides is seen
575 // from two triangles, and is taken from the lower-numbered one.
576 for (TriIndex t = 0; t < firstGhost_; ++t) {
577 if (!inDomain(t)) {
578 continue;
579 }
580 for (int side = 0; side < 3; ++side) {
581 if (!blocksVisibility(t, side)) {
582 continue;
583 }
584 const TriIndex across = triangles_[static_cast<std::size_t>(t)].nbr[side];
585 if (inDomain(across) && across < t) {
586 continue;
587 }
588 const VertexIndex a = triangles_[static_cast<std::size_t>(t)].v[(side + 1) % 3];
589 const VertexIndex b = triangles_[static_cast<std::size_t>(t)].v[(side + 2) % 3];
590 adjacency[static_cast<std::size_t>(a)].push_back(b);
591 adjacency[static_cast<std::size_t>(b)].push_back(a);
592 }
593 }
594
595 // Collinear closure. What `adjacency` holds so far is exactly the pairs
596 // seeing each other with no vertex in between, so a segment that does pass
597 // through vertices is in the domain precisely when each of its pieces is:
598 // walking a chain of such pieces in a fixed direction enumerates the rest.
599 // Only a vertex a line can cross starts a chain, which spares a convex
600 // domain — the one where this relation is densest — the walk entirely.
601 std::vector<std::uint8_t> crossable(vertices_.size(), 0);
602 bool anyCrossable = false;
603 for (VertexIndex m = GHOST + 1; m < vertexCount; ++m) {
604 crossable[static_cast<std::size_t>(m)] = passesThrough(m) ? 1 : 0;
605 anyCrossable = anyCrossable || crossable[static_cast<std::size_t>(m)] != 0;
606 }
607 if (!anyCrossable) {
608 return adjacency;
609 }
610
611 // Collected apart, since a chain reads the adjacency it is about to extend.
612 std::vector<std::pair<VertexIndex, VertexIndex>> chained;
613 for (VertexIndex source = GHOST + 1; source < vertexCount; ++source) {
614 const std::size_t direct = adjacency[static_cast<std::size_t>(source)].size();
615 for (std::size_t k = 0; k < direct; ++k) {
616 VertexIndex previous = source;
617 VertexIndex current = adjacency[static_cast<std::size_t>(source)][k];
618 while (crossable[static_cast<std::size_t>(current)] != 0) {
619 const VertexIndex next =
620 nextVertexAlongRay(vertices_[static_cast<std::size_t>(previous)], current);
621 if (next == GHOST) {
622 break;
623 }
624 chained.emplace_back(source, next);
625 previous = current;
626 current = next;
627 }
628 }
629 }
630 for (const auto& [a, b] : chained) {
631 adjacency[static_cast<std::size_t>(a)].push_back(b);
632 adjacency[static_cast<std::size_t>(b)].push_back(a);
633 }
634 return adjacency;
635}
636
637template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
638std::vector<std::vector<typename Triangulation<TriangleType_, SegmentType_>::VertexIndex>>
639Triangulation<TriangleType_, SegmentType_>::wallNeighbors() const {
640 std::vector<std::vector<VertexIndex>> walls(vertices_.size());
641 for (TriIndex t = 0; t < firstGhost_; ++t) {
642 if (!inDomain(t)) {
643 continue;
644 }
645 for (int side = 0; side < 3; ++side) {
646 if (!blocksVisibility(t, side)) {
647 continue;
648 }
649 const TriIndex across = triangles_[static_cast<std::size_t>(t)].nbr[side];
650 if (inDomain(across) && across < t) {
651 continue;
652 }
653 const VertexIndex a = triangles_[static_cast<std::size_t>(t)].v[(side + 1) % 3];
654 const VertexIndex b = triangles_[static_cast<std::size_t>(t)].v[(side + 2) % 3];
655 walls[static_cast<std::size_t>(a)].push_back(b);
656 walls[static_cast<std::size_t>(b)].push_back(a);
657 }
658 }
659 return walls;
660}
661
662template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
663Graph<typename Triangulation<TriangleType_, SegmentType_>::PointType>
665 Graph<PointType> result;
666 const VertexIndex vertexCount = static_cast<VertexIndex>(vertices_.size());
667 for (VertexIndex v = GHOST + 1; v < vertexCount; ++v) {
668 result.addVertex(vertices_[static_cast<std::size_t>(v)]);
669 }
670 const auto adjacency = clearVisibleAdjacency();
671 for (VertexIndex u = GHOST + 1; u < vertexCount; ++u) {
672 for (const VertexIndex w : adjacency[static_cast<std::size_t>(u)]) {
673 if (u < w) {
674 result.addEdge(vertices_[static_cast<std::size_t>(u)],
675 vertices_[static_cast<std::size_t>(w)]);
676 }
677 }
678 }
679 return result;
680}
681
682template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
685 Graph<PointType> result;
686 const VertexIndex vertexCount = static_cast<VertexIndex>(vertices_.size());
687 for (VertexIndex v = GHOST + 1; v < vertexCount; ++v) {
688 result.addVertex(vertices_[static_cast<std::size_t>(v)]);
689 }
690 const auto adjacency = visibleAdjacency();
691 for (VertexIndex u = GHOST + 1; u < vertexCount; ++u) {
692 for (const VertexIndex w : adjacency[static_cast<std::size_t>(u)]) {
693 if (u < w) {
694 result.addEdge(vertices_[static_cast<std::size_t>(u)],
695 vertices_[static_cast<std::size_t>(w)]);
696 }
697 }
698 }
699 return result;
700}
701
702template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
705 Graph<PointType> result;
706 const VertexIndex vertexCount = static_cast<VertexIndex>(vertices_.size());
707 for (VertexIndex v = GHOST + 1; v < vertexCount; ++v) {
708 result.addVertex(vertices_[static_cast<std::size_t>(v)]);
709 }
710 const auto adjacency = visibleAdjacency();
711 const auto walls = wallNeighbors();
712
713 // A taut path can bend at u only by wrapping around the walls meeting there,
714 // which needs all of them on one side of the line it bends about. A wall
715 // running along that line counts for either side, which is what keeps the
716 // walls themselves — and anything collinear with one — in the graph.
717 const auto tangent = [&](VertexIndex u, VertexIndex w) {
718 const auto& incident = walls[static_cast<std::size_t>(u)];
719 if (incident.empty()) {
720 return false;
721 }
722 int seen = 0;
723 for (const VertexIndex x : incident) {
724 const int order = detail::signValue(
725 orientationSign(vertices_[static_cast<std::size_t>(u)],
726 vertices_[static_cast<std::size_t>(w)],
727 vertices_[static_cast<std::size_t>(x)]));
728 if (order != 0) {
729 if (seen != 0 && seen != order) {
730 return false;
731 }
732 seen = order;
733 }
734 }
735 return true;
736 };
737
738 for (VertexIndex u = GHOST + 1; u < vertexCount; ++u) {
739 for (const VertexIndex w : adjacency[static_cast<std::size_t>(u)]) {
740 if (u < w && tangent(u, w) && tangent(w, u)) {
741 result.addEdge(vertices_[static_cast<std::size_t>(u)],
742 vertices_[static_cast<std::size_t>(w)]);
743 }
744 }
745 }
746 return result;
747}
748
749template <TriangleConcept TriangleType_, SegmentConcept SegmentType_>
750template <class ResultNumber>
753 const PointType& query) const {
754 using ResultPoint = Point<ResultNumber>;
755 const VisibilitySeeds seeds = visibilitySeeds(query);
756 if (!seeds.located) {
757 return Polygon<ResultPoint>();
758 }
759 const ResultNumber originX = detail::asNumber<ResultNumber>(query.x());
760 const ResultNumber originY = detail::asNumber<ResultNumber>(query.y());
761
762 // Where the ray from the query through vertex `through` meets the line of the
763 // edge (a,b). Every cone that reaches a blocking edge lies inside the wedge
764 // that edge spans from the query, so the ray always meets it and the
765 // denominator never vanishes; when `through` is an end of the edge the
766 // quotient reproduces that end exactly. This is the only division in the
767 // whole construction, which is why the result type is the caller's to pick.
768 const auto rayHit = [&](VertexIndex through, VertexIndex a, VertexIndex b) {
769 const PointType& target = vertices_[static_cast<std::size_t>(through)];
770 const PointType& head = vertices_[static_cast<std::size_t>(a)];
771 const PointType& tail = vertices_[static_cast<std::size_t>(b)];
772 const ResultNumber dx = detail::asNumber<ResultNumber>(target.x()) - originX;
773 const ResultNumber dy = detail::asNumber<ResultNumber>(target.y()) - originY;
774 const ResultNumber edgeX =
775 detail::asNumber<ResultNumber>(tail.x()) - detail::asNumber<ResultNumber>(head.x());
776 const ResultNumber edgeY =
777 detail::asNumber<ResultNumber>(tail.y()) - detail::asNumber<ResultNumber>(head.y());
778 const ResultNumber toEdgeX = detail::asNumber<ResultNumber>(head.x()) - originX;
779 const ResultNumber toEdgeY = detail::asNumber<ResultNumber>(head.y()) - originY;
780 const ResultNumber along = toEdgeX * edgeY - toEdgeY * edgeX;
781 const ResultNumber sweep = dx * edgeY - dy * edgeX;
782 const ResultNumber scale = along / sweep;
783 return ResultPoint(originX + scale * dx, originY + scale * dy);
784 };
785
786 std::vector<ResultPoint> ring;
787 const auto append = [&](const ResultPoint& p) {
788 if (ring.empty() || !(ring.back() == p)) {
789 ring.push_back(p);
790 }
791 };
792
793 std::vector<VisibilityCone> scratch;
794 const auto ignore = [](VertexIndex) {};
795 const std::size_t count = seeds.cones.size();
796 for (std::size_t arc = 0; arc < seeds.arcs.size(); ++arc) {
797 const std::size_t begin = seeds.arcs[arc];
798 const std::size_t end = arc + 1 < seeds.arcs.size() ? seeds.arcs[arc + 1] : count;
799 // A lobe that stops short of a full turn is bounded by the two boundary
800 // edges the query itself lies on, which meet at the query: it belongs to
801 // the ring, and hinges the lobe onto whatever came before.
802 if (!seeds.fullTurn) {
803 append(ResultPoint(originX, originY));
804 }
805 for (std::size_t k = begin; k < end; ++k) {
806 expandVisibility(query, seeds.cones[k], scratch, ignore,
807 [&](const VisibilityCone& cone) {
808 // The wall this cone ran into. Its visible stretch is the part
809 // between the two bounding rays, and the leaves arrive
810 // counterclockwise, so appending as they come lays out the ring.
811 const auto& v = triangles_[static_cast<std::size_t>(cone.tri)].v;
812 const VertexIndex a = v[(cone.side + 1) % 3];
813 const VertexIndex b = v[(cone.side + 2) % 3];
814 append(rayHit(cone.right, a, b));
815 append(rayHit(cone.left, a, b));
816 });
817 }
818 }
819 while (ring.size() > 1 && ring.front() == ring.back()) {
820 ring.pop_back();
821 }
822 if (ring.size() < 3) {
823 return Polygon<ResultPoint>(); // no area to bound
824 }
825 // Already counterclockwise by construction, so rotating the lexicographically
826 // smallest vertex to the front is the whole of the canonical form and the
827 // constructor need not settle the orientation — which for a rational
828 // coordinate type costs more than everything above.
829 std::rotate(ring.begin(), std::min_element(ring.begin(), ring.end()), ring.end());
830 return Polygon<ResultPoint>(ring, true);
831}
832
833// -----------------------------------------------------------------------------
834// Polygon
835// -----------------------------------------------------------------------------
836
837template <class PointType, class LabelType>
839 const auto corners = vertices();
840 const std::size_t n = corners.size();
841 // A convex polygon — including one collapsed to a point or a segment —
842 // contains every segment between its vertices, so the answer is the complete
843 // graph and there is nothing to triangulate. Besides being much the cheaper
844 // route for the case where this relation is densest, this is what handles
845 // the documented degeneracies, which have no triangulation to speak of.
846 if (n < 3 || isDegenerate() || isConvex()) {
847 Graph<PointType> result;
848 for (const auto& corner : corners) {
849 result.addVertex(corner);
850 }
851 for (std::size_t i = 0; i < n; ++i) {
852 for (std::size_t j = i + 1; j < n; ++j) {
853 result.addEdge(corners[i], corners[j]);
854 }
855 }
856 return result;
857 }
858 return triangulation().visibilityGraph();
859}
860
861template <class PointType, class LabelType>
863 const auto corners = vertices();
864 if (corners.size() < 3 || isDegenerate()) {
865 Graph<PointType> result; // no interior, hence no clear sight anywhere
866 for (const auto& corner : corners) {
867 result.addVertex(corner);
868 }
869 return result;
870 }
871 return triangulation().clearVisibilityGraph();
872}
873
874template <class PointType, class LabelType>
876 const auto corners = vertices();
877 const std::size_t n = corners.size();
878 if (n < 3 || isDegenerate()) {
879 // Every vertex is collinear with every side, so tangency holds
880 // everywhere and nothing is reduced away.
881 return visibilityGraph();
882 }
883 return triangulation().reducedVisibilityGraph();
884}
885
886template <class PointType, class LabelType>
888 const PointType& query) const {
889 if (size() < 3 || isDegenerate()) {
890 return {}; // no area to see across
891 }
892 return triangulation().visibleVertices(query);
893}
894
895template <class PointType, class LabelType>
897 const PointType& query) const {
898 if (size() < 3 || isDegenerate()) {
899 return {}; // no interior to see through
900 }
901 return triangulation().clearlyVisibleVertices(query);
902}
903
904template <class PointType, class LabelType>
905template <class ResultNumber>
913
914// -----------------------------------------------------------------------------
915// PolygonWithHoles
916// -----------------------------------------------------------------------------
917
918template <class PointType_, class LabelType>
920 // Without holes the outer ring answers on its own, keeping its convex and
921 // degenerate shortcuts.
922 return holes().empty() ? outer().visibilityGraph() : triangulation().visibilityGraph();
923}
924
925template <class PointType_, class LabelType>
927 return holes().empty() ? outer().clearVisibilityGraph()
928 : triangulation().clearVisibilityGraph();
929}
930
931template <class PointType_, class LabelType>
933 return holes().empty() ? outer().reducedVisibilityGraph()
934 : triangulation().reducedVisibilityGraph();
935}
936
937template <class PointType_, class LabelType>
939 const PointType& query) const {
940 return holes().empty() ? outer().visibleVertices(query)
941 : triangulation().visibleVertices(query);
942}
943
944template <class PointType_, class LabelType>
946 const PointType& query) const {
947 return holes().empty() ? outer().clearlyVisibleVertices(query)
948 : triangulation().clearlyVisibleVertices(query);
949}
950
951template <class PointType_, class LabelType>
952template <class ResultNumber>
960
961} // namespace pgl
Undirected simple graph stored as adjacency sets.
Definition graph.hpp:38
void addVertex(const Vertex &vertex)
Adds a vertex if it is not already present.
Definition graph.hpp:213
void addEdge(const Vertex &u, const Vertex &v)
Adds an undirected edge and its endpoints.
Definition graph.hpp:225
Simple undirected graph with hashable vertices.
Definition arrangement.hpp:67
@ x
Definition intervaltree.hpp:24
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
void sortAround(std::vector< Point< Number, Label > > &points, const Point< CenterNumber, CenterLabel > &p)
Sorts points counterclockwise around a center point.
Definition sortpoints.hpp:48
Two-dimensional point with optional label payload.
Definition point.hpp:129
std::vector< PointType > clearlyVisibleVertices(const PointType &query) const
The region's vertices clearly visible from query.
Definition visibilitygraph.hpp:945
Graph< PointType > reducedVisibilityGraph() const
Returns the reduced visibility graph of the region's vertices.
Definition visibilitygraph.hpp:932
Graph< PointType > visibilityGraph() const
Returns the visibility graph of the region's vertices.
Definition visibilitygraph.hpp:919
std::vector< PointType > visibleVertices(const PointType &query) const
The region's vertices visible from query.
Definition visibilitygraph.hpp:938
constexpr const PolygonType & outer() const
Returns the outer boundary.
Definition polygonwithholes.hpp:178
Graph< PointType > clearVisibilityGraph() const
Returns the clear visibility graph of the region's vertices.
Definition visibilitygraph.hpp:926
PointType_ PointType
Definition polygonwithholes.hpp:90
constexpr const std::vector< PolygonType > & holes() const
Returns the holes in canonical order.
Definition polygonwithholes.hpp:202
auto triangulation() const
Builds the constrained Delaunay triangulation of this region.
Definition triangulation.hpp:6930
Polygon< Point< ResultNumber > > regularizedVisiblePolygon(const PointType &query) const
The part of the region visible from query, regularized.
Definition visibilitygraph.hpp:954
Closed simple polygon stored by its vertices.
Definition polygon.hpp:59
Graph< PointType > visibilityGraph() const
Returns the visibility graph of the polygon vertices.
Definition visibilitygraph.hpp:838
Graph< PointType > clearVisibilityGraph() const
Returns the clear visibility graph of the polygon vertices.
Definition visibilitygraph.hpp:862
std::vector< PointType > clearlyVisibleVertices(const PointType &query) const
The polygon vertices clearly visible from query.
Definition visibilitygraph.hpp:896
constexpr bool isConvex() const
Tests whether the polygon is convex.
Definition polygon.hpp:423
auto triangulation() const
Builds the constrained Delaunay triangulation of this polygon.
Definition triangulation.hpp:6860
constexpr Polygon()=default
Creates a polygon with no vertex.
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
Polygon< Point< ResultNumber > > regularizedVisiblePolygon(const PointType &query) const
The part of the polygon visible from query, regularized.
Definition visibilitygraph.hpp:906
std::vector< PointType > visibleVertices(const PointType &query) const
The polygon vertices visible from query.
Definition visibilitygraph.hpp:887
PointType_ PointType
Definition polygon.hpp:60
constexpr std::vector< PointType > vertices() const
Returns the vertices of the polygon (translation applied).
Definition polygon.hpp:587
Graph< PointType > reducedVisibilityGraph() const
Returns the reduced visibility graph of the polygon vertices.
Definition visibilitygraph.hpp:875
constexpr bool isDegenerate() const
Checks if the polygon is degenerate (has zero area).
Definition polygon.hpp:319
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
Graph< PointType > reducedVisibilityGraph() const
Returns the reduced visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:704
Graph< PointType > visibilityGraph() const
Returns the visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:684
Polygon< Point< ResultNumber > > regularizedVisiblePolygon(const PointType &query) const
The region of the domain visible from query, regularized.
Definition visibilitygraph.hpp:752
std::vector< PointType > visibleVertices(const PointType &query) const
The mesh vertices visible from query.
Definition visibilitygraph.hpp:396
typename TriangleType::PointType PointType
Definition triangulation.hpp:176
std::vector< PointType > clearlyVisibleVertices(const PointType &query) const
The mesh vertices clearly visible from query.
Definition visibilitygraph.hpp:383
Graph< PointType > clearVisibilityGraph() const
Returns the clear visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:664