Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
atxy.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10
11namespace pgl {
12
13// -----------------------------------------------------------------------------
14// Segment
15
27template <class PointType, class LabelType>
28template <class ResultNumber, class OtherNumber>
29constexpr std::optional<ResultNumber>
30Segment<PointType, LabelType>::yAtX(const OtherNumber &x) const {
31 if (x < min().x() || x > max().x()) {
32 return {};
33 }
34 if (x == min().x())
35 return static_cast<ResultNumber>(min().y());
36 if (x == max().x())
37 return static_cast<ResultNumber>(max().y());
38
39 const ResultNumber minX = static_cast<ResultNumber>(min().x());
40 const ResultNumber minY = static_cast<ResultNumber>(min().y());
41 const ResultNumber deltaX = static_cast<ResultNumber>(max().x() - min().x());
42 const ResultNumber deltaY = static_cast<ResultNumber>(max().y() - min().y());
43
44 const ResultNumber y = minY + (detail::asNumber<ResultNumber>(x) - minX) * deltaY / deltaX;
45 return y;
46}
47
59template <class PointType, class LabelType>
60template <class ResultNumber, class OtherNumber>
61constexpr std::optional<ResultNumber> Segment<PointType, LabelType>::xAtY(const OtherNumber &y) const {
62 const auto min_y = min().y() < max().y() ? min().y() : max().y();
63 const auto max_y = max().y() < min().y() ? min().y() : max().y();
64
65 if (y < min_y || y > max_y) {
66 return {};
67 }
68 if (y == min().y()) {
69 return static_cast<ResultNumber>(min().x());
70 }
71 if (y == max().y()) {
72 return static_cast<ResultNumber>(max().x());
73 }
74
75 const ResultNumber min_x = static_cast<ResultNumber>(min().x());
76 const ResultNumber min_y_value = static_cast<ResultNumber>(min().y());
77 const ResultNumber delta_x = static_cast<ResultNumber>(max().x() - min().x());
78 const ResultNumber delta_y = static_cast<ResultNumber>(max().y() - min().y());
79
80 return min_x + (detail::asNumber<ResultNumber>(y) - min_y_value) * delta_x / delta_y;
81}
82
83// -----------------------------------------------------------------------------
84// OrientedSegment
85
92template <class PointType, class LabelType>
93template <class ResultNumber, class OtherNumber>
94constexpr std::optional<ResultNumber>
96 return static_cast<Segment<PointType>>(*this).template yAtX<ResultNumber>(x);
97}
98
105template <class PointType, class LabelType>
106template <class ResultNumber, class OtherNumber>
107constexpr std::optional<ResultNumber>
109 return static_cast<Segment<PointType>>(*this).template xAtY<ResultNumber>(y);
110}
111
112// -----------------------------------------------------------------------------
113// Line
114
127template <class PointType, class LabelType>
128template <class ResultNumber, class OtherNumber>
129constexpr std::optional<ResultNumber>
130Line<PointType, LabelType>::yAtX(const OtherNumber& x) const {
131 if (isVertical()) {
132 if (x == min().x()) {
133 return static_cast<ResultNumber>(min().y());
134 }
135 return {};
136 }
137
138 if (x == min().x()) {
139 return static_cast<ResultNumber>(min().y());
140 }
141 if (x == max().x()) {
142 return static_cast<ResultNumber>(max().y());
143 }
144
145 const ResultNumber min_x = static_cast<ResultNumber>(min().x());
146 const ResultNumber min_y = static_cast<ResultNumber>(min().y());
147 const ResultNumber delta_x = static_cast<ResultNumber>(max().x() - min().x());
148 const ResultNumber delta_y = static_cast<ResultNumber>(max().y() - min().y());
149
150 return min_y + (detail::asNumber<ResultNumber>(x) - min_x) * delta_y / delta_x;
151}
152
165template <class PointType, class LabelType>
166template <class ResultNumber, class OtherNumber>
167constexpr std::optional<ResultNumber>
168Line<PointType, LabelType>::xAtY(const OtherNumber& y) const {
169 if (isHorizontal()) {
170 if (y == min().y()) {
171 return static_cast<ResultNumber>(min().x());
172 }
173 return {};
174 }
175
176 if (y == min().y()) {
177 return static_cast<ResultNumber>(min().x());
178 }
179 if (y == max().y()) {
180 return static_cast<ResultNumber>(max().x());
181 }
182
183 const ResultNumber min_x = static_cast<ResultNumber>(min().x());
184 const ResultNumber min_y = static_cast<ResultNumber>(min().y());
185 const ResultNumber delta_x = static_cast<ResultNumber>(max().x() - min().x());
186 const ResultNumber delta_y = static_cast<ResultNumber>(max().y() - min().y());
187
188 return min_x + (detail::asNumber<ResultNumber>(y) - min_y) * delta_x / delta_y;
189}
190
191// -----------------------------------------------------------------------------
192// OrientedLine
193
205template <class PointType, class LabelType>
206template <class ResultNumber, class OtherNumber>
207constexpr std::optional<ResultNumber>
209 return this->asLine().template yAtX<ResultNumber>(x);
210}
211
223template <class PointType, class LabelType>
224template <class ResultNumber, class OtherNumber>
225constexpr std::optional<ResultNumber>
227 return this->asLine().template xAtY<ResultNumber>(y);
228}
229
230// -----------------------------------------------------------------------------
231// Ray
232
239template <class PointType, class LabelType>
240template <class ResultNumber, class OtherNumber>
241constexpr std::optional<ResultNumber>
242Ray<PointType, LabelType>::yAtX(const OtherNumber &x) const {
243 const Line<PointType> supporting_line(*this);
244 const std::optional<ResultNumber> candidate_y = supporting_line.template yAtX<ResultNumber>(x);
245 if (!candidate_y.has_value()) {
246 return {};
247 }
248
249 const ResultNumber candidate_x = static_cast<ResultNumber>(x);
250 const Point<ResultNumber, typename PointType::LabelType> candidate_point(candidate_x, *candidate_y);
251 if (!contains(candidate_point)) {
252 return {};
253 }
254
255 return candidate_y;
256}
257
264template <class PointType, class LabelType>
265template <class ResultNumber, class OtherNumber>
266constexpr std::optional<ResultNumber>
267Ray<PointType, LabelType>::xAtY(const OtherNumber &y) const {
268 const Line<PointType> supporting_line(*this);
269 const std::optional<ResultNumber> candidate_x = supporting_line.template xAtY<ResultNumber>(y);
270 if (!candidate_x.has_value()) {
271 return {};
272 }
273
274 const ResultNumber candidate_y = static_cast<ResultNumber>(y);
275 const Point<ResultNumber, typename PointType::LabelType> candidate_point(*candidate_x, candidate_y);
276 if (!contains(candidate_point)) {
277 return {};
278 }
279
280 return candidate_x;
281}
282
283
284// ---------------------------------------------------------------------------
285// Convex
286
287template <class PointType, class LabelType>
288template<class OtherNumberType>
289constexpr std::optional<std::array<Segment<PointType>, 2>> Convex<PointType, LabelType>::edgesAtX(OtherNumberType x) const {
290 const size_t n = points_.size();
291 if (n < 3) {
292 return {};
293 }
294 using CommonNumberType = std::common_type_t<NumberType, OtherNumberType>;
295 const CommonNumberType target =
296 detail::asNumber<CommonNumberType>(x) - detail::asNumber<CommonNumberType>(translation_.x());
297 const size_t m = maxIndex();
298 const CommonNumberType min_x = static_cast<CommonNumberType>(points_[0].x());
299 const CommonNumberType max_x = static_cast<CommonNumberType>(points_[m].x());
300 if (target < min_x || target > max_x) {
301 return {}; // the vertical line misses the polygon
302 }
303 const bool at_max = (target == max_x);
304
305 // Walk a monotone-in-x chain and return the non-vertical edge whose x-span
306 // covers `target`. `at(s)` is the x of the chain's s-th vertex (non-decreasing);
307 // `pos(s)` maps a chain position to a vertex index. The edge runs from pos(s-1)
308 // to pos(s). Skipping to the first vertex strictly past `target` (or, at the
309 // right extreme, the first vertex reaching it) lands on a non-vertical edge,
310 // because a vertical edge can only sit at the leftmost/rightmost x.
311 const auto edge = [&](auto at, auto pos, size_t count) {
312 size_t lo = 1, hi = count - 1;
313 while (lo < hi) {
314 const size_t mid = lo + (hi - lo) / 2;
315 const bool past = at_max ? (at(mid) >= target) : (at(mid) > target);
316 if (past) {
317 hi = mid;
318 } else {
319 lo = mid + 1;
320 }
321 }
322 return Segment<PointType>(points_[pos(lo - 1)], points_[pos(lo)]) + translation_;
323 };
324
325 // Lower chain: indices 0..m (x non-decreasing).
326 const auto lower = edge(
327 [this](size_t i) { return static_cast<CommonNumberType>(points_[i].x()); },
328 [](size_t i) { return i; },
329 m + 1);
330 // Upper chain read leftmost->rightmost: positions 0..(n-m) map to vertices
331 // 0, n-1, n-2, ..., m, whose x is non-decreasing.
332 const auto upper = edge(
333 [this, n](size_t s) { return static_cast<CommonNumberType>(points_[(n - s) % n].x()); },
334 [n](size_t s) { return (n - s) % n; },
335 n - m + 1);
336
337 return std::array<Segment<PointType>, 2>{lower, upper};
338}
339
340// -----------------------------------------------------------------------------
341// MonotoneChain
342
343template <class PointType, class LabelType, class Storage>
344template <class OtherNumber>
345constexpr std::optional<std::size_t>
347 // Compare translated x-coordinates in the common type so mixed coordinate
348 // types (e.g. an int chain queried with a Rational) compare exactly.
349 using Compare = std::common_type_t<NumberType, OtherNumber>;
350 if (points_.empty()) {
351 return {};
352 }
353 const auto tx = translation_.x();
354 if (detail::asNumber<Compare>(x) < static_cast<Compare>(points_.front().x() + tx) ||
355 static_cast<Compare>(points_.back().x() + tx) < detail::asNumber<Compare>(x)) {
356 return {};
357 }
358 // First index whose x is >= the query x; lower_bound gives the smallest
359 // such index, which at a vertical run of vertices is the bottom one.
360 const auto it = std::lower_bound(
361 points_.begin(), points_.end(), x,
362 [&tx](const PointType& p, const OtherNumber& value) {
363 return static_cast<Compare>(p.x() + tx) < detail::asNumber<Compare>(value);
364 });
365 assert(it != points_.end());
366 const std::size_t i = static_cast<std::size_t>(it - points_.begin());
367 if (static_cast<Compare>(it->x() + tx) == detail::asNumber<Compare>(x)) {
368 return i;
369 }
370 // x lies strictly between vertex i-1 and vertex i; the range check above
371 // guarantees i > 0 here.
372 return i - 1;
373}
374
375template <class PointType, class LabelType, class Storage>
376template <class ResultNumber, class OtherNumber>
377constexpr std::optional<ResultNumber>
379 using Compare = std::common_type_t<NumberType, OtherNumber>;
380 const auto idx = indexAtX(x);
381 if (!idx) {
382 return {};
383 }
384 const PointType a = (*this)[*idx];
385 if (detail::asNumber<Compare>(a.x()) == detail::asNumber<Compare>(x)) {
386 // Exactly at a vertex; at a vertical run this is its bottom vertex.
387 return static_cast<ResultNumber>(a.y());
388 }
389 // Strictly inside edge (idx, idx + 1); the edge's Segment overload does
390 // the one interpolation division in ResultNumber.
391 const Segment<PointType> edge(a, (*this)[*idx + 1]);
392 return edge.template yAtX<ResultNumber>(x);
393}
394
395template <class PointType, class LabelType, class Storage>
396template <PointConcept OtherPoint>
397constexpr std::optional<std::size_t>
399 using Compare = std::common_type_t<NumberType, typename OtherPoint::NumberType>;
400 const auto idx = indexAtX(point.x());
401 if (!idx) {
402 return {};
403 }
404 const PointType a = (*this)[*idx];
405 if (detail::asNumber<Compare>(a.x()) == detail::asNumber<Compare>(point.x())) {
406 // idx is the bottom vertex of the vertical run at x = point.x(); the
407 // chain lies strictly below the point only when the run's *top* vertex
408 // is still below it (a point inside the run lies on the chain, so it
409 // counts as neither above nor below). Locate the top with a second
410 // binary search — the run is contiguous because the vertices are sorted
411 // lexicographically.
412 const auto tx = translation_.x();
413 const auto it = std::upper_bound(
414 points_.begin() + static_cast<std::ptrdiff_t>(*idx), points_.end(), point.x(),
415 [&tx](const auto& value, const PointType& p) {
416 return detail::asNumber<Compare>(value) < static_cast<Compare>(p.x() + tx);
417 });
418 const PointType top = (*this)[static_cast<std::size_t>(it - points_.begin()) - 1];
419 if (detail::asNumber<Compare>(top.y()) < detail::asNumber<Compare>(point.y())) {
420 return idx;
421 }
422 return {};
423 }
424 // Strictly inside edge (idx, idx + 1), which runs left to right, so the
425 // point is above the edge iff it is not on the right of it.
426 if (orientationSign(a, (*this)[*idx + 1], point) > 0) {
427 return idx;
428 }
429 return {};
430}
431
432template <class PointType, class LabelType, class Storage>
433template <PointConcept OtherPoint>
434constexpr std::optional<std::size_t>
436 using Compare = std::common_type_t<NumberType, typename OtherPoint::NumberType>;
437 const auto idx = indexAtX(point.x());
438 if (!idx) {
439 return {};
440 }
441 const PointType a = (*this)[*idx];
442 if (detail::asNumber<Compare>(a.x()) == detail::asNumber<Compare>(point.x())) {
443 // idx is the bottom vertex of the vertical run at x = point.x(); the
444 // chain lies strictly above the point only when even that bottom vertex
445 // is above it (a point inside the run lies on the chain, so it counts as
446 // neither above nor below).
447 if (detail::asNumber<Compare>(a.y()) > detail::asNumber<Compare>(point.y())) {
448 return idx;
449 }
450 return {};
451 }
452 if (orientationSign(a, (*this)[*idx + 1], point) < 0) {
453 return idx;
454 }
455 return {};
456}
457
458
459template <class PointType, class LabelType, class Storage>
460template <PointConcept OtherPoint>
461constexpr std::optional<std::size_t>
463 using Compare = std::common_type_t<NumberType, typename OtherPoint::NumberType>;
464 const auto idx = indexAtX(point.x());
465 if (!idx) {
466 return {};
467 }
468 const PointType a = (*this)[*idx];
469 if (detail::asNumber<Compare>(a.x()) == detail::asNumber<Compare>(point.x())) {
470 // The chain covers x = point.x() from the bottom vertex of this run
471 // upward; a downward ray from the point hits iff the bottom is not
472 // above the point.
473 if (detail::asNumber<Compare>(a.y()) <= detail::asNumber<Compare>(point.y())) {
474 return idx;
475 }
476 return {};
477 }
478 // Strictly inside edge (idx, idx + 1), which runs left to right, so the
479 // point is on or above the edge iff it is not on the right of it.
480 if (orientationSign(a, (*this)[*idx + 1], point) >= 0) {
481 return idx;
482 }
483 return {};
484}
485
486template <class PointType, class LabelType, class Storage>
487template <PointConcept OtherPoint>
488constexpr std::optional<std::size_t>
490 using Compare = std::common_type_t<NumberType, typename OtherPoint::NumberType>;
491 const auto idx = indexAtX(point.x());
492 if (!idx) {
493 return {};
494 }
495 const PointType a = (*this)[*idx];
496 if (detail::asNumber<Compare>(a.x()) == detail::asNumber<Compare>(point.x())) {
497 // The chain covers x = point.x() up to the *top* vertex of the run
498 // starting at idx; locate it with a second binary search (the run is
499 // contiguous because the vertices are sorted lexicographically).
500 const auto tx = translation_.x();
501 const auto it = std::upper_bound(
502 points_.begin() + static_cast<std::ptrdiff_t>(*idx), points_.end(), point.x(),
503 [&tx](const auto& value, const PointType& p) {
504 return detail::asNumber<Compare>(value) < static_cast<Compare>(p.x() + tx);
505 });
506 const PointType top = (*this)[static_cast<std::size_t>(it - points_.begin()) - 1];
507 if (detail::asNumber<Compare>(top.y()) >= detail::asNumber<Compare>(point.y())) {
508 return idx;
509 }
510 return {};
511 }
512 if (orientationSign(a, (*this)[*idx + 1], point) <= 0) {
513 return idx;
514 }
515 return {};
516}
517
518template <class PointType, class LabelType, class Storage>
519template <class LowNumber, class HighNumber>
520constexpr std::optional<std::pair<std::size_t, std::size_t>>
521MonotoneChain<PointType, LabelType, Storage>::edgeWindow(const LowNumber& xlo, const HighNumber& xhi) const {
522 using CompareLow = std::common_type_t<NumberType, LowNumber>;
523 using CompareHigh = std::common_type_t<NumberType, HighNumber>;
524 if (points_.size() < 2) {
525 return {};
526 }
527 const auto tx = translation_.x();
528 if (detail::asNumber<CompareHigh>(xhi) < static_cast<CompareHigh>(points_.front().x() + tx) ||
529 static_cast<CompareLow>(points_.back().x() + tx) < static_cast<CompareLow>(xlo)) {
530 return {};
531 }
532 std::size_t first = 0;
533 if (static_cast<CompareLow>(points_.front().x() + tx) < static_cast<CompareLow>(xlo)) {
534 // The disjointness check above guarantees xlo is inside the x-extent.
535 first = *indexAtX(xlo);
536 if (first > 0) {
537 // When xlo lands exactly on vertex `first`, edge (first - 1, first)
538 // still touches the window at that single x.
539 --first;
540 }
541 }
542 std::size_t last = points_.size() - 1;
543 if (detail::asNumber<CompareHigh>(xhi) < static_cast<CompareHigh>(points_.back().x() + tx)) {
544 // Last vertex with x <= xhi; the disjointness check guarantees one exists.
545 const auto it = std::upper_bound(
546 points_.begin(), points_.end(), xhi,
547 [&tx](const HighNumber& value, const PointType& p) {
548 return detail::asNumber<CompareHigh>(value) < static_cast<CompareHigh>(p.x() + tx);
549 });
550 last = static_cast<std::size_t>(it - points_.begin()) - 1;
551 }
552 // Vertex index -> index of the last edge starting no later than it.
553 last = std::min(last, points_.size() - 2);
554 if (last < first) {
555 return {};
556 }
557 return std::pair<std::size_t, std::size_t>{first, last};
558}
559
560} // namespace pgl
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
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
Exact equality of the point sets represented by arbitrary shapes.
constexpr size_t maxIndex() const
Returns the index of the maximum vertex (rightmost and highest in case of ties).
Definition predicates.hpp:1022
constexpr std::optional< std::array< Segment< PointType >, 2 > > edgesAtX(OtherNumberType x) const
Returns two edges of the convex polygon that intersect with the vertical line at x.
Definition atxy.hpp:289
Unoriented infinite line.
Definition line.hpp:52
constexpr bool isHorizontal() const
Returns whether the line is horizontal.
Definition predicates.hpp:466
constexpr const PointType & max() const
Returns the largest stored defining point.
Definition line.hpp:189
constexpr bool isVertical() const
Returns whether the line is vertical.
Definition predicates.hpp:461
constexpr const PointType & min() const
Returns the smallest stored defining point.
Definition line.hpp:180
constexpr std::optional< ResultNumber > xAtY(const OtherNumber &y) const
Returns the x-coordinate of the line at a given y-coordinate, if defined.
Definition atxy.hpp:168
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Returns the y-coordinate of the line at a given x-coordinate, if defined.
Definition atxy.hpp:130
constexpr std::optional< std::size_t > isStrictlyAbove(const OtherPoint &point) const
Tests whether the whole chain lies strictly above a point at its x.
Definition atxy.hpp:435
constexpr std::optional< std::size_t > indexAtX(const OtherNumber &x) const
Locates the vertex or edge of the chain at a given x-coordinate.
Definition atxy.hpp:346
constexpr std::optional< std::size_t > isBelow(const OtherPoint &point) const
Tests whether the chain passes weakly below a point.
Definition atxy.hpp:462
PointType_ PointType
Definition monotonechain.hpp:147
constexpr std::optional< std::size_t > isAbove(const OtherPoint &point) const
Tests whether the chain passes weakly above a point.
Definition atxy.hpp:489
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Evaluates the y-coordinate of the chain at a given x-coordinate.
Definition atxy.hpp:378
constexpr std::optional< std::size_t > isStrictlyBelow(const OtherPoint &point) const
Tests whether the whole chain lies strictly below a point at its x.
Definition atxy.hpp:398
constexpr std::optional< ResultNumber > xAtY(const OtherNumber &y) const
Returns the x-coordinate of the supporting line at a given y-coordinate, if defined.
Definition atxy.hpp:226
constexpr Line< PointType > asLine() const
Returns the line without orientation.
Definition orientedline.hpp:321
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Returns the y-coordinate of the supporting line at a given x-coordinate, if defined.
Definition atxy.hpp:208
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Returns the value of the y coordinate for a given x, if it exists.
Definition atxy.hpp:95
constexpr std::optional< ResultNumber > xAtY(const OtherNumber &y) const
Returns the value of the x coordinate for a given y, if it exists.
Definition atxy.hpp:108
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr std::optional< ResultNumber > xAtY(const OtherNumber &y) const
Returns the value of the x coordinate for a given y, if it exists.
Definition atxy.hpp:267
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Returns the value of the y coordinate for a given x, if it exists.
Definition atxy.hpp:242
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:625
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr const PointType & max() const
Returns the largest stored endpoint.
Definition segment.hpp:199
constexpr const PointType & min() const
Returns the smallest stored endpoint.
Definition segment.hpp:190
constexpr std::optional< ResultNumber > yAtX(const OtherNumber &x) const
Returns the value of the y coordinate for a given x, if it exists.
Definition atxy.hpp:30
constexpr std::optional< ResultNumber > xAtY(const OtherNumber &y) const
Returns the value of the x coordinate for a given y, if it exists.
Definition atxy.hpp:61