/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file contour_edge.h Declares class Edge.
 * @ingroup contour_files
 */

#if !defined(CONTOUR_EDGE_H)
#define CONTOUR_EDGE_H

#include <map>
#include <vector>
#include <list>
#include <span>
#include <array>
#include <type_traits>
#include <cstdint>
#include "cairo.h"
#include "contour_distance.h"

namespace contour {

/** Value to govern how precisely we calculate crosspoints in pixels.*/
#define CP_PRECISION (1e-8)

/** @addtogroup contour_internal
 * @{
 */


/** A helper class describing how an edge arrives/leaves a crosspoint.
 *
 * This is used to decide, which edge to continue the walk at contour
 * intersect/union operations. For straight edges this is just a direction,
 * but for curvy edges, we also need the radius of the curve, to differentiate
 * between curvy edges that have the same tangent, but different radius.
 * We do not store a very precise angle here, this is just used to sort
 * rays at a crosspoint in clockwise order.
 * Note that the direction of the ray (incoming or outgoing) plays no
 * role in determining this angle. (This is since each edge crossing the crosspoint
 * will have 2 rays added for it, one incoming one outgoing and both will
 * have an angle (each as if the were outgoing rays, so to speak.)
 */
struct RayAngle {
    /** The false angle [0..4], each integer corresponds to 90 degrees.
     *
     * In order to save computation we do not store the radian of the direction
     * merely its sine, since we only do sorting. We call this *fake angle*.
     * Values between [0..1] thus correspond to radians [0..PI/2], values
     * between [1..2] to radians [PI/2..PI], etc.
     * Note that the radian 0 is towards growing x axis ("right" you can say)
     * and increasing radian is clockwise (in a space where y grows "downwards").
     */
    double angle;
    /** The curvature of the angle.
     * This is actually 1/radius.
     * 0 is used for straight edges, positive values if the edge curves toward
     * clockwise, larger positive values the smaller the turn radius is.
     */
    double curve = 0;
    constexpr bool ExactEqual(const RayAngle&o) const noexcept { return angle==o.angle && curve == o.curve; }  ///<Compares two RayAngles
    constexpr bool IsSimilar(const RayAngle& o) const noexcept { return test_equal(angle, o.angle) && test_equal(curve, o.curve); }  ///<Compares two RayAngles
    constexpr bool IsABitSimilar(const RayAngle& o) const noexcept { return -0.1<angle-o.angle && angle-o.angle<0.1; }  ///<Compares two RayAngles a bit looser
    constexpr bool Smaller(const RayAngle&o) const noexcept {return test_equal(angle, o.angle) ? test_smaller(curve, o.curve) : angle < o.angle;} ///<True we are smaller than `o`, that is, `o` is clockwise after us. We do not define operator < since this is not strict, but approximate.
    constexpr RayAngle Opposite() const noexcept { return RayAngle(fmod_negative_safe(angle+2, 4.), -curve); }
    /** Is a1 closer to a2 than b1 to b2?
     * We can go clockwise from a1->a2 / b1->b2 or counterclockwise,
     * we return true if the angle to cover is smaller for a1->a2.*/
    constexpr static bool IsCloserAbs(RayAngle a1, RayAngle a2, RayAngle b1, RayAngle b2) noexcept {
        //note: we do this manual logic as fmod_negative_safe makes tiny mistakes that
        //may be relevant around angle==3.9999999999999996
        _ASSERT(0<=a1.angle && a1.angle<4);
        _ASSERT(0<=a2.angle && a2.angle<4);
        _ASSERT(0<=b1.angle && b1.angle<4);
        _ASSERT(0<=b2.angle && b2.angle<4);
        if (std::pair(a1.angle, a1.curve)>std::pair(a2.angle, a2.curve)) std::swap(a1, a2);
        if (std::pair(b1.angle, b1.curve)>std::pair(b2.angle, b2.curve)) std::swap(b1, b2);
        return std::pair(a2.angle-a1.angle, a2.curve-a1.curve)
             < std::pair(b2.angle-b1.angle, b2.curve-b1.curve);
    }
};
RayAngle Avg(const RayAngle &a1, const RayAngle &a2) noexcept;

/** Test if q is on the a->b circular section.
* If the section is warped, that is b < a, we test if q is outside (b,a).
* If q is almost equal to a or b, we return false
* If a is almost equal to b the return value is quite undecided.*/
constexpr bool really_between_warp(const RayAngle &q, const RayAngle &a, const RayAngle &b) noexcept {
    if (a.Smaller(b)) return q.Smaller(b) && !q.Smaller(a);
    return a.Smaller(q) || q.Smaller(b);
}


/** @} */ //addtogroup contour_internal

/** Describe how edge joints shall be handled at an Expand operation.
 * @ingroup contour
 *
 * This has effect only if the join is expansive, that is for
 * - a convex vertex (with inner angle <180 degrees) at expansion (with a positive value).
 * - a concave vertex (with inner angle >180 degrees) at shrinkage (negative value).
 */
enum EExpandType {
    /** The simplest: continue edges until they meet, if they don't: add a direct segment.
     *
     * This method uses `miter_limit`, to limit the length of the tip for very
     * narrow angle vertices. `miter_limit` is understood as a multiple of the expansion
     * gap: the length of edge increases at most `miter_limit*gap`.
     *
     * If we encounter parallel edges, which do not cross even if linearly extended,
     * we still produce a very long miter (at 5 degrees), see the right example below.
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting. (The
     * straight edge being a tangent of the half circle.)
     * @verbatim
        -->-----+         ----->-----------
                |                          \------------
        .>..+   |          ..>...+                      \----+
            :   |               :                 ----------/
            :   |              :      +----------/
            :   |             :      /
            :   |             :      |
       @endverbatim
     * If there is a miter_limit in effect the right figure becomes like this
     * @verbatim
                          ----->-----------
                                           \--------+
                           ..>...+                  |
                                :                 --+
                               :      +----------/
                              :      /
                              :      |
       @endverbatim     */
    EXPAND_MITER,
    /** Same as EXPAND_MITER, but for parallel edges add a round join.
     *
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting.
     * @verbatim
        -->-----+         ----->------+_
                |                       \
        .>..+   |          ..>...+       |
            :   |               :      _/
            :   |              :      +
            :   |             :      /
            :   |             :      |
       @endverbatim */
    EXPAND_MITER_ROUND,
    /** Same as EXPAND_MITER, but for parallel edges add a flat/direct join.
     *
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting.
     * @verbatim
        -->-----+         ----->------+
                |                     |
        .>..+   |          ..>...+    |
            :   |               :     |
            :   |              :      +
            :   |             :      /
            :   |             :      |
       @endverbatim */
    EXPAND_MITER_BEVEL,
    /** Same as EXPAND_MITER, but for parallel edges add a square.
     *
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting.
     * @verbatim
        -->-----+         ----->----------+
                |                         |
        .>..+   |          ..>...+        |
            :   |               :         |
            :   |              :      +---+
            :   |             :      /
            :   |             :      |
       @endverbatim */
    EXPAND_MITER_SQUARE, ///<Same as EXPAND_MITER, but for parallel edges add a square.
    /** At the line join add a circle to connect the two edges.
     *
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting.
     * @verbatim
        -->-+-           ----->------+_
               \                        \
        .>..+   +          ..>...+       |
            :   |               :      _/
            :   |              :      +
            :   |             :      /
            :   |             :      |
       @endverbatim */
    EXPAND_ROUND,
    /** <At the line join add a direct line to connect the two edges.
     *
     * Below see two edges with dots, and their and expanded version in solid lines.
     * The right example shows a straight edge and a half circle meeting.
     * @verbatim
        -->-+            ----->-------+
              \                       |
        .>..+   +          ..>...+    |
            :   |               :     |
            :   |              :      +
            :   |             :      /
            :   |             :      |
       @endverbatim */
    EXPAND_BEVEL
};

/** Returns true if t is one of EXPAND_MITER_XXX */
constexpr bool IsMiter(EExpandType t) { return t==EXPAND_MITER || t==EXPAND_MITER_BEVEL || t==EXPAND_MITER_ROUND|| t==EXPAND_MITER_SQUARE; }
/** Returns if t dictates a round join for parallel edge endings. */
constexpr bool IsRound(EExpandType t) { return t==EXPAND_ROUND || t==EXPAND_MITER_ROUND; }
/** Returns if t dictates a bevelled join for parallel edge endings. */
constexpr bool IsBevel(EExpandType t) { return t==EXPAND_BEVEL || t==EXPAND_MITER_BEVEL; }

/** Return which of two points are clockwise if seen from a third point.
*
* @param [in] from Seen from this point...
* @param [in] A ...is this or ...
* @param [in] B ...this point is in the clockwise direction.
* @param [in] clockwise If false we return the one in counterclockwise direction.
* @return `A` or `B` depending on which we selected.
*/
inline const XY &minmax_clockwise(const XY &from, const XY &A, const XY &B, bool clockwise) noexcept
{
    return (clockwise == (CLOCKWISE == triangle_dir(from, A, B))) ? B : A;
}

/** @returns true if 'B' is more clockwise (or counterclockwise, depending on the
 * template parameter) from 'from' than 'A'.
 * If they form a straight line or any two equal, we return false.*/
inline bool max_clockwise(bool clockwise, const XY &from, const XY &A, const XY &B) noexcept
{
    return (clockwise ? CLOCKWISE : COUNTERCLOCKWISE) == triangle_dir(from, A, B);
}

/** Describes how two sections can cross each other.*/
enum class ELineCrossingType
{
    PARALLEL,  ///< No crossing, the two sections are parallel.
    INSIDE,    ///< Real crossing, the crosspoint is inside both sections.
    OUTSIDE_FW,///< The line of the two lines cross, but the crosspoint is outside at least one of the sections - and towards the right end for all outside. (beyond B and/or M)
    OUTSIDE_BK ///< The line of the two lines cross, but the crosspoint is outside at least one of the sections - towards the wrong end for at least one of the edges. (beyond A and/or N)
};

ELineCrossingType crossing_line_line(const XY &A, const XY &B, const XY &M, const XY &N, XY &r) noexcept;
ELineCrossingType crossing_line_line(const XY &A, const XY &B, const XY &M, const XY &N, XY &r, double &posAB, double &posMN) noexcept;

/** Assuming 'r' is on the A->B line (maybe outside the A-B segment),
 * return the position of it (maybe outside [0,1]).*/
constexpr double get_pos_on_segment(const XY &A, const XY &B, const XY &r)
{ return (constexpr_fabs(A.x-B.x) > constexpr_fabs(A.y-B.y)) ? (r.x-A.x)/(B.x-A.x) : (r.y-A.y)/(B.y-A.y); }


/** Returns the point on position 't' of the section A->B.
 * 't' must be [0..1], 0 returns 'A', 1 returns 'B', 0.5 returns their midpoint.*/
constexpr XY Mid(const XY &A, const XY &B, double t = 0.5) noexcept { return A+t*(B-A); }


/** Calculates the distance of point 'M' from the infinite line of 'A'->'B'.
 * In 'point' we return the projection of 'M' onto A->B line and its position
 * relative to A->B (for A we return 0, for B we return 1, (0..1) in between, etc.)
 * In case 'A'~='B', we return the distance of 'M' to these points and undefined in 'pos'*/
double LinePointDistance(const XY &A, const XY &B, const XY &M, XY *point = nullptr, double *pos = nullptr);

/** Calculates the distance of point 'M' from section 'A'->'B'.
 * In 'point' we return the point on A->B closest to 'M' and its position.
 * In case 'A'~='B', we return the distance of 'M' to these points and undefined in 'pos'*/
double SectionPointDistance(const XY &A, const XY &B, const XY &M, XY *point = nullptr, double *pos = nullptr);


/** Enumerates options on how to change a bool value.*/
enum class EBinaryValueUpdate : uint8_t
{
    DONT_CHANGE = 0, ///<Do not change the bool value.
    SET = 1,         ///<Set the bool value to true
    CLEAR = 2,       ///<Set the bool value to false
    INVERT = 3       ///<Toggle the bool value
};

constexpr EBinaryValueUpdate operator +(EBinaryValueUpdate a, EBinaryValueUpdate b) noexcept
{ return b==EBinaryValueUpdate::DONT_CHANGE ? a : b==EBinaryValueUpdate::INVERT ? EBinaryValueUpdate(3-unsigned(a)) : b; }

/** Applies a EBinaryValueUpdate structure to a bool value*/
constexpr bool ApplyValueUpdate(bool b, EBinaryValueUpdate v) noexcept { return v==EBinaryValueUpdate::DONT_CHANGE ? b : v==EBinaryValueUpdate::INVERT ? !b : v==EBinaryValueUpdate::SET; }
/** Applies a EBinaryValueUpdate structure to an unsigned value.
 * 'uint' must be a scalar type.
 * if v==SET, we return 'value', if INVERT and b==0, we return 1, not 'value'*/
template <typename uint> requires(std::is_integral_v<uint> && !std::is_signed_v<uint>)
constexpr uint ApplyValueUpdate(uint b, EBinaryValueUpdate v, uint value) noexcept { return v==EBinaryValueUpdate::DONT_CHANGE ? b : v==EBinaryValueUpdate::INVERT ? b==0 : v==EBinaryValueUpdate::SET ? value : 0; }
/** Checks if the value update matches the current value.
 * If DONT_CHANGE or INVERT, we return true. */
constexpr bool MatchValueUpdate(bool b, EBinaryValueUpdate v) noexcept { return v==EBinaryValueUpdate::DONT_CHANGE || v==EBinaryValueUpdate::INVERT || (v==EBinaryValueUpdate::SET && b) || (v==EBinaryValueUpdate::CLEAR && !b); }
/** Checks if the value update matches the current value.
 * If DONT_CHANGE or INVERT, we return true. */
template <typename uint> requires(std::is_integral_v<uint> && !std::is_signed_v<uint>)
constexpr bool MatchValueUpdate(uint b, EBinaryValueUpdate v, uint value) noexcept { return v==EBinaryValueUpdate::DONT_CHANGE || v==EBinaryValueUpdate::INVERT || (v==EBinaryValueUpdate::SET && b==value) || (v==EBinaryValueUpdate::CLEAR && b==0); }
/** DONT_CHANGE and INVERT remains, but SET and CLEAR are flipped.*/
constexpr EBinaryValueUpdate Invert(EBinaryValueUpdate v) noexcept { return v == EBinaryValueUpdate::DONT_CHANGE || v == EBinaryValueUpdate::INVERT ? v : v == EBinaryValueUpdate::SET ? EBinaryValueUpdate::CLEAR : EBinaryValueUpdate::SET; }


template <unsigned num_cp>
struct EdgeCPsData
{
    unsigned num = 0;
    std::array<double, num_cp> pos_my;
    std::array<double, num_cp> pos_other;
    void Swap(unsigned u, unsigned v) {
        _ASSERT(u<num && v<num);
        std::swap(pos_my[u], pos_my[v]);
        std::swap(pos_other[u], pos_other[v]);
    }
    void Erase(unsigned u) {
        _ASSERT(u<num);
        num--;
        if (num==0) return;
        while (u<num) {
            pos_my[u] = pos_my[u+1];
            pos_other[u] = pos_other[u+1];
        }
    }
    template <bool my=true> void Sort() {
        for (unsigned u = 1; u<num; u++)
            for (unsigned v = u; v<num; v++)
                if ((my && pos_my[u-1]>pos_my[v]) || (!my && pos_other[u-1]>pos_other[v]))
                    Swap(u-1, v);
    }
};

template <unsigned num_cp>
struct EdgeCPsDataWithXY : public EdgeCPsData<num_cp>
{
    using EdgeCPsData<num_cp>::num;
    using EdgeCPsData<num_cp>::pos_my;
    using EdgeCPsData<num_cp>::pos_other;
    std::array<XY, num_cp> xy;
    void Swap(unsigned u, unsigned v) {
        EdgeCPsData<num_cp>::Swap(u, v);
        std::swap(xy[u], xy[v]);
    }
    void Erase(unsigned u)
    {
        _ASSERT(u<num);
        num--;
        if (num==0) return;
        while (u<num) {
            pos_my[u] = pos_my[u+1];
            pos_other[u] = pos_other[u+1];
            xy[u] = xy[u+1];
        }
    }
    template <bool my = true> void Sort()
    {
        for (unsigned u = 1; u<num; u++)
            for (unsigned v = u; v<num; v++)
                if ((my && pos_my[u-1]>pos_my[v]) || (!my && pos_other[u-1]>pos_other[v]))
                    Swap(u-1, v);
    }
};

class EdgeBase
{
public:
    /** Describes, how two expanded edges can relate to each other. */
    enum EExpandCPType
    {
        CP_DEGENERATE,     ///<One of the edges have `start==end`.
        CP_TRIVIAL,        ///<The crosspoint is exactly at the end of the edge - used between segments of an edge when an edge needed to be split to several segments at expansion

