1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
|
/** @file PlanarGraph – a graph geometrically embedded in the plane.
*/
/*
* Authors:
* Rafał Siejakowski <rs@rs-math.net>
*
* Copyright 2022 the Authors
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*/
// WARNING: This is a private header. Do not include it directly.
#ifndef LIB2GEOM_SEEN_PLANAR_GRAPH_H
#define LIB2GEOM_SEEN_PLANAR_GRAPH_H
#include <algorithm>
#include <iterator>
#include <list>
#include <2geom/angle.h>
#include <2geom/coord.h>
#include <2geom/line.h>
#include <2geom/point.h>
#include <2geom/path.h>
#include <2geom/path-intersection.h>
#include <2geom/utils.h>
namespace Geom {
/**
* \class PlanarGraph
* \brief Planar graph - a graph geometrically embedded in the plane.
*
* A planar graph is composed of vertices with assigned locations (as points in the plane)
* and of edges (arcs), which are imagined as non-intersecting paths in the plane connecting
* the vertices. The edges can hold user-supplied labels (e.g., weights) which support event
* callbacks for when the graph is reconfigured, allowing the labels to be updated accordingly.
*
* \tparam EdgeLabel A user-supplied type; an object of this type will be attached to each
* edge of the planar graph (e.g., a "weight" of the edge). The type must
* satisfy requirements described further below.
*
* In order to construct a planar graph, you should specify the clumping precision (passed as
* a constructor argument) and then use the method insertEdge() to add edges to the graph, as
* many times as necessary. The graph will automatically figure out the locations of the
* vertices based on the endpoints of the inserted edges. Vertices will be combined into one
* when they are positioned within the distance specified as the clumping threshold, and the
* inserted edges will be attached to them accordingly. It is also possible to insert paths
* (typically, closed) not attached to any vertices, using the method insertDetached().
*
* After the edges are inserted, the graph is in a potentially degenerate state, where several
* edges may exactly coincide in part or in full. If this is not desired, you can regularize
* the graph by calling regularize(). During the regularization process, any overlapping edges
* are combined into one. Partially overlapping edges are first split into overlapping and
* non-overlapping portions, after which the overlapping portions are combined. If the edges
* or their parts overlap but run in opposite directions, one of them will be reversed before
* being merged with the other one. The overlaps are detected using the precision setting passed
* as the clumping precision in the constructor argument.
*
* Note however that the regularization procedure does NOT detect transverse intersections
* between the edge paths: if such intersections are not desired, the user must pass non-\
* intersecting paths to the insertEdge() method (the paths may still have common endpoints,
* which is fine: that's how common vertices are created).
*
* The insertion of new edges invalidates the regularized status, which you can check at any
* time by calling isRegularized().
*
* The vertices stored by the graph are sorted by increasing X-coordinate, and if they have
* equal X-coordinates, by increasing Y-coordinate. Even before regularization, incidences of
* edges to each vertex are sorted by increasing azimuth of the outgoing tangent (departure
* heading, but in radians, in the interval \f$(-\pi, \pi]\f$). After regularization, the edges
* around each vertex are guaranteed to be sorted counterclockwise (when the Y-axis points up)
* by where they end up going eventually, even if they're tangent at the vertex and therefore
* have equal or nearly equal departure azimuths.
*
* \note
* Requirements on the \c EdgeLabel template parameter type.
* In order for the template to instantiate correctly, the following must be satisfied:
* \li The \c EdgeLabel type provides a method \c onReverse() which gets called whenever
* the orientation of the labeled edge is reversed. This is useful when implementing
* a directed graph, since the label can keep track of the logical direction.
* \li The \c EdgeLabel type provides a method \c onMergeWith(EdgeLabel const&) which gets
* called when the labeled edge is combined with a geometrically identical (coinciding)
* edge (both combined edges having the same orientations). The label of the edge merged
* with the current one is provided as an argument to the method. This is useful when
* implementing a graph with weights: for example, when two edges are merged, you may
* want to combine their weights in some way.
* \li There is a method \c onDetach() called when the edge is removed from the graph. The
* edge objects are never destroyed but may be disconnected from the graph when they're no
* longer needed; this allows the user to put the labels of such edges in a "dead" state.
* \li The \c EdgeLabel objects must be copy-constructible and copy-assignable. This is
* because when an edge is subdivided into two, the new edges replacing it get decorated
* with copies of the original edge's label.
*/
template<typename EdgeLabel>
#if __cplusplus >= 202002L
requires requires(EdgeLabel el, EdgeLabel const &other) {
el.onReverse();
el.onMergeWith(other);
el.onDetach();
el = other;
}
#endif
class PlanarGraph
{
public:
/** Represents the joint between an edge and a vertex. */
struct Incidence
{
using Sign = bool;
inline static Sign const START = false;
inline static Sign const END = true;
double azimuth; ///< Angle of the edge's departure.
unsigned index; ///< Index of the edge in the parent graph.
Sign sign; ///< Whether this is the start or end of the edge.
bool invalid = false; ///< Whether this incidence has been marked for deletion.
Incidence(unsigned edge_index, double departure_azimuth, Sign which_end)
: azimuth{departure_azimuth}
, index{edge_index}
, sign{which_end}
{
}
~Incidence() = default;
/// Compare incidences based on their azimuths in radians.
inline bool operator<(Incidence const &other) const { return azimuth < other.azimuth; }
/// Compare the azimuth of an incidence with the given angle.
inline bool operator<(double angle) const { return azimuth < angle; }
/// Check equality (only tests edges and their ends).
inline bool operator==(Incidence const &other) const
{
return index == other.index && sign == other.sign;
}
};
using IncIt = typename std::list<Incidence>::iterator;
using IncConstIt = typename std::list<Incidence>::const_iterator;
/** Represents the vertex of a planar graph. */
class Vertex
{
private:
Point const _position; ///< Geometric position of the vertex.
std::list<Incidence> _incidences; ///< List of incidences of edges to this vertex.
unsigned mutable _flags = 0; ///< User-settable flags.
inline static Point::LexLess<X> const _cmp; ///< Point sorting function object.
public:
Vertex(Point const &pos)
: _position{pos}
{
}
/** Get the geometric position of the vertex. */
Point const &point() const { return _position; }
/** Get the list of incidences to the vertex. */
auto const &getIncidences() const { return _incidences; }
/** Compare vertices based on their coordinates (lexicographically). */
bool operator<(Vertex const &other) const { return _cmp(_position, other._position); }
unsigned flags() const { return _flags; } ///< Get the user flags.
void setFlags(unsigned flags) const { _flags = flags; } ///< Set the user flags.
/** Get the cyclic-next incidence after the passed one, in the CCW direction. */
IncConstIt cyclicNextIncidence(IncConstIt it) const { return cyclic_next(it, _incidences); }
/** Get the cyclic-next incidence after the passed one, in the CW direction. */
IncConstIt cyclicPrevIncidence(IncConstIt it) const { return cyclic_prior(it, _incidences); }
/** The same but with pointers. */
Incidence *cyclicNextIncidence(Incidence *from)
{
return &(*cyclicNextIncidence(_incidencePtr2It(from)));
}
Incidence *cyclicPrevIncidence(Incidence *from)
{
return &(*cyclicPrevIncidence(_incidencePtr2It(from)));
}
private:
/** Same as above, but not const (so only for private use). */
IncIt cyclicNextIncidence(IncIt it) { return cyclic_next(it, _incidences); }
IncIt cyclicPrevIncidence(IncIt it) { return cyclic_prior(it, _incidences); }
/** Insert an incidence; for internal use by the PlanarGraph class. */
Incidence &_addIncidence(unsigned edge_index, double azimuth, typename Incidence::Sign sign)
{
auto where = std::find_if(_incidences.begin(), _incidences.end(), [=](auto &inc) -> bool {
return inc.azimuth >= azimuth;
});
return *(_incidences.emplace(where, edge_index, azimuth, sign));
}
/** Return a valid iterator to an incidence passed by pointer;
* if the pointer is invalid, return a start iterator. */
IncIt _incidencePtr2It(Incidence *pointer)
{
auto it = std::find_if(_incidences.begin(), _incidences.end(),
[=](Incidence const &i) -> bool { return &i == pointer; });
return (it == _incidences.end()) ? _incidences.begin() : it;
}
friend class PlanarGraph<EdgeLabel>;
};
using VertexIterator = typename std::list<Vertex>::iterator;
/** Represents an edge of the planar graph. */
struct Edge
{
Vertex *start = nullptr, *end = nullptr; ///< Start and end vertices.
Path path; ///< The path associated to the edge.
bool detached = false; ///< Whether the edge is detached from the graph.
bool inserted_as_detached = false; ///< Whether the edge was inserted as detached.
EdgeLabel mutable label; ///< The user-supplied label of the edge.
/** Construct an edge with a given label. */
Edge(Path &&movein_path, EdgeLabel &&movein_label)
: path{movein_path}
, label{movein_label}
{
}
/** Detach the edge from the graph. */
void detach()
{
detached = true;
label.onDetach();
}
};
using EdgeIterator = typename std::vector<Edge>::iterator;
using EdgeConstIterator = typename std::vector<Edge>::const_iterator;
private:
double const _precision; ///< Numerical epsilon for vertex clumping.
std::list<Vertex> _vertices; ///< Vertices of the graph.
std::vector<Edge> _edges; ///< Edges of the graph.
std::vector< std::pair<Vertex *, Incidence *> > _junk; ///< Incidences that should be purged.
bool _regularized = true; // An empty graph is (trivially) regularized.
public:
PlanarGraph(Coord precision = EPSILON)
: _precision{precision}
{
}
std::list<Vertex> const &getVertices() const { return _vertices; }
std::vector<Edge> const &getEdges() const { return _edges; }
Edge const &getEdge(size_t index) const { return _edges.at(index); }
size_t getEdgeIndex(Edge const &edge) const { return &edge - _edges.data(); }
double getPrecision() const { return _precision; }
size_t numVertices() const { return _vertices.size(); }
size_t numEdges(bool include_detached = true) const
{
if (include_detached) {
return _edges.size();
}
return std::count_if(_edges.begin(), _edges.end(),
[](Edge const &e) -> bool { return !e.detached; });
}
/** Check if the graph has been regularized. */
bool isRegularized() const { return _regularized; }
// 0x1p-50 is about twice the distance between M_PI and the next representable double.
void regularize(double angle_precision = 0x1p-50, bool remove_collapsed_loops = true);
/** Allocate memory to store the specified number of edges. */
void reserveEdgeCapacity(size_t capacity) { _edges.reserve(capacity); }
unsigned insertEdge(Path &&path, EdgeLabel &&edge = EdgeLabel());
unsigned insertDetached(Path &&path, EdgeLabel &&edge = EdgeLabel());
/** Edge insertion with a copy of the path. */
unsigned insertEdge(Path const &path, EdgeLabel &&edge = EdgeLabel())
{
return insertEdge(Path(path), std::forward<EdgeLabel>(edge));
}
unsigned insertDetached(Path const &path, EdgeLabel &&edge = EdgeLabel())
{
return insertDetached(Path(path), std::forward<EdgeLabel>(edge));
}
/** \brief Find the incidence at the specified endpoint of the edge.
*
* \param edge_index The index of the edge whose incidence we wish to query.
* \param sign Which end of the edge do we want an incidence of.
* \return A pair consisting of pointers to the vertex and the incidence.
* If not found, both pointers will be null.
*/
std::pair<Vertex *, Incidence *>
getIncidence(unsigned edge_index, typename Incidence::Sign sign) const
{
if (edge_index >= _edges.size() || _edges[edge_index].detached) {
return {nullptr, nullptr};
}
Vertex *vertex = (sign == Incidence::START) ? _edges[edge_index].start
: _edges[edge_index].end;
if (!vertex) {
return {nullptr, nullptr};
}
auto it = std::find(vertex->_incidences.begin(), vertex->_incidences.end(),
Incidence(edge_index, 42, sign)); // azimuth doesn't matter.
if (it == vertex->_incidences.end()) {
return {nullptr, nullptr};
}
return {vertex, &(*it)};
}
/**
* \brief Go clockwise or counterclockwise around the vertex and find the next incidence.
* The notions of "clockwise"/"counterclockwise" correspond to the y-axis pointing up.
*
* \param vertex The vertex around which to orbit.
* \param incidence The incidence from which to start traversal.
* \param clockwise Whether to go clockwise instead of (default) counterclockwise.
* \return The next incidence encountered going in the specified direction.
*/
inline Incidence const &nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence,
bool clockwise = false) const
{
return clockwise ? *(vertex->_cyclicPrevIncidence(incidence))
: *(vertex->_cyclicNextIncidence(incidence));
}
/** As above, but taking references instead of iterators. */
inline Incidence const &nextIncidence(Vertex const &vertex, Incidence const &incidence,
bool clockwise = false) const
{
IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
if (it == vertex._incidences.end()) {
return incidence;
}
return clockwise ? *(vertex.cyclicPrevIncidence(it))
: *(vertex.cyclicNextIncidence(it));
}
/** As above, but return an iterator to a const incidence. */
inline IncConstIt nextIncidenceIt(Vertex const &vertex, Incidence const &incidence,
bool clockwise = false) const
{
IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
if (it == vertex._incidences.end()) {
return vertex._incidences.begin();
}
return clockwise ? vertex.cyclicPrevIncidence(it)
: vertex.cyclicNextIncidence(it);
}
inline IncConstIt nextIncidenceIt(Vertex const &vertex, IncConstIt const &incidence,
bool clockwise = false) const
{
return clockwise ? vertex.cyclicPrevIncidence(incidence)
: vertex.cyclicNextIncidence(incidence);
}
/** As above, but start at the prescribed departure azimuth (in radians).
*
* \return A pointer to the incidence emanating from the vertex at or immediately after
* the specified azimuth, when going around the vertex in the specified direction.
* If the vertex has no incidences, return value is nullptr.
*/
Incidence *nextIncidence(VertexIterator const &vertex, double azimuth,
bool clockwise = false) const;
/** Get the incident path, always oriented away from the vertex. */
Path getOutgoingPath(Incidence const *incidence) const
{
return incidence ? _getPathImpl(incidence, Incidence::START) : Path();
}
/** Get the incident path, always oriented towards the vertex. */
Path getIncomingPath(Incidence const *incidence) const
{
return incidence ? _getPathImpl(incidence, Incidence::END) : Path();
}
private:
inline Path _getPathImpl(Incidence const *incidence, typename Incidence::Sign origin) const
{
return (incidence->sign == origin) ? _edges[incidence->index].path
: _edges[incidence->index].path.reversed();
}
/** Earmark an incidence for future deletion. */
inline void _throwAway(Vertex *vertex, Incidence *incidence)
{
if (!vertex || !incidence) {
return;
}
incidence->invalid = true;
_junk.emplace_back(vertex, incidence);
}
// Topological reconfiguration functions; see their definitions for documentation.
bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
Vertex *_ensureVertexAt(Point const &position);
void _mergeCoincidingEdges(Incidence *first, Incidence *second);
void _mergeShorterLonger(Vertex &vertex, Incidence *shorter, Incidence *longer,
PathTime const &time_on_longer);
void _mergeWyeConfiguration(Vertex &vertex, Incidence *first, Incidence *second,
PathIntersection const &split);
void _purgeJunkIncidences();
void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second,
PathIntersection const &split);
bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
void _reglueTangentFan(Vertex &vertex, IncIt const &first, IncIt const &last, bool deloop);
void _regularizeVertex(Vertex &vertex, double angle_precision, bool deloop);
// === Static stuff ===
/** Return the angle between the vector and the positive X axis or 0 if undefined. */
inline static double _getAzimuth(Point const &vec) { return vec.isZero() ? 0.0 : atan2(vec); }
/** Return path time corresponding to the same point on the reversed path. */
inline static PathTime _reversePathTime(PathTime const &time, Path const &path)
{
int new_index = path.size() - time.curve_index - 1;
Coord new_time = 1.0 - time.t;
if (new_index < 0) {
new_index = 0;
new_time = 0;
}
return PathTime(new_index, new_time);
}
/** Return path time at the end of the path. */
inline static PathTime _pathEnd(Path const &path) { return PathTime(path.size() - 1, 1.0); }
inline static auto const PATH_START = PathTime(0, 0);
public:
static double closedPathArea(Path const &path);
static bool deviatesLeft(Path const &first, Path const &second);
};
/**
* \brief Insert a new vertex or reuse an existing one.
*
* Ensures that there is a vertex at or near the specified position
* (within the distance of _precision).
*
* \param pos The desired geometric position of the new vertex.
* \return A pointer to the inserted vertex or a pre-existing vertex near the
* desired position.
*/
template<typename EL>
typename PlanarGraph<EL>::Vertex *PlanarGraph<EL>::_ensureVertexAt(Point const &pos)
{
auto const insert_at_front = [&, this]() -> Vertex* {
_vertices.emplace_front(pos);
return &(_vertices.front());
};
if (_vertices.empty()) {
return insert_at_front();
}
// TODO: Use a heap?
auto it = std::find_if(_vertices.begin(), _vertices.end(), [&](Vertex const &v) -> bool {
return Vertex::_cmp(pos, v._position); // existing vertex position > pos.
});
if (it != _vertices.end()) {
if (are_near(pos, it->_position, _precision)) {
return &(*it); // Reuse existing vertex.
}
if (it == _vertices.begin()) {
return insert_at_front();
}
}
// Look at the previous element, reuse if near, insert before `it` otherwise.
return &(*(are_near(pos, std::prev(it)->_position, _precision) ? std::prev(it)
: _vertices.emplace(it, pos)));
}
/**
* \brief Move-insert a new labeled edge into the planar graph.
*
* \param path The geometric path of the edge.
* \param label Optionally, the label (extra user data) associated to this edge.
* If absent, a default-constructed label will be used.
* \return The index of the inserted edge.
*/
template<typename EdgeLabel>
unsigned PlanarGraph<EdgeLabel>::insertEdge(Path &&path, EdgeLabel &&label)
{
unsigned edge_index = _edges.size();
auto &inserted = _edges.emplace_back(std::forward<Path>(path),
std::forward<EdgeLabel>(label));
// Calculate the outgoing azimuths at both endpoints.
double const start_azimuth = _getAzimuth(inserted.path.initialUnitTangent());
double const end_azimuth = _getAzimuth(-inserted.path.finalUnitTangent());
// Get the endpoints into the graph.
auto start = _ensureVertexAt(inserted.path.initialPoint());
auto end = _ensureVertexAt(inserted.path.finalPoint());
// Inform the edge about its endpoints.
inserted.start = start;
inserted.end = end;
// Add incidences at the endpoints.
start->_addIncidence(edge_index, start_azimuth, Incidence::START);
end->_addIncidence(edge_index, end_azimuth, Incidence::END);
_regularized = false;
return edge_index;
}
/**
* \brief Move-insert a new labeled edge but do not connect it to the graph.
*
* Although the graph will hold the path data of an edge inserted in this way, the edge
* will not be connected to any vertex. This can be used to store information about closed
* paths (loops) in the instance, without having to specify starting points for them.
*
* \param path The geometric path of the edge.
* \param label Optionally, the label (extra user data) associated to this edge; if absent,
* the label will be default-constructed.
* \return The index of the inserted edge.
*/
template<typename EdgeLabel>
unsigned PlanarGraph<EdgeLabel>::insertDetached(Path &&path, EdgeLabel &&label)
{
unsigned edge_index = _edges.size();
auto &inserted = _edges.emplace_back(std::forward<Path>(path),
std::forward<EdgeLabel>(label));
inserted.detached = true;
inserted.inserted_as_detached = true;
return edge_index;
}
/** Remove incidences previously marked as junk. */
template<typename EdgeLabel>
void PlanarGraph<EdgeLabel>::_purgeJunkIncidences()
{
for (auto &[vertex, incidence] : _junk) {
Incidence *to_remove = incidence;
auto it = std::find_if(vertex->_incidences.begin(), vertex->_incidences.end(),
[=](Incidence const &inc) -> bool { return &inc == to_remove; });
if (it != vertex->_incidences.end()) {
vertex->_incidences.erase(it);
}
}
_junk.clear();
}
/**
* \brief Merge overlapping edges or their portions, adding vertices if necessary.
*
* \param angle_precision The numerical epsilon for radian angle comparisons.
* \param remove_collapsed_loops Whether to detach edges with both ends incident to the same
* vertex (loops) when these loops don't enclose any area.
*
* This function performs the following operations:
* \li Edges that are tangent at a vertex but don't otherwise overlap are sorted correctly
* in the counterclockwise cyclic order around the vertex.
* \li Degenerate loops which don't enclose any area are removed if the argument is true.
* \li Edges that coincide completely are reversed if needed and merged into one.
* \li Edges that coincide partially are split into overlapping and non-overlapping portions.
* Any overlapping portions are oriented consistently and then merged.
* \li As a sub-case of the above, any non-degenerate loop with an initial self-everlap
* (a "lasso") is replaced with a shorter non-overlapping loop and a simple path leading
* to it.
*/
template<typename EdgeLabel>
void PlanarGraph<EdgeLabel>::regularize(double angle_precision, bool remove_collapsed_loops)
{
for (auto it = _vertices.begin(); it != _vertices.end(); ++it) {
// Note: the list of vertices may grow during the execution of this loop,
// so don't replace it with a range-for (which stores the end iterator).
// We want the loop to carry on going over the elements it inserted.
if (it->_incidences.size() < 2) {
continue;
}
_regularizeVertex(*it, angle_precision, remove_collapsed_loops);
}
_purgeJunkIncidences();
_regularized = true;
}
/**
* \brief Analyze and regularize all edges emanating from a given vertex.
*
* This function goes through the list of incidences at the vertex (roughly sorted by
* azimuth, i.e., departure heading in radians), picking out runs of mutually tangent
* edges and calling _reglueTangentFan() on each run. The algorithm is quite complicated
* because the incidences have to be treated as a cyclic container and a run of mutually
* tangent edges may straddle the "end" of the list, including the possibility that the
* entire list is a single such run.
*
* \param vertex The vertex whose incidences should be analyzed.
* \param angle_precision The numerical epsilon for radian angle comparisons.
* \param deloop Whether loops that don't enclose any area should be detached.
*/
template<typename EdgeLabel>
void PlanarGraph<EdgeLabel>::_regularizeVertex(typename PlanarGraph<EdgeLabel>::Vertex &vertex,
double angle_precision, bool deloop)
{
auto &incidences = vertex._incidences;
/// Compare two polar angles in the interval [-π, π] modulo 2π to within angle_precision:
auto const angles_equal = [=](double az1, double az2) -> bool {
static double const twopi = 2.0 * M_PI;
return are_near(std::fmod(az1 + twopi, twopi), std::fmod(az2 + twopi, twopi),
angle_precision);
};
IncIt run_begin; // First element in the last discovered run of equal azimuths.
/// Find and reglue runs of nearly identical azimuths in the specified range.
auto const process_runs = [&](IncIt begin, IncIt end) -> bool
{
double current_azimuth = 42; // Invalid radian angle.
bool in_a_run = false;
for (auto it = begin; it != end; ++it) {
bool const equal = angles_equal(it->azimuth, current_azimuth);
if (equal && !in_a_run) {
run_begin = std::prev(it); // Save to enclosing scope.
in_a_run = true;
} else if (!equal && in_a_run) {
_reglueTangentFan(vertex, run_begin, std::prev(it), deloop);
in_a_run = false;
}
current_azimuth = it->azimuth;
}
return in_a_run;
};
double const last_azimuth = incidences.back().azimuth;
if (angles_equal(incidences.front().azimuth, last_azimuth)) {
// The cyclic list contains a run of equal azimuths which straddles the "end".
// This means that we must skip the part of this run on the "begin" side on the
// first pass and handle it once we've traversed the remainder of the list.
bool processed = false; ///< Whether we've cleared the straddling run.
double previous_azimuth = last_azimuth;
IncIt straddling_run_last;
for (auto it = incidences.begin(); it != incidences.end(); ++it) {
if (!angles_equal(it->azimuth, previous_azimuth)) {
straddling_run_last = std::prev(it);
process_runs(it, incidences.end());
processed = true;
break;
}
previous_azimuth = it->azimuth;
}
if (processed) {
// Find the first element of the straddling run.
auto it = std::prev(incidences.end());
while (angles_equal(it->azimuth, last_azimuth)) {
--it;
}
++it; // Now we're at the start of the straddling run.
_reglueTangentFan(vertex, it, straddling_run_last, deloop);
} else {
// We never encountered anything outside of the straddling run: reglue everything.
_reglueTangentFan(vertex, incidences.begin(), std::prev(incidences.end()), deloop);
}
} else if (process_runs(incidences.begin(), incidences.end())) {
// Our run got rudely interrupted by the end of the container; reglue till the end.
_reglueTangentFan(vertex, run_begin, std::prev(incidences.end()), deloop);
}
}
/**
* \brief Regularize a fan of mutually tangent edges emanating from a vertex.
*
* This function compares the tangent edges pairwise and ensures that the sequence of their
* incidences to the vertex ends up being sorted by the ultimate direction in which the
* emanating edges fan out, in the counterclockwise order.
*
* If a partial or complete overlap between edges is detected, these edges are reglued.
*
* \param vertex The vertex from which the fan emanates.
* \param first An iterator pointing to the first incidence in the fan.
* \param last An iterator pointing to the last incidence in the fan.
* NOTE: This iterator must point to the actual last incidence, not "past" it.
* The reason is that we're iterating over a cyclic collection, so there
* isn't really a meaningful end.
* \param deloop Whether loops that don't enclose any area should be detached.
*/
template<typename EL>
void PlanarGraph<EL>::_reglueTangentFan(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::IncIt const &first,
typename PlanarGraph<EL>::IncIt const &last, bool deloop)
{
// Search all pairs (triangular pattern), skipping invalid incidences.
for (auto it = first; it != last; it = vertex.cyclicNextIncidence(it)) {
if (it->invalid) {
continue;
}
for (auto is = vertex.cyclicNextIncidence(it); true; is = vertex.cyclicNextIncidence(is)) {
if (!is->invalid && _compareAndReglue(vertex, &(*it), &(*is), deloop)) {
// Swap the incidences, effectively implementing "bubble sort".
std::swap(*it, *is);
}
if (is == last) {
break;
}
}
}
}
/**
* \brief Compare a pair of edges emanating from the same vertex in the same direction.
*
* If the edges overlap in part or in full, they get reglued, which means that the topology
* of the graph may get modified. Otherwise, if the detailed comparison shows that the edges
* aren't correctly ordered around the vertex (because the second edge deviates to the right
* instead of to the left of the first, when looking away from the vertex), then the function
* will return true, signalling that the incidences should be swapped.
*
* \param vertex The vertex where the mutually tangent paths meet.
* \param first The incidence appearing as the first one in the provisional cyclic order.
* \param second The incidence appearing as the second one in the provisional cyclic order.
* \param deloop Whether to detach collapsed loops (backtracks) which don't enclose any area.
* \return Whether the incidences should be swapped.
*/
template<typename EL>
bool PlanarGraph<EL>::_compareAndReglue(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::Incidence *first,
typename PlanarGraph<EL>::Incidence *second, bool deloop)
{
if (first->index == second->index) {
return _reglueTeardrop(vertex, first, second, deloop);
}
// Get paths corresponding to the edges but travelling away from the vertex.
auto first_path_out = getOutgoingPath(first);
auto second_path_out = getOutgoingPath(second);
auto split = parting_point(first_path_out, second_path_out, _precision);
if (are_near(split.point(), vertex.point(), _precision)) {
// Paths deviate immediately, so no gluing is needed. The incidences should
// be swapped if the first edge path departs to the left of the second one.
return deviatesLeft(first_path_out, second_path_out);
}
// Determine the nature of the initial overlap between the paths.
bool const till_end_of_1st = are_near(split.point(), first_path_out.finalPoint(), _precision);
bool const till_end_of_2nd = are_near(split.point(), second_path_out.finalPoint(), _precision);
if (till_end_of_1st && till_end_of_2nd) { // Paths coincide completely.
_mergeCoincidingEdges(first, second);
} else if (till_end_of_1st) {
// Paths coincide until the end of the 1st one, which however isn't the end of the
// 2nd one; for example, the first one could be the vertical riser of the letter L
// whereas the second one – the entire letter stroke.
_mergeShorterLonger(vertex, first, second, split.second);
} else if (till_end_of_2nd) {
// The same but with with the second edge shorter than the first one.
_mergeShorterLonger(vertex, second, first, split.first);
} else { // A Y-shaped split.
_mergeWyeConfiguration(vertex, first, second, split);
}
return false; // We've glued so no need to swap anything.
}
/**
* \brief Analyze a loop path a with self-tangency at the attachment point (a teardrop).
*
* The following steps are taken:
* \li If the loop encloses zero area and \c deloop is true, the loop is detached.
* \li If the two arms of the loop split out immediately, the loop is left alone and we
* only check whether the incidences should be swapped.
* \li If the loop overlaps itself near the vertex, resembling a lasso, we split it into
* a shorter simple path and a smaller loop attached to the end of the shorter path.
*
* \param vertex The vertex at which the teardrop originates.
* \param first The first incidence of the loop to the vertex.
* \param second The second incidence of the loop to the vertex.
* \param deloop Whether the loop should be removed if it doesn't enclose any area
* (i.e., the path exactly backtracks on itself).
* \return Whether the two incidences of the loop to the vertex should be swapped.
*/
template<typename EL>
bool PlanarGraph<EL>::_reglueTeardrop(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::Incidence *first,
typename PlanarGraph<EL>::Incidence *second, bool deloop)
{
// Calculate the area enclosed by the teardrop.
// The convention is that the unit circle (cos(t), sint(t)), t from 0 to 2pi,
// encloses an area of +pi.
auto &edge = _edges[first->index];
Path loop = edge.path; loop.close();
double signed_area = closedPathArea(loop);
if (deloop && are_near(signed_area, 0.0, _precision)) {
edge.detach();
_throwAway(&vertex, first);
_throwAway(&vertex, second);
return false;
}
auto split = parting_point(loop, loop.reversed(), _precision);
if (are_near(split.point(), vertex.point(), _precision)) {
// The loop spreads out immediately. We simply check if the incidences should be swapped.
// We want them to be ordered such that the signed area encircled by the path going out
// at the first incidence and coming back at the second (with this orientation) is > 0.
return (first->sign == Incidence::START) ^ (signed_area > 0.0);
}
// The loop encloses a nonzero area, but the two branches don't separate at the starting
// point. Instead, they travel together for a while before they split like a lasso.
_reglueLasso(vertex, first, second, split);
return false;
}
/**
* \brief Reglue a lasso-shaped loop, separating it into the "free rope" and the "hoop".
*
* The lasso is an edge looping back to the same vertex, where the closed path encloses
* a non-zero area, but its two branches don't separate at the starting point. Instead,
* they travel together for a while (forming the doubled-up "free rope") before they
* split like a lasso. This function cuts the lasso at the split point:
* \code{.unparsed}
* ____ ____
* / \ / \
* VERTEX =====< | ==> VERTEX ------ NEW + NEW < |
* \____/ (lasso) (rope) \____/ (hoop)
*
* \endcode
*
* \param vertex A reference to the vertex where the lasso is attached.
* \param first The first incidence of the lasso to the vertex.
* \param second The second incidence of the lasso to the vertex.
* \param split The point where the free rope of the lasso ends and the hoop begins.
*/
template<typename EL>
void PlanarGraph<EL>::_reglueLasso(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::Incidence *first,
typename PlanarGraph<EL>::Incidence *second,
PathIntersection const &split)
{
unsigned lasso = first->index;
// Create the "free rope" path.
auto rope = _edges[lasso].path.portion(PATH_START, split.first);
rope.setInitial(vertex.point());
rope.setFinal(split.point());
double const rope_final_backward_azimuth = _getAzimuth(-rope.finalUnitTangent());
// Compute the new label of the rope edge.
auto oriented_as_loop = _edges[lasso].label;
auto reversed = oriented_as_loop; reversed.onReverse();
oriented_as_loop.onMergeWith(reversed);
// Insert the rope and its endpoint.
unsigned const rope_index = _edges.size();
auto &rope_edge = _edges.emplace_back(std::move(rope), std::move(oriented_as_loop));
auto const new_split_vertex = _ensureVertexAt(split.point());
// Reuse lasso's first incidence as the incidence to the rope (azimuth can stay).
first->index = rope_index;
first->sign = Incidence::START;
// Connect the rope to the newly created split vertex.
new_split_vertex->_addIncidence(rope_index, rope_final_backward_azimuth, Incidence::END);
rope_edge.start = &vertex;
rope_edge.end = new_split_vertex;
// Insert the hoop
auto hoop = _edges[lasso].path.portion(split.first,
_reversePathTime(split.second, _edges[lasso].path));
hoop.setInitial(split.point());
hoop.setFinal(split.point());
insertEdge(std::move(hoop), EL(_edges[lasso].label));
// Detach the original lasso edge and mark the second incidence for cleanup.
_edges[lasso].detach();
_throwAway(&vertex, second);
}
/**
* \brief Completely coallesce two fully overlapping edges.
*
* In practice, the first edge stays and the second one gets detached from the graph.
*
* \param first An iterator to the first edge's incidence to a common vertex.
* \param second An iterator to the second edge's incidence to a common vertex.
*/
template<typename EL>
void PlanarGraph<EL>::_mergeCoincidingEdges(typename PlanarGraph<EL>::Incidence *first,
typename PlanarGraph<EL>::Incidence *second)
{
auto &surviver = _edges[first->index];
auto &casualty = _edges[second->index];
auto other_label = casualty.label;
if (first->sign != second->sign) { // Logically reverse the label before merging.
other_label.onReverse();
}
surviver.label.onMergeWith(other_label);
// Mark both incidences of the second edge as junk and detach it.
auto [start_vertex, start_inc] = getIncidence(second->index, Incidence::START);
_throwAway(start_vertex, start_inc);
auto [end_vertex, end_inc] = getIncidence(second->index, Incidence::END);
_throwAway(end_vertex, end_inc);
casualty.detach();
}
/**
* \brief Merge a longer edge with a shorter edge that overlaps it.
*
* In practice, the shorter edge remains unchanged and the longer one is trimmed to
* become just the part extending past the shorter one.
*
* \param vertex The vertex where the overlap starts.
* \param shorter The incidence of the shorter edge to the common vertex.
* \param longer The incidence of the longer edge to the common vertex.
* \param time_on_longer The PathTime on the longer edge at which it passes through
* the endpoint of the shorter edge.
*/
template<typename EL>
void PlanarGraph<EL>::_mergeShorterLonger(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::Incidence *shorter,
typename PlanarGraph<EL>::Incidence *longer,
PathTime const &time_on_longer)
{
auto &shorter_edge = _edges[shorter->index];
auto &longer_edge = _edges[longer->index];
// Get the vertices at the far ends of both edges.
auto shorter_far_end = (shorter->sign == Incidence::START) ? shorter_edge.end
: shorter_edge.start;
/// Whether the longer edge heads out of the vertex.
bool const longer_out = (longer->sign == Incidence::START);
auto longer_far_end = longer_out ? longer_edge.end : longer_edge.start;
// Copy the longer edge's label and merge with that of the shorter.
auto longer_label = longer_edge.label;
if (shorter->sign != longer->sign) {
longer_label.onReverse();
}
shorter_edge.label.onMergeWith(longer_label);
// Create the trimmed path (longer minus shorter).
Path trimmed;
double trimmed_departure_azimuth;
if (longer_out) {
trimmed = longer_edge.path.portion(time_on_longer, _pathEnd(longer_edge.path));
longer_edge.start = shorter_far_end;
trimmed.setInitial(shorter_far_end->point());
trimmed.setFinal(longer_far_end->point());
trimmed_departure_azimuth = _getAzimuth(trimmed.initialUnitTangent());
} else {
trimmed = longer_edge.path.portion(PATH_START, _reversePathTime(time_on_longer,
longer_edge.path));
longer_edge.end = shorter_far_end;
trimmed.setInitial(longer_far_end->point());
trimmed.setFinal(shorter_far_end->point());
trimmed_departure_azimuth = _getAzimuth(-trimmed.finalUnitTangent());
}
// Set the trimmed path as the new path of the longer edge and set up the incidences:
longer_edge.path = std::move(trimmed);
shorter_far_end->_addIncidence(longer->index, trimmed_departure_azimuth, longer->sign);
// Throw away the old start incidence of the longer edge.
_throwAway(&vertex, longer);
}
/**
* \brief Merge a pair of partially overlapping edges, producing a Y-split at a new vertex.
*
* This topological modification is performed by inserting a new vertex at the three-way
* point (where the two paths separate) and clipping the original edges to that point.
* In this way, the original edges become the "arms" of the Y-shape. In addition, a new
* edge is inserted, forming the "stem" of the Y.
*
* \param vertex The vertex from which the partially overlapping edges originate (bottom of Y).
* \param first The incidence to the first edge (whose path is the stem and one arm of the Y).
* \param second The incidence to the second edge (stem and the other arm of the Y).
* \param fork The splitting point of the two paths.
*/
template<typename EL>
void PlanarGraph<EL>::_mergeWyeConfiguration(typename PlanarGraph<EL>::Vertex &vertex,
typename PlanarGraph<EL>::Incidence *first,
typename PlanarGraph<EL>::Incidence *second,
PathIntersection const &fork)
{
bool const first_is_out = (first->sign == Incidence::START);
bool const second_is_out = (second->sign == Incidence::START);
auto &first_edge = _edges[first->index];
auto &second_edge = _edges[second->index];
// Calculate the path forming the stem of the Y:
auto stem_path = getOutgoingPath(first).portion(PATH_START, fork.first);
stem_path.setInitial(vertex.point());
stem_path.setFinal(fork.point());
/// A closure to clip the path of an original edge to the fork point.
auto const clip_to_fork = [&](PathTime const &t, Edge &e, bool out) {
if (out) { // Trim from time to end
e.path = e.path.portion(t, _pathEnd(e.path));
e.path.setInitial(fork.point());
} else { // Trim from reverse-end to reverse-time
e.path = e.path.portion(PATH_START, _reversePathTime(t, e.path));
e.path.setFinal(fork.point());
}
};
/// A closure to find the departing azimuth of an edge at the fork point.
auto const departing_azimuth = [&](Edge const &e, bool out) -> double {
return _getAzimuth((out) ? e.path.initialUnitTangent()
: -e.path.finalUnitTangent());
};
// Clip the paths obtaining the arms of the Y.
clip_to_fork(fork.first, first_edge, first_is_out);
clip_to_fork(fork.second, second_edge, second_is_out);
// Create the fork vertex and set up its incidences.
auto const fork_vertex = _ensureVertexAt(fork.point());
fork_vertex->_addIncidence(first->index, departing_azimuth(first_edge, first_is_out),
first->sign);
fork_vertex->_addIncidence(second->index, departing_azimuth(second_edge, second_is_out),
second->sign);
// Repoint the ends of the edges that were clipped
(first_is_out ? first_edge.start : first_edge.end) = fork_vertex;
(second_is_out ? second_edge.start : second_edge.end) = fork_vertex;
/// A closure to get a consistently oriented label of an edge.
auto upwards_oriented_label = [&](Edge const &e, bool out) -> EL {
auto label = e.label;
if (!out) {
label.onReverse();
}
return label;
};
auto stem_label = upwards_oriented_label(first_edge, first_is_out);
stem_label.onMergeWith(upwards_oriented_label(second_edge, second_is_out));
auto stem_departure_from_fork = _getAzimuth(-stem_path.finalUnitTangent());
// Insert the stem of the Y-configuration.
unsigned const stem_index = _edges.size();
auto &stem_edge = _edges.emplace_back(std::move(stem_path), std::move(stem_label));
stem_edge.start = &vertex;
stem_edge.end = fork_vertex;
// Set up the incidences.
fork_vertex->_addIncidence(stem_index, stem_departure_from_fork, Incidence::END);
first->index = stem_index;
first->sign = Incidence::START;
_throwAway(&vertex, second);
}
template<typename EL>
typename PlanarGraph<EL>::Incidence*
PlanarGraph<EL>::nextIncidence(typename PlanarGraph<EL>::VertexIterator const &vertex,
double azimuth, bool clockwise) const
{
auto &incidences = vertex._incidences;
Incidence *result = nullptr;
if (incidences.empty()) {
return result;
}
// Normalize azimuth to the interval [-pi; pi].
auto angle = Angle(azimuth);
if (clockwise) { // Go backwards and find a lower bound
auto it = std::find_if(incidences.rbegin(), incidences.rend(), [=](auto inc) -> bool {
return inc.azimuth <= angle;
});
if (it == incidences.rend()) {
// azimuth is lower than the azimuths of all incidences;
// going clockwise we wrap back to the highest azimuth (last incidence).
return &incidences.back();
}
result = &(*it);
} else {
auto it = std::find_if(incidences.begin(), incidences.end, [=](auto inc) -> bool {
return inc.azimuth >= angle;
});
if (it == incidences.end()) {
// azimuth is higher than the azimuths of all incidences;
// going counterclockwise we wrap back to the lowest azimuth.
return &incidences.front();
}
result = &(*it);
}
return result;
}
/** Return the signed area enclosed by a closed path. */
template<typename EL>
double PlanarGraph<EL>::closedPathArea(Path const &path)
{
double area;
Point _;
centroid(path.toPwSb(), _, area);
return -area; // Our convention is that the Y-axis points up
}
/** \brief Determine whether the first path deviates to the left of the second.
*
* The two paths are assumed to have identical or nearly identical starting points
* but not an overlapping initial portion. The concept of "left" is based on the
* y-axis pointing up.
*
* \param first The first path.
* \param second The second path.
*
* \return True if the first path deviates towards the left of the second;
* False if the first path deviates towards the right of the second.
*/
template<typename EL>
bool PlanarGraph<EL>::deviatesLeft(Path const &first, Path const &second)
{
auto start = middle_point(first.initialPoint(), second.initialPoint());
auto tangent_between = middle_point(first.initialUnitTangent(), second.initialUnitTangent());
if (tangent_between.isZero()) {
return false;
}
auto tangent_line = Line::from_origin_and_vector(start, tangent_between);
// Find the first non-degenerate curves on both paths
std::unique_ptr<Curve> c[2];
auto const find_first_nondegen = [](std::unique_ptr<Curve> &pointer, Path const &path) {
for (auto const &c : path) {
if (!c.isDegenerate()) {
pointer.reset(c.duplicate());
return;
}
}
};
find_first_nondegen(c[0], first);
find_first_nondegen(c[1], second);
if (!c[0] || !c[1]) {
return false;
}
// Find the bounding boxes
Rect const bounding_boxes[] {
c[0]->boundsExact(),
c[1]->boundsExact()
};
// For a bounding box, find the corner that goes the furthest in the direction of the
// tangent vector.
auto const furthest_corner = [&](Rect const &r) -> unsigned {
Coord max_dot = dot(r.corner(0) - start, tangent_between);
unsigned result = 0;
for (unsigned i = 1; i < 4; i++) {
auto current_dot = dot(r.corner(i), tangent_between);
if (current_dot > max_dot) {
max_dot = current_dot;
result = i;
} else if (current_dot == max_dot) {
// Disambiguate based on proximity to the tangent line.
auto const offset = start + tangent_between;
if (distance(offset, r.corner(i)) < distance(offset, r.corner(result))) {
result = i;
}
}
}
return result;
};
// Calculate the corner points overlooking the "rift" between the paths.
Point corner_points[2];
for (size_t i : {0, 1}) {
corner_points[i] = bounding_boxes[i].corner(furthest_corner(bounding_boxes[i]));
}
// Find a vantage point from which we can best observe the splitting paths.
Point vantage_point;
bool found = false;
if (corner_points[0] != corner_points[1]) {
auto line_connecting_corners = Line(corner_points[0], corner_points[1]);
auto xing = line_connecting_corners.intersect(tangent_line);
if (!xing.empty()) {
vantage_point = xing[0].point();
found = true;
}
}
if (!found) {
vantage_point = tangent_line.pointAt(tangent_line.timeAtProjection(corner_points[0]));
}
// Move to twice as far in the direction of the vantage point.
vantage_point += vantage_point - start;
// Find the points on both curves that are nearest to the vantage point.
Coord nearest[2];
for (size_t i : {0, 1}) {
nearest[i] = c[i]->nearestTime(vantage_point);
}
// Clip to the nearest points and examine the closed contour.
Path closed_contour(start);
closed_contour.setStitching(true);
closed_contour.append(c[0]->portion(0, nearest[0]));
closed_contour = closed_contour.reversed();
closed_contour.setStitching(true);
closed_contour.append(c[1]->portion(0, nearest[1]));
closed_contour.close();
return !path_direction(closed_contour); // Reverse to match the convention that y-axis is up.
}
} // namespace Geom
#endif // LIB2GEOM_SEEN_PLANAR_GRAPH_H
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
|