Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
planar-graph.h
Go to the documentation of this file.
1
3/*
4 * Authors:
5 * RafaƂ Siejakowski <rs@rs-math.net>
6 *
7 * Copyright 2022 the Authors
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it either under the terms of the GNU Lesser General Public
11 * License version 2.1 as published by the Free Software Foundation
12 * (the "LGPL") or, at your option, under the terms of the Mozilla
13 * Public License Version 1.1 (the "MPL"). If you do not alter this
14 * notice, a recipient may use your version of this file under either
15 * the MPL or the LGPL.
16 *
17 * You should have received a copy of the LGPL along with this library
18 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * You should have received a copy of the MPL along with this library
21 * in the file COPYING-MPL-1.1
22 *
23 * The contents of this file are subject to the Mozilla Public License
24 * Version 1.1 (the "License"); you may not use this file except in
25 * compliance with the License. You may obtain a copy of the License at
26 * http://www.mozilla.org/MPL/
27 *
28 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
29 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
30 * the specific language governing rights and limitations.
31 */
32
33// WARNING: This is a private header. Do not include it directly.
34
35#ifndef LIB2GEOM_SEEN_PLANAR_GRAPH_H
36#define LIB2GEOM_SEEN_PLANAR_GRAPH_H
37
38#include <algorithm>
39#include <iterator>
40#include <list>
41
42#include <2geom/angle.h>
43#include <2geom/coord.h>
44#include <2geom/line.h>
45#include <2geom/point.h>
46#include <2geom/path.h>
48#include <2geom/utils.h>
49
50namespace Geom {
51
52template<typename EdgeLabel>
53concept EdgeLabelConcept = requires(EdgeLabel el, EdgeLabel const &other) {
54 el.onReverse();
55 el.onMergeWith(other);
56 el.onDetach();
57 el = other;
58};
59
125template<typename EdgeLabel>
128{
129public:
130
133 {
134 using Sign = bool;
135 inline static Sign const START = false;
136 inline static Sign const END = true;
137
138 double azimuth;
139 unsigned index;
141 bool invalid = false;
142
143 Incidence(unsigned edge_index, double departure_azimuth, Sign which_end)
144 : azimuth{departure_azimuth}
145 , index{edge_index}
146 , sign{which_end}
147 {
148 }
149 ~Incidence() = default;
150
152 inline bool operator<(Incidence const &other) const { return azimuth < other.azimuth; }
153
155 inline bool operator<(double angle) const { return azimuth < angle; }
156
158 inline bool operator==(Incidence const &other) const
159 {
160 return index == other.index && sign == other.sign;
161 }
162 };
163 using IncIt = std::list<Incidence>::iterator;
164 using IncConstIt = std::list<Incidence>::const_iterator;
165
167 class Vertex
168 {
169 private:
171 std::list<Incidence> _incidences;
172 unsigned mutable _flags = 0;
173
174 inline static Point::LexLess<X> const _cmp;
175
176 public:
177 Vertex(Point const &pos)
178 : _position{pos}
179 {
180 }
181
183 Point const &point() const { return _position; }
184
186 auto const &getIncidences() const { return _incidences; }
187
189 bool operator<(Vertex const &other) const { return _cmp(_position, other._position); }
190
191 unsigned flags() const { return _flags; }
192 void setFlags(unsigned flags) const { _flags = flags; }
193
196
199
209
210 private:
214
216 Incidence &_addIncidence(unsigned edge_index, double azimuth, Incidence::Sign sign)
217 {
218 auto where = std::find_if(_incidences.begin(), _incidences.end(), [=](auto &inc) -> bool {
219 return inc.azimuth >= azimuth;
220 });
221 return *(_incidences.emplace(where, edge_index, azimuth, sign));
222 }
223
227 {
228 auto it = std::find_if(_incidences.begin(), _incidences.end(),
229 [=](Incidence const &i) -> bool { return &i == pointer; });
230 return (it == _incidences.end()) ? _incidences.begin() : it;
231 }
232
233 friend class PlanarGraph<EdgeLabel>;
234 };
235 using VertexIterator = std::list<Vertex>::iterator;
236
238 struct Edge
239 {
240 Vertex *start = nullptr, *end = nullptr;
242 bool detached = false;
243 bool inserted_as_detached = false;
244 EdgeLabel mutable label;
245
247 Edge(Path &&movein_path, EdgeLabel &&movein_label)
248 : path{movein_path}
249 , label{movein_label}
250 {
251 }
252
254 void detach()
255 {
256 detached = true;
257 label.onDetach();
258 }
259 };
260 using EdgeIterator = std::vector<Edge>::iterator;
261 using EdgeConstIterator = std::vector<Edge>::const_iterator;
262
263private:
264 double const _precision;
265 std::list<Vertex> _vertices;
266 std::vector<Edge> _edges;
267 std::vector< std::pair<Vertex *, Incidence *> > _junk;
268 bool _regularized = true; // An empty graph is (trivially) regularized.
269
270public:
272 : _precision{precision}
273 {
274 }
275
276 std::list<Vertex> const &getVertices() const { return _vertices; }
277 std::vector<Edge> const &getEdges() const { return _edges; }
278 Edge const &getEdge(size_t index) const { return _edges.at(index); }
279 size_t getEdgeIndex(Edge const &edge) const { return &edge - _edges.data(); }
280 double getPrecision() const { return _precision; }
281 size_t numVertices() const { return _vertices.size(); }
282 size_t numEdges(bool include_detached = true) const
283 {
284 if (include_detached) {
285 return _edges.size();
286 }
287 return std::count_if(_edges.begin(), _edges.end(),
288 [](Edge const &e) -> bool { return !e.detached; });
289 }
290
292 bool isRegularized() const { return _regularized; }
293
294 // 0x1p-50 is about twice the distance between M_PI and the next representable double.
295 void regularize(double angle_precision = 0x1p-50, bool remove_collapsed_loops = true);
296
298 void reserveEdgeCapacity(size_t capacity) { _edges.reserve(capacity); }
299
300 unsigned insertEdge(Path &&path, EdgeLabel &&edge = EdgeLabel());
301 unsigned insertDetached(Path &&path, EdgeLabel &&edge = EdgeLabel());
302
304 unsigned insertEdge(Path const &path, EdgeLabel &&edge = EdgeLabel())
305 {
306 return insertEdge(Path(path), std::forward<EdgeLabel>(edge));
307 }
308 unsigned insertDetached(Path const &path, EdgeLabel &&edge = EdgeLabel())
309 {
310 return insertDetached(Path(path), std::forward<EdgeLabel>(edge));
311 }
312
320 std::pair<Vertex *, Incidence *>
321 getIncidence(unsigned edge_index, Incidence::Sign sign) const
322 {
323 if (edge_index >= _edges.size() || _edges[edge_index].detached) {
324 return {nullptr, nullptr};
325 }
326 Vertex *vertex = (sign == Incidence::START) ? _edges[edge_index].start
327 : _edges[edge_index].end;
328 if (!vertex) {
329 return {nullptr, nullptr};
330 }
331 auto it = std::find(vertex->_incidences.begin(), vertex->_incidences.end(),
332 Incidence(edge_index, 42, sign)); // azimuth doesn't matter.
333 if (it == vertex->_incidences.end()) {
334 return {nullptr, nullptr};
335 }
336 return {vertex, &(*it)};
337 }
338
348 inline Incidence const &nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence,
349 bool clockwise = false) const
350 {
351 return clockwise ? *(vertex->_cyclicPrevIncidence(incidence))
352 : *(vertex->_cyclicNextIncidence(incidence));
353 }
354
356 inline Incidence const &nextIncidence(Vertex const &vertex, Incidence const &incidence,
357 bool clockwise = false) const
358 {
359 IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
360 if (it == vertex._incidences.end()) {
361 return incidence;
362 }
363 return clockwise ? *(vertex.cyclicPrevIncidence(it))
364 : *(vertex.cyclicNextIncidence(it));
365 }
366
368 inline IncConstIt nextIncidenceIt(Vertex const &vertex, Incidence const &incidence,
369 bool clockwise = false) const
370 {
371 IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
372 if (it == vertex._incidences.end()) {
373 return vertex._incidences.begin();
374 }
375 return clockwise ? vertex.cyclicPrevIncidence(it)
376 : vertex.cyclicNextIncidence(it);
377 }
378 inline IncConstIt nextIncidenceIt(Vertex const &vertex, IncConstIt const &incidence,
379 bool clockwise = false) const
380 {
381 return clockwise ? vertex.cyclicPrevIncidence(incidence)
382 : vertex.cyclicNextIncidence(incidence);
383 }
384
391 Incidence *nextIncidence(VertexIterator const &vertex, double azimuth,
392 bool clockwise = false) const;
393
395 Path getOutgoingPath(Incidence const *incidence) const
396 {
397 return incidence ? _getPathImpl(incidence, Incidence::START) : Path();
398 }
399
401 Path getIncomingPath(Incidence const *incidence) const
402 {
403 return incidence ? _getPathImpl(incidence, Incidence::END) : Path();
404 }
405
406private:
407 inline Path _getPathImpl(Incidence const *incidence, Incidence::Sign origin) const
408 {
409 return (incidence->sign == origin) ? _edges[incidence->index].path
410 : _edges[incidence->index].path.reversed();
411 }
412
414 inline void _throwAway(Vertex *vertex, Incidence *incidence)
415 {
416 if (!vertex || !incidence) {
417 return;
418 }
419 incidence->invalid = true;
420 _junk.emplace_back(vertex, incidence);
421 }
422
423 // Topological reconfiguration functions; see their definitions for documentation.
424 bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
425 Vertex *_ensureVertexAt(Point const &position);
426 void _mergeCoincidingEdges(Incidence *first, Incidence *second);
427 void _mergeShorterLonger(Vertex &vertex, Incidence *shorter, Incidence *longer,
428 PathTime const &time_on_longer);
429 void _mergeWyeConfiguration(Vertex &vertex, Incidence *first, Incidence *second,
430 PathIntersection const &split);
432 void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second,
433 PathIntersection const &split);
434 bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
435 void _reglueTangentFan(Vertex &vertex, IncIt const &first, IncIt const &last, bool deloop);
436 void _regularizeVertex(Vertex &vertex, double angle_precision, bool deloop);
437
438 // === Static stuff ===
439
441 inline static double _getAzimuth(Point const &vec) { return vec.isZero() ? 0.0 : atan2(vec); }
442
444 inline static PathTime _reversePathTime(PathTime const &time, Path const &path)
445 {
446 int new_index = path.size() - time.curve_index - 1;
447 Coord new_time = 1.0 - time.t;
448 if (new_index < 0) {
449 new_index = 0;
450 new_time = 0;
451 }
452 return PathTime(new_index, new_time);
453 }
454
456 inline static PathTime _pathEnd(Path const &path) { return PathTime(path.size() - 1, 1.0); }
457 inline static auto const PATH_START = PathTime(0, 0);
458
459public:
460 static double closedPathArea(Path const &path);
461 static bool deviatesLeft(Path const &first, Path const &second);
462};
463
474template<typename EL>
477{
478 auto const insert_at_front = [&, this]() -> Vertex* {
479 _vertices.emplace_front(pos);
480 return &(_vertices.front());
481 };
482
483 if (_vertices.empty()) {
484 return insert_at_front();
485 }
486
487 // TODO: Use a heap?
488 auto it = std::find_if(_vertices.begin(), _vertices.end(), [&](Vertex const &v) -> bool {
489 return Vertex::_cmp(pos, v._position); // existing vertex position > pos.
490 });
491
492 if (it != _vertices.end()) {
493 if (are_near(pos, it->_position, _precision)) {
494 return &(*it); // Reuse existing vertex.
495 }
496 if (it == _vertices.begin()) {
497 return insert_at_front();
498 }
499 }
500 // Look at the previous element, reuse if near, insert before `it` otherwise.
501 return &(*(are_near(pos, std::prev(it)->_position, _precision) ? std::prev(it)
502 : _vertices.emplace(it, pos)));
503}
504
513template<typename EdgeLabel>
515unsigned PlanarGraph<EdgeLabel>::insertEdge(Path &&path, EdgeLabel &&label)
516{
517 unsigned edge_index = _edges.size();
518 auto &inserted = _edges.emplace_back(std::forward<Path>(path),
519 std::forward<EdgeLabel>(label));
520
521 // Calculate the outgoing azimuths at both endpoints.
522 double const start_azimuth = _getAzimuth(inserted.path.initialUnitTangent());
523 double const end_azimuth = _getAzimuth(-inserted.path.finalUnitTangent());
524
525 // Get the endpoints into the graph.
526 auto start = _ensureVertexAt(inserted.path.initialPoint());
527 auto end = _ensureVertexAt(inserted.path.finalPoint());
528
529 // Inform the edge about its endpoints.
530 inserted.start = start;
531 inserted.end = end;
532
533 // Add incidences at the endpoints.
534 start->_addIncidence(edge_index, start_azimuth, Incidence::START);
535 end->_addIncidence(edge_index, end_azimuth, Incidence::END);
536
537 _regularized = false;
538 return edge_index;
539}
540
553template<typename EdgeLabel>
556{
557 unsigned edge_index = _edges.size();
558 auto &inserted = _edges.emplace_back(std::forward<Path>(path),
559 std::forward<EdgeLabel>(label));
560 inserted.detached = true;
561 inserted.inserted_as_detached = true;
562 return edge_index;
563}
564
566template<typename EdgeLabel>
569{
570 for (auto &[vertex, incidence] : _junk) {
571 Incidence *to_remove = incidence;
572 auto it = std::find_if(vertex->_incidences.begin(), vertex->_incidences.end(),
573 [=](Incidence const &inc) -> bool { return &inc == to_remove; });
574 if (it != vertex->_incidences.end()) {
575 vertex->_incidences.erase(it);
576 }
577 }
578 _junk.clear();
579}
580
599template<typename EdgeLabel>
601void PlanarGraph<EdgeLabel>::regularize(double angle_precision, bool remove_collapsed_loops)
602{
603 for (auto it = _vertices.begin(); it != _vertices.end(); ++it) {
604 // Note: the list of vertices may grow during the execution of this loop,
605 // so don't replace it with a range-for (which stores the end iterator).
606 // We want the loop to carry on going over the elements it inserted.
607 if (it->_incidences.size() < 2) {
608 continue;
609 }
610 _regularizeVertex(*it, angle_precision, remove_collapsed_loops);
611 }
612 _purgeJunkIncidences();
613 _regularized = true;
614}
615
630template<typename EdgeLabel>
633 double angle_precision, bool deloop)
634{
635 auto &incidences = vertex._incidences;
636
638 auto const angles_equal = [=](double az1, double az2) -> bool {
639 static double const twopi = 2.0 * M_PI;
640 return are_near(std::fmod(az1 + twopi, twopi), std::fmod(az2 + twopi, twopi),
641 angle_precision);
642 };
643
644 IncIt run_begin; // First element in the last discovered run of equal azimuths.
645
647 auto const process_runs = [&](IncIt begin, IncIt end) -> bool
648 {
649 double current_azimuth = 42; // Invalid radian angle.
650 bool in_a_run = false;
651
652 for (auto it = begin; it != end; ++it) {
653 bool const equal = angles_equal(it->azimuth, current_azimuth);
654 if (equal && !in_a_run) {
655 run_begin = std::prev(it); // Save to enclosing scope.
656 in_a_run = true;
657 } else if (!equal && in_a_run) {
658 _reglueTangentFan(vertex, run_begin, std::prev(it), deloop);
659 in_a_run = false;
660 }
661 current_azimuth = it->azimuth;
662 }
663 return in_a_run;
664 };
665
666 double const last_azimuth = incidences.back().azimuth;
667
668 if (angles_equal(incidences.front().azimuth, last_azimuth)) {
669 // The cyclic list contains a run of equal azimuths which straddles the "end".
670 // This means that we must skip the part of this run on the "begin" side on the
671 // first pass and handle it once we've traversed the remainder of the list.
672
673 bool processed = false;
674 double previous_azimuth = last_azimuth;
675 IncIt straddling_run_last;
676
677 for (auto it = incidences.begin(); it != incidences.end(); ++it) {
678 if (!angles_equal(it->azimuth, previous_azimuth)) {
679 straddling_run_last = std::prev(it);
680 process_runs(it, incidences.end());
681 processed = true;
682 break;
683 }
684 previous_azimuth = it->azimuth;
685 }
686 if (processed) {
687 // Find the first element of the straddling run.
688 auto it = std::prev(incidences.end());
689 while (angles_equal(it->azimuth, last_azimuth)) {
690 --it;
691 }
692 ++it; // Now we're at the start of the straddling run.
693 _reglueTangentFan(vertex, it, straddling_run_last, deloop);
694 } else {
695 // We never encountered anything outside of the straddling run: reglue everything.
696 _reglueTangentFan(vertex, incidences.begin(), std::prev(incidences.end()), deloop);
697 }
698 } else if (process_runs(incidences.begin(), incidences.end())) {
699 // Our run got rudely interrupted by the end of the container; reglue till the end.
700 _reglueTangentFan(vertex, run_begin, std::prev(incidences.end()), deloop);
701 }
702}
703
721template<typename EL>
724 IncIt const &first,
725 IncIt const &last, bool deloop)
726{
727 // Search all pairs (triangular pattern), skipping invalid incidences.
728 for (auto it = first; it != last; it = vertex.cyclicNextIncidence(it)) {
729 if (it->invalid) {
730 continue;
731 }
732 for (auto is = vertex.cyclicNextIncidence(it); true; is = vertex.cyclicNextIncidence(is)) {
733 if (!is->invalid && _compareAndReglue(vertex, &(*it), &(*is), deloop)) {
734 // Swap the incidences, effectively implementing "bubble sort".
735 std::swap(*it, *is);
736 }
737 if (is == last) {
738 break;
739 }
740 }
741 }
742}
743
759template<typename EL>
762 Incidence *first,
763 Incidence *second, bool deloop)
764{
765 if (first->index == second->index) {
766 return _reglueTeardrop(vertex, first, second, deloop);
767 }
768
769 // Get paths corresponding to the edges but travelling away from the vertex.
770 auto first_path_out = getOutgoingPath(first);
771 auto second_path_out = getOutgoingPath(second);
772 auto split = parting_point(first_path_out, second_path_out, _precision);
773
774 if (are_near(split.point(), vertex.point(), _precision)) {
775 // Paths deviate immediately, so no gluing is needed. The incidences should
776 // be swapped if the first edge path departs to the left of the second one.
777 return deviatesLeft(first_path_out, second_path_out);
778 }
779
780 // Determine the nature of the initial overlap between the paths.
781 bool const till_end_of_1st = are_near(split.point(), first_path_out.finalPoint(), _precision);
782 bool const till_end_of_2nd = are_near(split.point(), second_path_out.finalPoint(), _precision);
783
784 if (till_end_of_1st && till_end_of_2nd) { // Paths coincide completely.
785 _mergeCoincidingEdges(first, second);
786 } else if (till_end_of_1st) {
787 // Paths coincide until the end of the 1st one, which however isn't the end of the
788 // 2nd one; for example, the first one could be the vertical riser of the letter L
789 // whereas the second one – the entire letter stroke.
790 _mergeShorterLonger(vertex, first, second, split.second);
791 } else if (till_end_of_2nd) {
792 // The same but with with the second edge shorter than the first one.
793 _mergeShorterLonger(vertex, second, first, split.first);
794 } else { // A Y-shaped split.
795 _mergeWyeConfiguration(vertex, first, second, split);
796 }
797 return false; // We've glued so no need to swap anything.
798}
799
817template<typename EL>
820 Incidence *first,
821 Incidence *second, bool deloop)
822{
823 // Calculate the area enclosed by the teardrop.
824 // The convention is that the unit circle (cos(t), sint(t)), t from 0 to 2pi,
825 // encloses an area of +pi.
826 auto &edge = _edges[first->index];
827 Path loop = edge.path; loop.close();
828 double signed_area = closedPathArea(loop);
829
830 if (deloop && are_near(signed_area, 0.0, _precision)) {
831 edge.detach();
832 _throwAway(&vertex, first);
833 _throwAway(&vertex, second);
834 return false;
835 }
836
837 auto split = parting_point(loop, loop.reversed(), _precision);
838 if (are_near(split.point(), vertex.point(), _precision)) {
839 // The loop spreads out immediately. We simply check if the incidences should be swapped.
840 // We want them to be ordered such that the signed area encircled by the path going out
841 // at the first incidence and coming back at the second (with this orientation) is > 0.
842 return (first->sign == Incidence::START) ^ (signed_area > 0.0);
843 }
844
845 // The loop encloses a nonzero area, but the two branches don't separate at the starting
846 // point. Instead, they travel together for a while before they split like a lasso.
847 _reglueLasso(vertex, first, second, split);
848 return false;
849}
850
871template<typename EL>
874 Incidence *first,
875 Incidence *second,
876 PathIntersection const &split)
877{
878 unsigned lasso = first->index;
879
880 // Create the "free rope" path.
881 auto rope = _edges[lasso].path.portion(PATH_START, split.first);
882 rope.setInitial(vertex.point());
883 rope.setFinal(split.point());
884 double const rope_final_backward_azimuth = _getAzimuth(-rope.finalUnitTangent());
885
886 // Compute the new label of the rope edge.
887 auto oriented_as_loop = _edges[lasso].label;
888 auto reversed = oriented_as_loop; reversed.onReverse();
889 oriented_as_loop.onMergeWith(reversed);
890
891 // Insert the rope and its endpoint.
892 unsigned const rope_index = _edges.size();
893 auto &rope_edge = _edges.emplace_back(std::move(rope), std::move(oriented_as_loop));
894 auto const new_split_vertex = _ensureVertexAt(split.point());
895
896 // Reuse lasso's first incidence as the incidence to the rope (azimuth can stay).
897 first->index = rope_index;
898 first->sign = Incidence::START;
899
900 // Connect the rope to the newly created split vertex.
901 new_split_vertex->_addIncidence(rope_index, rope_final_backward_azimuth, Incidence::END);
902 rope_edge.start = &vertex;
903 rope_edge.end = new_split_vertex;
904
905 // Insert the hoop
906 auto hoop = _edges[lasso].path.portion(split.first,
907 _reversePathTime(split.second, _edges[lasso].path));
908 hoop.setInitial(split.point());
909 hoop.setFinal(split.point());
910 insertEdge(std::move(hoop), EL(_edges[lasso].label));
911
912 // Detach the original lasso edge and mark the second incidence for cleanup.
913 _edges[lasso].detach();
914 _throwAway(&vertex, second);
915}
916
925template<typename EL>
928 Incidence *second)
929{
930 auto &surviver = _edges[first->index];
931 auto &casualty = _edges[second->index];
932
933 auto other_label = casualty.label;
934 if (first->sign != second->sign) { // Logically reverse the label before merging.
935 other_label.onReverse();
936 }
937 surviver.label.onMergeWith(other_label);
938
939 // Mark both incidences of the second edge as junk and detach it.
940 auto [start_vertex, start_inc] = getIncidence(second->index, Incidence::START);
941 _throwAway(start_vertex, start_inc);
942 auto [end_vertex, end_inc] = getIncidence(second->index, Incidence::END);
943 _throwAway(end_vertex, end_inc);
944 casualty.detach();
945}
946
959template<typename EL>
962 Incidence *shorter,
963 Incidence *longer,
964 PathTime const &time_on_longer)
965{
966 auto &shorter_edge = _edges[shorter->index];
967 auto &longer_edge = _edges[longer->index];
968
969 // Get the vertices at the far ends of both edges.
970 auto shorter_far_end = (shorter->sign == Incidence::START) ? shorter_edge.end
971 : shorter_edge.start;
973 bool const longer_out = (longer->sign == Incidence::START);
974 auto longer_far_end = longer_out ? longer_edge.end : longer_edge.start;
975
976 // Copy the longer edge's label and merge with that of the shorter.
977 auto longer_label = longer_edge.label;
978 if (shorter->sign != longer->sign) {
979 longer_label.onReverse();
980 }
981 shorter_edge.label.onMergeWith(longer_label);
982
983 // Create the trimmed path (longer minus shorter).
984 Path trimmed;
985 double trimmed_departure_azimuth;
986 if (longer_out) {
987 trimmed = longer_edge.path.portion(time_on_longer, _pathEnd(longer_edge.path));
988 longer_edge.start = shorter_far_end;
989 trimmed.setInitial(shorter_far_end->point());
990 trimmed.setFinal(longer_far_end->point());
991 trimmed_departure_azimuth = _getAzimuth(trimmed.initialUnitTangent());
992 } else {
993 trimmed = longer_edge.path.portion(PATH_START, _reversePathTime(time_on_longer,
994 longer_edge.path));
995 longer_edge.end = shorter_far_end;
996 trimmed.setInitial(longer_far_end->point());
997 trimmed.setFinal(shorter_far_end->point());
998 trimmed_departure_azimuth = _getAzimuth(-trimmed.finalUnitTangent());
999 }
1000
1001 // Set the trimmed path as the new path of the longer edge and set up the incidences:
1002 longer_edge.path = std::move(trimmed);
1003 shorter_far_end->_addIncidence(longer->index, trimmed_departure_azimuth, longer->sign);
1004
1005 // Throw away the old start incidence of the longer edge.
1006 _throwAway(&vertex, longer);
1007}
1008
1022template<typename EL>
1023requires EdgeLabelConcept<EL>
1025 Incidence *first,
1026 Incidence *second,
1027 PathIntersection const &fork)
1028{
1029 bool const first_is_out = (first->sign == Incidence::START);
1030 bool const second_is_out = (second->sign == Incidence::START);
1031
1032 auto &first_edge = _edges[first->index];
1033 auto &second_edge = _edges[second->index];
1034
1035 // Calculate the path forming the stem of the Y:
1036 auto stem_path = getOutgoingPath(first).portion(PATH_START, fork.first);
1037 stem_path.setInitial(vertex.point());
1038 stem_path.setFinal(fork.point());
1039
1041 auto const clip_to_fork = [&](PathTime const &t, Edge &e, bool out) {
1042 if (out) { // Trim from time to end
1043 e.path = e.path.portion(t, _pathEnd(e.path));
1044 e.path.setInitial(fork.point());
1045 } else { // Trim from reverse-end to reverse-time
1046 e.path = e.path.portion(PATH_START, _reversePathTime(t, e.path));
1047 e.path.setFinal(fork.point());
1048 }
1049 };
1050
1052 auto const departing_azimuth = [&](Edge const &e, bool out) -> double {
1053 return _getAzimuth((out) ? e.path.initialUnitTangent()
1054 : -e.path.finalUnitTangent());
1055 };
1056
1057 // Clip the paths obtaining the arms of the Y.
1058 clip_to_fork(fork.first, first_edge, first_is_out);
1059 clip_to_fork(fork.second, second_edge, second_is_out);
1060
1061 // Create the fork vertex and set up its incidences.
1062 auto const fork_vertex = _ensureVertexAt(fork.point());
1063 fork_vertex->_addIncidence(first->index, departing_azimuth(first_edge, first_is_out),
1064 first->sign);
1065 fork_vertex->_addIncidence(second->index, departing_azimuth(second_edge, second_is_out),
1066 second->sign);
1067
1068 // Repoint the ends of the edges that were clipped
1069 (first_is_out ? first_edge.start : first_edge.end) = fork_vertex;
1070 (second_is_out ? second_edge.start : second_edge.end) = fork_vertex;
1071
1073 auto upwards_oriented_label = [&](Edge const &e, bool out) -> EL {
1074 auto label = e.label;
1075 if (!out) {
1076 label.onReverse();
1077 }
1078 return label;
1079 };
1080
1081 auto stem_label = upwards_oriented_label(first_edge, first_is_out);
1082 stem_label.onMergeWith(upwards_oriented_label(second_edge, second_is_out));
1083 auto stem_departure_from_fork = _getAzimuth(-stem_path.finalUnitTangent());
1084
1085 // Insert the stem of the Y-configuration.
1086 unsigned const stem_index = _edges.size();
1087 auto &stem_edge = _edges.emplace_back(std::move(stem_path), std::move(stem_label));
1088 stem_edge.start = &vertex;
1089 stem_edge.end = fork_vertex;
1090
1091 // Set up the incidences.
1092 fork_vertex->_addIncidence(stem_index, stem_departure_from_fork, Incidence::END);
1093 first->index = stem_index;
1094 first->sign = Incidence::START;
1095 _throwAway(&vertex, second);
1096}
1097
1098template<typename EL>
1099requires EdgeLabelConcept<EL>
1102 double azimuth, bool clockwise) const
1103{
1104 auto &incidences = vertex._incidences;
1105 Incidence *result = nullptr;
1106
1107 if (incidences.empty()) {
1108 return result;
1109 }
1110 // Normalize azimuth to the interval [-pi; pi].
1111 auto angle = Angle(azimuth);
1112
1113 if (clockwise) { // Go backwards and find a lower bound
1114 auto it = std::find_if(incidences.rbegin(), incidences.rend(), [=](auto inc) -> bool {
1115 return inc.azimuth <= angle;
1116 });
1117 if (it == incidences.rend()) {
1118 // azimuth is lower than the azimuths of all incidences;
1119 // going clockwise we wrap back to the highest azimuth (last incidence).
1120 return &incidences.back();
1121 }
1122 result = &(*it);
1123 } else {
1124 auto it = std::find_if(incidences.begin(), incidences.end, [=](auto inc) -> bool {
1125 return inc.azimuth >= angle;
1126 });
1127 if (it == incidences.end()) {
1128 // azimuth is higher than the azimuths of all incidences;
1129 // going counterclockwise we wrap back to the lowest azimuth.
1130 return &incidences.front();
1131 }
1132 result = &(*it);
1133 }
1134 return result;
1135}
1136
1138template<typename EL>
1139requires EdgeLabelConcept<EL>
1141{
1142 double area;
1143 Point _;
1144 centroid(path.toPwSb(), _, area);
1145 return -area; // Our convention is that the Y-axis points up
1146}
1147
1160template<typename EL>
1161requires EdgeLabelConcept<EL>
1162bool PlanarGraph<EL>::deviatesLeft(Path const &first, Path const &second)
1163{
1164 auto start = middle_point(first.initialPoint(), second.initialPoint());
1165 auto tangent_between = middle_point(first.initialUnitTangent(), second.initialUnitTangent());
1166 if (tangent_between.isZero()) {
1167 return false;
1168 }
1169 auto tangent_line = Line::from_origin_and_vector(start, tangent_between);
1170
1171 // Find the first non-degenerate curves on both paths
1172 std::unique_ptr<Curve> c[2];
1173 auto const find_first_nondegen = [](std::unique_ptr<Curve> &pointer, Path const &path) {
1174 for (auto const &c : path) {
1175 if (!c.isDegenerate()) {
1176 pointer.reset(c.duplicate());
1177 return;
1178 }
1179 }
1180 };
1181
1182 find_first_nondegen(c[0], first);
1183 find_first_nondegen(c[1], second);
1184 if (!c[0] || !c[1]) {
1185 return false;
1186 }
1187
1188 // Find the bounding boxes
1189 Rect const bounding_boxes[] {
1190 c[0]->boundsExact(),
1191 c[1]->boundsExact()
1192 };
1193
1194 // For a bounding box, find the corner that goes the furthest in the direction of the
1195 // tangent vector.
1196 auto const furthest_corner = [&](Rect const &r) -> unsigned {
1197 Coord max_dot = dot(r.corner(0) - start, tangent_between);
1198 unsigned result = 0;
1199 for (unsigned i = 1; i < 4; i++) {
1200 auto current_dot = dot(r.corner(i), tangent_between);
1201 if (current_dot > max_dot) {
1202 max_dot = current_dot;
1203 result = i;
1204 } else if (current_dot == max_dot) {
1205 // Disambiguate based on proximity to the tangent line.
1206 auto const offset = start + tangent_between;
1207 if (distance(offset, r.corner(i)) < distance(offset, r.corner(result))) {
1208 result = i;
1209 }
1210 }
1211 }
1212 return result;
1213 };
1214
1215 // Calculate the corner points overlooking the "rift" between the paths.
1216 Point corner_points[2];
1217 for (size_t i : {0, 1}) {
1218 corner_points[i] = bounding_boxes[i].corner(furthest_corner(bounding_boxes[i]));
1219 }
1220
1221 // Find a vantage point from which we can best observe the splitting paths.
1222 Point vantage_point;
1223 bool found = false;
1224 if (corner_points[0] != corner_points[1]) {
1225 auto line_connecting_corners = Line(corner_points[0], corner_points[1]);
1226 auto xing = line_connecting_corners.intersect(tangent_line);
1227 if (!xing.empty()) {
1228 vantage_point = xing[0].point();
1229 found = true;
1230 }
1231 }
1232 if (!found) {
1233 vantage_point = tangent_line.pointAt(tangent_line.timeAtProjection(corner_points[0]));
1234 }
1235
1236 // Move to twice as far in the direction of the vantage point.
1237 vantage_point += vantage_point - start;
1238
1239 // Find the points on both curves that are nearest to the vantage point.
1240 Coord nearest[2];
1241 for (size_t i : {0, 1}) {
1242 nearest[i] = c[i]->nearestTime(vantage_point);
1243 }
1244
1245 // Clip to the nearest points and examine the closed contour.
1246 Path closed_contour(start);
1247 closed_contour.setStitching(true);
1248 closed_contour.append(c[0]->portion(0, nearest[0]));
1249 closed_contour = closed_contour.reversed();
1250 closed_contour.setStitching(true);
1251 closed_contour.append(c[1]->portion(0, nearest[1]));
1252 closed_contour.close();
1253 return !path_direction(closed_contour); // Reverse to match the convention that y-axis is up.
1254}
1255
1256} // namespace Geom
1257
1258#endif // LIB2GEOM_SEEN_PLANAR_GRAPH_H
1259
1260/*
1261 Local Variables:
1262 mode:c++
1263 c-file-style:"stroustrup"
1264 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1265 indent-tabs-mode:nil
1266 fill-column:99
1267 End:
1268*/
1269// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Path - a sequence of contiguous curves.
Cartesian point / 2D vector and related operations.
Various utility functions.
Point origin
Definition aa.cpp:227
Various trigoniometric helper functions.
bool is(S const *s)
Equivalent to the boolean value of dynamic_cast<T const*>(...).
Definition cast.h:37
Wrapper for angular values.
Definition angle.h:73
Intersection between two shapes.
TimeB second
Second shape and time value.
Point point() const
Intersection point, as calculated by the intersection algorithm.
TimeA first
First shape and time value.
Infinite line on a plane.
Definition line.h:53
static Line from_origin_and_vector(Point const &o, Point const &v)
Create a line from origin and unit vector.
Definition line.h:114
Sequence of contiguous curves, aka spline.
Definition path.h:353
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
Definition path.h:827
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
const_iterator end() const
Definition path.h:465
Path portion(Coord f, Coord t) const
Definition path.h:645
Point finalUnitTangent() const
Get the unit tangent vector at the end of the path, or the zero vector if undefined.
Definition path.h:724
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
Path reversed() const
Obtain a reversed version of the current path.
Definition path.cpp:866
void setFinal(Point const &p)
Definition path.h:740
Point initialUnitTangent() const
Get the unit tangent vector at the start of the path, or the zero vector if undefined.
Definition path.h:713
size_type size() const
Natural size of the path.
Definition path.h:490
void setInitial(Point const &p)
Definition path.h:734
void start(Point const &p)
Definition path.cpp:426
Represents the vertex of a planar graph.
std::list< Incidence > _incidences
List of incidences of edges to this vertex.
Point const & point() const
Get the geometric position of the vertex.
unsigned flags() const
Get the user flags.
IncIt cyclicNextIncidence(IncIt it)
Same as above, but not const (so only for private use).
IncConstIt cyclicPrevIncidence(IncConstIt it) const
Get the cyclic-next incidence after the passed one, in the CW direction.
Point const _position
Geometric position of the vertex.
IncIt cyclicPrevIncidence(IncIt it)
IncConstIt cyclicNextIncidence(IncConstIt it) const
Get the cyclic-next incidence after the passed one, in the CCW direction.
static Point::LexLess< X > const _cmp
Point sorting function object.
Incidence * cyclicPrevIncidence(Incidence *from)
Vertex(Point const &pos)
Incidence * cyclicNextIncidence(Incidence *from)
The same but with pointers.
void setFlags(unsigned flags) const
Set the user flags.
bool operator<(Vertex const &other) const
Compare vertices based on their coordinates (lexicographically).
IncIt _incidencePtr2It(Incidence *pointer)
Return a valid iterator to an incidence passed by pointer; if the pointer is invalid,...
Incidence & _addIncidence(unsigned edge_index, double azimuth, Incidence::Sign sign)
Insert an incidence; for internal use by the PlanarGraph class.
unsigned _flags
User-settable flags.
auto const & getIncidences() const
Get the list of incidences to the vertex.
Planar graph - a graph geometrically embedded in the plane.
std::vector< Edge >::iterator EdgeIterator
IncConstIt nextIncidenceIt(Vertex const &vertex, IncConstIt const &incidence, bool clockwise=false) const
unsigned insertEdge(Path const &path, EdgeLabel &&edge=EdgeLabel())
Edge insertion with a copy of the path.
void _mergeShorterLonger(Vertex &vertex, Incidence *shorter, Incidence *longer, PathTime const &time_on_longer)
Merge a longer edge with a shorter edge that overlaps it.
void _reglueTangentFan(Vertex &vertex, IncIt const &first, IncIt const &last, bool deloop)
Regularize a fan of mutually tangent edges emanating from a vertex.
void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second, PathIntersection const &split)
Reglue a lasso-shaped loop, separating it into the "free rope" and the "hoop".
std::list< Vertex >::iterator VertexIterator
void _mergeCoincidingEdges(Incidence *first, Incidence *second)
Completely coallesce two fully overlapping edges.
void _throwAway(Vertex *vertex, Incidence *incidence)
Earmark an incidence for future deletion.
PlanarGraph(Coord precision=EPSILON)
void regularize(double angle_precision=0x1p-50, bool remove_collapsed_loops=true)
Merge overlapping edges or their portions, adding vertices if necessary.
Path getIncomingPath(Incidence const *incidence) const
Get the incident path, always oriented towards the vertex.
Vertex * _ensureVertexAt(Point const &position)
Insert a new vertex or reuse an existing one.
std::list< Incidence >::iterator IncIt
unsigned insertDetached(Path &&path, EdgeLabel &&edge=EdgeLabel())
Move-insert a new labeled edge but do not connect it to the graph.
std::vector< Edge >::const_iterator EdgeConstIterator
Edge const & getEdge(size_t index) const
std::vector< Edge > _edges
Edges of the graph.
double getPrecision() const
double const _precision
Numerical epsilon for vertex clumping.
Incidence const & nextIncidence(Vertex const &vertex, Incidence const &incidence, bool clockwise=false) const
As above, but taking references instead of iterators.
size_t numVertices() const
Path _getPathImpl(Incidence const *incidence, Incidence::Sign origin) const
bool isRegularized() const
Check if the graph has been regularized.
static double _getAzimuth(Point const &vec)
Return the angle between the vector and the positive X axis or 0 if undefined.
void _regularizeVertex(Vertex &vertex, double angle_precision, bool deloop)
Analyze and regularize all edges emanating from a given vertex.
bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second, bool deloop)
Analyze a loop path a with self-tangency at the attachment point (a teardrop).
static bool deviatesLeft(Path const &first, Path const &second)
Determine whether the first path deviates to the left of the second.
void _mergeWyeConfiguration(Vertex &vertex, Incidence *first, Incidence *second, PathIntersection const &split)
Merge a pair of partially overlapping edges, producing a Y-split at a new vertex.
Path getOutgoingPath(Incidence const *incidence) const
Get the incident path, always oriented away from the vertex.
std::list< Vertex > _vertices
Vertices of the graph.
static double closedPathArea(Path const &path)
Return the signed area enclosed by a closed path.
static PathTime _reversePathTime(PathTime const &time, Path const &path)
Return path time corresponding to the same point on the reversed path.
std::list< Vertex > const & getVertices() const
static auto const PATH_START
size_t getEdgeIndex(Edge const &edge) const
Incidence const & nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence, bool clockwise=false) const
Go clockwise or counterclockwise around the vertex and find the next incidence.
std::pair< Vertex *, Incidence * > getIncidence(unsigned edge_index, Incidence::Sign sign) const
Find the incidence at the specified endpoint of the edge.
void reserveEdgeCapacity(size_t capacity)
Allocate memory to store the specified number of edges.
std::list< Incidence >::const_iterator IncConstIt
IncConstIt nextIncidenceIt(Vertex const &vertex, Incidence const &incidence, bool clockwise=false) const
As above, but return an iterator to a const incidence.
std::vector< Edge > const & getEdges() const
unsigned insertDetached(Path const &path, EdgeLabel &&edge=EdgeLabel())
std::vector< std::pair< Vertex *, Incidence * > > _junk
Incidences that should be purged.
void _purgeJunkIncidences()
Remove incidences previously marked as junk.
size_t numEdges(bool include_detached=true) const
unsigned insertEdge(Path &&path, EdgeLabel &&edge=EdgeLabel())
Move-insert a new labeled edge into the planar graph.
bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second, bool deloop)
Compare a pair of edges emanating from the same vertex in the same direction.
static PathTime _pathEnd(Path const &path)
Return path time at the end of the path.
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr bool isZero() const
Check whether both coordinates are zero.
Definition point.h:227
Axis aligned, non-empty rectangle.
Definition rect.h:92
Integral and real coordinate types and some basic utilities.
Css & result
double c[8][4]
double const _precision
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
Infinite straight line.
double offset
Geom::Point start
Glib::ustring label
Geom::Point end
Various utility functions.
Definition affine.h:22
PathIntersection parting_point(Path const &first, Path const &second, Coord precision=EPSILON)
Find the first point where two paths diverge away from one another.
bool path_direction(Path const &p)
This function should only be applied to simple paths (regions), as otherwise a boolean winding direct...
Iter cyclic_next(Iter i, Container &c)
Get the next iterator in the container with wrap-around.
Definition utils.h:81
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
static float sign(double number)
Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise.
double atan2(Point const &p)
int centroid(std::vector< Geom::Point > const &p, Geom::Point &centroid, double &area)
polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon,...
Definition geom.cpp:366
Iter cyclic_prior(Iter i, Container &c)
Get the previous iterator in the container with wrap-around.
Definition utils.h:93
static double area(Geom::Point a, Geom::Point b, Geom::Point c)
void split(vector< Point > const &p, double t, vector< Point > &left, vector< Point > &right)
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
iter inc(iter const &x, unsigned n)
Definition path.cpp:410
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Point middle_point(LineSegment const &_segment)
Path intersection.
Generalized time value in the path.
Definition path.h:139
size_type curve_index
Index of the curve in the path.
Definition path.h:143
Coord t
Time value in the curve.
Definition path.h:142
Represents an edge of the planar graph.
Path path
The path associated to the edge.
bool detached
Whether the edge is detached from the graph.
bool inserted_as_detached
Whether the edge was inserted as detached.
EdgeLabel label
The user-supplied label of the edge.
Edge(Path &&movein_path, EdgeLabel &&movein_label)
Construct an edge with a given label.
Vertex * end
Start and end vertices.
void detach()
Detach the edge from the graph.
Represents the joint between an edge and a vertex.
bool operator==(Incidence const &other) const
Check equality (only tests edges and their ends).
bool operator<(Incidence const &other) const
Compare incidences based on their azimuths in radians.
bool invalid
Whether this incidence has been marked for deletion.
double azimuth
Angle of the edge's departure.
Incidence(unsigned edge_index, double departure_azimuth, Sign which_end)
Sign sign
Whether this is the start or end of the edge.
bool operator<(double angle) const
Compare the azimuth of an incidence with the given angle.
unsigned index
Index of the edge in the parent graph.
int index