        /** They have a crosspoint
         *
         * Usually this happens at concave vertices (or when we shrink).
         * Below see two edges with dots, the expanded edges with solid lines. The `o` marks the crosspoint.
         * Note the edge direction!
         * for clarification you can see the whole shape to the right in small. The edges in question
         * are in dots there.
         *
          @verbatim
            ..<.....+
                |   :
            -<--o---:         +-------+
                |   :         |       |
                |   :         +...+   |
                |   :             :   |
                |   :             +---+

          @endverbatim */
        CP_REAL,
        /** They do not have a crosspoint, but if we extend them, they have
         *
         * Note that "extension" for means its a continuation in a straight line.
         * This is the most common case for expansion.
         *
         * Below see two edges with dots, the expanded edges with solid lines and their extension
         * also using dots. The `o` marks the crosspoint of the extensions.
         *
          @verbatim
             -->--...o
                     :
             .>..+   :
                 :   |
                 :   |
                 :   |
                 :   |

          @endverbatim */
        CP_EXTENDED,
        /** They are parallel and hence have no crosspoints.
         *
         * In this case there is no crosspoint not even if linearly extrapolated.
         * The main example is below. A straight edge meets a half-circle.
         * (The straight edge being a tangent of the half circle.)
         * In this case linear extension of the half circle is still parallel to the
         * straight edge.
         * No potential crosspoint can be identified here, so what we return is a
         * point in between the two edges, currently 5 times further than the distance
         * between the two `x`s. See `o` below. * `x` shows the end of the expanded edges
         * and solid lines show linear extensions.
         *
          @verbatim
             ----->-x------------------

              ..>...+                 o
                   :
                  :      -x----------------
                 :      /
                 :      |

          @endverbatim */
        NO_CP_PARALLEL,
        /** The two edges do not meet, but their extension do - but at the opposite end
         * This may usually happen either at expanding a concave vertex or shrinking a convex one.
         *
         * On the figure below we see 4 edges (with dashes and pipe symbols, vertices
         * use "+" signs). We shrink considerably. The shrunken version of edges 1 and 2
         * do not cross and only their extension do. However, this extended cp (marked
         * with an X) is beyond the wrong (bottom) end of edge 2. Originally the cp
         * between edges 1 and 2 was at the top end of edge 2, but after expansion it
         * got to the other end - this makes the expanded edge of 2 sort of
         * unnecessary to construct the expanded (shrunken) contour. So we mark this
         * case separately.
         *
          @verbatim
             -->-1---------+
                    :      |2
                    :      |
                    :      +->---------3----+
                                    :       |
             .......X......         :       V
                                    :       |
                          ..........:.......|
                                    :       4
                                    :       |
          @endverbatim */
        CP_INVERSE
    };
    /** True if two expanded edges or their linear extensions has crosspoints.*/
    static constexpr bool HasCP(EExpandCPType t) { return t==CP_REAL || t==CP_EXTENDED || t==CP_TRIVIAL; }
    static constexpr unsigned MAX_CP = 9; ///<The maximum number of crosspoints two Edges may have.
    static constexpr unsigned MARK_BIT_WIDTH = 23; ///<How many bits the mark field of an edge holds

