35#ifndef LIB2GEOM_SEEN_PLANAR_GRAPH_H
36#define LIB2GEOM_SEEN_PLANAR_GRAPH_H
52template<
typename EdgeLabel>
55 el.onMergeWith(other);
125template<
typename EdgeLabel>
163 using IncIt = std::list<Incidence>::iterator;
174 inline static Point::LexLess<X>
const _cmp;
219 return inc.azimuth >= azimuth;
229 [=](
Incidence const &i) ->
bool { return &i == pointer; });
249 ,
label{movein_label}
267 std::vector< std::pair<Vertex *, Incidence *> >
_junk;
282 size_t numEdges(
bool include_detached =
true)
const
284 if (include_detached) {
288 [](
Edge const &e) ->
bool { return !e.detached; });
295 void regularize(
double angle_precision = 0x1p-50,
bool remove_collapsed_loops =
true);
320 std::pair<Vertex *, Incidence *>
323 if (edge_index >=
_edges.size() ||
_edges[edge_index].detached) {
324 return {
nullptr,
nullptr};
329 return {
nullptr,
nullptr};
334 return {
nullptr,
nullptr};
336 return {vertex, &(*it)};
349 bool clockwise =
false)
const
351 return clockwise ? *(vertex->_cyclicPrevIncidence(incidence))
352 : *(vertex->_cyclicNextIncidence(incidence));
357 bool clockwise =
false)
const
369 bool clockwise =
false)
const
379 bool clockwise =
false)
const
392 bool clockwise =
false)
const;
416 if (!vertex || !incidence) {
420 _junk.emplace_back(vertex, incidence);
424 bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second,
bool deloop);
432 void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second,
434 bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second,
bool deloop);
447 Coord new_time = 1.0 - time.
t;
452 return PathTime(new_index, new_time);
478 auto const insert_at_front = [&,
this]() ->
Vertex* {
479 _vertices.emplace_front(pos);
480 return &(_vertices.front());
483 if (_vertices.empty()) {
484 return insert_at_front();
488 auto it = std::find_if(_vertices.begin(), _vertices.end(), [&](
Vertex const &v) ->
bool {
489 return Vertex::_cmp(pos, v._position);
492 if (it != _vertices.end()) {
496 if (it == _vertices.begin()) {
497 return insert_at_front();
502 : _vertices.emplace(it, pos)));
513template<
typename EdgeLabel>
517 unsigned edge_index = _edges.size();
518 auto &inserted = _edges.emplace_back(std::forward<Path>(path),
519 std::forward<EdgeLabel>(
label));
522 double const start_azimuth = _getAzimuth(inserted.path.initialUnitTangent());
523 double const end_azimuth = _getAzimuth(-inserted.path.finalUnitTangent());
526 auto start = _ensureVertexAt(inserted.path.initialPoint());
527 auto end = _ensureVertexAt(inserted.path.finalPoint());
530 inserted.start =
start;
534 start->_addIncidence(edge_index, start_azimuth, Incidence::START);
535 end->_addIncidence(edge_index, end_azimuth, Incidence::END);
537 _regularized =
false;
553template<
typename EdgeLabel>
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;
566template<
typename EdgeLabel>
570 for (
auto &[vertex, incidence] : _junk) {
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);
599template<
typename EdgeLabel>
603 for (
auto it = _vertices.begin(); it != _vertices.end(); ++it) {
607 if (it->_incidences.size() < 2) {
610 _regularizeVertex(*it, angle_precision, remove_collapsed_loops);
612 _purgeJunkIncidences();
630template<
typename EdgeLabel>
633 double angle_precision,
bool deloop)
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),
647 auto const process_runs = [&](
IncIt begin,
IncIt end) ->
bool
649 double current_azimuth = 42;
650 bool in_a_run =
false;
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);
657 }
else if (!equal && in_a_run) {
658 _reglueTangentFan(vertex, run_begin, std::prev(it), deloop);
661 current_azimuth = it->azimuth;
666 double const last_azimuth = incidences.back().azimuth;
668 if (angles_equal(incidences.front().azimuth, last_azimuth)) {
673 bool processed =
false;
674 double previous_azimuth = last_azimuth;
675 IncIt straddling_run_last;
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());
684 previous_azimuth = it->azimuth;
688 auto it = std::prev(incidences.end());
689 while (angles_equal(it->azimuth, last_azimuth)) {
693 _reglueTangentFan(vertex, it, straddling_run_last, deloop);
696 _reglueTangentFan(vertex, incidences.begin(), std::prev(incidences.end()), deloop);
698 }
else if (process_runs(incidences.begin(), incidences.end())) {
700 _reglueTangentFan(vertex, run_begin, std::prev(incidences.end()), deloop);
725 IncIt const &last,
bool deloop)
733 if (!
is->invalid && _compareAndReglue(vertex, &(*it), &(*
is), deloop)) {
766 return _reglueTeardrop(vertex, first, second, deloop);
770 auto first_path_out = getOutgoingPath(first);
771 auto second_path_out = getOutgoingPath(second);
777 return deviatesLeft(first_path_out, second_path_out);
784 if (till_end_of_1st && till_end_of_2nd) {
785 _mergeCoincidingEdges(first, second);
786 }
else if (till_end_of_1st) {
790 _mergeShorterLonger(vertex, first, second,
split.second);
791 }
else if (till_end_of_2nd) {
793 _mergeShorterLonger(vertex, second, first,
split.first);
795 _mergeWyeConfiguration(vertex, first, second,
split);
826 auto &edge = _edges[first->
index];
828 double signed_area = closedPathArea(loop);
832 _throwAway(&vertex, first);
833 _throwAway(&vertex, second);
842 return (first->
sign == Incidence::START) ^ (signed_area > 0.0);
847 _reglueLasso(vertex, first, second,
split);
878 unsigned lasso = first->
index;
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());
887 auto oriented_as_loop = _edges[lasso].label;
888 auto reversed = oriented_as_loop; reversed.onReverse();
889 oriented_as_loop.onMergeWith(reversed);
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());
897 first->
index = rope_index;
898 first->
sign = Incidence::START;
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;
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));
913 _edges[lasso].detach();
914 _throwAway(&vertex, second);
930 auto &surviver = _edges[first->
index];
931 auto &casualty = _edges[second->
index];
933 auto other_label = casualty.label;
935 other_label.onReverse();
937 surviver.label.onMergeWith(other_label);
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);
966 auto &shorter_edge = _edges[shorter->
index];
967 auto &longer_edge = _edges[longer->
index];
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;
977 auto longer_label = longer_edge.label;
978 if (shorter->
sign != longer->
sign) {
979 longer_label.onReverse();
981 shorter_edge.label.onMergeWith(longer_label);
985 double trimmed_departure_azimuth;
987 trimmed = longer_edge.path.
portion(time_on_longer, _pathEnd(longer_edge.path));
988 longer_edge.
start = shorter_far_end;
990 trimmed.
setFinal(longer_far_end->point());
993 trimmed = longer_edge.path.
portion(PATH_START, _reversePathTime(time_on_longer,
995 longer_edge.
end = shorter_far_end;
997 trimmed.
setFinal(shorter_far_end->point());
1002 longer_edge.path = std::move(trimmed);
1003 shorter_far_end->_addIncidence(longer->
index, trimmed_departure_azimuth, longer->
sign);
1006 _throwAway(&vertex, longer);
1022template<
typename EL>
1029 bool const first_is_out = (first->
sign == Incidence::START);
1030 bool const second_is_out = (second->
sign == Incidence::START);
1032 auto &first_edge = _edges[first->
index];
1033 auto &second_edge = _edges[second->
index];
1036 auto stem_path = getOutgoingPath(first).portion(PATH_START, fork.
first);
1037 stem_path.setInitial(vertex.
point());
1038 stem_path.setFinal(fork.
point());
1041 auto const clip_to_fork = [&](
PathTime const &t,
Edge &e,
bool out) {
1043 e.path = e.path.portion(t, _pathEnd(e.path));
1044 e.path.setInitial(fork.
point());
1046 e.path = e.path.portion(PATH_START, _reversePathTime(t, e.path));
1047 e.path.setFinal(fork.
point());
1052 auto const departing_azimuth = [&](
Edge const &e,
bool out) ->
double {
1053 return _getAzimuth((out) ? e.path.initialUnitTangent()
1054 : -e.path.finalUnitTangent());
1058 clip_to_fork(fork.
first, first_edge, first_is_out);
1059 clip_to_fork(fork.
second, second_edge, second_is_out);
1062 auto const fork_vertex = _ensureVertexAt(fork.
point());
1063 fork_vertex->_addIncidence(first->
index, departing_azimuth(first_edge, first_is_out),
1065 fork_vertex->_addIncidence(second->
index, departing_azimuth(second_edge, second_is_out),
1069 (first_is_out ? first_edge.start : first_edge.end) = fork_vertex;
1070 (second_is_out ? second_edge.start : second_edge.end) = fork_vertex;
1073 auto upwards_oriented_label = [&](
Edge const &e,
bool out) -> EL {
1074 auto label = e.label;
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());
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;
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);
1098template<
typename EL>
1102 double azimuth,
bool clockwise)
const
1104 auto &incidences = vertex._incidences;
1107 if (incidences.empty()) {
1111 auto angle =
Angle(azimuth);
1114 auto it = std::find_if(incidences.rbegin(), incidences.rend(), [=](
auto inc) ->
bool {
1115 return inc.azimuth <= angle;
1117 if (it == incidences.rend()) {
1120 return &incidences.back();
1124 auto it = std::find_if(incidences.begin(), incidences.end, [=](
auto inc) ->
bool {
1125 return inc.azimuth >= angle;
1127 if (it == incidences.end()) {
1130 return &incidences.front();
1138template<
typename EL>
1160template<
typename EL>
1166 if (tangent_between.isZero()) {
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());
1182 find_first_nondegen(
c[0], first);
1183 find_first_nondegen(
c[1], second);
1184 if (!
c[0] || !
c[1]) {
1189 Rect const bounding_boxes[] {
1190 c[0]->boundsExact(),
1196 auto const furthest_corner = [&](
Rect const &r) ->
unsigned {
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;
1204 }
else if (current_dot == max_dot) {
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]));
1222 Point vantage_point;
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();
1233 vantage_point = tangent_line.pointAt(tangent_line.timeAtProjection(corner_points[0]));
1237 vantage_point += vantage_point -
start;
1241 for (
size_t i : {0, 1}) {
1242 nearest[i] =
c[i]->nearestTime(vantage_point);
1249 closed_contour = closed_contour.
reversed();
1252 closed_contour.
close();
Path - a sequence of contiguous curves.
Cartesian point / 2D vector and related operations.
Various utility functions.
Various trigoniometric helper functions.
bool is(S const *s)
Equivalent to the boolean value of dynamic_cast<T const*>(...).
Wrapper for angular values.
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.
static Line from_origin_and_vector(Point const &o, Point const &v)
Create a line from origin and unit vector.
Sequence of contiguous curves, aka spline.
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
void close(bool closed=true)
Set whether the path is closed.
Piecewise< D2< SBasis > > toPwSb() const
const_iterator end() const
Path portion(Coord f, Coord t) const
Point finalUnitTangent() const
Get the unit tangent vector at the end of the path, or the zero vector if undefined.
void append(Curve *curve)
Add a new curve to the end of the path.
Point initialPoint() const
Get the first point in the path.
Path reversed() const
Obtain a reversed version of the current path.
void setFinal(Point const &p)
Point initialUnitTangent() const
Get the unit tangent vector at the start of the path, or the zero vector if undefined.
size_type size() const
Natural size of the path.
void setInitial(Point const &p)
void start(Point const &p)
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)
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.
constexpr bool isZero() const
Check whether both coordinates are zero.
Axis aligned, non-empty rectangle.
Integral and real coordinate types and some basic utilities.
double Coord
Floating point type used to store coordinates.
constexpr Coord EPSILON
Default "acceptably small" value.
Various utility functions.
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.
Angle distance(Angle const &a, Angle const &b)
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 ¢roid, double &area)
polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon,...
Iter cyclic_prior(Iter i, Container &c)
Get the previous iterator in the container with wrap-around.
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)
iter inc(iter const &x, unsigned n)
T dot(D2< T > const &a, D2< T > const &b)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Point middle_point(LineSegment const &_segment)
Generalized time value in the path.
size_type curve_index
Index of the curve in the path.
Coord t
Time value in the curve.
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.