    /** A structure containing instructions to change the mutable status of an edge*/
    class Update
    {
        unsigned visible_ : 2;  ///<How to change the visibility of the edge
        unsigned imark_ : 2;    ///<The mark bit we use internally in contour
        unsigned mark_ : 2;     ///<How to change the marking of the edge. If SET we use mark_value, if INVERT we set to 0 or 1.
        unsigned mark_value_ : MARK_BIT_WIDTH;///<This value is used when mark==SET
    public:
        constexpr Update(EBinaryValueUpdate v = EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate im = EBinaryValueUpdate::DONT_CHANGE,
                         EBinaryValueUpdate m = EBinaryValueUpdate::DONT_CHANGE, unsigned mv=1) :
            visible_(unsigned(v)), imark_(unsigned(im)), mark_(unsigned(m)), mark_value_(mv) {}
        constexpr EBinaryValueUpdate visible() const noexcept { return EBinaryValueUpdate(visible_); }
        constexpr EBinaryValueUpdate internal_mark() const noexcept { return EBinaryValueUpdate(imark_); }
        constexpr EBinaryValueUpdate mark() const noexcept { return EBinaryValueUpdate(mark_); }
        constexpr unsigned mark_value() const noexcept { return mark_value_; }
        constexpr bool DoesSomething() const { return visible()!=EBinaryValueUpdate::DONT_CHANGE || internal_mark() != EBinaryValueUpdate::DONT_CHANGE || mark()!=EBinaryValueUpdate::DONT_CHANGE; }
        constexpr static Update set_visible() { return Update(EBinaryValueUpdate::SET); }
        constexpr static Update clear_visible() { return Update(EBinaryValueUpdate::CLEAR); }
        constexpr static Update set_mark(unsigned v) { return Update(EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::SET, v); }
        constexpr static Update clear_mark() { return Update(EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::CLEAR); }
        constexpr static Update set_internal_mark() { return Update(EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::SET); }
        constexpr static Update clear_internal_mark() { return Update(EBinaryValueUpdate::DONT_CHANGE, EBinaryValueUpdate::CLEAR); }
        constexpr Update operator +(Update u) const { return Update(visible()+u.visible(), internal_mark()+u.internal_mark(), mark()+u.mark(), u.mark_value()); }
        constexpr bool operator ==(const Update& o) const noexcept {
            return visible_==o.visible_ && imark_==o.imark_ && mark_==o.mark_ && (mark()==EBinaryValueUpdate::DONT_CHANGE || mark_value_==o.mark_value_);
        }
        constexpr Update Invert() const noexcept { return Update(contour::Invert(visible()), contour::Invert(internal_mark()), contour::Invert(mark()), mark_value()); }
    };

    struct Meta {
        mutable unsigned mark_ : MARK_BIT_WIDTH = 0; ///<Mark value to be used by the user. It is generally preserved and carried over during path or contour operations to the resulting edge
        mutable unsigned imark_ : 1 = false;         ///<Marker used by Path::DoubleWiden() and others. It is generally preserved and carried over during path or contour operations to the resulting edge
        mutable unsigned visible_ : 1 = true;        ///<True if the edge is visible.
        unsigned straight : 1 = true;                ///<True if the Edge is a straight line (the edge may still contain control points, but those lie very close to start->end), false if a cubic bezier.
        unsigned has_cp : 1 = false;                 ///<True if the Edge has no control points. For straight lines this can be true if we have flattened the edge & kept the control points for more precise pos->length calculations. For beziers (straight=false), this MUST be true.
        unsigned connects_to_previous : 1 = false;   ///<True if the endpoint of the prev Edge in the series equals to our startpoint (and hence is not stored twice). Ignored (shall be false) on the first edge of the path
        constexpr void Apply(Update u) const noexcept {
            visible_ = ApplyValueUpdate(visible_, u.visible());
            imark_ = ApplyValueUpdate(imark_, u.internal_mark());
            mark_ = ApplyValueUpdate(mark_, u.mark(), u.mark_value());
        }
        constexpr bool Match(Update u) const noexcept { return MatchValueUpdate(visible_, u.visible()) && MatchValueUpdate(imark_, u.internal_mark()) && MatchValueUpdate(mark_, u.mark(), u.mark_value()); }
        constexpr bool IsSane() const noexcept { return straight || has_cp; } //The combination of not straight without control points is not valid.
    };
    /** Governs, how flat a bezier curve do we assume as straight.
     * see Edge::MakeStraightIfNeeded(). */
    static constexpr double flatness_tolerance = 0.001;
};

static_assert(sizeof(EdgeBase::Meta) == 4);

class EdgeStorage : public EdgeBase {
protected:
    constexpr XY& start() noexcept { return start_; }
    constexpr XY& end() noexcept { return end_; }
    constexpr XY& c1() noexcept { return c1_; }
    constexpr XY& c2() noexcept { return c2_; }
    constexpr Meta& meta() noexcept { return meta_; }
    constexpr const XY& start() const noexcept { return start_; }
    constexpr const XY& end() const noexcept { return end_; }
    constexpr const XY& c1() const noexcept { return c1_; }
    constexpr const XY& c2() const noexcept { return c2_; }
    constexpr const Meta& meta() const noexcept { return meta_; }
    constexpr EdgeStorage(const XY& s, const XY& e, Meta m) noexcept : start_(s), end_(e), meta_(m) { _ASSERT(meta_.straight && !meta_.has_cp); }
    constexpr EdgeStorage(const XY &s, const XY &e, const XY& c1, const XY& c2, Meta m) noexcept : start_(s), end_(e), c1_(c1), c2_(c2), meta_(m) { _ASSERT(!meta_.straight && meta_.has_cp); }
    constexpr EdgeStorage(const XY p[], Meta m) noexcept : start_(p[0]), end_(p[m.has_cp ? 3 : 1]), meta_(m) { if (m.has_cp) { c1_ = p[1]; c2_ = p[2]; } }
    constexpr EdgeStorage() noexcept = default;
    constexpr bool IsSane() const noexcept { return meta_.IsSane() && start().IsSane() && end().IsSane() && (!meta_.has_cp || (c1().IsSane() && c2().IsSane())); }
private:
    XY start_; ///<The startpoint of the Edge (first control point for cubic beziers)
    XY end_;   ///<The endpoint of the Edge (fourth control point for cubic beziers)
    XY c1_;    ///<If a cubic bezier, then the second control point - the one associated with 'start'. If a straight line, this is equal to start or on the start->end line.
    XY c2_;    ///<If a cubic bezier, then the third control point - the one associated with 'end'. If a straight line, this is equal to end or on the start->end line.
    Meta meta_;
};

/** Holds either a straight line or a cubic bezier.
 * We hold one invariant: straight signals if members c1 or c2 have meaning.
 * May be visible or invisible. */
class Edge : public EdgeStorage {
    template <bool CONST> friend class EdgeView;
    /** Construct from a series of points and metadata. If meta.has_cp then 'points' has 4 points (start,c1,c2,end), else 2 (start,end). */
    constexpr Edge(const XY p[], Meta m) noexcept : EdgeStorage(p, m) {}
public:
    /** Construct a straight edge. */
    constexpr Edge(const XY &A, const XY &B, bool v = true, bool im = false) noexcept : EdgeStorage(A, B, Meta{.imark_=im, .visible_=v}) { _ASSERT(IsSane()); }
    /** Construct a cubic bezier. Will be converted to straight line if too flat. */
    Edge(const XY &A, const XY &B, const XY &C, const XY &D, bool v = true, bool im = 0, bool keep_cp = false) noexcept : EdgeStorage(A, B, C, D, Meta{.imark_=im, .visible_=v, .straight=false, .has_cp=true}) { MakeStraightIfNeeded(flatness_tolerance, keep_cp); }
    constexpr Edge() noexcept = default;
    constexpr Edge(const Edge &) noexcept = default;
    /** Construct the Edge as a section of another one.
     * Will be converted to straight line if too flat.
     * @param [in] e The other edge.
     * @param [in] t The starting parameter of the section to cut out of 'e'. Must be in [0..s).
     * @param [in] s The ending parameter of the section to cut out of 'e'. Must be in (t..1].*/
    Edge(const Edge &e, double t, double s) noexcept : Edge(e) { Chop(t, s); MakeStraightIfNeeded(); }
    constexpr Edge & operator = (const Edge &) noexcept = default;
    /** Only checks shape, not visibility or mark */
    constexpr bool operator ==(const Edge& p) const noexcept { return start()==p.start() && end()==p.end() && IsStraight() ==p.IsStraight() && HasControlPoints() == p.HasControlPoints() && (!HasControlPoints() || (c1() == p.c1() && c2() == p.c2())); }
    /** Only checks shape, not visibility or mark */
    constexpr bool operator < (const Edge& p) const noexcept { return start()!=p.start() ? start()<p.start() : end()!=p.end() ? end()<p.end() : HasControlPoints() !=p.HasControlPoints() ? !HasControlPoints() ? true : !p.HasControlPoints() ? false : c1()!=p.c1() ? c1()<p.c1() : c2()<p.c2() : false; }
    /** Only checks shape, not visibility or mark */
    constexpr bool test_equal(const Edge& p) const noexcept { return start().test_equal(p.start()) && end().test_equal(p.end()) && HasControlPoints() ==p.HasControlPoints() && (!HasControlPoints() || (c1().test_equal(p.c1()) && c2().test_equal(p.c2()))); }

    constexpr const XY &GetStart() const noexcept { return start(); } ///<Returns the starting point of the edge.
    constexpr const XY &GetEnd() const noexcept { return end(); } ///<Returns the endpoint of the edge.
    constexpr const XY &GetC1() const noexcept { _ASSERT(HasControlPoints()); return c1(); } ///<Returns the second control point for cubic beziers.
    constexpr const XY &GetC2() const noexcept { _ASSERT(HasControlPoints()); return c2(); } ///<Returns the third control point for cubic beziers.
    constexpr bool IsStraight() const noexcept { return meta().straight; } ///<True if the Edge is a straight line.
    constexpr bool HasControlPoints() const noexcept { return meta().has_cp; } ///<True if the control points are used. If straight this means that they lie on (or close to) the start->end line and kept for parameter<->length conversions.
    constexpr bool IsDot() const noexcept { return start().test_equal(end()) && (IsStraight() || (start().test_equal(c1()) && end().test_equal(c2()))); } ///<Ture if the edge is degenerated to a single point.
    constexpr bool IsDot(double tolerance) const noexcept { return start().test_equal(end(), tolerance) && (IsStraight() || (start().test_equal(c1(), tolerance) && end().test_equal(c2(), tolerance))); } ///<Ture if the edge is degenerated to a single point.
    constexpr bool IsHorizontal() const noexcept { return IsStraight() && start().y == end().y; }
    constexpr bool IsVertical() const noexcept { return IsStraight() && start().x == end().x; }
    /** Returns the point at position 'pos' on the Edge. 'pos' must be in [0..1]*/
    constexpr XY Pos2Point(double pos) const noexcept { return pos==0 ? start() : pos==1 ? end() : IsStraight() ? Mid(start(), end(), pos) : Split(pos); }
    constexpr void Apply(Update u) const noexcept { meta().Apply(u); }
    void SetVisible(bool visible = true) const noexcept { meta().visible_ = visible; }
    void SetInternalMark(bool imark = true) const noexcept { meta().imark_ = imark; }
    void SetMark(unsigned mark) const noexcept { meta().mark_ = mark; }
    bool IsVisible() const noexcept { return meta().visible_; }
    bool IsInternalMarked() const noexcept { return meta().imark_; }
    unsigned GetMark() const noexcept { return meta().mark_; }

    /** Returns the point at position 'pos' of a bezier curve. 'pos' must be in [0..1]*/
    constexpr XY Split(double t) const noexcept {
        _ASSERT(HasControlPoints());
        return (1-t)*(1-t)*(1-t) * start()
            + 3 * (1-t)*(1-t) * t * c1()
            + 3 * (1-t) * t*t  * c2()
            + t*t*t * end();
    }
    XY Split(double t, XY &r1, XY &r2) const noexcept;
    XY Split() const noexcept { XY a, b; return Split(a, b); } ///<Returns the midpoint of a bezier curve
    XY Split(XY &r1, XY &r2) const noexcept;
    void Split(Edge& r1, Edge& r2, bool keep_cp = false) const noexcept;
    void Split(double t, Edge &r1, Edge &r2, bool keep_cp=false) const noexcept;
    bool Chop(double t, double s) noexcept;
    std::pair<bool, Edge> LinearExtend(double length, bool forward, bool visible) noexcept;
    int WhichSide(const XY &A, const XY &B) const noexcept;
    bool MakeStraightIfNeeded(double maximum_distance_sqr = flatness_tolerance, bool keep_cp = false) noexcept;
    void Still() noexcept;
    bool Match(const XY& p, double pos = -1, double tolerance = 0.1) const noexcept;
    constexpr bool Match(Update u) const noexcept { return meta().Match(u); }

    Edge& SetStart(double pos) noexcept { return SetStart(Pos2Point(pos), pos); }
    Edge& SetStart(const XY &p, double pos) noexcept;
    Edge& SetEnd(double pos) noexcept { return SetEnd(Pos2Point(pos), pos); }
    Edge& SetEnd(const XY &p, double pos) noexcept;
    Edge& SetStartIgn(const XY &p, double pos) noexcept;
    Edge& SetEndIgn(const XY &p, double pos) noexcept;
    Edge& SetStartEndIgn(double spos, double dpos) noexcept { return SetStartEndIgn(Pos2Point(spos), Pos2Point(dpos), spos, dpos); }
    Edge& SetStartEndIgn(const XY &s, const XY &d, double spos, double dpos) noexcept;
    Edge &SetStartOnly(const XY &p) noexcept { start() = p; meta().has_cp = !IsStraight(); return *this; }
    Edge &SetEndOnly(const XY &p) noexcept { end() = p; meta().has_cp = !IsStraight(); return *this; }
    Edge& SetC1C2(const XY &C1, const XY &C2) noexcept;
    Edge& SetC1(const XY &C1) noexcept;
    Edge& SetC2(const XY &C2) noexcept;

    Edge &Shift(const XY&p) noexcept { start() += p; end() += p; if (HasControlPoints()) { c1() += p; c2() += p; } return *this; }  ///<Shift the egde
    Edge CreateShifted(const XY&p) const noexcept { return Edge(*this).Shift(p); } ///<Create a shifted version of the edge
    Edge &Scale(double sc) noexcept { start() *= sc; end() *= sc; if (HasControlPoints()) { c1() *= sc; c2() *= sc; } return *this; }    ///<Scale the edge.
    Edge CreateScaled(double sc) const noexcept { return Edge(*this).Scale(sc); }    ///<Scale the edge.
    Edge &Scale(const XY &sc) noexcept { start().Scale(sc); end().Scale(sc); if (HasControlPoints()) { c1().Scale(sc); c2().Scale(sc); } return *this; }    ///<Scale the edge.
    Edge CreateScaled(const XY &sc) const noexcept { return Edge(*this).Scale(sc); }    ///<Scale the edge.
    Edge &Rotate(double cos, double sin) noexcept { start().Rotate(cos, sin); end().Rotate(cos, sin); if (HasControlPoints()) { c1().Rotate(cos, sin); c2().Rotate(cos, sin); } return *this; } ///<Rotates the edge around the origin
    Edge CreateRotated(double cos, double sin) const noexcept { return Edge(*this).Rotate(cos, sin); } ///<Creates a rotated version of the edge
    Edge &RotateAround(const XY&c, double cos, double sin) noexcept { start().RotateAround(c, cos, sin); end().RotateAround(c, cos, sin); if (HasControlPoints()) { c1().RotateAround(c, cos, sin); c2().RotateAround(c, cos, sin); } return *this; } ///<Rotates the edge around a point
    Edge CreateRotatedAround(const XY&c, double cos, double sin) const noexcept { return Edge(*this).RotateAround(c, cos, sin); } ///<Creates a version of the edge rotated around a point
    Edge &SwapXY() noexcept { start().SwapXY(); end().SwapXY(); if (HasControlPoints()) { c1().SwapXY(); c2().SwapXY(); } return *this; } ///<Swaps XY coordinates
    Edge CreateSwapXYd() const noexcept { return Edge(*this).SwapXY(); } ///<Created a version of the edge with swapped x and y coordinates.
    Edge &Invert() noexcept { std::swap(start(), end()); if (HasControlPoints()) std::swap(c1(), c2()); return *this; } ///<Reverses the direction of the edge effectively swapping start and end().
    Edge &Apply(const TRMatrix &M) noexcept { start().Apply(M); end().Apply(M); if (HasControlPoints()) { c1().Apply(M); c2().Apply(M); } return *this; } ///Apply a transformation

    /** Returns a point on the line of a tangent at "pos", the point being towards the start of curve/edge.*/
    XY PrevTangentPoint(double pos) const noexcept { return IsStraight() ? 2*start()-end() : pos ? Mid(Mid(start(), c1(), pos), Mid(c1(), c2(), pos), pos) : start().test_equal(c1()) ? 2*start()-c2() : 2*start()-c1(); }
    /** Returns a point on the line of a tangent at "pos", the point being towards the end of curve/edge. */
    XY NextTangentPoint(double pos) const noexcept { return IsStraight() ? 2*end()-start() : pos<1 ? Mid(Mid(c1(), c2(), pos), Mid(c2(), end(), pos), pos) : end().test_equal(c2()) ? 2*end()-c1() : 2*end()-c2(); }

    bool CheckAndCombine(const Edge &next, double tolerance=0.01, double *pos = nullptr);
    template <unsigned DIFF_MAGNITUDE>
    int TestOverlap(const Edge & A, XY r[Edge::MAX_CP], double pos_my[Edge::MAX_CP], double pos_other[Edge::MAX_CP]) const;
    int CrossingWithOverlap(const Edge& A, bool is_next, XY r[Edge::MAX_CP], double pos_my[Edge::MAX_CP], double pos_other[Edge::MAX_CP]) const;
    unsigned Crossing(const Edge& A, bool is_next, XY r[Edge::MAX_CP], double pos_my[Edge::MAX_CP], double pos_other[Edge::MAX_CP]) const;
    EdgeCPsDataWithXY<MAX_CP> Crossing(const Edge &A, bool is_next, bool *overlap = nullptr) const;
    unsigned Crossing(const XY &A, const XY &B, double pos_my[], double pos_segment[]) const;
    EdgeCPsData<MAX_CP> Crossing(const XY &A, const XY &B) const;
    unsigned SelfCrossing(XY r[Edge::MAX_CP], double pos1[Edge::MAX_CP], double pos2[Edge::MAX_CP]) const;
    EdgeCPsDataWithXY<MAX_CP> SelfCrossing() const;
    int CrossingVertical(double x, double y[3], double pos[3], int cross_dir[3]) const noexcept;
    int CrossingHorizontalCPEvaluate(const XY &xy, bool self) const;
    RayAngle Angle(bool incoming, double pos) const noexcept;
    Block CreateBoundingBox() const noexcept; ///<Returns the bounding box of the edge. Somewhat expensive for beziers
    template<bool y>
    std::array<std::pair<XY, double>, 2> Extremes() const;
    std::array<std::pair<XY, double>, 2> Extremes(bool y) const { return y ? Extremes<true>() : Extremes<false>(); }
    std::pair<XY, double> XMinExtreme() const { return Extremes<false>()[0]; }
    std::pair<XY, double> YMinExtreme() const { return Extremes<true>()[0]; }
    std::pair<XY, double> XMaxExtreme() const { return Extremes<false>()[1]; }
    std::pair<XY, double> YMaxExtreme() const { return Extremes<true>()[1]; }

    double Distance(const XY &, XY &point, double &pos) const; //always nonnegative
    DistanceType Distance(const Edge &o) const { DistanceType ret; Distance(o, ret); return ret; } ///<Returns the distance of 'o' from 'this'.
    bool Distance(const Edge &, DistanceType &ret) const;

    double GetAreaAboveAdjusted() const noexcept; ///<The area above the edge (adjusted).
    double GetLength() const noexcept; ///<Returns the length of the edge.
    double GetLength(double pos_from, double pos_till) const noexcept; ///<Returns the length of the edge between two positions. Both must be in [0..1]. If from>till, we return a negative length.
    bool MovePos(double &pos, double &len) const;
    /** Moves 'pos' by (signed) length. If we go over the end of the end/start of the edge, we return 1/0 resp.*/
    double MovePos2(double pos, double len) const { MovePos(pos, len); return pos; }
    //returns the centroid of the area above multipled by the (signed) area above
    XY GetCentroidAreaAboveUpscaled() const noexcept;

    void CreateExpand2D(const XY &gap, std::vector<Edge> &ret, int &stype, int &etype) const;

    //helpers for offsetbelow
    double OffsetBelow(const Edge &M, double &touchpoint, double min_so_far) const;

    template <bool forward>
    std::pair<bool, double> TangentFromNoInflection(const XY &xy) const;
    template <bool include_end>
    std::pair<std::pair<double, XY>, std::pair<double, XY>>
        TangentFrom(const XY &from) const;
    template <bool include_end>
    void TangentFrom(const Edge &from, XY clockwise[2], XY cclockwise[2]) const;

    //inflection points
    unsigned InflectionPoints(std::span<double, 2> t) const;

    void PathTo(cairo_t *cr) const { if (IsStraight()) cairo_line_to(cr, end().x, end().y); else cairo_curve_to(cr, c1().x, c1().y, c2().x, c2().y, end().x, end().y); } ///<Adds the edge to a cairo path. * It assumes cairo position is at `start`.
    void PathDashed(cairo_t *cr, std::span<const double> pattern, int &pos, double &offset, bool reverse = false) const;

    bool CreateExpand(double gap, std::list<Edge> &expanded, XY &prev_tangent, XY &next_tangent, std::vector<Edge> *original = nullptr) const;
    bool CreateLinearExpand(double gap1, double gap2, double cos, double sin,
                            std::list<Edge> &expanded, XY &prev_tangent, XY &next_tangent,
                            std::vector<Edge> *original) const;

    /** Return a series of parameter values where the x coordinate of the curve is 'x'
    * @param[in] x The x coordinate in question.
    * @param[out] roots The parameter values returned.All will be in[0..1]
    * @param[in] precision The precision (in pixels) to apply when refining the position values.
    *            When it is zero or negative, no refinement happens(default).
    * @returns the number of crosses found. **/
    unsigned atX(double x, double roots[3], double precision = 0) const
    { return atXorY<false>(x, roots, precision); }
    /** Return a series of parameter values where the y coordinate of the curve is 'y'
    * @param[in] y The y coordinate in question.
    * @param[out] roots The parameter values returned.All will be in[0..1]
    * @param[in] precision The precision (in pixels) to apply when refining the position values.
    *            When it is zero or negative, no refinement happens(default).
    * @returns the number of crosses found. **/
    unsigned atY(double y, double roots[3], double precision = 0) const
    { return atXorY<true>(y, roots, precision); }

    //helpers
    std::pair<double, double> UnboundedBezierDistance(const XY & M) const;
    bool HullOverlap(const Edge &, bool is_next) const;
    double HullDistance(const XY &A, const XY &B) const;
    double HullDistance(const XY &A, const XY &B, const XY &C, const XY &D) const;
    unsigned SolveForDistance(const XY &p, double ret[5]) const;
    Edge ExtendBezier(double t) const;
    /** Extend the bezier beyond its start (t<0) and its end (u>1).
    * If any of t or u is [0..1] we assert and do nothing, we dont clip. */
    Edge ExtendBezier(double t, double u) const
    {
        if (t>=0 || u<=1 || IsStraight()) { _ASSERT(0); return *this; }
        return ExtendBezier(t).ExtendBezier(1+(u-1)/(1-t));
    }
    double FindBezierParam(const XY &p) const;
    Block GetBezierHullBlock() const;
    Range GetHullXRange() const;
    Range GetHullYRange() const;
    template <bool use_y_coord>
    unsigned atXorY(double v, double roots[3], double precision = 0) const;
    template <bool use_y_coord>
    bool RefineXorY(double v, double &root, double precision = CP_PRECISION) const;

    unsigned CrossingSegments(const Edge &o, XY r[], double pos_my[], double pos_other[]) const;
    unsigned CrossingSegments_NoSnap(const Edge &o, XY r[], double pos_my[], double pos_other[]) const;
    unsigned CrossingLine(const XY &A, const XY &B, double pos_my[], double pos_segment[]) const;
    unsigned CrossingBezier(const Edge &A, XY r[], double pos_my[], double pos_other[],
                            double pos_my_mul, double pos_other_mul,
                            double pos_my_offset, double pos_other_offset, unsigned alloc_size) const;

    //Helpers for expand
    EExpandCPType FindExpandedEdgesCP(const Edge &M, XY &newcp,
                                      const XY &my_next_tangent, const XY &Ms_prev_tangent,
                                      bool is_my_origin_bezier, bool is_Ms_origin_bezier,
                                      double &my_pos, double &M_pos) const;
    template <typename E, typename Iterator>
    static void RemoveLoop(std::list<E> &edges, Iterator first, Iterator last, bool self = false,
                           std::vector<Edge>* original = nullptr, size_t orig_offset = 0);
    template <typename E>
    static bool RemoveLoops(std::vector<E> &edges, bool self=true);
    bool CreateExpandOneSegment(double gap, std::list<Edge> &expanded, std::vector<Edge> *original) const;
    bool CreateLinearExpandOneSegment(double gap1, double gap2, double cos, double sin, std::list<Edge> &expanded, std::vector<Edge> *original) const;
    /** Prints the content of the edge into a string as a
     * valid C++ ctor call.
     * @param [in] precise When set, we emit the hex representation of doubles, else a human readable one.*/
    std::string Dump(bool precise) const;
};

/** Changes a bezier curve to straight if it is too flat.
 * @param [in] maximum_distance_sqr The sum of the square distance of c1 and c2 from the start->end segment.
 *                                  If the actual distance is smaller than this, we switch to straight.
 * @param [in] keep_cp If set and we make the edge straight, we keep the control points (and set has_cp),
 *                     so that we can more accurately compute length->position.
 * @returns true if we have switched from bezier to straight line. False if we started with a straight line.*/
inline bool Edge::MakeStraightIfNeeded(double maximum_distance_sqr, bool keep_cp) noexcept
{
    _ASSERT(IsSane());
    if (IsStraight()) return false;
    //see http://antigrain.com/research/adaptive_bezier/
    const double dx = end().x-start().x;
    const double dy = end().y-start().y;
    const double d2 = fabs(((c1().x - end().x) * dy - (c1().y - end().y) * dx));
    const double d3 = fabs(((c2().x - end().x) * dy - (c2().y - end().y) * dx));

    meta().straight = (d2 + d3) * (d2 + d3) < maximum_distance_sqr * (dx * dx + dy * dy);
    meta().has_cp = !IsStraight() || keep_cp;
    _ASSERT(IsSane());
    return IsStraight();
}

/** Ensures that start->C1 and end->C2 do not cross, by moving the control points closer to their respective end.*/
inline void Edge::Still() noexcept
{
    if (IsStraight()) return;
    if (start().test_equal(GetC1()) || end().test_equal(GetC2())) return; //not a full cubic bezier
    double p1, p2;
    XY xy;
    switch (crossing_line_line(start(), GetC1(), end(), GetC2(), xy, p1, p2)) {
    default: _ASSERT(0); FALLTHROUGH;
    case ELineCrossingType::PARALLEL: break; //nothing to do
    case ELineCrossingType::INSIDE: SetC1C2(xy, xy); break; //limit both crosspoints
    case ELineCrossingType::OUTSIDE_FW:
    case ELineCrossingType::OUTSIDE_BK:
        //only one endpoint shall be adjusted
        if (0<p1 && p1<1) SetC1(xy);
        if (0<p2 && p1<1) SetC2(xy);
    }
    _ASSERT(IsSane());
}

/** Checks whether 'p' is on the edge and matches 'pos'
 * well enough.
 * @param [in] p The point supposedly on the edge
 * @param [in] pos The position parameter to check. Ignored if outside [0..1]
 *             In that case only the distance of 'p' from the edge is checked.
 * @param [in] Tolerance in terms of pixel distnce. We fail if 'p' is further
 *             away from the edge than this or if the point of the edge
 *             corresponding to 'pos' is further from 'p' than this. */
inline bool Edge::Match(const XY& p, double pos, double tolerance) const noexcept {
    XY closest_on_edge;
    double pos_on_edge;
    //test that p and pos correspond - compiles to NOP in release mode
    return fabs(Distance(p, closest_on_edge, pos_on_edge))<tolerance    //'p' is close to the edge; and
        && (!between01(pos)                                             //..either 'pos' is outside [0..1]; or
            || Pos2Point(pos).DistanceSqr(p)<tolerance*tolerance);      //'p' is close to the requested position
}


/** Set the start of the Edge thereby effectively chopping its head away.
 * We assume 'p' and 'pos' correspond to the same point and use the one resulting
 * in faster operation. This is very small processing when compiled in optimized mode.
 * For straight edges, only an assignment and "pos" need not be calculated in the
 * caller context (as it is not used) due to inlining.*/
inline Edge& Edge::SetStart(const XY &p, double pos) noexcept
{
    _ASSERT(Match(p, pos));
    if (!IsStraight())
        Chop(pos, 1);
    start() = p;
    meta().has_cp = !IsStraight();
    return *this;
}

/** Set the end of the Edge thereby effectively chopping its tail away.
 * We assume 'p' and 'pos' correspond to the same point and use the one resulting
 * in faster operation. This is very small processing when compiled in optimized mode.
 * For straight edges, only an assignment and "pos" need not be calculated in the
 * caller context (as it is not used) due to inlining.*/
inline Edge& Edge::SetEnd(const XY &p, double pos) noexcept
{
    _ASSERT(Match(p, pos));
    if (!IsStraight())
        Chop(0, pos);
    end() = p;
    meta().has_cp = !IsStraight();
    return *this;
}

/** Set the start of the Edge thereby effectively chopping its head away.
 * We assume 'p' and 'pos' correspond to the same point and use the one resulting
 * in faster operation. This is very small processing when compiled in optimized mode.
 * For straight edges, only an assignment and "pos" need not be calculated in the
 * caller context (as it is not used) due to inlining.
 * This version ignores the "pos" completely for staright lines even in debug mode*/
inline Edge& Edge::SetStartIgn(const XY &p, double pos) noexcept
{
    if (!IsStraight()) {
        _ASSERT(Match(p, pos));
        Chop(pos, 1);
    }
    start() = p;
    meta().has_cp = !IsStraight();
    return *this;
}

/** Set the end of the Edge thereby effectively chopping its tail away.
 * We assume 'p' and 'pos' correspond to the same point and use the one resulting
 * in faster operation. This is very small processing when compiled in optimized mode.
 * For straight edges, only an assignment and "pos" need not be calculated in the
 * caller context (as it is not used) due to inlining.
 * This version ignores the "pos" completely for staright lines even in debug mode*/
inline Edge& Edge::SetEndIgn(const XY &p, double pos) noexcept
{
    if (!IsStraight()) {
        _ASSERT(Match(p, pos));
        Chop(0, pos);
    }
    end() = p;
    meta().has_cp = !IsStraight();
    return *this;
}

/** Set the start and end of the Edge thereby effectively chopping its head and tail away.
 * We assume 'p' and 'pos' correspond to the same point and use the one resulting
 * in faster operation. This is very small processing when compiled in optimized mode.
 * For straight edges, only an assignment and "pos" need not be calculated in the
 * caller context (as it is not used) due to inlining. Ignores the "pos" completely
 * for straight lines even in debug mode.*/
inline Edge& Edge::SetStartEndIgn(const XY &s, const XY &d, double spos, double dpos) noexcept
{
    if (!IsStraight()) {
        _ASSERT(Match(s, spos));
        _ASSERT(Match(d, dpos));
        Chop(spos, dpos);
    }
    start() = s;
    end() = d;
    meta().has_cp = !IsStraight();
    return *this;
}

/** Set the bezier points. This function checks if
 * the resulting edge is straight and re-sets the straight flag, if needed.
 * In other words, it maintains the invariant of Edge.
 * If you want to set only one of the points use SetC1() or SetC2(). */
inline Edge & Edge::SetC1C2(const XY & C1, const XY & C2) noexcept
{
    c1() = C1;
    c2() = C2;
    meta().straight = false;
    meta().has_cp = true;
    MakeStraightIfNeeded();
    return *this;
}

/** Set the first bezier point. This function checks if
 * the resulting edge is straight and re-sets the straight flag, if needed.
 * We assert that the original edge is already a bezier.
 * If you want to set both control points at once, use SetC1C2().*/
inline Edge & Edge::SetC1(const XY & C1) noexcept
{
    return SetC1C2(C1, c2());
}

/** Set the second bezier point. This function checks if
 * the resulting edge is straight and re-sets the straight flag, if needed.
 * We assert that the original edge is already a bezier.
 * If you want to set both control points at once, use SetC1C2().*/
inline Edge & Edge::SetC2(const XY & C2) noexcept
{
    return SetC1C2(c1(), C2);
}



/** Returns the bounding box of the hull of the bezier curve.*/
inline Block Edge::GetBezierHullBlock() const
{
    _ASSERT(IsSane());
    _ASSERT(HasControlPoints());
    //Construction via Range(s) avoids the extra check of from<till
    return Block(Range(std::min(std::min(start().x, end().x), std::min(c1().x, c2().x)),
                       std::max(std::max(start().x, end().x), std::max(c1().x, c2().x))),
                 Range(std::min(std::min(start().y, end().y), std::min(c1().y, c2().y)),
                       std::max(std::max(start().y, end().y), std::max(c1().y, c2().y))));
}

/** Returns the x range of the hull's bounding box.
 * (Cheaper than GetBezierHullBlock() & works for straight edges, too.)*/
inline Range Edge::GetHullXRange() const
{
    if (IsStraight())
        return Range(std::min(start().x, end().x), std::max(start().x, end().x));
    return Range(std::min(std::min(start().x, end().x), std::min(c1().x, c2().x)),
                 std::max(std::max(start().x, end().x), std::max(c1().x, c2().x)));
}

/** Returns the y range of the hull's bounding box.
* (Cheaper than GetBezierHullBlock() & works for straight edges, too.)*/
inline Range Edge::GetHullYRange() const
{
    if (IsStraight())
        return Range(std::min(start().y, end().y), std::max(start().y, end().y));
    return Range(std::min(std::min(start().y, end().y), std::min(c1().y, c2().y)),
                 std::max(std::max(start().y, end().y), std::max(c1().y, c2().y)));
}

/** Returns the position of (one of) the leftmost/rightmost or topmost/bottommost point of the edge */
template<bool y>
std::array<std::pair<XY, double>,2> Edge::Extremes() const
{
    if (IsStraight())
        return start()[y]<end()[y]
             ? std::array<std::pair<XY, double>, 2>{std::pair<XY, double>{start(), 0.}, std::pair<XY, double>{end(), 1.}}
             : std::array<std::pair<XY, double>, 2>{std::pair<XY, double>{ end(), 1. }, std::pair<XY, double>{start(), 0.}};
    
    //from: http://stackoverflow.com/questions/2587751/an-algorithm-to-find-bounding-box-of-closed-bezier-curves
    std::vector<double> pos;
    std::vector<XY> xy;
    pos.reserve(6);   xy.reserve(6);
    pos.push_back(0); xy.push_back(start());
    pos.push_back(1); xy.push_back(end());

    double b = 6 * start()[y] - 12 * c1()[y] + 6 * c2()[y];
    double a = -3 * start()[y] + 9 * c1()[y] - 9 * c2()[y] + 3 * end()[y];
    double c = 3 * c1()[y] - 3 * start()[y];
    if (a == 0) {
        if (b != 0) {
            double t = -c / b;
            if (0 < t && t < 1) {
                pos.push_back(t);
                xy.push_back(Split(t));
            }
        }
    } else {
        double b2ac = pow(b, 2) - 4 * c * a;
        if (b2ac >= 0) {
            double  t1 = (-b + sqrt(b2ac))/(2 * a);
            if (0 < t1 && t1 < 1) {
                pos.push_back(t1);
                xy.push_back(Split(t1));
            }
            double  t2 = (-b - sqrt(b2ac))/(2 * a);
            if (0 < t2 && t2 < 1) {
                pos.push_back(t2);
                xy.push_back(Split(t2));
            }
        }
    }
    //If we have equal x or y values, take the one with the smaller index
    //(more likely to be an endpoint and hence precise)
    std::pair<unsigned, unsigned> X = { 0,0 };
    for (unsigned u = 0; u<xy.size(); u++) {
        if (xy[u][y]<xy[X.first][y])
            X.first = u;
        if (xy[u][y]>xy[X.second][y])
            X.second = u;
    }
    return std::array<std::pair<XY, double>, 2>{ std::pair<XY, double>{ xy[X.first],  pos[X.first] },
                                                 std::pair<XY, double>{ xy[X.second], pos[X.second] }};
}


/** Find and eliminate any potential loop in the segment [first,last)
 * does not work with vectors (iterators must survive insert+delete ops) */
template <typename E, typename Iterator>
void Edge::RemoveLoop(std::list<E> &edges, Iterator first, Iterator last, bool self,
                      std::vector<Edge>* original, size_t orig_offset)
{
    XY r[9];
    double pos_one[9], pos_two[9];
    size_t orig_u = orig_offset, orig_v;
    //Start by removing self-intersections
    if (self)
        for (auto i = first; i!=last; i++,orig_u++)
            if (!i->IsStraight()) {
                unsigned num = i->SelfCrossing(r, pos_one, pos_two);
                if (num==0) continue;
                if (num==2) {
                    if (pos_one[0]<pos_one[1] && pos_two[0]>pos_two[1])
                        num--;
                    else if (pos_one[0]>pos_one[1] && pos_two[0]<pos_two[1]) {
                        num--;
                        pos_one[0] = pos_one[1];
                        pos_two[0] = pos_two[1];
                    } else
                        //a strange crossing system - give up
                        continue;
                }
                const double p1 = std::min(pos_one[0], pos_two[0]);
                const double p2 = std::max(pos_one[0], pos_two[0]);
                //Chop to remove the part between the two positions
                auto next_i = i;
                next_i++;
                edges.emplace(next_i, *i)->Chop(p2, 1);
                i->Chop(0, p1);
                if (original) {
                    original->emplace(original->begin()+orig_u+1, (*original)[orig_u], p2, 1);
                    (*original)[orig_u].Chop(0, p1);
                }
            }
    //Now find the two outermost crosspoints.
    //If crosspoints do not happen like that, we will be unsuccessful here.
    auto prev_last = last;
    prev_last--;
    orig_u = orig_offset;
        for (auto u = first; u!=last; u++, orig_u++) {
            orig_v = original ? original->size()-1 : 0;
            for (auto v = prev_last; v!=u; v--, orig_v--) {
                auto next_u = u; next_u++;
                unsigned num = u->Crossing(*v, next_u==v, r, pos_one, pos_two);
                if (!num) continue;
                //Check if in case of several crosspoints the one with smallest pos in "pos_one"
                //corresponds to the one with largest pos in "pos_two"
                size_t smalll, large;
                smalll = std::min_element(pos_one, pos_one+num) - pos_one;
                large = std::max_element(pos_two, pos_two+num) - pos_two;
                //If they do not - we could not easily remove the loop - we give up
                if (smalll!=large) {
                    //_ASSERT(0);
                    return;
                }
                //Loop found. Chop these edges and remove all edges in-between
                //Note that This is not perfect. We may have a bad loop still or even several
                //But I give up coding here.
                u->Chop(0, pos_one[smalll]);
                v->Chop(pos_two[large], 1);
                //make the two edges connect. If one is straight, use that, if both are bezier, use midpoint
                if (u->IsStraight()) {
                    if (v->IsStraight()) {
                        //both are straight - stive to keep horizontal & vertical edges so
                        if (u->start().x==u->end().x) v->start().x = u->start().x;
                        else if (v->start().x==v->end().x) u->end().x = v->start().x;
                        else u->end().x = v->start().x = (u->end().x + v->start().x)/2;
                        if (u->start().y==u->end().y) v->start().y = u->start().y;
                        else if (v->start().y==v->end().y) u->end().y = v->start().y;
                        else u->end().y = v->start().y = (u->end().y + v->start().y)/2;
                    } else
                        // v is bezier
                        v->start() = u->end();
                } else if (v->IsStraight())
                    // u is bezier
                    u->end() = v->start();
                else
                    //both are beziers
                    u->end() = v->start() = (u->end() + v->start())/2;
                edges.erase(++u, v); //we modify u here, but use it no longer
                if (original) {
                    (*original)[orig_u].Chop(0, pos_one[smalll]);
                    (*original)[orig_v].Chop(pos_two[large], 1);
                    original->erase(original->begin()+orig_u+1, original->begin()+orig_v);
                }
                return;
            }
    }
}

template <typename E>
inline bool Edge::RemoveLoops(std::vector<E> &edges, bool self) {
    //Start by removing self-intersections
    bool ret = false;
    XY r[9];
    double pos_one[9], pos_two[9];
    if (self)
        for (size_t i = 0; i<edges.size(); i++)
            if (!edges[i].IsStraight()) {
                unsigned num = edges[i].SelfCrossing(r, pos_one, pos_two);
                if (num==0) continue;
                if (num==2) {
                    if (pos_one[0]<pos_one[1] && pos_two[0]>pos_two[1])
                        num--;
                    else if (pos_one[0]>pos_one[1] && pos_two[0]<pos_two[1]) {
                        num--;
                        pos_one[0] = pos_one[1];
                        pos_two[0] = pos_two[1];
                    } else
                        //a strange crossing system - give up
                        continue;
                }
                const double p1 = std::min(pos_one[0], pos_two[0]);
                const double p2 = std::max(pos_one[0], pos_two[0]);
                //Chop to remove the part between the two positions
                edges.emplace(edges.begin()+(i+1));
                edges[i+1] = edges[i];
                edges[i].Chop(0, p1);
                edges[i+1].Chop(p2, 1);
                ++i;
                ret = true;
            }
    if (edges.size()<=1) return ret;
    for (size_t u = 0; u<edges.size()-1; u++)
        for (size_t v = edges.size()-1; v>u; v--) {
            unsigned num = edges[u].Crossing(edges[v], u+1==v, r, pos_one, pos_two);
            if (!num) continue;
            //In case of several crosspoints, take the first one on edge 'u'.
            const size_t cp = std::min_element(pos_one, pos_one+num) - pos_one;
            //Loop found. Chop these edges and remove all edges in-between
            //Note that This is not perfect. We may have a bad loop still or even several
            //But I give up coding here.
            edges[u].Chop(0, pos_one[cp]);
            edges[v].Chop(pos_two[cp], 1);
            //make the two edges connect. If one is straight, use that, if both are bezier, use midpoint
            if (edges[u].IsStraight()) {
                if (edges[v].IsStraight()) {
                    //both are straight - stive to keep horizontal & vertical edges so
                    if (edges[u].start().x==edges[u].end().x) edges[v].start().x = edges[u].start().x;
                    else if (edges[v].start().x==edges[v].end().x) edges[u].end().x = edges[v].start().x;
                    else edges[u].end().x = edges[v].start().x = (edges[u].end().x + edges[v].start().x)/2;
                    if (edges[u].start().y==edges[u].end().y) edges[v].start().y = edges[u].start().y;
                    else if (edges[v].start().y==edges[v].end().y) edges[u].end().y = edges[v].start().y;
                    else edges[u].end().y = edges[v].start().y = (edges[u].end().y + edges[v].start().y)/2;
                } else
                    // v is bezier
                    edges[v].start() = edges[u].end();
            } else if (edges[v].IsStraight())
                // u is bezier
                edges[u].end() = edges[v].start();
            else
                //both are beziers
                edges[u].end() = edges[v].start() = (edges[u].end() + edges[v].start())/2;
            edges.erase(edges.begin()+u+1, edges.begin()+v);
            ret = true;
            break;
        }
    return ret;
}


/** Calculates the crosspoints with another edge.
 *
 * This must ensure that we do not return pos values that are very close to 0 or 1
 * such values are to be rounded to 0 (or 1) in that case exactly the start point
 * of the arc shall be returned.
 * In case the two edges overlap and have common sections, we return 2 and the two
 * endpoint of the common section (and flag this in 'overlap' param if non-null).
 * If one of the edges is a dot (very-very short), and it lies on the other edge
 * the position returned on it will be guaranteed to be [0..1] but is undefined
 * otherwise.
 * The arrays shall be at least Edge::MAX_CP big.
 *
 * @param [in] A The other edge.
 * @param [in] is_next If true we assume A follows *this (and thus do not report
 *             if their start and endpoints (respectively) meet. If A is a dot
 *             and lies on the end of 'this', we also return 0. Likewise if
 *             'this' is a dot and lies on A.start. Similar if both are dots and
 *             equal (roughly).
 * @param [out] r The crosspoints.
 * @param [out] pos_my The pos values of the corresponding crosspoints on me.
 * @param [out] pos_other The pos values of the corresponding crosspoints on the other edge.
 * @returns The number of crosspoints found [0..MAX_CP]
 */
inline unsigned Edge::Crossing(const Edge& A, bool is_next, XY r[Edge::MAX_CP], double pos_my[Edge::MAX_CP], double pos_other[Edge::MAX_CP]) const {    
    return HullOverlap(A, is_next) ? abs(CrossingWithOverlap(A, is_next, r, pos_my, pos_other)) : 0;
}

/** Calculates the crosspoints with another edge.
 *
 * This must ensure that we do not return pos values that are very close to 0 or 1
 * such values are to be rounded to 0 (or 1) in that case exactly the start point
 * of the arc shall be returned.
 * In case the two edges overlap and have common sections, we return 2 and the two
 * endpoint of the common section (and flag this in 'overlap' param if non-null).
 * The arrays shall be at least Edge::MAX_CP big.
 *
 * @param [in] A The other edge.
 * @param [in] is_next If true we assume A follows *this (and thus do not report
 *             if their start and endpoints (respectively) meet.
 * @param [out] overlap If non-null, the function returns a true here, when the two edges
 *                      overlap and run together, else a false.
 * @returns The crosspoints found [0..MAX_CP] */
inline EdgeCPsDataWithXY<Edge::MAX_CP> Edge::Crossing(const Edge &A, bool is_next, bool *overlap) const
{
    EdgeCPsDataWithXY<Edge::MAX_CP> ret;
    const int n = HullOverlap(A, is_next) 
        ? CrossingWithOverlap(A, is_next, ret.xy.data(), ret.pos_my.data(), ret.pos_other.data())
        : 0;
    ret.num = abs(n);
    if (overlap) *overlap = n<0;
    return ret;
}

/** Returns the crossing of an edge with an infinite line.
 * @returns A number of crosspoints and two static arrays, the first for the positions
 * of the crosspoints on 'this' the second for the positions on 'A'->'B'.
 * We do not filter the result by position values falling outside [0,1] for pos_other.
 * In contrast all values of pos_my will be inside [0,1] pos_my.
 * If the edge is straight and it coincides with the infinite line, we return two crosspoints,
 * with pos_my =0 and =1.*/
inline EdgeCPsData<Edge::MAX_CP> Edge::Crossing(const XY &A, const XY &B) const
{
    EdgeCPsData<Edge::MAX_CP> ret;
    ret.num = Crossing(A, B, ret.pos_my.data(), ret.pos_other.data());
    return ret;
}

/** Finds where an edge crosses itself.
 * @returns the crosspoints found. The my and other positions
 * denote the two positions of a crosspoint all on 'this'.*/
inline EdgeCPsDataWithXY<Edge::MAX_CP> Edge::SelfCrossing() const
{
    EdgeCPsDataWithXY<Edge::MAX_CP> ret;
    ret.num = SelfCrossing(ret.xy.data(), ret.pos_my.data(), ret.pos_other.data());
    return ret;
}

/** Return a tangent drawn from 'xy' to the edge.
 * Note: we assume the edge is not straight and has no inflection points.
 * If you edge has, Split it to 2 or 3 parts.
 * Note: we only consider a tangent drawn towards the end of the edge (forward)
 * or the start of the edge (backward).
 * In general use TangentFrom().
 * @returns true if the tangent is an actual touching tangent.
 *          The position where a the tangent touches the edge.
 * We can return false only with pos == 1 or == 0. */
template <bool forward>
std::pair<bool, double> Edge::TangentFromNoInflection(const XY &xy) const
{
    _ASSERT(!IsStraight());
    double tmp[2];
    (void)tmp;
    _ASSERT(InflectionPoints(tmp)==0);
    const bool cc = contour::max_clockwise(true, start(), GetC2(), end());
    bool going_fw = true;
    double pos = 0.5;
    double width = 0.25;
    //binary search
    while (width>0.0001 && 0<=pos && pos<=1) {
        auto dir = triangle_dir(Pos2Point(pos), (forward ? NextTangentPoint(pos) : PrevTangentPoint(pos)), xy);
        if (dir !=CLOCKWISE && dir != COUNTERCLOCKWISE) return {true, pos}; //we are straight line or degenerate
        if ((dir==CLOCKWISE)==cc) {
            pos += width;
            if (!going_fw) width /= 2;
            going_fw = true;
        } else {
            pos -= width;
            if (going_fw) width /= 2;
            going_fw = false;
        }
    }
    if (pos<0) return {false, 0};
    if (1<pos) return {false, 1};
    return {true, pos};
}


/** Calculates the touchpoint of tangents drawn from a given point.
 *
 * For curvy edges:
 *     Given the point `from` draw tangents to the edge (two can be drawn)
 *     and calculate where these tangents touch the it.
 *     In this context the *clockwise tangent* is the one which is traversed from
 *     `from` towards the arc touches the ellipse in the clockwise direction.
 * For straight edges:
 *     We return the `start` in both clockwise and counterclockwise.
 *     This is because unless 'include_end' is true we ignore the endpoint -
 *     then it will be a helper for connected paths and the endpoint will be the
 *     startpoint of the next edge and will be considered there.
 *     If the 'include_end' template parameter is true, we also include the endpoint.
 * @param [in] from The point from which the tangents are drawn.
 * @returns[out] The points where the clockwise (first) and counterclckwise (second)
 *               tangent touches the curve.
 */
template <bool include_end>
std::pair<std::pair<double, XY>, std::pair<double, XY>>
Edge::TangentFrom(const XY &from) const
{
    std::pair<std::pair<double, XY>, std::pair<double, XY>> ret;
    if (IsStraight()) {
        if (include_end) {
            ret.first = max_clockwise(true, from, start(), end()) ? std::pair<double, XY>{0, start()} : std::pair<double, XY>{1, end()};
            ret.second = max_clockwise(false, from, start(), end()) ? std::pair<double, XY>{0, start()} : std::pair<double, XY>{1, end()};
        } else {
            ret.second = ret.first = std::pair<double, XY>{0, start()};
        }
        return ret;
    }
    std::array<std::pair<double, XY>, 8> pos;
    pos[0] = { 0, start() };
    unsigned num = 1;
    std::array<double, 2> infl;
    switch (InflectionPoints(infl)) {
    case 0:
        pos[num++].first = TangentFromNoInflection<true>(from).second;
        pos[num++].first = TangentFromNoInflection<false>(from).second;
        break;
    case 1:
    {
        Edge E1, E2;
        Split(infl[0], E1, E2);
        pos[num++].first = E1.TangentFromNoInflection<true>(from).second;
        pos[num++].first = E1.TangentFromNoInflection<false>(from).second;
        pos[num++].first = E2.TangentFromNoInflection<true>(from).second;
        pos[num++].first = E2.TangentFromNoInflection<false>(from).second;
        break;
    }
    case 2:
    {
        if (infl[0]>infl[1]) std::swap(infl[0], infl[1]);
        Edge E1(*this, 0, infl[0]), E2(*this, infl[0], infl[1]), E3(*this, infl[1], 1);
        pos[num++].first = E1.TangentFromNoInflection<true>(from).second;
        pos[num++].first = E1.TangentFromNoInflection<false>(from).second;
        pos[num++].first = E2.TangentFromNoInflection<true>(from).second;
        pos[num++].first = E2.TangentFromNoInflection<false>(from).second;
        pos[num++].first = E3.TangentFromNoInflection<true>(from).second;
        pos[num++].first = E3.TangentFromNoInflection<false>(from).second;
        break;
    }
    }
    for (unsigned u = 0; u<num; u++)
        pos[u].second = Pos2Point(pos[u].first);
    auto pair =
        std::minmax_element(std::begin(pos), std::begin(pos)+num, [&](const auto &p1, const auto &p2) {
        return max_clockwise(false, from, p1.second, p2.second); });
    ret.first = *pair.first;
    ret.second = *pair.second;
    return ret;
}



/** Calculates the touchpoint of tangents drawn to touch two edges.
 *
 * Given the two edges, four such tangents can be drawn, here we focus on the two
 * outer ones, the ones that touch either both edges from above or both from below,
 * but not mixed.
 * Note that unless 'include_end' is true, we do not consider the endpoint -
 * in that case this is a helper for closed contours - where the start next edge
 * will be the same as our endpoint.
 * The clockwise and cclockwise members are considered - we only change them if
 * we find a more clockwise or cclockwise tangent. At start they shall be initialized
 * to a point on the curves.
 * @param [in] o The other edge.
 * @param [out] clockwise The points where the clockwise tangent touches us
 *                        (clockwise[0]) and `o` (clockwise[1]).
 * @param [out] cclockwise The points where the counterclockwise tangent touches us
 *                         (cclockwise[0]) and `o` (cclockwise[1]).*/
template <bool include_end>
void Edge::TangentFrom(const Edge &o, XY clockwise[2], XY cclockwise[2]) const
{
    if (IsStraight() && o.IsStraight()) {
        clockwise[1] = minmax_clockwise(clockwise[0], o.start(), clockwise[1], true);
        cclockwise[1] = minmax_clockwise(cclockwise[0], o.start(), cclockwise[1], false);

        clockwise[0] = minmax_clockwise(clockwise[1], start(), clockwise[0], false);
        cclockwise[0] = minmax_clockwise(cclockwise[1], start(), cclockwise[0], true);

        if (include_end) {
            clockwise[1] = minmax_clockwise(clockwise[0], o.end(), clockwise[1], true);
            cclockwise[1] = minmax_clockwise(cclockwise[0], o.end(), cclockwise[1], false);

            clockwise[0] = minmax_clockwise(clockwise[1], end(), clockwise[0], false);
            cclockwise[0] = minmax_clockwise(cclockwise[1], end(), cclockwise[0], true);
        }
        return;
    }
    std::array<Edge, 3> e1, e2;
    double infl[2];
    unsigned num1 = 1, num2 = 1;
    if (IsStraight()) {
        e1[0] = *this;
    } else switch (num1 = 1+InflectionPoints(infl)) {
    default: _ASSERT(0); FALLTHROUGH;
    case 1:
        e1[0] = *this; break;
    case 2:
        e1[1] = e1[0] = *this;
        e1[0].SetEnd(infl[0]);
        e1[1].SetStart(infl[1]);
        break;
    case 3:
        e1[2] = e1[1] = e1[0] = *this;
        e1[0].SetEnd(infl[0]);
        e1[1].Chop(infl[0], infl[1]);
        e1[2].SetStart(infl[1]);
        break;
    }
    if (IsStraight()) {
        e2[0] = o;
    } else switch (num2 = 1+o.InflectionPoints(infl)) {
    default: _ASSERT(0); FALLTHROUGH;
    case 1:
        e2[0] = o; break;
    case 2:
        e2[1] = e2[0] = o;
        e2[0].SetEnd(infl[0]);
        e2[1].SetStart(infl[1]);
        break;
    case 3:
        e2[2] = e2[1] = e2[0] = o;
        e2[0].SetEnd(infl[0]);
        e2[1].Chop(infl[0], infl[1]);
        e2[2].SetStart(infl[1]);
        break;
    }
    double delta = 0;
    do {
        XY store_cwise = clockwise[1];
        XY store_ccwise = cclockwise[1];
        for (unsigned u = 0; u<num1; u++) {
            if (e2[u].IsStraight()) {
                clockwise[1] = minmax_clockwise(clockwise[0], e2[u].start(), clockwise[1], true);
                clockwise[1] = minmax_clockwise(clockwise[0], e2[u].end(), clockwise[1], true);
                cclockwise[1] = minmax_clockwise(cclockwise[0], e2[u].start(), cclockwise[1], false);
                cclockwise[1] = minmax_clockwise(cclockwise[0], e2[u].end(), cclockwise[1], false);
            } else {
                clockwise[1] = minmax_clockwise(clockwise[0], e2[u].Pos2Point(e2[u].TangentFromNoInflection<true>(clockwise[0]).second), clockwise[1], true);
                clockwise[1] = minmax_clockwise(clockwise[0], e2[u].Pos2Point(e2[u].TangentFromNoInflection<false>(clockwise[0]).second), clockwise[1], true);
                cclockwise[1] = minmax_clockwise(cclockwise[0], e2[u].Pos2Point(e2[u].TangentFromNoInflection<true>(cclockwise[0]).second), cclockwise[1], false);
                cclockwise[1] = minmax_clockwise(cclockwise[0], e2[u].Pos2Point(e2[u].TangentFromNoInflection<false>(cclockwise[0]).second), cclockwise[1], false);
            }
        }
        delta = store_cwise.DistanceSqr(clockwise[1]) + store_ccwise.DistanceSqr(cclockwise[1]);
        store_cwise = clockwise[0];
        store_ccwise = cclockwise[0];
        for (unsigned u = 0; u<num1; u++) {
            if (e1[u].IsStraight()) {
                clockwise[0] = minmax_clockwise(clockwise[1], e1[u].start(), clockwise[0], false);
                clockwise[0] = minmax_clockwise(clockwise[1], e1[u].end(), clockwise[0], false);
                cclockwise[0] = minmax_clockwise(cclockwise[1], e1[u].start(), cclockwise[0], true);
                cclockwise[0] = minmax_clockwise(cclockwise[1], e1[u].end(), cclockwise[0], true);
            } else {
                clockwise[0] = minmax_clockwise(clockwise[1], e1[u].Pos2Point(e1[u].TangentFromNoInflection<true>(clockwise[1]).second), clockwise[0], false);
                clockwise[0] = minmax_clockwise(clockwise[1], e1[u].Pos2Point(e1[u].TangentFromNoInflection<false>(clockwise[1]).second), clockwise[0], false);
                cclockwise[0] = minmax_clockwise(cclockwise[1], e1[u].Pos2Point(e1[u].TangentFromNoInflection<true>(cclockwise[1]).second), cclockwise[0], true);
                cclockwise[0] = minmax_clockwise(cclockwise[1], e1[u].Pos2Point(e1[u].TangentFromNoInflection<false>(cclockwise[1]).second), cclockwise[0], true);
            }
        }
        delta += store_cwise.DistanceSqr(clockwise[0]) + store_ccwise.DistanceSqr(cclockwise[0]);
    } while (delta>0.0001);
}


/** Checks if the bounding box of the hull of two overlap.
 * @returns True if there is overlap */
inline bool HullOverlap(const Block& T, const Block &O, bool is_next) {
    //We allow hulls a bit apart to be reported as overlapping.
    //This is after all just to exclude edges far apart
    //was: if (!O.Overlaps(T)) return false;
    if (test_smaller(T.y.till, O.y.from) || test_smaller(O.y.till, T.y.from)) return false;
    if (test_smaller(T.x.till, O.x.from) || test_smaller(O.x.till, T.x.from)) return false;
    //test if 'o' and 'this' only overlaps at their start/endpoint
    if (!is_next) return true;
    if (O.y.from != T.y.till && O.y.till != T.y.from)
        return true;
    if (O.x.from != T.x.till && O.x.till != T.x.from)
        return true;
    //OK, now we know that the two BBs touch only at a single vertex
    //since end==o.start, this can only be that point
    return false;
}


/** Checks if the bounding box of the hull of two overlap.
 * We place it here for possible inlining as this is called very often.
 * @param [in] o The other edge
 * @param [in] is_next If true 'o' is an edge following 'this' and we ignore if they
 *             connect via this->end == o.start.
 * @returns True if there is overlap */
inline bool Edge::HullOverlap(const Edge& o, bool is_next) const {
    const Block O = o.IsStraight() ? Block(o.start(), o.end()) : o.GetBezierHullBlock();
    const Block T = IsStraight() ? Block(start(), end()) : GetBezierHullBlock();
    return contour::HullOverlap(T, O, is_next && end()==o.start());
}


/** Calculates the tight bounding box of 'this'.
 * Moderately expensive for beziers. We place it to the
 * header to be able to get inlined, which would make 
 * straight edges much faster.*/
inline Block Edge::CreateBoundingBox() const noexcept
{
    if (IsStraight())
        return Block(start(), end());
    //from: http://stackoverflow.com/questions/2587751/an-algorithm-to-find-bounding-box-of-closed-bezier-curves
    std::array<XY, 6> bounds = {start(), end()};
    unsigned p = 2;

    for (unsigned i = 0; i < 2; ++i) {
        double b = 6 * start()[i] - 12 * c1()[i] + 6 * c2()[i];
        double a = -3 * start()[i] + 9 * c1()[i] - 9 * c2()[i] + 3 * end()[i];
        double c = 3 * c1()[i] - 3 * start()[i];
        if (fabs(a) < 1e-8) {
            if (fabs(b) < 1e-8) {
                continue;
            }
            double t = -c / b;
            if (0 < t && t < 1)
                bounds[p++] = Split(t);
            continue;
        }
        double b2ac = pow(b, 2) - 4 * c * a;
        if (b2ac < 0) {
            continue;
        }
        double  t1 = (-b + sqrt(b2ac))/(2 * a);
        if (0 < t1 && t1 < 1)
            bounds[p++] = Split(t1);
        double  t2 = (-b - sqrt(b2ac))/(2 * a);
        if (0 < t2 && t2 < 1)
            bounds[p++] = Split(t2);
    }
    const auto X = std::minmax_element(bounds.begin(), bounds.begin()+p,
                     [](const XY &a, const XY &b) {return a.x<b.x; });
    const auto Y = std::minmax_element(bounds.begin(), bounds.begin()+p,
                     [](const XY &a, const XY &b) {return a.y<b.y; });
    return Block(X.first->x, X.second->x, Y.first->y, Y.second->y);
}

inline double Edge::GetAreaAboveAdjusted() const noexcept {
    if (IsStraight())
        return start().x * end().y - start().y * end().x;
    Edge E1, E2;
    Split(E1, E2);
    return E1.GetAreaAboveAdjusted() + E2.GetAreaAboveAdjusted();
}

inline double Edge::GetLength() const noexcept {
    if (IsStraight())
        return (start() - end()).length();
    Edge E1, E2;
    Split(E1, E2);
    return E1.GetLength() + E2.GetLength();
}




} //namespace contour 



#endif //CONTOUR_EDGE_H
