/*
    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/>.
*/

/*Parts of this file is based on WildMagic 5.4, which uses the Boost license,
  which requires me to repeat it here. See http://www.geometrictools.com

    Boost Software License - Version 1.0 - August 17th, 2003

    Permission is hereby granted, free of charge, to any person or organization
    obtaining a copy of the software and accompanying documentation covered by
    this license (the "Software") to use, reproduce, display, distribute,
    execute, and transmit the Software, and to prepare derivative works of the
    Software, and to permit third-parties to whom the Software is furnished to
    do so, all subject to the following:

    The copyright notices in the Software and this entire statement, including
    the above license grant, this restriction and the following disclaimer,
    must be included in all copies of the Software, in whole or in part, and
    all derivative works of the Software, unless such copies or derivative
    works are solely in the form of machine-executable object code generated by
    a source language processor.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
    SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
    FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
*/

/** @file contour_edge.cpp Defines Edge.
 * @ingroup contour_files
 */


#include <cassert>
#include <vector>
#include <list>
#include <algorithm>
#include "contour_edge.h"
#include "contour.h"

namespace contour {

/** Return the angle halfway between a1 and a2.*/
RayAngle Avg(const RayAngle & a1, const RayAngle & a2) noexcept
{
    _ASSERT(0<=a1.angle && a1.angle<4);
    _ASSERT(0<=a2.angle && a2.angle<4);
    RayAngle ret;
    //test if they are on different sides of angle==0
    if (fabs(a1.angle-a2.angle)>2) ret.angle = fmod_negative_safe((a1.angle+a2.angle+4)/2, 4.);
    else ret.angle = (a1.angle+a2.angle)/2;
    ret.curve = (a1.curve+a2.curve)/2;
    _ASSERT(0<=ret.angle && ret.angle<4);
    return ret;
}



//////////////////HELPERS

    /** Solve quadratic equation.
*
* @param [in] afCoeff Parameters of the equation. `m_afCoeff[0]` is the constant, `m_afCoeff[2]` is the coeff of `x^2`.
* @param [out] afRoot Returns the root(s).
* @returns the number of roots [0..2].
*/
template <typename real>
unsigned solve_degree2(real afCoeff[3], real afRoot[2]) noexcept
{
    // compute real roots to c[2]x^2+c[1]*x+c[0] = 0
    if (afCoeff[2] == 0) {
        //linear
        if (test_zero(afCoeff[1]))
            return 0;
        afRoot[0] = -afCoeff[0]/afCoeff[1];
        return 1;
    }

    // make polynomial monic
    if (afCoeff[2] != 1.) {
        afCoeff[0] = afCoeff[0]/afCoeff[2];
        afCoeff[1] = afCoeff[1]/afCoeff[2];
    }

    real fDiscr = afCoeff[1]*afCoeff[1]-4.*afCoeff[0];
    if (test_zero(fDiscr)) {
        afRoot[0] = 0.5*(-afCoeff[1]);
        return 1;
    }

    if (fDiscr >= 0.) {
        fDiscr = sqrt(fDiscr);
        afRoot[0] = 0.5*(-afCoeff[1]-fDiscr);
        afRoot[1] = 0.5*(-afCoeff[1]+fDiscr);
        return 2;
    }
    return 0;
}

/** Solve qubic equation.
*
* @param [in] afCoeff Parameters of the equation. `m_afCoeff[0]` is the constant, `m_afCoeff[3]` is the coeff of `x^3`.
* @param [out] afRoot Returns the root(s).
* @returns the number of roots [1..3].
*/
template <typename real>
unsigned solve_degree3(real afCoeff[4], real afRoot[3]) noexcept
{
    //one can also consider this page
    //http://www.pouet.net/topic.php?which=9119&page=1

    // compute real roots to c[3]*x^3+c[2]*x^2+c[1]*x+c[0] = 0
    //Special case of the 2 degree equation
    if (afCoeff[3] == 0)
        return solve_degree2(afCoeff, afRoot);
    //Special case of the zero being a root.
    if (afCoeff[0] == 0) {
        afRoot[0] = 0;
        return solve_degree2(afCoeff+1, afRoot+1)+1;
    }

    // make polynomial monic
    if (afCoeff[3] != 1.) {
        afCoeff[0] = afCoeff[0]/afCoeff[3];
        afCoeff[1] = afCoeff[1]/afCoeff[3];
        afCoeff[2] = afCoeff[2]/afCoeff[3];
    }

    // convert to y^3+a*y+b = 0 by x = y-c[2]/3 and
    const real fA = (1./3.)*(3.*afCoeff[1]-afCoeff[2]*afCoeff[2]);
    const real fB = (1./27.)*(2.*afCoeff[2]*afCoeff[2]*afCoeff[2] -
        9.*afCoeff[1]*afCoeff[2]+27.*afCoeff[0]);
    const real fOffset = (1./3.)*afCoeff[2];

    real fDiscr = 0.25*fB*fB + (1./27.)*fA*fA*fA;
    const real fHalfB = 0.5*fB;
    if (fabs(fDiscr)<1e-10) {    //3 real roots, but at least two are equal
        real fTemp;
        if (fHalfB >= 0.)
            fTemp = -pow(fHalfB, real(1./3.));
        else
            fTemp = pow(-fHalfB, real(1./3.));
        if (test_zero(fTemp)) {
            afRoot[0] = -fOffset;
            return 1;
        }
        afRoot[0] = 2.*fTemp-fOffset;
        afRoot[1] = -fTemp-fOffset;
        return 2;
    }

    if (fDiscr > 0.) { // 1 real, 2 complex roots
        fDiscr = sqrt(fDiscr);
        real fTemp = -fHalfB + fDiscr;
        if (fTemp >= 0.)
            afRoot[0] = pow(fTemp, real(1./3.));
        else
            afRoot[0] = -pow(-fTemp, real(1./3.));
        fTemp = -fHalfB - fDiscr;
        if (fTemp >= 0.)
            afRoot[0] += pow(fTemp, real(1./3.));
        else
            afRoot[0] -= pow(-fTemp, real(1./3.));
        afRoot[0] -= fOffset;
        return 1;
    } else if (fDiscr < 0.) {  //3 real roots
        real fDist = sqrt(-1./3.*fA);
        real fAngle = (1./3.)*atan2(sqrt(-fDiscr), -fHalfB);
        real fCos = cos(fAngle);
        real fSin = sin(fAngle);
        static const real sqrt3 = sqrt(3.);
        afRoot[0] = 2.*fDist*fCos-fOffset;
        afRoot[1] = -fDist*(fCos+sqrt3*fSin)-fOffset;
        afRoot[2] = -fDist*(fCos-sqrt3*fSin)-fOffset;
        return 3;
    }

    return true;
}

/** Solve quadratic equation.
*
* Based on David Eberly's code at
* <http://svn.berlios.de/wsvn/lwpp/incubator/deeppurple/math/FreeMagic/Source/Core/MgcPolynomial.cpp>
* @param [in] afCoeff Parameters of the equation. `m_afCoeff[0]` is the constant, `m_afCoeff[4]` is the coeff of `x^4`.
* @param [out] afRoot Returns the root(s).
* @returns the number of roots [0..4]
*/
template <typename real>
unsigned solve_degree4(real afCoeff[5], real afRoot[4]) noexcept
{
    // compute real roots to c[4]*x^4+c[3]*x^3+c[2]*x^2+c[1]*x+c[0] = 0
    if (afCoeff[4] == 0)
        return solve_degree3(afCoeff, afRoot);

    // make polynomial monic
    if (afCoeff[4] != 1.) {
        afCoeff[0] = afCoeff[0]/afCoeff[4];
        afCoeff[1] = afCoeff[1]/afCoeff[4];
        afCoeff[2] = afCoeff[2]/afCoeff[4];
        afCoeff[3] = afCoeff[3]/afCoeff[4];
    }

    // reduction to resolvent cubic polynomial
    real kResolve[4];
    kResolve[3] = 1.;
    kResolve[2] = -afCoeff[2];
    kResolve[1] = afCoeff[3]*afCoeff[1]-4.*afCoeff[0];
    kResolve[0] = -afCoeff[3]*afCoeff[3]*afCoeff[0] +
        4.*afCoeff[2]*afCoeff[0]-afCoeff[1]*afCoeff[1];
    real afResolveRoot[3];
    /*int iResolveCount = */ solve_degree3(kResolve, afResolveRoot);
    real fY = afResolveRoot[0];

    unsigned num = 0;
    real fDiscr = 0.25*afCoeff[3]*afCoeff[3]-afCoeff[2]+fY;
    if (test_zero(fDiscr)) {
        real fT2 = fY*fY-4.*afCoeff[0];
        if (test_positive(fT2)) {
            if (fT2 < 0.) // round to zero
                fT2 = 0.;
            fT2 = 2.*sqrt(fT2);
            real fT1 = 0.75*afCoeff[3]*afCoeff[3]-2.*afCoeff[2];
            if (test_positive(fT1+fT2)) {
                real fD = sqrt(fT1+fT2);
                afRoot[0] = -0.25*afCoeff[3]+0.5*fD;
                afRoot[1] = -0.25*afCoeff[3]-0.5*fD;
                num = 2;
            }
            if (test_positive(fT1-fT2)) {
                real fE = sqrt(fT1-fT2);
                afRoot[num++] = -0.25*afCoeff[3]+0.5*fE;
                afRoot[num++] = -0.25*afCoeff[3]-0.5*fE;
            }
        }
        return num;
    }

    if (fDiscr > 0.) {
        real fR = sqrt(fDiscr);
        real fT1 = 0.75*afCoeff[3]*afCoeff[3]-fR*fR-2.*afCoeff[2];
        real fT2 = (4.*afCoeff[3]*afCoeff[2]-8.*afCoeff[1]-
            afCoeff[3]*afCoeff[3]*afCoeff[3]) / (4.*fR);

        real fTplus = fT1+fT2;
        if (test_zero(fTplus)) {
            afRoot[0] = -0.25*afCoeff[3]+0.5*fR;
            num = 1;
        } else if (fTplus >= 0.) {
            real fD = sqrt(fTplus);
            afRoot[0] = -0.25*afCoeff[3]+0.5*(fR+fD);
            afRoot[1] = -0.25*afCoeff[3]+0.5*(fR-fD);
            num = 2;
        }
        real fTminus = fT1-fT2;
        if (test_zero(fTminus))
            fTminus = 0.;
        if (fTminus >= 0.) {
            real fE = sqrt(fTminus);
            afRoot[num++] = -0.25*afCoeff[3]+0.5*(fE-fR);
            afRoot[num++] = -0.25*afCoeff[3]-0.5*(fE+fR);
        }
        return num;
    }

    //if ( fDiscr < 0. )
    return 0;
}

/////////////////Edge


/** Splits to two bezier curves
 * The first will be start, ret, Mid(start, c1, t), r1
 * The second will be ret, end, r2, Mid(c2, end, t) */
XY Edge::Split(double t, XY &r1, XY &r2) const noexcept
{
    if (IsStraight()) {
        r1 = start();
        r2 = end();
    } else {
        const XY C = Mid(c1(), c2(), t);
        r1 = Mid(Mid(start(), c1(), t), C, t);
        r2 = Mid(C, Mid(c2(), end(), t), t);
    }
    return Mid(r1, r2, t);
}

/** Splits to two bezier curves at t=0.5
* The first will be start, ret, Mid(start, c1, t);, r1,
* The second will be ret, end, r2, Mid(c2, end, t); */
XY Edge::Split(XY &r1, XY &r2) const noexcept
{
    if (IsStraight()) {
        r1 = start();
        r2 = end();
    } else {
        const XY C = (c1()+c2())/2;
        r1 = ((start()+c1())/2 + C)/2;
        r2 = (C+(c2()+end())/2)/2;
    }
    return (r1+r2)/2;
}

/** Governs, how flat a bezier curve do we assume as straight.
 * see Edge::MakeStraightIfNeeded(). */
const double flatness_tolerance = 0.001;

/** Split the bezier curve to two halves.
 * If keep_cp is set and any one of the halves becomes straight,
 * it will have its control points kept for more precise length->pos computation.*/
void Edge::Split(Edge &r1, Edge &r2, bool keep_cp) const noexcept
{
    r1.start() = start();
    r2.end() = end();
    r1.meta() = r2.meta() = meta(); //copy all flags. straight & has_cp will be overwritten later
    if (IsStraight()) {
        r2.start() = r1.end() = (start()+end())/2;
        r1.meta().has_cp = r2.meta().has_cp = false; //r1.straight and r2.straight is already true
    } else {
        r2.start() = r1.end() = Split(r1.c2(), r2.c1());
        r1.c1() = (start()+c1())/2;
        r2.c2() = (c2()+end())/2;
        r1.meta().straight = r1.start().test_equal(r1.end());
        r1.meta().has_cp = !r1.IsStraight();
        r1.MakeStraightIfNeeded(flatness_tolerance, keep_cp);
        r2.meta().straight = r2.start().test_equal(r2.end());
        r2.meta().has_cp = !r2.IsStraight();
        r2.MakeStraightIfNeeded(flatness_tolerance, keep_cp);
    }
}

/** Split the bezier curve at position t. t must be in (0..1).*/
void Edge::Split(double t, Edge &r1, Edge &r2, bool keep_cp) const noexcept
{
    r1.start() = start();
    r2.end() = end();
    r1.meta() = r2.meta() = meta(); //copy all flags. straight & has_cp will be overwritten later
    if (IsStraight()) {
        r2.start() = r1.end() = Mid(start(), end(), t);
        r1.meta().has_cp = r2.meta().has_cp = false;  //r1.straight and r2.straight is already true
    } else {
        r2.start() = r1.end() = Split(t, r1.c2(), r2.c1());
        r1.c1() = Mid(start(), c1(), t);
        r2.c2() = Mid(c2(), end(), t);
        r1.meta().straight = r1.start().test_equal(r1.end());
        r1.meta().has_cp = !r1.IsStraight();
        r1.MakeStraightIfNeeded(flatness_tolerance, keep_cp);
        r2.meta().straight = r2.start().test_equal(r2.end());
        r2.meta().has_cp = !r2.IsStraight();
        r2.MakeStraightIfNeeded(flatness_tolerance, keep_cp);
    }
}

/** Cuts away the beginning and the end of the edge.
 * Keep only the parts between 't' and 's'.
 * Returns true if the result is degenerate to a single point.
 * 's' and 't' must be between [0..1].*/
bool Edge::Chop(double t, double s) noexcept
{
    _ASSERT(t>=0 && s>=0 && t<=1 && s<=1);
    if (s<t) std::swap(s, t);
    XY r1, r2;
    if (IsStraight()) {
        r1 = (s<1) ? start()+(end()-start())*s : end();
        if (t) start() = start() + (end()-start())*t;
        end() = r1;
        return t==s;
    }
    if (s<1) {
        end() = Split(s, r1, r2);
        c1() = Mid(start(), c1(), s);
        c2() = r1;
        t /= s;
    }
    if (t>0) {
        start() = Split(t, r1, r2);
        c1() = r2;
        c2() = Mid(c2(), end(), t);
    }
    return t==s;
}

/** Extend the edge with a linear segment of 'length' length.
    * Note that if edge is straight, we simple make it longer.
    * @param [in] length The length of the extension. Should be positive.
    * @param [in] forward If true we extend at the end, else in the start.
    * @param [in] v The visibility of the extension. If different from
    *               that of the edge, we return a new edge even for
    *               straight edges.
    * @returns true, if an edge needs to be added, and the edge to be added.*/
std::pair<bool, Edge> Edge::LinearExtend(double length, bool forward, bool v) noexcept
{

    if (forward) {
        const XY new_end = (NextTangentPoint(1)-end()).Normalize()*length + end();
        if (IsStraight() && v==IsVisible())
            end() = new_end;
        else
            return {true, Edge(end(), new_end, v)};
    } else {
        const XY new_start = (PrevTangentPoint(0)-start()).Normalize()*length + start();
        if (IsStraight() && v==IsVisible())
            start() = new_start;
        else
            return {true, Edge(new_start, start(), v)};
    }
    return {false, {}};
}


/** Check if two segments cross
 * returns 0 if none, 1 if yes, 2 if they are rectilinear and overlap.
 * If they cross t will be the position of the crosspoint on A1->A2, s will be on B1->B2.
 * If they are rectilinear, t and s will be the pos of B1 and B2 on A1->A2 */
int Cross(const XY &A1, const XY &A2, const XY &B1, const XY &B2, double &t, double &s)
{
    if (B1.test_equal(B2)) {
        if (A1.test_equal(A2)) {
            s = t = 0;
            return B1.test_equal(A1) ? 1 : 0;
        }
        s = t = fabs(A2.x-A1.x) > fabs(A2.y-A1.y) ?
                    (B1.x-A1.x)/(A2.x-A1.x) :
                    (B1.y-A1.y)/(A2.y-A1.y);
        return 0<=t && t<=1 ? 1 : 0;
    }
    if (A1.test_equal(A2)) {
        t = s = fabs(B2.x-B1.x) > fabs(B2.y-B1.y) ?
                    (A1.x-B1.x)/(B2.x-B1.x) :
                    (A1.y-B1.y)/(B2.y-B1.y);
        return 0<=s && s<=1 ? 1 : 0;
    }
    const double denom = ((B2.y-B1.y)*(A2.x-A1.x) - (B2.x-B1.x)*(A2.y-A1.y));
    if (test_zero(denom)) {
        //they are parallel t and s will be positions of B1 and B2 on A1->A2 segment
        t = fabs(A2.x-A1.x) > fabs(A2.y-A1.y) ?
                (B1.x-A1.x)/(A2.x-A1.x) :
                (B1.y-A1.y)/(A2.y-A1.y);
        s = fabs(A2.x-A1.x) > fabs(A2.y-A1.y) ?
                (B2.x-A1.x)/(A2.x-A1.x) :
                (B2.y-A1.y)/(A2.y-A1.y);
        return ((t<0 && s<0) || (t>1 && s>1)) ? 0 : 2;
    }
    s = ((B1.x-A1.x)*(A2.y-A1.y) - (B1.y-A1.y)*(A2.x-A1.x)) / denom;
    if (s<0 || s>1) return 0;
    t = fabs(A2.x-A1.x) > fabs(A2.y-A1.y) ?
            (s*(B2.x-B1.x) + B1.x-A1.x)/(A2.x-A1.x) :
            (s*(B2.y-B1.y) + B1.y-A1.y)/(A2.y-A1.y);
    return 0<=t && t<=1 ? 1 : 0;
}

constexpr double PIXEL_ERROR = 0.0001;
constexpr bool between01_adjust2(double &pos, double len) noexcept {
    const double l = pos*len;
    if (l<-PIXEL_ERROR) return false;
    if (l<PIXEL_ERROR) { pos = 0; return true; }
    if (l<len-PIXEL_ERROR) return true;
    if (l<len+PIXEL_ERROR) { pos = 1; return true; }
    return false;
}


/** Check if two segments cross
 * returns 0 if none, 1 if yes, 2 if they are rectilinear and overlap.
 * 0 - no crossing
 * 1 - one crossing point (in r[0])
 * 2 - the two sections intersects from r[0] to r[1]
 * if any of the two sections are degenerate we return 1 only if it lies on the other section, else 0
 * in pos_in_ab we return the relative pos of the crosspoint(s) in AB, in pos_in_mn for MN
 * See http://softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm
 * crossing points that result in a pos value close to 1 are ignored
 * In case AB is the same (or lies on the same line) as MN, the two endpoint of the common
 * sections are returned (if they overlap or touch).
 * If they cross close to the end of one of the section we snap the positions
 * to exact 0 or 1.
 */
unsigned Edge::CrossingSegments(const Edge &o, XY r[], double pos_my[], double pos_other[]) const
{
    _ASSERT(IsStraight() && o.IsStraight());
    const double l_my = (  end()-  start()).length();
    const double l_other = (o.end()-o.start()).length();
    const double ll = l_my*l_other;
    if (ll>1e-8 && fabs((end()-start()).PerpProduct(o.end()-o.start()))/ll>1e-5) {
        //They are not parallel (and none of them are degenerate, but that does not matter now)
        double t = (end()-start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
        if (!between01_adjust2(t, l_other)) return 0;
        double s = (o.end()-o.start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
        if (!between01_adjust2(s, l_my)) return 0; //the intersection of the line is outside AB
        if (t==0 || t==1)
            r[0] = o.start() + (o.end()-o.start())*t;
        else
            r[0] =   start() + (  end()-  start())*s;
        pos_other[0] = t;
        pos_my[0] = s;
        return 1;
    }
    //either they are parallel or one or both sections are degenerate (= a point)
    if (o.start()==o.end()) {
        if (!test_zero((o.start()-start()).PerpProduct(end()-start())))
            return 0; //o.start() is not on AB's line and AB is not degenerate
        if (start()==end()) {
            if (start()==o.start()) {
                r[0] = o.start();
                pos_other[0] = 0;
                pos_my[0] = 0;
                return 1;
            }
            return 0; //both sections degenerate to a point, but do not overlap
        }
        double s;
        //select x or y depending on precision
        //o.start lies on AB, let us see if between start and end
        if (fabs(start().x-end().x) > fabs(start().y-end().y))
            s = (o.start().x-start().x)/(end().x-start().x);
        else
            s = (o.start().y-start().y)/(end().y-start().y);
        if (!between01_adjust2(s, l_my)) return 0;  //if outside [0..1]
        r[0] = o.start(); //o.start is on AB
        pos_other[0] = 0;
        pos_my[0] = s;
        return 1;
    }
    if (!test_zero((start()-o.start()).PerpProduct(o.end()-o.start())/ll))
        return 0; //start is not on MN's line
    //They are either parallel and on the same line or AB is degenerate and lies on MN's line
    double t[2];
    //select x or y depending on precision
    if (fabs(o.start().x-o.end().x) > fabs(o.start().y-o.end().y)) {
        t[0] = (start().x-o.start().x)/(o.end().x-o.start().x);
        t[1] = (end().x-o.start().x)/(o.end().x-o.start().x);
    } else {
        t[0] = (start().y-o.start().y)/(o.end().y-o.start().y);
        t[1] = (end().y-o.start().y)/(o.end().y-o.start().y);
    }
    if (t[0] > t[1]) std::swap(t[0], t[1]);
    if (t[1]*l_other<-PIXEL_ERROR || l_other+PIXEL_ERROR<t[0]*l_other)
        return 0; //AB lies outside MN
    if (t[0]*l_other<PIXEL_ERROR) t[0] = 0;                  //if close to 0 or even smaller snap it there
    else if (l_other-PIXEL_ERROR < t[0]*l_other) t[0] = 1;   //This is to cater for t[0->1] == 1.000001->2.1 or 0.999990->1.2 types.
    if (l_other-PIXEL_ERROR<t[1]*l_other) t[1] = 1;          //if close to 1 or even larger, snap it there
    else if (t[1]*l_other<PIXEL_ERROR) t[1] = 0;             //This is to cater for t[0->1] == -2.1-> -0.000001->2.1 or -1.2->1.000001 types.

    const bool dosecond = !contour::test_equal(t[0], t[1]);
    unsigned num = 0;
    for (int i = 0; i<(dosecond ? 2 : 1); i++) {
        r[num] = o.start() + (o.end()-o.start())*t[i];
        if (fabs(start().x-end().x) > fabs(start().y-end().y))
            pos_my[num] = (r[num].x-start().x)/(end().x-start().x);
        else
            pos_my[num] = (r[num].y-start().y)/(end().y-start().y);
        if (between01_adjust2(pos_my[num], l_my)) {
            if (pos_my[num]==0)
                r[num] = start();
            else if (pos_my[num]==1)
                r[num] = end();
            pos_other[num] = t[i];
            num++;
        }
    }
    if (num==2 && (r[0]==r[1] || pos_my[0]==pos_my[1] || pos_other[0]==pos_other[1])) num = 1;
    return num;
}


/** Check if two segments cross
 * returns 0 if none, 1 if yes, 2 if they are rectilinear and overlap.
 * 0 - no crossing
 * 1 - one crossing point (in r[0])
 * 2 - the two sections intersects from r[0] to r[1]
 * if any of the two sections are degenerate we return 1 only if it lies on the other section, else 0
 * in pos_in_ab we return the relative pos of the crosspoint(s) in AB, in pos_in_mn for MN
 * See http://softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm
 * crossing points that result in a pos value close to 1 are ignored
 * In case AB is the same (or lies on the same line) as MN, the two endpoint of the common
 * sections are returned (if they overlap or touch)
 * If they cross close to the end of one of the section we DO NOT snap the positions
 * to exact 0 or 1 (as opposed to CrossingSegments()).
 */
unsigned Edge::CrossingSegments_NoSnap(const Edge &o, XY r[], double pos_my[], double pos_other[]) const
{
    _ASSERT(IsStraight() && o.IsStraight());
    const double ll = (end()-start()).length()*(o.end()-o.start()).length();
    if (ll>1e-8 && fabs((end()-start()).PerpProduct(o.end()-o.start()))/ll>1e-5) {
        //They are not parallel (and none of them are degenerate, but that does not matter now)
        double t = (end()-start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
        if (t<0 || t>1) return 0;
        double s = (o.end()-o.start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
        if (s<0 || s>1) return 0; //the intersection of the line is outside AB
        r[0] = o.start() + (o.end()-o.start())*t;
        pos_other[0] = t;
        pos_my[0] = s;
        return 1;
    }
    //either they are parallel or one or both sections are degenerate (= a point)
    if (o.start().test_equal(o.end())) {
        if (!test_zero((o.start()-start()).PerpProduct(end()-start())))
            return 0; //o.start() is not on AB's line and AB is not degenerate
        if (start().test_equal(end())) {
            if (start()==o.start()) {
                r[0] = o.start();
                pos_other[0] = 0;
                pos_my[0] = 0;
                return 1;
            }
            return 0; //both sections degenerate to a point, but do not overlap
        }
        double s;
        //select x or y depending on precision
        //o.start lies on AB, let us see if between start and end
        if (fabs(start().x-end().x) > fabs(start().y-end().y))
            s = (o.start().x-start().x)/(end().x-start().x);
        else
            s = (o.start().y-start().y)/(end().y-start().y);
        if (s<0 || s>1) return 0;  //if outside [0..1]
        r[0] = o.start(); //o.start is on AB
        pos_other[0] = 0;
        pos_my[0] = s;
        return 1;
    }
    if (!test_zero((start()-o.start()).PerpProduct(o.end()-o.start())/ll))
        return 0; //start is not on MN's line
    //They are either parallel and on the same line or AB is degenerate and lies on MN's line
    double t[2];
    //select x or y depending on precision
    if (fabs(o.start().x-o.end().x) > fabs(o.start().y-o.end().y)) {
        t[0] = (start().x-o.start().x)/(o.end().x-o.start().x);
        t[1] = (end().x-o.start().x)/(o.end().x-o.start().x);
    } else {
        t[0] = (start().y-o.start().y)/(o.end().y-o.start().y);
        t[1] = (end().y-o.start().y)/(o.end().y-o.start().y);
    }
    if (t[0] > t[1]) std::swap(t[0], t[1]);
    if (t[0]>1 || t[1]<0)
        return 0; //AB lies outside MN
    unsigned num = 0;
    for (int i = 0; i<2; i++) {
        if (t[i]<0) t[i] = 0;
        if (t[i]>1) t[i] = 1;
        r[num] = o.start() + (o.end()-o.start())*t[i];
        pos_other[num] = t[i];
        if (start()==end())
            pos_my[num] = 0;
        else if (fabs(start().x-end().x) > fabs(start().y-end().y))
            pos_my[num] = (r[num].x-start().x)/(end().x-start().x);
        else
            pos_my[num] = (r[num].y-start().y)/(end().y-start().y);
        _ASSERT(pos_my[num]>=0 && pos_my[num]<=1);
        if (pos_my[num]>=0 && pos_my[num]<=1) num++;
    }
    return num;
}

/** Returns the crossing of a bezier with an infinite line.
 * We do not filter the result by position values falling outside [0,1]
 * Those that fall out in pos_my contains invalid values for pos_segment - should be
 * completely ignored by the caller.*/
unsigned Edge::CrossingLine(const XY &A, const XY &B, double pos_my[], double pos_segment[]) const
{
    _ASSERT(!IsStraight());
    _ASSERT(!B.test_equal(A));
    using real = double;
    //rotate and shift the bezier so that the line we cross becomes the X axis
    const double l = A.Distance(B);
    const double sin_r = (A.y-B.y)/l;
    const double cos_r = (B.x-A.x)/l;
    Edge e = CreateShifted(-A);
    e.Rotate(cos_r, sin_r);
    //Now find the parameter values of crossing x, (where y=0)
    //A==start, B==c1, C==c2, D==end
    //Curve is: (-A+3B-3C+D)t^3 + (3A-6B+3C)t^2 + (-3A+3B)t + A
    const real coeff[4] = {e.start().y,
                           3*(e.c1().y-e.start().y),
                           3*(e.start().y-2*e.c1().y+e.c2().y),
                           -e.start().y+3*(e.c1().y-e.c2().y)+e.end().y};
    real cc[4] = {coeff[0], coeff[1], coeff[2], coeff[3]};
    if (fabs(cc[3])<1e-5)
        cc[3] = 0;
    real loc_pos_my[3];
    const unsigned num = solve_degree3(cc, loc_pos_my);
    unsigned ret = 0;
    //x coordinates of the crosspoints divided by segment length serve as positions of the segment
    for (unsigned u = 0; u<num; u++)
        if (between01_adjust(loc_pos_my[u])) {
            //refine position found
            while(1) {
                real y = ((coeff[3]*loc_pos_my[u] + coeff[2])*loc_pos_my[u] + coeff[1])*loc_pos_my[u] + coeff[0];
                if (std::isnan(y) || fabs(y)<CP_PRECISION) break; //done. y is in pixel space (just rotated) so this means a precision of CP_PRECISION pixels
                //approximate with the derivative
                //Derivatie is: 3*(-A+3B-3C+D)t^2 + (6A-12B+6C)t + (-3A+3B)
                const real derive[3] = {coeff[1], 2*coeff[2], 3*coeff[3]};
                const real dy_per_dpos = (derive[2]*loc_pos_my[u] +derive[1])*loc_pos_my[u] + derive[0];
                //protect against horizontal tangent
                if (std::isnan(dy_per_dpos) || fabs(dy_per_dpos)<1e-10 || std::isnan(y/dy_per_dpos))
                    break;
                loc_pos_my[u] -= y/dy_per_dpos;
            }
            pos_my[ret] = loc_pos_my[u];
            const XY xy = e.Split(pos_my[ret]);
            _ASSERT(test_zero(fabs(xy.y)));
            pos_segment[ret] = xy.x/l;
            ret++;
            //const XY xy2 = Split(pos_my[u]);   The above way seems exact enough
            //const real pos = get_pos_on_segment(A, B, xy2))
        }
    return ret;
}


/** Determines which side of a line four points are.
 * Returns +1 or -1 if all points are on one or the other side.
 * Returns 0 if some points are on one and others on the other side;
 * or if any point is exactly on the line.*/
int Edge::WhichSide(const XY &A, const XY &B) const noexcept
{
    //http://www.geometrictools.com/LibMathematics/Intersection/Intersection.html
    // Vertices are projected to the form A+t*B.  Return value is +1 if all
    // t > 0, -1 if all t < 0, 0 otherwise, in which case the line splits the
    // curve's hull or straight segment we are.

    const unsigned num = IsStraight() ? 2 : 4;
    const XY Perp = (A-B).Rotate90CW();
    unsigned positive = 0, negative = 0;
    for (unsigned i = 0; i < num; ++i) {
        const double t = Perp.DotProduct((&start())[i] - A);
        if (t > 0) ++positive;
        else if (t < 0) ++negative;
        if (positive > 0 && negative > 0) return 0;
    }
    return positive==num ? +1 : negative==num ? -1 : 0;
}


/** A helper to find edge crosspoints, to be called recursively.
 * We subdivide each edge and try to find crosspoints that way.
 * So 'this' and 'A' may be only part of the original edges we search
 * crosspoints of.
 * @param [in] A (part of) the other edge.
 * @param [out] r The resulting crosspoints.
 * @param [out] pos_my The positions of the resulting crosspoints on the
 *                     original edge of 'this'.
 * @param [out] pos_other The positions of the resulting crosspoints on the
 *                     original edge of 'A'.
 * @param [in] pos_my_mul The multiplier to apply to a found position on
 *             'this' to convert it to a position on the original of 'this'.
 * @param [in] pos_other_mul The multiplier to apply to a found position on
 *             'A' to convert it to a position on the original of 'A'.
 * @param [in] pos_my_offset The offset to add to a found (and multiplied)
 *             position on 'this' to convert it to a position on the original of 'this'.
 * @param [in] pos_other_offset The offset to add to a found (and multiplied)
 *             position on 'A' to convert it to a position on the original of 'A'.
 * @param [in] alloc_size The number of crosspoints for which 'r', 'pos_my' and
 *             'pos_other' has space for.
 * @returns the number of crosspoints found.*/
unsigned Edge::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
{
    _ASSERT(alloc_size);
    //If both straight, we calculate and leave
    if (IsStraight()) {
        if (!A.IsStraight())
            return A.CrossingBezier(*this, r, pos_other, pos_my, pos_other_mul, pos_my_mul,
                                    pos_other_offset, pos_my_offset, alloc_size);
        switch (CrossingSegments_NoSnap(A, r, pos_my, pos_other)) {
        default:
            _ASSERT(0); FALLTHROUGH;
        case 0:
            return 0;
        case 2:
            //parallel segments: take midpoint
            //(this is clearly a parallel segment due to numeric precision issues)
            pos_my[0] = (pos_my[0]+pos_my[1])/2*pos_my_mul+pos_my_offset;
            pos_other[0] = (pos_other[0]+pos_other[1])/2*pos_other_mul+pos_other_offset;
            r[0] = (r[0]+r[1])/2;
            return 1;
        case 1:
            pos_my[0] = pos_my[0]*pos_my_mul+pos_my_offset;
            pos_other[0] = pos_other[0]*pos_other_mul+pos_other_offset;
            return 1;
        }
    }
    if (!HullOverlap(A, false)) return 0;
    Edge M1, M2, A1, A2;
    Split(M1, M2);
    pos_my_mul /= 2;
    unsigned num;
    if (A.IsStraight()) {
        num = M1.CrossingBezier(A, r, pos_my, pos_other, pos_my_mul, pos_other_mul,
            pos_my_offset, pos_other_offset, alloc_size);
        if (num >= alloc_size) return num;
        num += M2.CrossingBezier(A, r+num, pos_my+num, pos_other+num, pos_my_mul, pos_other_mul,
            pos_my_offset+pos_my_mul, pos_other_offset, alloc_size-num);
        return num;
    }
    A.Split(A1, A2);
    pos_other_mul /= 2;
    num = M1.CrossingBezier(A1, r, pos_my, pos_other, pos_my_mul, pos_other_mul,
        pos_my_offset, pos_other_offset, alloc_size);
    if (num >= alloc_size) return num;
    num += M2.CrossingBezier(A1, r+num, pos_my+num, pos_other+num, pos_my_mul, pos_other_mul,
        pos_my_offset+pos_my_mul, pos_other_offset, alloc_size-num);
    if (num >= alloc_size) return num;
    num += M1.CrossingBezier(A2, r+num, pos_my+num, pos_other+num, pos_my_mul, pos_other_mul,
        pos_my_offset, pos_other_offset+pos_other_mul, alloc_size-num);
    if (num >= alloc_size) return num;
    num += M2.CrossingBezier(A2, r+num, pos_my+num, pos_other+num, pos_my_mul, pos_other_mul,
        pos_my_offset+pos_my_mul, pos_other_offset+pos_other_mul, alloc_size-num);
    return num;
}

/** Checks if this edge and `next` can be combined into a single edge.
  * If so, also update "this" to be the combined edge & returns true.
  * Note that for straight edges we may end up in the situation when the
  * combined edge becomes a degenerate dot.
  * In general we assume none of the edges are degenerate (usually we check beforehand).
  * @param [in] next The edge after us. We don't do anything if if does not connect to our 'end'.
  * @param [in] tolerance The max distance deviation we can tolerate from the original this+next combo.
  * @param [in] pos We returns the position of "next.start" in the new edge. (If the new edge became a dot, we return zero.)
  * @returns true if a combination has been made and 'this' is updated. 'next' can be removed from the path. */
bool Edge::CheckAndCombine(const Edge &next, double tolerance, double *pos)
{
    if (IsVisible()!=next.IsVisible()) return false;
    if (end()!=next.start()) return false; //TODO: or well, next could also start *inside* this and run along (and perhaps beyond) it
    if (IsStraight() && next.IsStraight()) {
        //http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
        //const double m = fabs((start().x-next.end().x)*(end().x-start().x)-(end().y-start().y)*(start().y-next.end().y))/start().DistanceSqr(end());
        //const double dist_sqr = next.end().Distance(start() + (end()-start())*m); //distance^2 of next.end() from the 'start()->end()' infinite line.

        //const double m = ((start().x-end().x)*(next.end().x-start().x)-(next.end().y-start().y)*(start().y-end().y))/start().DistanceSqr(next.end());
        //m is the position of the projection of 'end' to the start->next.end line, may be outside [0..1]
        //const double dist_sqr = end().Distance(start() + (next.end()-start())*m); //distance^2 of end from the 'start->next.end' infinite line.

        //http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
        //P1=start, P2=next.end, P0=end=next.start
        //const double final_len_sqr = start().DistanceSqr(next.end());
        //if (final_len_sqr>tolerance_sqr) {
        //    const double dist_sqr = fabs((next.end().x - start().x)*(start().y-end().y)-(start().x-end().x)*(next.end().y-start().y))/final_len_sqr;
        //    if (dist_sqr>tolerance_sqr) return false;
        //    if (pos)
        //        *pos = (fabs(start().x-next.end().x)<fabs(start().y-next.end().y)) ? (next.start().y-start().y)/(next.end().y-start().y) : (next.start().x-start().x)/(next.end().x-start().x);
        //} else if (pos) *pos = 0;
        //end() = next.end();

        const bool ret = LinePointDistance(start(), end(), next.end(), nullptr, pos)<=tolerance;
        if (ret) end() = next.end();
        return ret;
    }
    if (IsStraight() && !next.IsStraight()) {
        return false; //Does not improve things by much....
        //Merge a line to a bezier from the start of the bezier
        //This primarily targets very short lines

        //Find the point on the bezier (probably out of the [0..1] range] closest to the new start
        const auto d = next.UnboundedBezierDistance(start());
        //_ASSERT(d.second<=0);
        if (d.second>0 || d.first>tolerance)
            return false;
        const double p = -d.second/(1-d.second);
        //const Edge repl(start(), next.end(),
        //                p<0.1 && start().DistanceSqr(end())<tolerance_sqr ?
        //                     next.c1() + (start()-next.start()) :  //a shifted version of next.c1
        //                ((next.c2() + (next.c1()-next.c2())/(1-p)) - (next.end() + (next.c2()-next.end())/(1-p)))/(1-p) + next.end() + (next.c2()-next.end())/(1-p), //this is how to calculate the original c1
        //                next.end() + (next.c2()-next.end())/(1-p));
        Edge repl = next.ExtendBezier(d.second);
        repl.start() = start();
        if (repl.Split(p).DistanceSqr(end())>tolerance*tolerance)
            return false;
        //OK, we are similar enough
        c1() = repl.c1();
        c2() = repl.c2();
        end() = repl.end();
        meta().straight = false;
        meta().has_cp = true;
        if (pos) *pos = p;
        return true;
    }
    if (!IsStraight() && next.IsStraight()) {
        return false; //Does not improve things by much....
        //Merge a line to a bezier from the start of the bezier
        //This primarily targets very short lines

        //Find the point on the bezier (probably out of the [0..1] range] closest to the new start
        const auto d = UnboundedBezierDistance(next.end());
        //_ASSERT(d.second>=1);
        if (d.second<1 || d.first>tolerance)
            return false;
        const double p = 1/d.second;
        Edge repl = ExtendBezier(d.second);
        repl.end() = next.end();
        if (repl.Split(p).DistanceSqr(end())>tolerance*tolerance)
            return false;
        //OK, we are similar enough
        c1() = repl.c1();
        c2() = repl.c2();
        end() = repl.end();
        meta().straight = false;
        meta().has_cp = true;
        if (pos) *pos = p;
        return true;
    }
    //Beziers
    //this->c2 => this->end shall be collinear with next->start => next->c1. The ratio of their length also gives
    //the parameter of this->end==next->start in the combined curve.
    //https://math.stackexchange.com/questions/877725/retrieve-the-initial-cubic-b%C3%A9zier-curve-subdivided-in-two-b%C3%A9zier-curves
    if (next.c1().test_equal(next.start()))
        return false; //Just cubic bezier, we don't work this case out yet
    double p;
    if (fabs(next.c1().x-c2().x) < fabs(next.c1().y-c2().y)) {
        p = (end().y-c2().y)/(next.c1().y-c2().y);
        if (!test_smaller(0, p) || !test_smaller(p, 1) ||
            sqr((end().x-c2().x) - p*(next.c1().x-c2().x))>tolerance*tolerance)
            return false;
    } else {
        p = (end().x-c2().x)/(next.c1().x-c2().x);
        if (!test_smaller(0, p) || !test_smaller(p, 1) ||
            sqr((end().y-c2().y) - p*(next.c1().y-c2().y))>tolerance*tolerance)
            return false;
    }
    //OK, we see that they are collinear (and p is the parameter)
    //Check that a combined curve would have this param at this point
    //Note that if p is small or large, we may make big mistakes in calculating the control points.
    //Also note that the combined edge may end up being straight
    const Edge repl(start(), next.end(),
                    p<0.1 && start().DistanceSqr(end())<=tolerance*tolerance ?
                         next.c1() + (start()-next.start()) :  //a shifted version of next.c1
                         start() + (c1()-start())/p,           //the way we should compute if p is not too small
                    p>0.9 && next.start().DistanceSqr(next.end())<=tolerance *tolerance ?
                         c2() + (next.end()-end()) :           //a shifted version of c2
                         next.end() + (next.c2()-next.end())/(1-p),
                    true, false, /*keep_cp=*/true);
    const XY mid_at_p = repl.HasControlPoints() ? repl.Split(p) : Mid(repl.start(), repl.end(), p);
    if (mid_at_p.DistanceSqr(end())>tolerance*tolerance)
        return false;
    //OK, we are similar enough.
    if (repl.HasControlPoints()) {
        c1() = repl.c1();
        c2() = repl.c2();
    } else
        meta().has_cp = false;
    meta().straight = repl.IsStraight();
    end() = repl.end();
    if (pos) *pos = p;
    return true;
}

/** Tests if two edges overlap by checking the distance of their endpoints from one another.
 * @returns how many crosspoints we found (0 if none, 1 if only their endpoint matches, 2 at overlap.
 * We guarantee not to change r, pos_my, pos_other and *overlap if we return 0.*/
template <unsigned DIFF_MAGNITUDE>
int Edge::TestOverlap(const Edge &A, XY r[Edge::MAX_CP],
                      double pos_my[Edge::MAX_CP], double pos_other[Edge::MAX_CP]) const
{
    static_assert(DIFF_MAGNITUDE<8);
    constexpr double DIFF = DIFF_MAGNITUDE==0 ? 1 : DIFF_MAGNITUDE==1 ? 0.1 : DIFF_MAGNITUDE==2 ? 0.01 : DIFF_MAGNITUDE==3 ? 0.001 :
                            DIFF_MAGNITUDE==4 ? 0.0001 : DIFF_MAGNITUDE == 5 ? 0.00001 : DIFF_MAGNITUDE==6 ? 0.000001 : 0.00000001;
    //We start by seeing whether any endpoint falls close to the other
    struct Data {
        XY xy;
        double me, you;
        double dist;
    } D[4];
    D[0].dist =   Distance(A.start(), D[0].xy, D[0].me ); D[0].you = 0; D[0].xy = A.start();
    D[1].dist =   Distance(A.end(),   D[1].xy, D[1].me ); D[1].you = 1; D[1].xy = A.end();
    D[2].dist = A.Distance(  start(), D[2].xy, D[2].you); D[2].me  = 0; D[2].xy =   start();
    D[3].dist = A.Distance(  end(),   D[3].xy, D[3].you); D[3].me  = 1; D[3].xy =   end();
    const Data * const D_end = std::remove_if(D, D+4, [DIFF](const Data& d) {return d.dist>=DIFF; });
    int num = 0;
    for (const Data *d = D; d<D_end; d++)
        if (std::none_of(r, r+num, [d](const XY& xy) { return xy.test_equal(d->xy); })
            && std::none_of(pos_my, pos_my+num, [d](double p) { return p == d->me; })
            && std::none_of(pos_other, pos_other+num, [d](double p) { return p == d->you; })) {
            pos_my[num] = d->me;
            pos_other[num] = d->you;
            r[num] = d->xy;
            if (++num>=2) break;
        }
    //If one of the edges is a dot close to one of the ends of the other
    //we will get D.size()==3, but num==1. It is still considered an overlap.
    if (2<=num || D+3<=D_end) return -num;
    return num;
}


/** Calculates the crosspoints with another edge & checks if the two edges
 * (partially) overlap or not.
 *
 * 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 a negative value
 * and the endpoint(s) of the common section. (The common section may have only one
 * endpoint if the curves cross themselves and overlap in a closed section. In this
 * case we return -1 and only one crosspoint.)
 * 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].
 * The arrays shall be at least Edge::MAX_CP big.
 * 
 * IMPORTANT: For speedup reasons, you should call HullOverlap(A, is_next) before
 * this, to exclude the case when the two edges are not even close to each other.
 * It is not called from here for inlining purposes.
 *
 * @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], or its negative, when the two edges
 *          overlap and run together
 */
int Edge::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
{
    constexpr unsigned OVERLAP_DIFF_MAGNITUDE_BEZIER = 2;
    if (IsDot()) {
        if (A.IsDot()) {
            if (is_next) return 0;
            const XY m1 = Mid(start(), end()), m2 = Mid(A.start(), A.end());
            if (!m1.test_equal(m2)) return 0;
            pos_my[0] = pos_other[0] = 0.5;
            r[0] = Mid(m1, m2);
            return 1;
        }
        r[0] = Mid(start(), end());
        if (XY dummy; !test_zero(A.Distance(r[0], dummy, pos_other[0]))) return 0;
        if (is_next && test_zero(pos_other[0])) return 0;
        between01_adjust(pos_other[0]);
        pos_my[0] = 0.5;
        return 1;
    } else if (A.IsDot()) {
        r[0] = Mid(A.start(), A.end());
        if (XY dummy; !test_zero(Distance(r[0], dummy, pos_my[0]))) return 0;
        if (is_next && contour::test_equal(pos_my[0], 1)) return 0;
        between01_adjust(pos_my[0]);
        pos_other[0] = 0.5;
        return 1;
    }
    const unsigned LOC_MAX_CP = MAX_CP*2;
    XY loc_r[LOC_MAX_CP+2];
    double loc_pos_my[LOC_MAX_CP+2], loc_pos_other[LOC_MAX_CP+2];
    int ret = 0;
    size_t num;
    if (IsStraight()) {
        if (A.IsStraight()) {
            ret = CrossingSegments(A, r, pos_my, pos_other);
            if (ret==1 && is_next && pos_my[0]==1 && pos_other[0]==0)
                return 0;
            if (ret==2) ret = -2; //the two edges overlap. On all other code paths ret is positive
            goto snap_ends;
        } else { //A is curvy
            num = A.CrossingLine(start(), end(), loc_pos_other, loc_pos_my);
            for (unsigned u = 0; u<num; u++)
                if (between01_adjust(loc_pos_other[u]) && between01_adjust(loc_pos_my[u])) //test pos_other first, since if that is outside [0..1] pos_my is uninitialized
                    if (!is_next || loc_pos_my[u]!=1 || loc_pos_other[u]!=0) {
                        pos_my[ret] = loc_pos_my[u];
                        pos_other[ret] = loc_pos_other[u];
                        if (pos_my[ret]==0) r[ret] = start();
                        else if (pos_my[ret]==1) r[ret] = end();
                        else if (pos_other[ret]==0) r[ret] = A.start();
                        else if (pos_other[ret]==1) r[ret] = A.end();
                        else r[ret] = A.Split(loc_pos_other[u]);
                        ret++;
                    }
            if (ret==0) {
                //test if they overlap
                //ret = TestOverlap<OVERLAP_DIFF_MAGNITUDE_BEZIER_LINE>(A, r, pos_my, pos_other, overlap);
                if (ret==0) return 0;
                //else snap ends;
            }
            goto snap_ends;
        }
    }
    if (A.IsStraight()) {
        //'this' is curvy
        num = CrossingLine(A.start(), A.end(), loc_pos_my, loc_pos_other);
        for (unsigned u = 0; u<num; u++)
            if (between01_adjust(loc_pos_my[u]) && between01_adjust(loc_pos_other[u])) //test pos_my first, since if that is outside [0..1] pos_other is uninitialized
                if (!is_next || loc_pos_my[u]!=1 || loc_pos_other[u]!=0) {
                    pos_my[ret] = loc_pos_my[u];
                    pos_other[ret] = loc_pos_other[u];
                    if (pos_my[ret]==0) r[ret] = start();
                    else if (pos_my[ret]==1) r[ret] = end();
                    else if (pos_other[ret]==0) r[ret] = A.start();
                    else if (pos_other[ret]==1) r[ret] = A.end();
                    else r[ret] = Split(loc_pos_my[u]);
                    ret++;
                }
        if (ret==0) {
            //test if they overlap
            //ret = TestOverlap<OVERLAP_DIFF_MAGNITUDE_BEZIER_LINE>(A, r, pos_my, pos_other, overlap);
            if (ret==0) return 0;
            //else snap ends;
        }
        goto snap_ends;
    }
    //Now both are curvy
    num = CrossingBezier(A, loc_r, loc_pos_my, loc_pos_other, 1, 1, 0, 0, LOC_MAX_CP+1);
    //If there were hits we check if one bezier is really a part of the other
    //This is a common scenario
    if (num>2) {
        int n = TestOverlap<OVERLAP_DIFF_MAGNITUDE_BEZIER>(A, r, pos_my, pos_other);
        if (n) return n;
        //else continue with what we have (pos_my, pos_other unchanged)
    }
    //Snap the crosspoints to the start of the curves
    for (unsigned i = 0; i<num; i++) {
        if (test_zero(loc_pos_my[i])) {
            loc_pos_my[i] = 0;
            loc_r[i] = start();
        } else if (contour::test_equal(1, loc_pos_my[i])) {
            loc_pos_my[i] = 1;
            loc_r[i] = end();
        }
        if (contour::test_equal(1, loc_pos_other[i])) {
            loc_pos_other[i] = 1;
            loc_r[i] = A.end();
        } else if (test_zero(loc_pos_other[i])) {
            loc_pos_other[i] = 0;
            loc_r[i] = A.start();
        }
    }

    //In case of multiple hits, we need to remove erroneously occurring ones
    //these are the ones, where both pos_my and pos_other are close
    //We do it pairwise
    for (unsigned i = 0; i<num; i++) {
        //We remove crosspoints where our end meets A's start (if is_next is true)
        if (is_next && loc_pos_my[i]==1 && loc_pos_other[i]==0)
            continue;
        //if i==num-1 the below coop will not roll
        unsigned j = i+1;
        for (; j<num; j++)
            if ((contour::test_equal(loc_pos_my[i], loc_pos_my[j]) && contour::test_equal(loc_pos_other[i], loc_pos_other[j])))
                break;
        if (j==num) {
            //index 'i' did not match any later element - we keep it
            if (ret >= int(MAX_CP)) {
                //we have too many crosspoints - maybe the two beziers run parallel at a segment?
                //Which of the endpoints are inside the other segment?
                struct data {
                    double d;
                    XY r;
                    double pos_my;
                    double pos_other;
                    bool operator <(const struct data &o) const { return d<o.d; }
                } d[4];
                d[0].d =   Distance(A.start(), d[0].r, d[0].pos_my);     d[0].pos_other = 0; d[0].r = A.start();
                d[1].d =   Distance(A.end(),   d[1].r, d[1].pos_my);     d[1].pos_other = 1; d[1].r = A.end();
                d[2].d = A.Distance(  start(), d[2].r, d[2].pos_other);  d[2].pos_my = 0;    d[2].r = start();
                d[3].d = A.Distance(  end()  , d[3].r, d[3].pos_other);  d[3].pos_my = 1;    d[3].r = end();
                std::sort(d, d+4);
                num = std::unique(d, d+4,   [&](const data &a, const data &b) {return contour::test_equal(a.pos_my, b.pos_my); }) - d;
                num = std::unique(d, d+num, [&](const data &a, const data &b) {return contour::test_equal(a.pos_other, b.pos_other); }) - d;
                _ASSERT(num>=2);
                _ASSERT(d[1].d<0.1);
                for (unsigned uu = 0; uu<2; uu++) {
                    r[uu] = d[uu].r;
                    pos_my[uu] = d[uu].pos_my;
                    pos_other[uu] = d[uu].pos_other;
                }
                return 2;
            }
            r[ret] = loc_r[i];
            pos_my[ret] = loc_pos_my[i];
            pos_other[ret] = loc_pos_other[i];
            ++ret;
        }
    }
snap_ends:
    //if one is a horizontal/vertical straight line, we need to snap the cp to that line
    //Here 'ret' can be negative again
    if (IsStraight()) {
        if (start().x==end().x)
            std::for_each(r, r+abs(ret), [&](XY &a) {a.x = start().x; });
        else if (start().y==end().y)
            std::for_each(r, r+abs(ret), [&](XY &a) {a.y = start().y; });
    } else if (A.IsStraight()) {
        if (A.start().x==A.end().x)
            std::for_each(r, r+abs(ret), [&](XY &a) {a.x = A.start().x; });
        else if (A.start().y==A.end().y)
            std::for_each(r, r+abs(ret), [&](XY &a) {a.y = A.start().y; });
    }
    //For beziers make the position more precise
    if (!IsStraight())
        for (int i = 0; i<abs(ret); i++) {
            double d; XY xy;
            const double dist = Distance(r[i], xy, d);
            (void)dist;
            _ASSERT(dist<1); //just to see how accurate this is.
            pos_my[i] = d;
        }
    if (!A.IsStraight())
        for (int i = 0; i<abs(ret); i++) {
            double d; XY xy;
            const double dist = A.Distance(r[i], xy, d);
            (void)dist;
            _ASSERT(dist<1); //just to see how accurate this is.
            pos_other[i] = d;
        }
    return ret;
}

/** Returns the crossing of an edge with an infinite line.
 * We do not filter the result by position values falling outside [0,1] for pos_segment.
 * In contrast all values of pos_my will be inside [0,1].
 * If the edge is straight and it coincides with the infinite line, we return two crosspoints,
 * with pos_my =0 and =1.*/
unsigned Edge::Crossing(const XY & A, const XY & B, double pos_my[], double pos_segment[]) const
{
    if (!IsStraight()) return CrossingLine(A, B, pos_my, pos_segment);
    XY cp;
    if (ELineCrossingType::PARALLEL == crossing_line_line(start(), end(), A, B, cp)) {
        //See if they are coinciding
        if (!test_zero((start()-A).PerpProduct(B-A)))
            return 0; //start is not on AB's line
        //Yes, they are coinciding. Return the two endpoints of us.
        pos_segment[0] = get_pos_on_segment(A, B, start());
        pos_segment[1] = get_pos_on_segment(A, B, end());
        pos_my[0] = 0;
        pos_my[1] = 1;
        return 2;
    }
    pos_my[0] = get_pos_on_segment(start(), end(), cp);
    if (!between01_adjust(pos_my[0])) return 0;
    pos_segment[0] = get_pos_on_segment(A, B, cp);
    return 1;
}

/** Finds where an edge crosses itself.
 * @param [out] r The crosspoints
 * @param [out] pos1 The positions associated with the crosspoints.
 * @param [out] pos2 The other positions associated with the crosspoints.
 * @returns the number fof crosspoints found.*/
unsigned Edge::SelfCrossing(XY r[Edge::MAX_CP], double pos1[Edge::MAX_CP], double pos2[Edge::MAX_CP]) const
{
    if (IsStraight()) return 0;
    //Split the edge at local X extremes (where the tangent is vertical)
    //(we ignore if this happens at an endpoint
    //A==start, B==c1, C==c2, D==end
    //Curve is: (-A+3B-3C+D)t^3 + (3A-6B+3C)t^2 + (-3A+3B)t + A
    //derivative is: (-3A+9B-9C+3D)t^2 + (6A-12B+6C)t + (-3A+3B)
    //substitutes are        [2]    t^2 +     [1]    t +    [0]
    double coeff[3] = {3*(c1().y-start().y),
                       6*(start().y-2*c1().y+c2().y),
                       3*(-start().y+3*(c1().y-c2().y)+end().y)};
    double roots[2];
    unsigned num = solve_degree2(coeff, roots);
    if (num==0) return 0; //if tangent is never vertical we cannot cross ourselves
    if (num==2 && !between01(roots[1]))
        num--;
    if (!between01(roots[0])) {
        num--;
        if (num==0) return 0;
        roots[0] = roots[1];
    }
    if (roots[0]==0 || roots[0]==1) {
        if (num==1) return 0;
        if (roots[1]==0 || roots[1]==1) return 0;
        roots[0] = roots[1];
    }
    Edge A, B, C;
    if (num==1) {
        /* If we have one such point it can happen like this
         *            __    /
         *          --  -- /      The asterisk is the point where the
         *         /      X       tangent is vertical. X is the crosspoint
         *        *      / \
         *         \____/
        */
        Split(roots[0], A, B);
        num = A.CrossingBezier(B, r, pos1, pos2, roots[0], 1-roots[0], 0, roots[0], MAX_CP);
    } else {
        /* Two such points may happen in two ways
        *                             \
        *                              |     In the first case we need to test only
        *          _-_                 *     the first and third segment.
        *         /   \                |     In the second case there is no CP,
        *        |     |              /      but testing the third and first segment
        *        *     *             /       will be quick (no overlap) and it is
        *        |     |            |        suitable to decide that we have the
        *         \   /             *        second case.
        *           X               |
        *          / \               \
        */
        _ASSERT(num==2);
        Split(roots[0], A, C); //C is a dummy
        Split(roots[1], C, B);
        num = A.CrossingBezier(B, r, pos1, pos2, roots[0], 1-roots[1], 0, roots[1], MAX_CP);
    }
    //remove degenerate values
    for (unsigned u = 0; u<num; u++)
        if (fabs(pos1[u]-pos2[u])<1e-10) {
            for (unsigned ndx = u; ndx+1<num; ndx++) {
                pos1[ndx] = pos1[ndx+1];
                pos2[ndx] = pos2[ndx+1];
                r[ndx] = r[ndx+1];
            }
            num--;
        }
    //remove duplicates
    for (unsigned u = 0; u+1<num; u++)
        for (unsigned uu = u+1; uu<num; uu++)
            if (fabs(pos1[u]-pos1[uu])<1e-10 || fabs(pos2[u]-pos2[uu])<1e-10) {
                for (unsigned ndx = uu; ndx+1<num; ndx++) {
                    pos1[ndx] = pos1[ndx+1];
                    pos2[ndx] = pos2[ndx+1];
                    r[ndx] = r[ndx+1];
                }
                num--;
            }
    if (num<=1) return num;
    //We can have a lot of crosspoints here, due to numerical problems,
    //e.g., when a
    //pick the widest -if possible
    ptrdiff_t min = std::min_element(pos1, pos1+num) - pos1;
    //_ASSERT(min == std::max_element(pos2, pos2+num) - pos2);
    if (min>0) {
        pos1[0] = pos1[min];
        pos2[0] = pos2[min];
        r[0] = r[min];
    }
    return 1;
}



/** Calculates where an edge crosses a vertical line.
*
* This is done for the purposes of SimpleContour::IsWithin.
* Certain rules apply to the case when the vertical line crosses one of the ends of the edge.
* 1. a leftward edge includes its starting endpoint, and excludes its final endpoint;
* 2. a rightward edge excludes its starting endpoint, and includes its final endpoint;
* In short: we include the endpoint with the larger x coordinate only.
* There are some additional rules to the above. If a bezier curve touches the line at 'x'
* we return the touchpoint as a crosspoint (marking it with 0 in 'forward').
* Except if the touchpoint is at one end of the bezier - in this case we count it as
* a crossing point and return either +1 or -1 in 'forward'.
* @param [in] x The x coordinate of the vertical line.
* @param [out] y The y coordinates of the crosspoints.
* @param [out] pos The pos values for the respective crosspoints [0..1]
* @param [out] forward +1 for a crosspoint if the line at x is crossed from left to right
*              (so inside of contour is below y), -1 if the line crosses from right to left,
*              and 0 if the (bezier curve) touches the line.
* @returns The number of crosspoints, or -1 if edge is a vertical line exactly on x.
*/
int Edge::CrossingVertical(double x, double y[3], double pos[3], int forward[3]) const noexcept
{
    if (IsStraight()) {
        if ((start().x >= x && end().x < x) ||      //we cross leftward
            (start().x < x && end().x >= x)) {      //we cross rightward
            //we cross p's line y
            pos[0] = (x - start().x)/(end().x - start().x);
            y[0] = (end().y - start().y)*pos[0]+ start().y;
            forward[0] = start().x < x ? +1 : -1;
            return 1;
        }
        if (start().x == x && end().x == x)
            return -1; //vertical line
        return 0;
    }

    //fast path: if the bezier's hull does not include X, we have no crossing.
    if (!GetHullXRange().IsWithinBool(x)) return 0;

    unsigned ret = atX(x, pos, CP_PRECISION);
    if (ret==0)
        return 0;
    std::sort(pos, pos+ret);
    for (unsigned i = 0; i<ret; i++) {
        const double fw_x = NextTangentPoint(pos[i]).x;
        if (contour::test_equal(x, fw_x))
            forward[i] = 0;
        else
            forward[i] = fsign(fw_x-x);
        y[i] = Split(pos[i]).y;
        _ASSERT(fabs(Split(pos[i]).x -x)<1e-3);
    }

    //Below: Sanitize touching cps.
    //This is needed, since we say that a cp is toucing on potentially
    //numerically unstable info
    //We ignore touching cps at the endpoints of the arc for now, those will
    //be converted to crossing cps later.

    switch (ret) {
    case 1:
        //1. A single touchpoint at the middle is valid only if start
        //and endpoints are on the same side of the vertical line
        if (forward[0]==0 && pos[0]!=1 && pos[0]!=0)
            if ((start().x < x) != (end().x < x))
                forward[0] = start().x < x ? +1 : -1;
        break;
    case 3:
        //2. A touchpoint (as middle cp) at the inside of the curve is valid only if
        //and the halfway points till the prev cp and next cp are on the same side
        //of the vertical line
        if (forward[1]==0)
            if ((Split((pos[0]+pos[1])/2).x < x) != (Split((pos[1]+pos[2])/2).x < x))
                forward[1] = Split((pos[0]+pos[1])/2).x < x ? +1 : -1;
        FALLTHROUGH;
    case 2:
        _ASSERT(forward[0] || forward[1]);
        //3. A first touchpoint at the inside of the curve is valid only if start point
        //and the halfway point till the next cp are on the same side of the vertical line
        if (forward[0]==0 && pos[0]!=0)
            if ((start().x < x) != (Split((pos[0]+pos[1])/2).x < x))
                forward[0] = start().x < x ? +1 : -1;
        //3. A last touchpoint at the inside of the curve is valid only if endpoint
        //and the halfway point till the prev cp are on the same side of the vertical line
        if (forward[ret-1]==0 && pos[ret-1]!=1)
            if ((end().x < x) != (Split((pos[ret-2]+pos[ret-1])/2).x < x))
                forward[ret-1] = end().x > x ? +1 : -1;
    }

    //Now convert touching cps if they occur at the endpoints
    //Either ignore them if they are at the wrong endpoint or convert them to crossing
    if (pos[ret-1]==1)
        switch(forward[ret-1]) {
        case 0:  //If a touchpoint is at he end, we convert it to a crossing one
            //determine if we arrive to the endpoint left or right
            if (ret>=2)
                forward[ret-1] = -forward[ret-2];
            else
                forward[ret-1] = start().x < x ? +1 : -1;
            if (forward[ret-1]==1)
                break;
            FALLTHROUGH; //if we set forward[ret-1] to -1
        case -1:
            //if the endpoint is right-to left, we ignore it
            if (--ret==0)
                return 0;
    }
    if (pos[0]==0)
        switch (forward[0]) {
        case 0: //If a touchpoint is at he start, we convert it to a crossing one
            //determine if we depart from the start point left or right
            if (ret>=2)
                forward[0] = -forward[1];
            else
                forward[0] = end().x>x ? +1 : -1;
            if (forward[0]==-1)
                break;
            FALLTHROUGH; //for forward[0]==1
        case 1:
            //if the start point is left to right, we ignore it
            if (--ret==0)
                return 0;
            for (unsigned u = 0; u<ret; u++) {
                forward[u] = forward[u+1];
                y[u] = y[u+1];
                pos[u] = pos[u+1];
            }
    }
    return ret;
}

/** Calculates how many times the edge crosses a vertical line right of a point.
* If we cross the vertical line clockwise (up-to-down, y grows downward) that counts
* as +1 crossing. If we cross it counterclockwise (down-to-up) that counts as -1.
* Crosses at or left of the point (lower or equal x values) are ignored.
* This is done for the purposes of ContoursHelper::CalcCoverageHelper().
*
* Certain rules apply to the case when the vertical line crosses one of the ends of the edge.
* 1. an upward edge includes its starting endpoint, and excludes its final endpoint;
* 2. a downward edge excludes its starting endpoint, and includes its final endpoint;
* In short: we include the endpoint with the larger y coordinate only (y grows downwards).
* Touchpoints (when the edge remains fully on one side of the line) are also ignored,
* except if it is exactly at one end of the bezier. In that case (if it is right of
* 'x') we count it for the rules above.
* We also ignore horizontal lines exactly on xy.y. (Return 0 for these.)
* @param [in] xy The y coordinate specifies the vertical line. The x coordinate
*                specifies the point (right of which we are interested in cps).
* @param [in] self True, if we know that 'xy' lies on 'this'. In this case
*                  a crossing in the vicinity of xy is considered to go through
*                  xy (and is therefore ignored).
* @returns The (signed) number of crosspoints.
*/
int Edge::CrossingHorizontalCPEvaluate(const XY &xy, bool self) const
{
    if (IsStraight()) {
        //If xy lies on us, we ignore the single crossing we may have.
        if (self) return 0;
        //if the edge is fully above or below the line or left of the point, we return 0.
        //We also return 0, if the endpoint with the smaller y coordinate is on the line
        //(to implement rules #1 and #2 above).
        if (std::min(start().y, end().y) >= xy.y || std::max(start().y, end().y) < xy.y ||
            std::max(start().x, end().x) < xy.x)
            return 0;
        //return 0 for horizontal straight edges (now start.y==end.y must equal to xy.y)
        if (start().y == end().y) return 0;
        //test for start and endpoints - anything that goes through xy is ignored
        if (start() == xy) return 0; // start.y > end.y ? -1 : 0;
        if (end() == xy) return 0; // start.y < end.y ? +1 : 0;
        //test if we cross right of xy.x
        if ((xy.y - start().y)/(end().y - start().y)*(end().x-start().x) + start().x <= xy.x)
            return 0;
        //Ok, we should not be ignored, return +1 or -1
            return start().y < xy.y ? +1 : -1;
    }
    //Here we handle beziers
    //Check if we are far off.
    const Block b = GetBezierHullBlock();
    if (!test_smaller(b.y.from, xy.y) || test_smaller(b.y.till, xy.y) || test_smaller(b.x.till, xy.x))
        return 0;

    double pos[3], x[3];
    unsigned num = atY(xy.y, pos, CP_PRECISION);
    if (num==0)
        return 0;
    std::sort(pos, pos+num);
    unsigned close = 0;
    unsigned last = 0;
    const double threshold = 0.2;
    for (unsigned i = 0; i<num; i++) {
        x[i] = Split(pos[i]).x;
        //if the crosspoint is much to the left or right (by 'threshold' units) do not refine
        if (x[i]+threshold < xy.x) continue;
        if (x[i]-threshold > xy.x) continue;
        //else refine
        close++;
        last = i;
    }
    if (self && close==1) {
        //if only one cp close to us and we know it exactly is on us, use exact value.
        x[last] = xy.x;
    }
    int ret = 0;
    for (unsigned i = 0; i<num; i++)
        if (x[i]>xy.x) {
            int dir;
            const double fw_y = NextTangentPoint(pos[i]).y;
            if (contour::test_equal(xy.y, fw_y)) {
                //a relevant crossing point, but the tangent is parallel to the horizontal line...
                //Get second derivative
                const double ss0 = 6 - 6 * pos[i];
                const double ss1 = -12 + 18 * pos[i];
                const double ss2 = 6 - 18 * pos[i];
                const double ss3 = 6 * pos[i];
                const double d2y = start().y * ss0 + c1().y * ss1 + c2().y * ss2 + end().y * ss3;
                if (test_zero(d2y)) {
                    //If the Y component of second derivative is zero (that is both
                    //first and second derivatives point to exact same/opposite direction =>
                    //this is an inflection point)
                    //then let us see the third derivative.
                    const double d3y = start().y * -6 + c1().y * 18 + c2().y * -18 + end().y * 6;
                    _ASSERT(!test_zero(d3y));
                    //if the Y component of the 3rd derivative is positive, then Y will grow
                    //as we move with increasing position (we cross downwards = clockwise)
                    dir = fsign(d3y);
                    //We continue after the two elses below
                } else {
                    //no inflection point - then this is a touchpoint (tangent is horizontal)
                    //We can ignore if not at the endpoint
                    if (pos[i]!=0. && pos[i]!=1) continue;

                    //now if d2y is positive we curl upwards - this is the endpoint (start or end)
                    //with the smaller y coordinate - we ignore
                    if (d2y>0) continue;

                    //else if this is the start point we cross upwards (-1) else we cross downwards (+1)
                    if (pos[i]==0)
                        ret--;
                    else
                        ret++;
                    continue;
                }
            } else
                dir = fsign(fw_y-xy.y);
            //check correct behaviour for endings
            if (pos[i]==0 && dir==+1) continue;
            if (pos[i]==1 && dir==-1) continue;
            ret += dir;
        }

    return ret;
}


/** Calculates the angle of the edge at point `p`.
*
* @param [in] incoming If true the angle of the incoming segment is calculated (as if it were outgoing),
*                      if false the outgoing part. In other words true is prev tangent, false is next tangent
* @param [in] pos The angle is calculated at this pos value. (indifferent for straight edges)
* @returns The angle of the edge
*/
RayAngle Edge::Angle(bool incoming,  double pos) const noexcept
{
    _ASSERT(!incoming || pos>0); //should not ask for an incoming angle at pos==0
    _ASSERT(incoming  || pos<1); //should not ask for an outgoing angle at pos==0
    if (IsStraight())
        as_straight:
        return RayAngle(incoming ? angle_to_horizontal(end(), start()) :
                                   angle_to_horizontal(start(), end()));

    //bezier curve radius calc, see http://blog.avangardo.com/2010/10/c-implementation-of-bezier-curvature-calculation/

    using real = long double;

    //first get the derivative
    const real pos2 = pos * pos;
    const real s0 = -3 + 6 * pos - 3 * pos2;
    const real s1 = 3 - 12 * pos + 9 * pos2;
    const real s2 = 6 * pos - 9 * pos2;
    const real s3 = 3 * pos2;
    const real d1x = start().x * s0 + c1().x * s1 + c2().x * s2 + end().x * s3;
    const real d1y = start().y * s0 + c1().y * s1 + c2().y * s2 + end().y * s3;

    //then get second derivative
    const real ss0 = 6 - 6 * pos;
    const real ss1 = -12 + 18 * pos;
    const real ss2 = 6 - 18 * pos;
    const real ss3 = 6 * pos;
    const real d2x = start().x * ss0 + c1().x * ss1 + c2().x * ss2 + end().x * ss3;
    const real d2y = start().y * ss0 + c1().y * ss1 + c2().y * ss2 + end().y * ss3;

    //then the radius
    const real r1_pre = d1x * d1x + d1y * d1y;
    if (r1_pre==0) goto as_straight; //If C1==C2==END (or START) we have 0 here
    const real r1 = pow(r1_pre, 1.5);
    const real r2 = d1x * d2y - d2x * d1y;
    const real invRadius = r2 / r1; //the inverse of the (signed) radius
    if (!std::isfinite(invRadius)) goto as_straight;

    if (pos==0) //must be outgoing
        return RayAngle(angle_to_horizontal(start(), c1()), invRadius);
    if (pos==1) //must be incoming
        return RayAngle(angle_to_horizontal(end(), c2()), -invRadius);
    XY A, B;
    const XY C = Split(pos, A, B);
    RayAngle ret;
    if (incoming)
        ret = RayAngle(angle_to_horizontal(C, A), -invRadius);
    else
        ret = RayAngle(angle_to_horizontal(C, B), invRadius);
    if (ret.angle!=-1) return ret;
    //we are too close to an end
    if (pos<0.5)
        ret = incoming ? RayAngle(angle_to_horizontal(c1(), start()), -invRadius) :
                         RayAngle(angle_to_horizontal(start(), c1()), invRadius);
    else
        ret =  incoming ? RayAngle(angle_to_horizontal(end(), c2()), -invRadius):
                          RayAngle(angle_to_horizontal(c2(), end()), invRadius);
    if (ret.angle == -1) 
        goto as_straight;
    return ret;
}


/**Solves a cubic equation of x^3 + a*x^2 + b*x + c
 * Returns the number of roots.*/
int solveCubic(double a, double b, double c, double* r)
{
    double p = b - a*a / 3;
    double q = a * (2*a*a - 9*b) / 27 + c;
    double p3 = p*p*p;
    double d = q*q + 4*p3 / 27;
    double offset = -a / 3;
    if (d >= 0) { // Single solution
        double z = sqrt(d);
        double u = (-q + z) / 2;
        double v = (-q - z) / 2;
        u = pow(u,1/3);
        v = pow(v, 1/3);
        r[0] = offset + u + v;
        return 1;
    }
    double u = sqrt(-p / 3);
    double v = acos(-sqrt(-27 / p3) * q / 2) / 3;
    double m = cos(v), n = sin(v)*1.732050808;
    r[0] = offset + u * (m + m);
    r[1] = offset - u * (n + m);
    r[2] = offset + u * (n - m);
    return 3;
}

double LinePointDistance(const XY &A, const XY &B, const XY &M, XY *point, double *pos) {
    if (test_equal(A.x, B.x)) {
        if (point) *point = {A.x, M.y};
        if (pos) *pos = test_equal(A.y, B.y) ? 0 : (M.y-A.y)/(B.y-A.y);
        return fabs(A.x - M.x);
    }
    if (test_equal(A.y, B.y)) {
        if (point) *point = {M.x, A.y};
        if (pos) *pos = test_equal(A.x, B.x) ? 0 : (M.x-A.x)/(B.x-A.x);
        return fabs(A.y - M.y);
    }
    const XY V{B.x - A.x, B.y - A.y};
    XY p; double m;
    if (V.x>V.y) {
        const double vxvy = V.x / V.y;
        const double y = (vxvy * (M.x - A.x + vxvy * A.y) + M.y) / (1. + vxvy * vxvy);
        p = {A.x - vxvy * (A.y - y), y};
        m = (p.x - A.x) / V.x;
    } else {
        const double vyvx = V.y / V.x;
        const double x = (vyvx * (M.y - A.y + vyvx * A.x) + M.x) / (1. + vyvx * vyvx);
        p = {x, A.y - vyvx * (A.x - x)};
        m = (p.y - A.y) / V.y;
    }
    if (point) *point = p;
    if (pos) *pos = m;
    return p.Distance(M);
}

double SectionPointDistance(const XY&A, const XY&B, const XY &M, XY *point, double *pos)
{
    XY p;
    if (test_equal(A.x, B.x)) {
        if (M.y<std::min(A.y, B.y)) {
            p = {A.x, std::min(A.y, B.y)};
            if (pos) *pos = B.y<A.y ? 1 : 0;
        } else if (M.y>std::max(A.y, B.y)) {
            p = {A.x, std::max(A.y, B.y)};
            if (pos) *pos = B.y>A.y ? 1 : 0;
        } else {
            if (point) *point = {A.x, M.y};
            if (pos) *pos = (M.y-A.y)/(B.y-A.y);
            return fabs(A.x - M.x);
        }
    } else if (test_equal(A.y, B.y)) {
        if (M.x<std::min(A.x, B.x)) {
            p = {std::min(A.x, B.x), A.y};
            if (pos) *pos = B.x<A.x ? 1 : 0;
        } else if (M.x>std::max(A.x, B.x)) {
            p = {std::max(A.x, B.x), A.y};
            if (pos) *pos = B.x>A.x ? 1 : 0;
        } else {
            if (point) *point = {M.x, A.y};
            if (pos) *pos = (M.x-A.x)/(B.x-A.x);
            return fabs(A.y - M.y);
        }
    } else {
        const XY V{B.x - A.x, B.y - A.y};
        double m;
        if (V.x>V.y) {
            const double vxvy = V.x / V.y;
            const double y = (vxvy * (M.x - A.x + vxvy * A.y) + M.y) / (1. + vxvy * vxvy);
            const double x = A.x - vxvy * (A.y - y);
            m = (x - A.x) / V.x;
        } else {
            const double vyvx = V.y / V.x;
            const double x = (vyvx * (M.y - A.y + vyvx * A.x) + M.x) / (1. + vyvx * vyvx);
            const double y = A.y - vyvx * (A.x - x);
            m = (y - A.y) / V.y;
        }
        if (m<0) m = 0;
        else if (m>1) m = 1;
        p = Mid(A, B, m);
        if (pos) *pos = m;
    }
    if (point) *point = p;
    return p.Distance(M);
}




/** Calculates the distance of a point from a bezier curve,
 * not limited to the [0..1] position values.
 * @returns the {distance^2, position} */
std::pair<double, double> Edge::UnboundedBezierDistance(const XY &M) const
{
    _ASSERT(!IsStraight());
    constexpr double extend = 1; //How much do we increase the len on both sides
    const Edge ext = ExtendBezier(-extend, 1+extend);
    double pos;
    XY dummy;
    double d = ext.Distance(M, dummy, pos);
    //Now calculate 'pos' back to the parameter space of the original curve.
    return { d, pos*(1+2*extend)-extend };
}

/** Calculates the distance of a point from the edge.
 * @param [in] M The point to calculate the distance of.
 * @param [out] point The point of 'this' closest to 'M'.
 * @param [out] pos The position corresponding to 'point'.
 * @returns the distance, always nonnegative.*/
double Edge::Distance(const XY &M, XY &point, double &pos) const //always nonnegative
{
    if (IsStraight())
        return SectionPointDistance(start(), end(), M, &point, &pos);

    double result[5];
    const unsigned num = SolveForDistance(M, result);
    double d = std::min(start().DistanceSqr(M), end().DistanceSqr(M));
    if (start().DistanceSqr(M)<end().DistanceSqr(M)) {
        point = start();
        pos = 0;
    } else {
        point = end();
        pos = 1;
    }
    for (unsigned u = 0; u<num; u++) {
        const XY pu = Split(result[u]);
        const double du = pu.DistanceSqr(M);
        if (du<d) {
            point = pu;
            pos = result[u];
            d = du;
        }
    }
    return sqrt(d);
}

/** Helper to calculate the distance between two sections.
 * This does not actually give the smallest distance, but only the distance of A to M->N.
 * Since we use this in taking the distance of the two contour, B will be applied next.
 * But if the two sections actually cross, we do return zero. */
double SectionDistance(const XY &A, const XY &B, const XY &M, const XY &N)
{
    if (!test_zero((B-A).PerpProduct(N-M))) {
        //They are not parallel (and none of them are degenerate, but that does not matter now)
        const double t = (B-A).PerpProduct(A-M) / (B-A).PerpProduct(N-M);
        const double s = (N-M).PerpProduct(A-M) / (B-A).PerpProduct(N-M);
        if (between01_approximate_inclusive(t) && between01_approximate_inclusive(s))
            return 0;
        //here the two infinite lines cross, but the cp is outside at least one
        //of the two sections
    }
    //They are either parallel, degenerate or just far away
    //calculate the A distance only, B will come anyway
    const double d1 = SectionPointDistance(A, B, M);
    const double d2 = SectionPointDistance(M, N, A);
    const double d3 = SectionPointDistance(A, B, N);
    const double d4 = SectionPointDistance(M, N, B);
    return std::min(std::min(d1, d2), std::min(d3, d4));
}

/** Distance of the hull from a section */
double Edge::HullDistance(const XY &A, const XY &B) const
{
    double ret = SectionDistance(start(), c1(), A, B);
    if (ret==0) return 0;
    ret = std::min(ret, SectionDistance(c1(), c2(), A, B));
    if (ret==0) return 0;
    ret = std::min(ret, SectionDistance(c2(), end(), A, B));
    if (ret==0) return 0;
    ret = std::min(ret, SectionDistance(end(), start(), A, B));
    if (ret==0) return 0;
    ret = std::min(ret, SectionDistance(start(), c2(), A, B));
    if (ret==0) return 0;
    return std::min(ret, SectionDistance(c1(), end(), A, B));
}

/** Distance of the hull from another hull */
double Edge::HullDistance(const XY &A, const XY &B, const XY &C, const XY &D) const
{
    double ret = HullDistance(A, B);
    if (ret==0) return 0;
    ret = std::min(ret, HullDistance(B, C));
    if (ret==0) return 0;
    ret = std::min(ret, HullDistance(C, D));
    if (ret==0) return 0;
    ret = std::min(ret, HullDistance(D, A));
    if (ret==0) return 0;
    ret = std::min(ret, HullDistance(A, C));
    if (ret==0) return 0;
    return std::min(ret, HullDistance(B, D));
}

/** Assuming 'p' is on the bezier, return the corresponding parameter */
double Edge::FindBezierParam(const XY &p) const
{
    if (IsStraight())
        return point2pos_straight(start(), end(), p);
    double result[4];
    const unsigned num = SolveForDistance(p, result);
    _ASSERT(num);
    double ret = result[0];
    double d = (p-Split(result[0])).length_sqr();
    for (unsigned u = 1; u<num; u++)
    if (d >(p-Split(result[u])).length_sqr()) {
        d = (p-Split(result[u])).length_sqr();
        ret = result[u];
    }
    return ret;
}

/** Merges the distance of 'o' from 'this' to 'ret'.
 * That is, if 'o' is closer to 'this' than the distance stored in 'ret',
 * we update 'ret' with this new distance.
 * @returns true if we have updated 'ret'*/
bool Edge::Distance(const Edge &o, DistanceType &ret) const    //always nonnegative
{
    if (ret.IsZero()) return false;
    if (IsStraight() && o.IsStraight()) {
        //test if we cross
        if (!test_zero((end()-start()).PerpProduct(o.end()-o.start()))) {
            //They are not parallel (and none of them are degenerate, but that does not matter now)
            const double t = (end()-start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
            const double s = (o.end()-o.start()).PerpProduct(start()-o.start()) / (end()-start()).PerpProduct(o.end()-o.start());
            if (between01_approximate_inclusive(t) && between01_approximate_inclusive(s))
                return ret.Merge(0, start() + (end()-start())*t, start() + (end()-start())*t, {0, t}, {0, s});
            //here the two infinite lines cross, but the cp is outside at least one
            //of the two sections
        }
        //They are either parallel, degenerate or just far away
        //calculate the start distance only, end will come anyway
        XY point1, point2;
        double pos1, pos2;
        const double d1 = Distance(o.start(), point1, pos1);
        const double d2 = o.Distance(start(), point2, pos2);
        if (d1<d2)
            return ret.Merge(d1, point1, o.start(), {0, pos1}, {0, 0});
        else
            return ret.Merge(d2, start(), point2, {0, 0}, {0, pos2});
    }
    if (IsStraight()) {
        bool b = o.Distance(*this, ret.SwapPoints());
        ret.SwapPoints();
        return b;
    }
    if (o.IsStraight()) {
        if (ret.distance<HullDistance(o.start(), o.end()))
            return false;
        Edge E1, E2;
        Split(E1, E2);
        bool b1 = E1.Distance(o, ret);
        if (b1) ret.pos_on_me.pos /= 2;
        bool b2 = E2.Distance(o, ret);
        if (b2) ret.pos_on_me.pos = 0.5 + ret.pos_on_me.pos/2;
        return b1 || b2;
    }
    if (ret.distance<HullDistance(o.start(), o.end(), o.c1(), o.c2()))
        return false;
    Edge E1, E2, F1, F2;
    Split(E1, E2);
    o.Split(F1, F2);
    bool b1 = E1.Distance(F1, ret);
    if (b1) {
        ret.pos_on_me.pos /= 2;
        ret.pos_on_other.pos /= 2;
    }
    bool b2 = E2.Distance(F1, ret);
    if (b2) {
        ret.pos_on_me.pos = 0.5 + ret.pos_on_me.pos/2;
        ret.pos_on_other.pos /= 2;
    }
    bool b3 = E1.Distance(F2, ret);
    if (b3) {
        ret.pos_on_me.pos /= 2;
        ret.pos_on_other.pos = 0.5 + ret.pos_on_other.pos/2;
    }
    bool b4 = E2.Distance(F2, ret);
    if (b4) {
        ret.pos_on_me.pos = 0.5 + ret.pos_on_me.pos/2;
        ret.pos_on_other.pos = 0.5 + ret.pos_on_other.pos/2;
    }
    return b1 || b2 || b3 || b4;
}

double Edge::GetLength(double pos_from, double pos_till) const noexcept
{
    _ASSERT(0<=pos_from && pos_from<=1);
    _ASSERT(0<=pos_till && pos_till<=1);
    if (pos_from==pos_till) return 0;
    if (pos_from>pos_till) return -GetLength(pos_till, pos_from);
    if (IsStraight())
        return (start()-end()).length()*(pos_till-pos_from);
    return Edge(*this, pos_from, pos_till).GetLength();
}

/** Here we have a straight bezier curve parallel to the X axis
 * (all Y coordinates are zero, so we ignore them) from 0->C.
 * The control points are A and B, (thus the bezier is in 2D:
 * (0,0) cp1=(0,A), cp2=(0,B), end=(0,C) and we seek the parameter
 * of the (0,len) point, where len is guaranteed to be within [0..C].
 * On some error we return -1 else a parameter value [0..1].*/
double Len2Pos(double A, double B, double C, double len) {
    double coeff[4] = {-len, 3*A, 3*B-6*A, 3*A-3*B+C};
    double roots[3];
    const unsigned num = solve_degree3(coeff, roots);
    if (num==0) return -1; //no solution
    //pick the one in (closest to) [0..1]
    unsigned hit = 0;
    double dist = roots[0] < 0 ? -roots[0] : 1 < roots[0] ? roots[0]-1 : 0;
    for (unsigned u = 1; u<num; u++) {
        const double d = roots[u] < 0 ? -roots[u] : 1 < roots[u] ? roots[u]-1 : 0;
        if (d<dist) {
            dist = d;
            hit = u;
        } else if (d==0) {
            _ASSERT(0); //two solutions between [0..1]
        }
    }
    if (dist==0) return roots[hit];
    //_ASSERT(0); //no root in [0..1] fallback to linear
    return -1;
}

/** Moves a position by 'len'.
 * Positive distance will walk forward, negative backwards.
 * If after the move we are still on this edge, we
 * return true and len is zero and pos is the moved pos.
 * If after the move we fell off the edge, we reduce
 * 'len' by the distance we moved and 'pos' is either 0 or 1
 * depending on which end of the edge we fell off. */
bool Edge::MovePos(double &pos, double &len) const
{
    if (len==0) return true;
    if (IsStraight()) {
        const double l = (start()-end()).length();
        if (HasControlPoints()) {
            //Here we assume that the control points are close to start->end line
            //We also assume we have either pos==0 len>0 or pos==1 len<0
            if (pos==0 && len>0) {
                if (len>l) {
                    len -= l;
                    pos = 1;
                    return false;
                }
                const double p = Len2Pos(start().Distance(c1()), start().Distance(c2()), l, len);
                if (0<=p && p<=1) {
                    len = 0;
                    pos = p;
                    return true;
                }
                //fallback to linear calc.
            } else if (pos==1 && len<0) {
                if (-len>l) {
                    len += l;
                    pos = 0;
                    return false;
                }
                const double p = Len2Pos(end().Distance(c2()), end().Distance(c1()), l, -len);
                if (0<=p && p<=1) {
                    len = 0;
                    pos = 1-p;
                    return true;
                }
                //fallback to linear calc.
            }
            //_ASSERT(0); //fallthrough to linear caclulation ignoring control points
        }
        pos += len/l;
        if (pos<0) {
            len = pos*l;
            pos = 0;
            return false;
        } else if (pos>1) {
            len = (pos-1)*l;
            pos = 1;
            return false;
        }
        len = 0;
        return true;
    }
    //for bezier edges, we split until straight
    Edge E1, E2;
    if (pos==0 && len>0) {
        //walk from the front of the edge
        Split(E1, E2, true);
        if (E1.MovePos(pos, len))
            pos /= 2; //we move on the first half and find the spot
        else {
            pos = 0;
            if (E2.MovePos(pos, len)) //if not, go on walk more (and reduce len more)
                pos = 0.5 + pos/2; //if we found
            else return false; //if we have not found, len is suitably decreased and pos==1
        }
        return true;
    } else if (pos==1 && len<0) {
        //walk from the back of the edge (backwards)
        Split(E1, E2, true);
        if (E2.MovePos(pos, len))
            pos = 0.5 + pos/2; //we move on the back half and find the spot
        else {
            pos = 1;
            if (E1.MovePos(pos, len)) //if not, go on walk more (and reduce len more)
                pos /= 2; //if we found
            else return false; //if we have not found, len is suitably decreased and pos==0
        }
        return true;
    }
    //OK, we walk from the middle
    Split(pos, E1, E2, true);
    double p;
    if (len<0) {
        p = 1;
        if (E1.MovePos(p, len)) {
            pos *= p;
            return true;
        } else {
            pos = 0;
            return false;
        }
    } else {
        p = 0;
        if (E2.MovePos(p, len)) {
            pos += (1-pos)*p;
            return true;
        } else {
            pos = 1;
            return false;
        }
    }
}

/** Return the centroid of the area below the edge multiplied by the area.
*
* We take the endpoints of the edge and draw a line from them to the x axis.
* The area bounded by these two lines, the edge itself and the x axis is
* the subject area of this function. If the `start.x < end.s` the return is
* negated. The return is also negated (perhaps 2nd time) if the edge is above
* the x axis (negative y values).
* The function returns the centroid of this area, scaled by the (signed)
* area of this area.
*
* In more visual terms, showing the x axis and a straight edge and the area.
* @verbatim
    ==========> =========>
    +++++        ------
    __+++        ------
    |\+++        \-----
      \++         \ ---
       \+          \---
        \           \--
        _\|
@endverbatim
* (Since in contour we treat y coordinates as growing downwards, we call these functions
* "xxAreaAbove", meaning the area between edge & X axis.)
* The dotted area is the area above an edge. It is counted as positive in the first example;
* and negative in the second. If we sum all such areas, we get the area of the contour
* (positive for clockwise and negative for counterclockwise), as it should be.
*
* Now, the formula for the above for an edge (X1,Y1)->(X2,Y2) is
* AreaAbove = (X1-X2)*(Y1+Y2)/2 = (X1*Y1 - X2*Y2)/2 + (X1*Y2 - X2*Y1)/2
* If we sum this for all edges of a connected contour the first term will
* cancel out. Thus in the edge::xxAreaAboveAdjusted functions we return
* the second term only (twice 2), thus we return
* AreaAbove*2 - (X1*Y1 - X2*Y2).
*/
XY Edge::GetCentroidAreaAboveUpscaled() const noexcept
{
    if (IsStraight()) {
        const XY &minY = start().y < end().y ? start() : end();
        const XY &maxY = start().y < end().y ? end() : start();
        //calculate the centroid of the rectangle and triangle below a (slanted) edge
        //multiplied by their area.
        //We do it in two steps: centroid by the Y coordinate of the area
        //then multiply by the (common) x coordinate of the area
        XY ret = XY(start().x+end().x, minY.y)*(minY.y/2) +                     //centroid of rectangle times y coord of its area
            XY(maxY.x*2+minY.x, maxY.y+minY.y*2)*((maxY.y-minY.y)/6);  //centroid of triangle times y coord of its area
        return ret*(start().x - end().x); //this makes ret negative if edge is from right to left (as it should)
    }
    Edge E1, E2;
    Split(E1, E2);
    return E1.GetCentroidAreaAboveUpscaled()+E2.GetCentroidAreaAboveUpscaled();
}

namespace Edge_CreateExpand2D {
    /** Compare two numbers and return -1, 0 +1 depending on their relation. */
    inline int comp_int(const double &a, const double &b)
    {
        return a<b ? -1 : a==b ? 0 : 1;
    }
    /** Compare two numbers and return -g, 0 +g depending on their relation. */
    inline double comp_dbl(const double &a, const double &b, double g)
    {
        return a<b ? -g : a==b ? 0 : g;
    }
}

/** Returns a list of edges resulting in expanding "this" in 2D.
 *
 * See overall documentation of what a 2D expansion is.
 * We return values describing the direction in which the edge
 * ends and starts as below.
 * @verbatim
   +4  _+3 _  +2
      |\ ^ /|
        \|/
   +1  <-0->  -1
        /|\
      |/ v \|
   -2   -3    -4
 * @endverbatim
 * The above picture describes, how the end is described,
 * the start is described in reverse, thus for lines
 * `stype==dtype`. For degenerate edges `type==0`.
 *
 * @param [in] gap The size of the expansion in the x and y directions.
 * @param [out] ret The edges resulting from the expansion (perhaps more than one).
 * @param [out] stype The type of the starting of the edge.
 * @param [out] etype The type of the ending of the edge.
 */
void Edge::CreateExpand2D(const XY &gap, std::vector<Edge> &ret, int &stype, int &etype) const
{
    if (IsStraight()) {
        const XY off(Edge_CreateExpand2D::comp_dbl(end().y, start().y, gap.x),
                     Edge_CreateExpand2D::comp_dbl(start().x, end().x, gap.y));
        ret.emplace_back(start()+off, end()+off, IsVisible());
        Edge &e = ret.back();
        //expand for horizontal and vertical edges
        if (start().x == end().x) {
            e.start().y += Edge_CreateExpand2D::comp_dbl(start().y, end().y, fabs(gap.y));
            e.end().y -= Edge_CreateExpand2D::comp_dbl(start().y, end().y, fabs(gap.y));
        }
        if (start().y == end().y) {
            e.start().x += Edge_CreateExpand2D::comp_dbl(start().x, end().x, fabs(gap.x));
            e.end().x -= Edge_CreateExpand2D::comp_dbl(start().x, end().x, fabs(gap.x));
        }
        etype = stype = Edge_CreateExpand2D::comp_int(start().x, end().x) +
                        Edge_CreateExpand2D::comp_int(start().y, end().y)*3;
        return;
    }
    //see extreme points
    //from: http://stackoverflow.com/questions/2587751/an-algorithm-to-find-bounding-box-of-closed-bezier-curves
    std::vector<double> bounds;
    bounds.reserve(6);
    bounds.push_back(0);
    bounds.push_back(1);

    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 (a == 0) {
            if (b == 0) {
                continue;
            }
            double t = -c / b;
            if (0 < t && t < 1)
                bounds.push_back(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.push_back(t1);
        double  t2 = (-b - sqrt(b2ac))/(2 * a);
        if (0 < t2 && t2 < 1)
            bounds.push_back(t2);
    }
    for (double &v : bounds)
        if (test_zero(v))
            v = 0;
        else if (contour::test_equal(1, v))
            v = 1;
    std::sort(bounds.begin(), bounds.end());
    bounds.erase(std::unique(bounds.begin(), bounds.end(), [](double a, double b) {return contour::test_equal(a, b); }), bounds.end());
    //now bounds contain a sorted list of extreme points
    //and all values are distinct


    const size_t original_size = ret.size();
    //first we segment the edge at bounding points
    ret.reserve(original_size + bounds.size()*2);
    ret.push_back(*this);
    ret.back().Chop(bounds[0], bounds[1]);
    stype = Edge_CreateExpand2D::comp_int(ret.back().start().x, ret.back().end().x) +
            Edge_CreateExpand2D::comp_int(ret.back().start().y, ret.back().end().y)*3;
    for (unsigned bound_index = 1; bound_index<bounds.size()-1; bound_index++) {
        ret.emplace_back(); //defaults to straight line
        ret.back().SetVisible(IsVisible());
        ret.back().SetInternalMark(IsInternalMarked());
        ret.back().SetMark(GetMark());
        ret.push_back(*this);
        ret.back().Chop(bounds[bound_index], bounds[bound_index+1]);
    }
    //Shift the segments to place
    for (size_t u = original_size; u<ret.size(); u += 2)
        ret[u].Shift(XY(Edge_CreateExpand2D::comp_dbl(ret[u].end().y, ret[u].start().y, gap.x),
            Edge_CreateExpand2D::comp_dbl(ret[u].start().x, ret[u].end().x, gap.y)));
    //if only one segment
    if (ret.size()==original_size+1) {
        etype = stype;
        return;
    }
    //connect the segments with lines
    for (size_t u = original_size+1; u<ret.size(); u += 2) {
        ret[u].start() = ret[u-1].end();
        ret[u].end() = ret[u+1].start();
    }
    etype = Edge_CreateExpand2D::comp_int(ret.back().start().x, ret.back().end().x) +
            Edge_CreateExpand2D::comp_int(ret.back().start().y, ret.back().end().y)*3;
}

/** Put a dashed/dotted path into a cairo context. Needed for backends not supporting dashed lines.
*
* We also assume cairo dash is set to continuous: we fake dash here.
* @param [in] cr The cairo context.
* @param [in] pattern Contains lengths of alternating on/off segments.
* @param pos Shows which pattern element we shall start at (updated on return).
* @param offset Shows at which point we shall start at within the segment selected by `pos` (updated on return).
* @param reverse If true the path is drawn from `end` to `start`.
*/
void Edge::PathDashed(cairo_t *cr, std::span<const double> pattern, int &pos, double &offset, bool reverse) const
{
    _ASSERT(pattern.size());
    _ASSERT(offset<pattern[pos]);
    if (IsStraight()) {
        const XY & fr = reverse ? end() : start();
        const XY & to = reverse ? start() : end();
        const double len = sqrt((to.x-fr.x)*(to.x-fr.x) + (to.y-fr.y)*(to.y-fr.y));
        const double ddx = (to.x-fr.x)/len;
        const double ddy = (to.y-fr.y)/len;
        double processed = 0;
        double x = fr.x, y = fr.y;
        if (offset) {
            if (pattern[pos]-offset > len) { //remaining segment is shorter than the entire length
                if (pos%2==0) {//start with drawn
                    cairo_move_to(cr, fr.x, fr.y);
                    cairo_line_to(cr, to.x, to.y);
                }
                offset += len;
                return;
            }
            if (pos%2==0) cairo_move_to(cr, x, y);
            x += (pattern[pos]-offset)*ddx;
            y += (pattern[pos]-offset)*ddy;
            if (pos%2==0) cairo_line_to(cr, x, y);
            processed += pattern[pos]-offset;
            pos = (pos+1)%pattern.size();
        }

        while (processed+pattern[pos] <= len) {
            if (pos%2==0) cairo_move_to(cr, x, y);
            x += pattern[pos]*ddx;
            y += pattern[pos]*ddy;
            if (pos%2==0) cairo_line_to(cr, x, y);
            processed += pattern[pos];
            pos = (pos+1)%pattern.size();
        }

        offset = len - processed;
        if (pos%2==0 && fabs(offset)>1e-10) {
            cairo_move_to(cr, x, y);
            cairo_line_to(cr, to.x, to.y);
        }
        return;
    }
    //curvy
    double local_s = 0, local_e = 1;
    //TODO Do proper length calculations
    const double len = GetLength();
    const double inc = 1/len;
    double processed = 0;
    if (offset) {
        if (pattern[pos]-offset > len) { //remaining segment is longer than the entire length
            offset += len;
            if (pos%2==0) {//start with drawn
                cairo_new_sub_path(cr);
                cairo_move_to(cr, start().x, start().y);
                cairo_curve_to(cr, c1().x, c1().y, c2().x, c2().y, end().x, end().y);
            }
            return;
        }
        const double new_s = local_s + inc*(pattern[pos]-offset);
        if (pos%2==0) {
            cairo_new_sub_path(cr);
            Edge e(*this, local_s, new_s);
            cairo_move_to(cr, e.start().x, e.start().y);
            cairo_curve_to(cr, e.c1().x, e.c1().y, e.c2().x, e.c2().y, e.end().x, e.end().y);
        }
        local_s = new_s;
        processed += pattern[pos]-offset;
        pos = (pos+1)%pattern.size();
    }

    while (processed+pattern[pos] <= len) {
        const double new_s = local_s + inc*pattern[pos];
        if (pos%2==0) {
            cairo_new_sub_path(cr);
            Edge e(*this, local_s, new_s);
            cairo_move_to(cr, e.start().x, e.start().y);
            cairo_curve_to(cr, e.c1().x, e.c1().y, e.c2().x, e.c2().y, e.end().x, e.end().y);
        }
        local_s = new_s;
        processed += pattern[pos];
        pos = (pos+1)%pattern.size();
    }

    offset = len - processed;
    if (pos%2==0 && !test_zero(offset)) {
        cairo_new_sub_path(cr);
        Edge e(*this, local_s, local_e);
        cairo_move_to(cr, e.start().x, e.start().y);
        cairo_curve_to(cr, e.c1().x, e.c1().y, e.c2().x, e.c2().y, e.end().x, e.end().y);
    }
}


/** Finds a crosspoint of two infinite lines defined by `A`->`B` and `M`->`N`
*/
ELineCrossingType crossing_line_line(const XY &A, const XY &B, const XY &M, const XY &N, XY &r) noexcept
{
    const double perp = (B-A).PerpProduct(N-M);
    if (test_zero(perp)) return ELineCrossingType::PARALLEL;
    //They are not parallel (and none of them are degenerate)
    const double t = (B-A).PerpProduct(A-M) / perp;
    r = M + (N-M)*t;
    //in case of horizontal or vertical lines, ensure that cp lies *exactly* on them
    if (A.x==B.x) r.x = A.x;
    else if (M.x==N.x) r.x = M.x;
    if (A.y==B.y) r.y = A.y;
    else if (M.y==N.y) r.y = M.y;
    const bool t_in = between01_approximate_inclusive(t);
    if (!t_in && t>0.5) return ELineCrossingType::OUTSIDE_BK;
    const double s = (N-M).PerpProduct(M-A) / -perp;
    const bool s_in = between01_approximate_inclusive(s);
    if (s_in) return t_in ? ELineCrossingType::INSIDE : ELineCrossingType::OUTSIDE_FW;
    return s<0.5 ? ELineCrossingType::OUTSIDE_BK : ELineCrossingType::OUTSIDE_FW;
}

/** Finds a crosspoint of two infinite lines defined by `A`->`B` and `M`->`N` & returns positions. All may be outside [0..1]*/
ELineCrossingType crossing_line_line(const XY &A, const XY &B, const XY &M, const XY &N, XY &r, double &posAB, double &posMN) noexcept
{
    const double perp = (B-A).PerpProduct(N-M);
    if (test_zero(perp)) return ELineCrossingType::PARALLEL;
    //They are not parallel (and none of them are degenerate)
    posMN = (B-A).PerpProduct(A-M) / perp;
    posAB = (N-M).PerpProduct(M-A) / -perp;
    r = M + (N-M)*posMN;
    //in case of horizontal or vertical lines, ensure that cp lies *exactly* on them
    if (A.x==B.x) r.x = A.x;
    else if (M.x==N.x) r.x = M.x;
    if (A.y==B.y) r.y = A.y;
    else if (M.y==N.y) r.y = M.y;
    const bool MN_in = between01_approximate_inclusive(posMN);
    if (!MN_in && posMN>0.5) return ELineCrossingType::OUTSIDE_BK;
    const bool AB_in = between01_approximate_inclusive(posAB);
    if (AB_in) return MN_in ? ELineCrossingType::INSIDE : ELineCrossingType::OUTSIDE_FW;
    return posAB<0.5 ? ELineCrossingType::OUTSIDE_BK : ELineCrossingType::OUTSIDE_FW;
}

/** Find the new crosspoint of two originally neighboring expanded edges.
*
* `this` and `M` are two expanded edges.
* The whole logic of this function assumes EXPAND_MITER, but this is called for all types of
* expansions to see how two edges relate.
* return value is
* DEGENERATE: One of the edges have start==end
*  (`newcp` is undefined on return)
* TRIVIAL: the two edges connect exactly at their end+start point
*  (`newcp` is this point on return)
* CP_REAL: The two edges actually cross (between "start" and "end")
*  (we return their crosspoint in `newcp`)
* CP_EXTENDED: The two edges do not meet but their extension does. Here extension means
* the continuation of the curve along the bezier's tangent for beziers.
*  (we return their crosspoint in `newcp`)
* NO_CP_PARALLEL: Either the two edges are parallel or their linear extension is such.
*   (`newcp` is a calculated convergence point: can be reached by adding linear segments)
* CP_INVERSE, if the extended cp is on the wrong side of one of the edges.
* (If one of the edges is a curvy one, we first try to find extended cps on the right
* side so we prefer to return CP_EXTENDED. Only if not possible, do we return this.
*
* @param [in] M The other expanded edge.
* @param [out] newcp Returns their new crosspoint.
* @param [in] my_next_tangent The ideal next tangent of *this.
*                             Used to detect parallel joins and for calculating CP_EXTENDED crosspoints.
* @param [in] Ms_prev_tangent The ideal previous tangent of M.
*                             Used to detect parallel joins and for calculating CP_EXTENDED crosspoints.
* @param [in] is_my_origin_bezier True, if the original edge of *this was a bezier. Used when calculating
*                                 'newcp' for parallel joins.
* @param [in] is_Ms_origin_bezier True, if the original edge of M was a bezier. Used when calculating
*                                 'newcp' for parallel joins.
* @param [out] my_pos In case of CP_REAL returns the position of the cp on 'this', if it is a bezier.
* @param [out] M_pos In case of CP_REAL returns the position of the cp on M, if that is a bezier.
* @returns The relation of the two expanded edges.
*/
Edge::EExpandCPType Edge::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
{
    if (start().test_equal(end()) || M.start().test_equal(M.end())) return CP_DEGENERATE;
    if (end().test_equal(M.start())) {
        newcp = end();
        return CP_TRIVIAL;
    }
    const double parallel_join_multipiler = 5;
    if (M.IsStraight()) {
        if (IsStraight()) {
            if (ELineCrossingType::INSIDE == crossing_line_line(start(), end(), M.start(), M.end(), newcp)) {
                //calculate positions
                if (fabs(start().x-end().x)>fabs(start().y-end().y))
                    my_pos = (start().x-newcp.x)/(start().x-end().x);
                else
                    my_pos = (start().y-newcp.y)/(start().y-end().y);
                if (fabs(M.start().x-M.end().x)>fabs(M.start().y-M.end().y))
                    M_pos = (M.start().x-newcp.x)/(M.start().x-M.end().x);
                else
                    M_pos = (M.start().y-newcp.y)/(M.start().y-M.end().y);
                return CP_REAL;
            }
            //fallthrough to very end if no real crosspoints
        } else {
            //we are bezier, but M is straight
            XY r[8];
            double pos_us[8], pos_M[8];
            int num = Crossing(M, false, r, pos_us, pos_M);
            if (num) {
                //if several crosspoints: use the one closest to the end of the bezier
                unsigned pos = 0;
                for (int u = 1; u<num; u++)
                    if (pos_us[u]> pos_us[pos])
                        pos = u;
                newcp = r[pos];
                my_pos = pos_us[pos];
                M_pos = pos_M[pos];
                return CP_REAL;
            }
            //fallthrough to very end if no real crosspoints
        }
    } else {
        XY r[8];
        if (IsStraight()) {
            //we are a straight, but M is bezier
            double pos_us[8], pos_M[8];
            int num = M.Crossing(*this, false, r, pos_M, pos_us);
            if (num) {
                //if several crosspoints: use the one closest to the start of the bezier
                unsigned pos = 0;
                for (int u = 1; u<num; u++)
                    if (pos_M[u] < pos_M[pos])
                        pos = u;
                newcp = r[pos];
                my_pos = pos_us[pos];
                M_pos = pos_M[pos];
                return CP_REAL;
            }
            //fallthrough to very end if no real crosspoints
        } else {
            //both are beziers
            double pos_us[8], pos_M[8];
            int num = Crossing(M, false, r, pos_us, pos_M);
            if (num) {
                //several crosspoints: use the one closest to the end of us
                unsigned pos = 0;
                for (int u = 1; u<num; u++)
                    if (pos_us[u]> pos_us[pos])
                        pos = u;
                newcp = r[pos];
                my_pos = pos_us[pos];
                M_pos = pos_M[pos];
                return CP_REAL;
            }
            //fallthrough to very end if no real crosspoints
        }
    }

    //We fall through here from all branches if the two edges do not actually cross themselves
    //We test if their linear extension meet
    switch (crossing_line_line(end(), my_next_tangent, Ms_prev_tangent, M.start(), newcp)) {
    case ELineCrossingType::INSIDE:
    case ELineCrossingType::OUTSIDE_FW:
        //They meet, no problem, we have an extended CP
        return CP_EXTENDED;
    default:
        _ASSERT(0); FALLTHROUGH;
    case ELineCrossingType::OUTSIDE_BK:
        //They meet but on the wrong side: an inverse join situ
        return CP_INVERSE;
    case ELineCrossingType::PARALLEL:
        //if we are parallel, calculate a convergence point
        const double dist = end().Distance(M.start()) * parallel_join_multipiler;
        if (is_Ms_origin_bezier) {
            const double len = end().Distance(my_next_tangent);
            if (is_my_origin_bezier)
                newcp = (end()+M.start())/2 - (end()-my_next_tangent)/len*dist;
            else
                newcp = end() + (my_next_tangent-end())/len*dist;
        } else if (is_my_origin_bezier) {
            const double mlen = M.start().Distance(Ms_prev_tangent);
            newcp = M.start() + (Ms_prev_tangent-M.start())/mlen*dist;
        } else {
            //two originally straight edges should not join parallel, so if we reach here
            //this is due to numerical imprecision - two (probably short and) almost
            //parallel edges
            //Fall back to a point in-between their edges
            newcp = (end() + M.start())/2;
            return CP_EXTENDED;
        }
        return NO_CP_PARALLEL;
    }
}



/*
Solving the Nearest Point-on-Curve Problem
and
A Bezier Curve-Based Root-Finder
by Philip J. Schneider
from "Graphics Gems", Academic Press, 1990
http://tog.acm.org/resources/GraphicsGems/category.html#Curves%20and%20Surfaces_link
http://tog.acm.org/resources/GraphicsGems/gems/NearestPoint.c
*/

const int MAXDEPTH = 64;	///< Maximum depth for recursion to find nearest point on curve.
const double EPSILON = (ldexp(1.0, -MAXDEPTH-1)); ///< Flatness control value to find nearest point on curve.



/** Count the number of times a Bezier control polygon
*	crosses the 0-axis. This number is >= the number of roots.*/
unsigned CrossingCount(const XY V[6])
{
    unsigned n_crossings = 0;	/*  Number of zero-crossings	*/
    bool old_sign = V[0].y < 0;
    for (unsigned i = 1; i <= 5; i++) {
        const bool sign = V[i].y < 0;
        if (sign != old_sign) {
            n_crossings++;
            old_sign = sign;
        }
    }
    return n_crossings;
}

/** Check if the control polygon of a Bezier curve is flat enough
*	for recursive subdivision to bottom out.
*
*  Corrections by James Walker, jw@jwwalker.com, as follows:

There seem to be errors in the ControlPolygonFlatEnough function in the
Graphics Gems book and the repository (NearestPoint.c). This function
is briefly described on p. 413 of the text, and appears on pages 793-794.
I see two main problems with it.

The idea is to find an upper bound for the error of approximating the x
intercept of the Bezier curve by the x intercept of the line through the
first and last control points. It is claimed on p. 413 that this error is
bounded by half of the difference between the intercepts of the bounding
box. I don't see why that should be true. The line joining the first and
last control points can be on one side of the bounding box, and the actual
curve can be near the opposite side, so the bound should be the difference
of the bounding box intercepts, not half of it.

Second, we come to the implementation. The values distance[i] computed in
the first loop are not actual distances, but squares of distances. I
realize that minimizing or maximizing the squares is equivalent to
minimizing or maximizing the distances.  But when the code claims that
one of the sides of the bounding box has equation
a * x + b * y + c + max_distance_above, where max_distance_above is one of
those squared distances, that makes no sense to me.

I have appended my version of the function. If you apply my code to the
cubic Bezier curve used to test NearestPoint.c,

static XY bezCurve[4] = {    /  A cubic Bezier curve    /
{ 0.0, 0.0 },
{ 1.0, 2.0 },
{ 3.0, 3.0 },
{ 4.0, 2.0 },
};

my code computes left_intercept = -3.0 and right_intercept = 0.0, which you
can verify by sketching a graph. The original code computes
left_intercept = 0.0 and right_intercept = 0.9.

*/

bool ControlPolygonFlatEnough(const XY V[6])
{
    /* Coefficients of implicit
     * eqn for line from V[0]-V[deg]
     * Derive the implicit equation for line connecting first
     *  and last control points */
    const double a = V[0].y - V[5].y;
    const double b = V[5].x - V[0].x;
    const double c = V[0].x * V[5].y - V[5].x * V[0].y;

    double  max_distance_above = 0;
    double  max_distance_below = 0;

    for (unsigned i = 1; i < 5; i++) {
        const double value = a * V[i].x + b * V[i].y + c;
        if (value > max_distance_above) {
            max_distance_above = value;
        } else if (value < max_distance_below) {
            max_distance_below = value;
        }
    }
    const double error_better = fabs((max_distance_below-max_distance_above)/a);
    return error_better < EPSILON;
}


//bool ControlPolygonFlatEnough_Original(const XY V[6])
//{
//    int     i;        /* Index variable        */
//    double  max_distance_above = 0;
//    double  max_distance_below = 0;
//    double  error;        /* Precision of root        */
//    double  intercept_1,
//            intercept_2,
//            left_intercept,
//            right_intercept;
//    double  det, dInv;
//    double  a2, b2, c2;
//
//    /* Coefficients of implicit
//     * eqn for line from V[0]-V[deg]
//     * Derive the implicit equation for line connecting first
//     *  and last control points */
//    const double a = V[0].y - V[5].y;
//    const double b = V[5].x - V[0].x;
//    const double c = V[0].x * V[5].y - V[5].x * V[0].y;
//
//
//    const double abSquared = (a * a) + (b * b);
//
//    for (i = 1; i < 5; i++) {
//        /* Compute distance from each of the points to that line    */
//        const double distance = a * V[i].x + b * V[i].y + c;
//        if (distance > 0.0)
//            max_distance_above = std::max((distance * distance) / abSquared, max_distance_above);
//        else if (distance < 0.0)
//            max_distance_below = std::min(-(distance * distance) / abSquared, max_distance_below);
//    }
//    /*  Implicit equation for zero line */
//    const double a1 = 0.0;
//    const double b1 = 1.0;
//    const double c1 = 0.0;
//
//    /*  Implicit equation for "above" line */
//    a2 = a;
//    b2 = b;
//    c2 = c + max_distance_above;
//
//    det = a1 * b2 - a2 * b1;
//    dInv = 1.0/det;
//
//    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
//
//    /*  Implicit equation for "below" line */
//    a2 = a;
//    b2 = b;
//    c2 = c + max_distance_below;
//
//    det = a1 * b2 - a2 * b1;
//    dInv = 1.0/det;
//
//    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
//
//    /* Compute intercepts of bounding box    */
//    left_intercept = std::min(intercept_1, intercept_2);
//    right_intercept = std::max(intercept_1, intercept_2);
//
//    error = 0.5 * (right_intercept-left_intercept);
//    const double error_better = fabs((max_distance_below-max_distance_above)/a)/2;
//
//    return (error_better < EPSILON) ? 1 : 0;
//}

/** Compute intersection of chord from first control point to last
*  	with 0-axis. */
/* NOTE: "T" and "Y" do not have to be computed, and there are many useless
* operations in the following (e.g. "0.0 - 0.0").
*/
double ComputeXIntercept(const XY V[6])
{
    //returns the x offset where the V[0]->V[5] line crosses the X axis
    const double XNM = V[5].x - V[0].x;
    const double YNM = V[5].y - V[0].y;
    const double XMK = V[0].x;
    const double YMK = V[0].y;

    return XMK - XNM*YMK / YNM;
}


/** Evaluate a Bezier curve at a particular parameter value
*      Fill in control points for resulting sub-curves if "Left" and
*	"Right" are non-null.*/
void Bezier(const XY V[6], double t, XY Left[6], XY Right[6])
{
    XY 	Vtemp[6][6];

    /* Copy control points	*/
    for (unsigned j = 0; j <= 5; j++)
        Vtemp[0][j] = V[j];

    /* Triangle computation	*/
    for (unsigned i = 1; i <= 5; i++)
        for (unsigned j = 0; j <= 5 - i; j++)
            Vtemp[i][j] = (1.0 - t) * Vtemp[i-1][j] + t * Vtemp[i-1][j+1];

    for (unsigned j = 0; j <= 5; j++)
        Left[j] = Vtemp[j][0];

    for (unsigned j = 0; j <= 5; j++)
        Right[j] = Vtemp[5-j][j];
}


/** Given a 5th-degree equation in Bernstein-Bezier form, find
*	all of the roots in the interval [0, 1].  Return the number
*	of roots found. */
unsigned FindRoots(XY w[6], double t[5], unsigned depth)
{
    XY 	Left[6],	/* New left and right 		*/
        Right[6];	/* control polygons		*/

    switch (CrossingCount(w)) {
    case 0: 	/* No solutions here	*/
        return 0;
    case 1:
        /* Unique solution	*/
        /* Stop recursion when the tree is deep enough	*/
        /* if deep enough, return 1 solution at midpoint 	*/
        if (depth >= MAXDEPTH) {
            t[0] = (w[0].x + w[5].x) / 2.0;
            return 1;
        }
        if (ControlPolygonFlatEnough(w)) {
            t[0] = ComputeXIntercept(w);
            return 1;
        }
    }

    /* Otherwise, solve recursively after	*/
    /* subdividing control polygon		*/
    Bezier(w, 0.5, Left, Right);
    const unsigned left_count = FindRoots(Left, t, depth+1);
    const unsigned right_count = FindRoots(Right, t+left_count, depth+1);

    /* Send back total number of solutions	*/
    return (left_count+right_count);
}


/** Return a series of parameter values potentially closest to 'p'*/
unsigned Edge::SolveForDistance(const XY &p, double pos[5]) const
{
    /*  Convert problem to 5th-degree Bezier form	*/
    static const double z[3][4] = {	/* Precomputed "z" for cubics	*/
        {1.0, 0.6, 0.3, 0.1},
        {0.4, 0.6, 0.6, 0.4},
        {0.1, 0.3, 0.6, 1.0},
    };
    XY c[4], d[3];

    /*Determine the c's -- these are vectors created by subtracting*/
    /* point P from each of the control points				*/
    c[0] = start() - p;
    c[1] = c1() - p;
    c[2] = c2() - p;
    c[3] = end() - p;

    /* Determine the d's -- these are vectors created by subtracting*/
    /* each control point from the next					*/
    //for (i = 0; i <= DEGREE - 1; i++) {
    //    d[i] = V2ScaleII(V2Sub(&V[i+1], &V[i], &d[i]), 3.0);
    //}
    d[0] = 3*(c1()-start());
    d[1] = 3*(c2()-c1());
    d[2] = 3*(end()-c2());

    // control-points for Bezier curve whose zeros represent candidates for closest point to the input parametric curve
    XY w[6] = {{0, 0}, {0.2, 0}, {0.4, 0}, {0.6, 0}, {0.8, 0}, {1, 0}};
    for (int k = 0; k <= 5; k++) {
        const unsigned lb = std::max(0, k - 2);
        const unsigned ub = std::min(k, 3);
        for (unsigned i = lb; i <= ub; i++) {
            const int j = k - i;
            w[k].y += d[j].DotProduct(c[i]) * z[j][i];
        }
    }

    /* Find all possible roots of 5th-degree equation */
    return FindRoots(w, pos, 0);
}


/** Extend the bezier beyond its start (t<0) or its end (t>1).
 * If t is [0..1] we assert and do nothing, we dont clip. */
Edge Edge::ExtendBezier(double t) const
{
    Edge ret(*this);
    if (IsStraight() || (0<=t && t<=1)) {
        _ASSERT(0);
    } else if (t<0) {
        //param of the original startpoint in the new curve
        const double p = -t/(1-t);
        ret.c2() = end() + (c2()-end())/(1-p);
        const XY tmp = c2() + (c1()-c2())/(1-p);
        ret.c1() = ret.c2() + (tmp-ret.c2())/(1-p);
        ret.start() = Split(t);
    } else {
        //param of the original startpoint in the new curve
        const double p = 1/t;
        ret.c1() = start() + (c1()-start())/p;
        const XY tmp = c1() + (c2()-c1())/p;
        ret.c2() = ret.c1() + (tmp-ret.c1())/p;
        ret.end() = Split(t);
    }
    return ret;
}

/** Return a series of parameter values where the x or y coordinate of the curve is 'y'.
 * @param [in] v The x or 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. **/
template <bool isY>
unsigned Edge::atXorY(double v, double roots[3], double precision) const
{
    _ASSERT(!IsStraight());
    //A==start, B==c1, C==c2, D==end
    //Curve is: (-A+3B-3C+D)t^3 + (3A-6B+3C)t^2 + (-3A+3B)t + A

    double coeff[4] = {start()[isY]-v,
                       3*(c1()[isY]-start()[isY]),
                       3*(start()[isY]+c2()[isY])-6*c1()[isY],
                       -start()[isY]+3*(c1()[isY]-c2()[isY])+end()[isY]};
    unsigned ret = solve_degree3(coeff, roots);
    for (unsigned i = 0; i<ret; /*nope*/) {
        //if the root is significantly outside the [0..1] range, drop it
        if (test_smaller(roots[i], 0) || test_smaller(1, roots[i]))
            goto kill;
        if constexpr (isY) {
            if (!GetHullYRange().Expand(1).IsWithinBool(Split(roots[i])[isY]))
                goto kill;
        } else {
            if (!GetHullXRange().Expand(1).IsWithinBool(Split(roots[i])[isY]))
                goto kill;
        }
        if (precision<=0 || RefineXorY<isY>(v, roots[i], precision)) {
            //Success, good cp;
            i++;
            continue;
        }
    kill:
        //remove this root
        for (unsigned k = i+1; k<ret; k++)
            roots[k-1] = roots[k];
        ret--;
    }
    std::sort(roots, roots+ret);
    return unsigned(std::unique(roots, roots+ret) - roots);  //remove duplicate solutions, unsigned is large enough
}


/** Refine the X or Y coordinate of crosspoint. 'v' is where we need to cross
 *'root' is the parameter, that we refine.*/
template <bool isY>
bool Edge::RefineXorY(double v, double &root, double precision) const
{
    //refine cps
    const double coeff[4] = {start()[isY],
                             3*(c1()[isY]-start()[isY]),
                             3*(start()[isY]-2*c1()[isY]+c2()[isY]),
                             -start()[isY]+3*(c1()[isY]-c2()[isY])+end()[isY]};
    unsigned count = 1000;
    while (1) {
        const double vv = ((coeff[3]*root + coeff[2])*root + coeff[1])*root + coeff[0];
        if (fabs(vv-v)<precision) break; //done. This means a precision of CP_PRECISION pixels
        //approximate with the derivative
        //Derivatie is: 3*(-A+3B-3C+D)t^2 + (6A-12B+6C)t + (-3A+3B)
        const double derive[3] = {coeff[1], 2*coeff[2], 3*coeff[3]};
        const double dv_per_dpos = (derive[2]*root +derive[1])*root + derive[0];
        //protect against vertical tangent
        if (fabs(dv_per_dpos)<1e-10)
            break;
        root += (v-vv)/dv_per_dpos;
        if (--count) {
            //If we could not get the precision good enough after this many iterations, drop cp
            return false;
        }
    }
    //re-test the refined cp
    //if the root is significantly outside the [0..1] range, drop it
    //also, snap to endpoints
    if (!between01_adjust(root))
        return false;
    //if the root is outside the bounding box of the edge drop it
    if (isY) {
        if (!GetHullYRange().Expand(1).IsWithinBool(Split(root)[isY]))
            return false;
    } else {
        if (!GetHullXRange().Expand(1).IsWithinBool(Split(root)[isY]))
            return false;
    }
    return true;
}


/** Expands the edge.
 *
 * This takes the direction of the edge to determine the 'outside' side of the
 * edge to expand towards (or away from in case of a negative `gap`).
 * (That is, going start->end a positive 'gap' will move the edge to the left side.)
 * We also return a previous and next tangent points of the expanded curve.
 * This is because a bezier might have been converted to a series of sub-curves at
 * expansion and the last/first one of them may have degenerated to a line.
 * In this case some of the Expand() algorithms fail.
 * The points returned represent the 'ideal' tangents as if the lines did not
 * degenerate. We return them even if nothing degenerated even for straight edges.
 * @param [in] gap The amount to expand (or shrink if <0)
 * @param [out] expanded Append the expanded edges to this. The elements of this container
 *                       must have a constructor (XY,XY,XY,XY,bool) for beziers and one
 *                       (XY,XY,bool) for straight lines.
 * @param [out] prev_tangent A point that is a previous tangent for the expanded edge.
 * @param [out] next_tangent A point that is a next tangent for the expanded edge.
 * @param [out] original Append the original edge to this. If the expansion
 *                       requires to split the edge to several pieces append
 *                       the split chunks of the original here.*/
bool Edge::CreateExpand(double gap, std::list<Edge> &expanded, XY &prev_tangent, XY &next_tangent,
                        std::vector<Edge>  *original) const
{
    _ASSERT(!IsDot());
    if (IsDot()) return false;
    if (gap==0) {
        expanded.push_back(*this);
        if (original)
            original->push_back(*this);
        return true;
    }
    if (IsStraight()) {
        const double length = start().Distance(end());
        const XY wh = (end()-start()).Rotate90CCW()/length*gap;
        expanded.emplace_back(start()+wh, end()+wh, IsVisible());
        if (original)
            original->push_back(*this);
        prev_tangent = 2*start()-end() + wh;
        next_tangent = 2*end()-start() + wh;
        return true;
    }
    //calculate X and Y extremes and inflection points
    //we can have at most 2 of each. Plus 2 for the two endpoints
    std::vector<double> t(8);
    //Start with extremes. X first
    //A==start, B==c1, C==c2, D==end
    //Curve is: (-A+3B-3C+D)t^3 + (3A-6B+3C)t^2 + (-3A+3B)t + A
    //derivative is: (-3A+9B-9C+3D)t^2 + (6A-12B+6C)t + (-3A+3B)
    //substitutes are        [2]    t^2 +     [1]    t +    [0]
    double Ycoeff[3] = {3*(c1().y-start().y),
        6*(start().y-2*c1().y+c2().y),
        3*(-start().y+3*(c1().y-c2().y)+end().y)};
    unsigned num = solve_degree2(Ycoeff, &t[1]);
    double Xcoeff[3] = {3*(c1().x-start().x),
        6*(start().x-2*c1().x+c2().x),
        3*(-start().x+3*(c1().x-c2().x)+end().x)};
    num += solve_degree2(Xcoeff, &t[num+1]);
    //The inflection ponts are
    const XY a = c1()-start(), b = c2()-c1()-a, c = end()-c2()-a-2*b;
    double Icoeff[3] = {a.x*b.y - a.y*b.x,
        a.x*c.y - a.y*c.x,
        b.x*c.y - b.y*c.x};
    num += solve_degree2(Icoeff, &t[num+1]);
    t.resize(num+1);
    //Second derivative is: (-6A+18B-18C+6D)t + (6A-12B+6C)
    double Y2coeff[2] = {6*(start().y-2*c1().y+c2().y),
        6*(-start().y+3*(c1().y-c2().y)+end().y)};
    if (!test_zero(Y2coeff[1])) {
        double s = -Y2coeff[0]/Y2coeff[1];
        if (between01_adjust(s))
            t.push_back(s);
    }
    double X2coeff[2] = {6*(start().x-2*c1().x+c2().x),
        6*(-start().x+3*(c1().x-c2().x)+end().x)};
    if (!test_zero(X2coeff[1])) {
        double s = -X2coeff[0]/X2coeff[1];
        if (between01_adjust(s))
            t.push_back(s);
    }
    //prune roots outside [0,1] range or close to 0 or one
    for (size_t k = t.size()-1; k>0; k--) //dont touch the first element
        if (t[k] < SMALL_NUM || t[k]>1-SMALL_NUM)
            t.erase(t.begin()+k);
    std::sort(++t.begin(), t.end());
    t[0] = 0;
    t.push_back(1);
    //remove duplicates (like a cusp at an X extreme)
    for (size_t k = t.size()-1; k>0; k--)
        if (contour::test_equal(t[k-1], t[k]))
            t.erase(t.begin()+k);

    std::list<Edge> tmp;
    const size_t orig_offset = original ? original->size() : 0;
    _ASSERT(t.size()>=2);
    if (t.size()==2) {
        _ASSERT(t[0]==0 && t[1]==1);
        CreateExpandOneSegment(gap, tmp, original);
    } else {
        for (unsigned k = 0; k<t.size()-1; k++) {
            Edge e(*this, t[k], t[k+1]);
            e.CreateExpandOneSegment(gap, tmp, original);
            //If e degenerated to a line, we need to adjust the endpoints to
            //expand along the original tangents.
            if (e.IsStraight() && k==0)
                tmp.front().start() = start() + (c1()-start()).Rotate90CCW().Normalize()*gap;
            if (e.IsStraight() && k==t.size()-1)
                tmp.back().end() = end() + (end()-c2()).Rotate90CCW().Normalize()*gap;
        }
    }

    //Remove loops in the created set - beware some of them degenerated to a segment
    RemoveLoop(tmp, tmp.begin(), tmp.end(), true, original, orig_offset);
    if (tmp.size()==0) return false;

    //Make sure connecting edges really connect
    for (auto i = tmp.begin(), j = ++tmp.begin(); j!=tmp.end(); i++, j++)
        i->end() = j->start() = (i->end() + j->start())/2;
    //Generate tangent points. These point towards the original direction of the edge ends
    //and ignore if the ends changed direction.
    prev_tangent = tmp.front().start() + (PrevTangentPoint(0)-start());
    next_tangent = tmp.back().end() + (NextTangentPoint(1)-end());
    expanded.splice(expanded.end(), tmp);
    return true;
}

/** Helper for CreateExpand(). */
bool Edge::CreateExpandOneSegment(double gap, std::list<Edge> &expanded, std::vector<Edge>  *original) const
{
    if (IsStraight()) {
        const double length = start().Distance(end());
        const XY wh = (end()-start()).Rotate90CCW()/length*gap;
        expanded.emplace_back(start()+wh, end()+wh, IsVisible());
        if (original)
            original->push_back(*this);
        return true;
    }
    //test assumptions
    //XY dummy;
    //_ASSERT(ELineCrossingType::INSIDE == crossing_line_line(start, c2, end, c1, dummy));
    //Tiller-Hansson: expand the start,c1,c2,end polygon.
    const double l0 = start().Distance(c1());
    const double l12 = c1().Distance(c2());
    const double l3 = c2().Distance(end());
    const XY wh0 = test_zero(l0) ? XY(0,0) : (c1()-start()).Rotate90CCW()/l0*gap;
    const XY wh12 = test_zero(l12) ? XY(0, 0) : (c2()-c1()).Rotate90CCW()/l12*gap;
    const XY wh3 = test_zero(l3) ? XY(0, 0) : (end()-c2()).Rotate90CCW()/l3*gap;
    const XY new_start = start()+wh0;
    const XY new_end = end()+wh3;
    XY new_c1, new_c2;
    //If we can compute new c1 and c2 from the intersection of the offset hull lines, do so.
    if (ELineCrossingType::PARALLEL == crossing_line_line(new_start, c1()+wh0, c1()+wh12, c2()+wh12, new_c1) ||
        ELineCrossingType::PARALLEL == crossing_line_line(c1()+wh12, c2()+wh12, c2()+wh3, new_end, new_c2)) {
        //if not, project the control points towards the original curve
        XY C1, C2;
        double dumm;
        //get the normal for all 4 control points
        const double l1 = Distance(c1(), C1, dumm);
        const double l2 = Distance(c2(), C2, dumm);
        const bool left_1 = (start()-C1).Rotate90CCW().DotProduct(c1()-C1) < 0;
        const bool left_2 = (start()-C1).Rotate90CCW().DotProduct(c1()-C1) < 0;
        const XY wh1 = test_zero(l1) ? XY(0, 0) : (c1()-C1)/l1*(left_1 ? gap : -gap);
        const XY wh2 = test_zero(l2) ? XY(0, 0) : (c2()-C2)/l2*(left_2 ? gap : -gap);
        new_c1 = c1()+wh1;
        new_c2 = c2()+wh2;
    }
    expanded.emplace_back(new_start, new_end, new_c1, new_c2, IsVisible());

    //Test error of the resulting offset curve
    const double EPSILON = 0.1;
    //offset the middle point of the result along the normal of the result back onto the original
    const XY Mid = expanded.back().Split();
    const XY Normal = (expanded.back().NextTangentPoint(0.5)-Mid).Rotate90CW()+Mid;
    double pos_me[9], pos_seg[9];
    unsigned num = CrossingLine(Mid, Normal, pos_me, pos_seg);
    //_ASSERT(num);
    //find crossing closest to Mid (pos_seg==0)
    double minpos = MaxVal(minpos);
    int p = -1;
    for (unsigned u = 0; u<num; u++)
        if (between01(pos_me[u]) && minpos>fabs(pos_seg[u])) {
            p = u;
            minpos = fabs(pos_seg[u]);
        }
    //if the normal does not even project back to the original
    //or if the projected point is too far away, we subdivide
    if (p==-1 || fabs(fabs(gap)-Split(pos_me[p]).Distance(Mid)) > EPSILON) {
        expanded.pop_back();
        Edge E1, E2;
        Split(E1, E2);
        std::list<Edge> tmp;
        E1.CreateExpandOneSegment(gap, tmp, original);
        E2.CreateExpandOneSegment(gap, tmp, original);
        //Set the endpoints as we have calculated here. These are to prevent
        //mis-aligned expansion if E1 or E2 became straight by the split above
        tmp.front().start() = new_start;
        tmp.back().end() = new_end;
        expanded.splice(expanded.end(), tmp);
    } else
        //else we are OK with the emplaced offset curve
        if (original)
            original->push_back(*this);

    return true;
}

/** Expands the edge with different distance at its start and end.
 *
 * This takes the direction of the edge to determine the 'outside' side of the
 * edge to expand towards (or away from in case of a negative `gap`).
 * (That is, going start->end a positive 'gap' will move the edge to the left side.)
 * We also return a previous and next tangent points of the expanded curve.
 * This is because a bezier might have been converted to a series of sub-curves at
 * expansion and the last/first one of them may have degenerated to a line.
 * In this case some of the Expand() algorithms fail.
 * The points returned represent the 'ideal' tangents as if the lines did not
 * degenerate. We return them even if nothing degenerated even for straight edges.
 * @param [in] gap1 The amount to expand (or shrink if <0) at the start
 * @param [in] gap2 The amount to expand (or shrink if <0) at the end. Distance will grow
 *                  linearly between the two. 'gap1' and 'gap2' must have the same sign
 *                  or they may be zero.
 * @param [in] cos The cosine of the angle of the extended edge with the original, pre-computed
 * @param [in] sin The sine of the angle of the extended edge with the original, pre-computed
 * @param [out] expanded Append the expanded edges to this. The elements of this container
 *                       must have a constructor (XY,XY,XY,XY,bool) for beziers and one
 *                       (XY,XY,bool) for straight lines.
 * @param [out] prev_tangent A point that is a previous tangent for the expanded edge.
 * @param [out] next_tangent A point that is a next tangent for the expanded edge.
 * @param [out] original Append the original edge to this. If the expansion
 *                       requires to split the edge to several pieces append
 *                       the split chunks of the original here.*/
bool Edge::CreateLinearExpand(double gap1, double gap2, double cos, double sin,
                              std::list<Edge> &expanded, XY &prev_tangent, XY &next_tangent,
                              std::vector<Edge>  *original) const
{
    _ASSERT(!IsDot());
    if (IsDot()) return false;
    if (IsStraight()) {
        const double length = start().Distance(end());
        const XY wh = (end()-start()).Rotate90CCW()/length;
        const XY new_start = start()+wh*gap1;
        const XY new_end = end()+wh*gap2;
        expanded.emplace_back(new_start, new_end, IsVisible());
        if (original)
            original->push_back(*this);
        prev_tangent = 2*new_start-new_end;
        next_tangent = 2*new_end-new_start;
        return true;
    }
    //calculate X and Y extremes and inflection points
    //we can have at most 2 of each. Plus 2 for the two endpoints
    std::vector<double> t(8);
    //Start with extremes. X first
    //A==start, B==c1, C==c2, D==end
    //Curve is: (-A+3B-3C+D)t^3 + (3A-6B+3C)t^2 + (-3A+3B)t + A
    //derivative is: (-3A+9B-9C+3D)t^2 + (6A-12B+6C)t + (-3A+3B)
    //substitutes are        [2]    t^2 +     [1]    t +    [0]
    double Ycoeff[3] = {3*(c1().y-start().y),
        6*(start().y-2*c1().y+c2().y),
        3*(-start().y+3*(c1().y-c2().y)+end().y)};
    unsigned num = solve_degree2(Ycoeff, &t[1]);
    double Xcoeff[3] = {3*(c1().x-start().x),
        6*(start().x-2*c1().x+c2().x),
        3*(-start().x+3*(c1().x-c2().x)+end().x)};
    num += solve_degree2(Xcoeff, &t[num+1]);
    //The inflection ponts are
    const XY a = c1()-start(), b = c2()-c1()-a, c = end()-c2()-a-2*b;
    double Icoeff[3] = {a.x*b.y - a.y*b.x,
        a.x*c.y - a.y*c.x,
        b.x*c.y - b.y*c.x};
    num += solve_degree2(Icoeff, &t[num+1]);
    t.resize(num+1);
    //Second derivative is: (-6A+18B-18C+6D)t + (6A-12B+6C)
    double Y2coeff[2] = {6*(start().y-2*c1().y+c2().y),
        6*(-start().y+3*(c1().y-c2().y)+end().y)};
    if (!test_zero(Y2coeff[1])) {
        double s = -Y2coeff[0]/Y2coeff[1];
        if (between01_adjust(s))
            t.push_back(s);
    }
    double X2coeff[2] = {6*(start().x-2*c1().x+c2().x),
        6*(-start().x+3*(c1().x-c2().x)+end().x)};
    if (!test_zero(X2coeff[1])) {
        double s = -X2coeff[0]/X2coeff[1];
        if (between01_adjust(s))
            t.push_back(s);
    }
    //prune roots outside [0,1] range or close to 0 or one
    for (size_t k = t.size()-1; k>0; k--) //don't touch the first element
        if (t[k] < SMALL_NUM || t[k]>1-SMALL_NUM)
            t.erase(t.begin()+k);
    std::sort(++t.begin(), t.end());
    t[0] = 0;
    t.push_back(1);
    //remove duplicates (like a cusp at an X extreme)
    for (size_t k = t.size()-1; k>0; k--)
        if (contour::test_equal(t[k-1], t[k]))
            t.erase(t.begin()+k);

    std::list<Edge> tmp;
    const size_t orig_offset = original ? original->size() : 0;
    _ASSERT(t.size()>=2);
    if (t.size()==2) {
        _ASSERT(t[0]==0 && t[1]==1);
        CreateLinearExpandOneSegment(gap1, gap2, cos, sin, tmp, original);
    } else {
        for (unsigned k = 0; k<t.size()-1; k++) {
            Edge e(*this, t[k], t[k+1]);
            const double fraction1 = t[k]; ///TODO: this shall be length-based
            const double fraction2 = t[k+1]; ///TODO: this shall be length-based
            const double local_gap1 = (gap2-gap1)*fraction1 + gap1;
            const double local_gap2 = (gap2-gap1)*fraction2 + gap1;
            e.CreateLinearExpandOneSegment(local_gap1, local_gap2, cos, sin, tmp, original);
            //If e degenerated to a line, we need to adjust the endpoints to
            //expand along the original tangents.
            if (e.IsStraight() && k==0)
                tmp.front().start() = start() + (c1()-start()).Rotate90CCW().Normalize()*local_gap1;
            if (e.IsStraight() && k==t.size()-1)
                tmp.back().end() = end() + (end()-c2()).Rotate90CCW().Normalize()*local_gap2;
        }
    }

    //Remove loops in the created set - beware some of them degenerated to a segment
    RemoveLoop(tmp, tmp.begin(), tmp.end(), true, original, orig_offset);
    if (tmp.size()==0) return false;

    //Make sure connecting edges really connect
    for (auto i = tmp.begin(), j = ++tmp.begin(); j!=tmp.end(); i++, j++)
        i->end() = j->start() = (i->end() + j->start())/2;
    //Generate tangent points. These point towards the original direction of the edge ends
    //and ignore if the ends changed direction.
    prev_tangent = tmp.front().start() + (PrevTangentPoint(0)-start()).Rotate(cos, -sin);
    next_tangent = tmp.back().end() + (NextTangentPoint(1)-end()).Rotate(cos, sin);
    expanded.splice(expanded.end(), tmp);
    return true;
}

/** Helper for CreateLinearExpand().
 * if the path goes ->
 * cos, sin are efficients of a clockwise turn of the angle for the top part (where gap1+2>0)*/
bool Edge::CreateLinearExpandOneSegment(double gap1, double gap2, double cos, double sin, std::list<Edge> &expanded, std::vector<Edge> *original) const
{
    if (IsStraight()) {
        straight:
        const double length = start().Distance(end());
        const XY wh = (end()-start()).Rotate90CCW()/length;
        expanded.emplace_back(start()+wh*gap1, end()+wh*gap2, IsVisible());
        if (original)
            original->push_back(*this);
        return true;
    }
    if (1) {
        //Tiller-Hansson: expand the start,c1,c2,end polygon.
        const double l0 = start().Distance(c1());
        const double l12 = c1().Distance(c2());
        const double l3 = c2().Distance(end());
        constexpr double threshold = 1e-8;
        if (l0<threshold || l3<threshold || l12<threshold) {
            if (l0<threshold && l3<threshold) //control points close to start/end: we are straight
                goto straight;
            const XY c = l3<threshold ? c1() : c2(); //pick the one control point not close to its start/end.
            //The above works when none of them is close to start/end, but they are close to each other (l12<threshold)
            //Any of these mean, we are only a quadratic bezier curve
            const XY wh0 = (c-start()).Rotate90CCW().Normalize()*gap1;
            const XY wh1 = (end()-c).Rotate90CCW().Normalize()*gap2;
            const XY new_start = start()+wh0;
            const XY new_end = end()+wh1;
            //If we can compute new c from the intersection of the offset hull lines, do so.
            XY new_c;
            if (ELineCrossingType::PARALLEL == crossing_line_line(new_start, c+wh0, c+wh1, new_end, new_c)) {
                //if not, project the control point towards the original curve
                XY C;
                double dumm;
                //get the normal for all 4 control points
                const double l1 = Distance(c, C, dumm);
                if (l1<threshold)
                    goto straight;
                const bool left = (start()-C).Rotate90CCW().DotProduct(c-C) < 0;
                const XY wh = (c-C)/l1*(left ? gap1 : -gap1);
                new_c = c+wh;
            }
            new_c = new_c.RotateAround(new_start, cos, -sin);
            expanded.emplace_back(new_start, new_end, new_c, new_c, IsVisible());
        } else {
            //proper quadratic bezier
            const XY wh0 = (c1()-start()).Rotate90CCW()/l0*gap1;
            const XY wh1 = (c2()-c1()).Rotate90CCW()/l12*gap1;
            const XY wh2 = (c2()-c1()).Rotate90CCW()/l12*gap2;
            const XY wh3 = (end()-c2()).Rotate90CCW()/l3*gap2;
            const XY new_start = start()+wh0;
            const XY new_end = end()+wh3;
            XY new_c1, new_c2;
            //If we can compute new c1 and c2 from the intersection of the offset hull lines, do so.
            if (ELineCrossingType::PARALLEL == crossing_line_line(new_start, c1()+wh0, c1()+wh1, c2()+wh2, new_c1) ||
                ELineCrossingType::PARALLEL == crossing_line_line(c1()+wh1, c2()+wh2, c2()+wh3, new_end, new_c2)) {
                //if not, project the control points towards the original curve
                XY C1, C2;
                double dumm;
                //get the normal for all 4 control points
                const double l1 = Distance(c1(), C1, dumm);
                const double l2 = Distance(c2(), C2, dumm);
                if (l1>threshold && l2>threshold) {
                    const bool left_1 = (start()-C1).Rotate90CCW().DotProduct(c1()-C1) < 0;
                    const bool left_2 = (start()-C1).Rotate90CCW().DotProduct(c1()-C1) < 0;
                    const XY wh1 = (c1()-C1)/l1*(left_1 ? gap1 : -gap1);
                    const XY wh2 = (c2()-C2)/l2*(left_2 ? gap2 : -gap2);
                    new_c1 = c1()+wh1;
                    new_c2 = c2()+wh2;
                }
            }
            new_c1 = new_c1.RotateAround(new_start, cos, -sin);
            new_c2 = new_c2.RotateAround(new_end, cos, -sin);
            expanded.emplace_back(new_start, new_end, new_c1, new_c2, IsVisible());
        }
    } else {
        //alternative algorithm - not so good
        const double l0 = start().Distance(c1());
        const double l3 = c2().Distance(end());
        const XY wh0 = (c1()-start()).Rotate90CCW()/l0*gap1;
        const XY wh3 = (end()-c2()).Rotate90CCW()/l3*gap2;
        const XY new_start = start()+wh0;
        const XY new_end = end()+wh3;
        const XY new_c1 = (c1()+wh0).RotateAround(new_start, cos, -sin);
        const XY new_c2 = (c2()+wh3).RotateAround(new_end, cos, -sin);
        expanded.emplace_back(new_start, new_end, new_c1, new_c2, IsVisible());
    }

    //Test error of the resulting curve
    const double EPSILON = 0.1;
    //offset the middle point of the result along the normal of the result
    //(compensated by sin and cos) back onto the original
    Edge E1, E2;
    Split(E1, E2);
    const XY Normal = (expanded.back().NextTangentPoint(0.5)-E1.end()).Rotate90CW().Rotate(cos, sin)+E1.end();
    double pos_me[9], pos_seg[9];
    unsigned num = CrossingLine(E1.end(), Normal, pos_me, pos_seg);
    _ASSERT(num);
    //find crossing closest to Mid (pos_seg==0)
    double minpos = MaxVal(minpos);
    int p = -1;
    for (unsigned u = 0; u<num; u++)
        if (between01(pos_me[u]) && minpos>fabs(pos_seg[u])) {
            p = u;
            minpos = fabs(pos_seg[u]);
        }
    const double l1 = E1.GetLength(), l2 = E2.GetLength();
    const double gap = (l1*gap2 + l2*gap1)/(l1+l2);
    //if the normal does not even project back to the original
    //or if the projected point is too far away, we subdivide
    if (p==-1 || fabs(fabs(gap)-Split(pos_me[p]).Distance(E1.end())) > EPSILON) {
        std::list<Edge> tmp;
        E1.CreateLinearExpandOneSegment(gap1, gap, cos, sin, tmp, original);
        E2.CreateLinearExpandOneSegment(gap, gap2, cos, sin, tmp, original);
        //Set the endpoints as we have calculated here. These are to prevent
        //mis-aligned expansion if E1 or E2 became straight by the split above
        tmp.front().start() = expanded.back().start();
        tmp.back().end() = expanded.back().end();
        expanded.pop_back();
        expanded.splice(expanded.end(), tmp);
    } else
        //else we are OK with the emplaced offset curve
        if (original)
            original->push_back(*this);
    return true;
}

std::string Edge::Dump(bool precise) const
{
    std::string ret = "Edge("+start().Dump(precise)+", "+end().Dump(precise);
    if (!IsStraight())
        ret += ", "+c1().Dump(precise)+", "+c2().Dump(precise);
    if (IsInternalMarked() || !IsVisible())
        ret += IsVisible() ? ", true" : ", false";
    if (IsInternalMarked())
        ret += ", true";
    return ret+")";
}


/** Determines how much one edge is below us.
 * @param [in] o The other edge to consider.
 * @param [out] touchpoint The y coordinate where 'o' would touch
 *                         us if shifted upwards by what we return.
 * @param [in] min_so_far If o is further down below us by more than this value,
 *                        return this value. This also allows optimization for
 *                        bezier curves: if the hull or bounding box of 'o' is
 *                        below our hull or bounding box more than 'min_so_far'
 *                        we do not need to calculate the exact offset between
 *                        us and 'o', just return min_so_far.
 * @returns how much 'o' should be shifted upwards to touch us. If o is above us
 *          or overlaps us, we return a negative value (since o need shifting
 *          down). If o is besides us, we consider the offset CONTOUR_INFINITE
 *          and return min_so_far.*/
double Edge::OffsetBelow(const Edge &o, double &touchpoint, double min_so_far) const
{
    const Range AB = GetHullXRange();
    const Range MN = o.GetHullXRange();
    if (!AB.Overlaps(MN)) return min_so_far;
    if (GetHullYRange().till+min_so_far <= o.GetHullYRange().from)
        return min_so_far;

    if (IsStraight() && o.IsStraight()) {
        //calc for two straight edges
        const double x1 = std::max(AB.from, MN.from);
        const double x2 = std::min(AB.till, MN.till);
        if (start().x == end().x) {
            double off;
            if (o.start().x == o.end().x)
                off = std::min(o.start().y, o.end().y) - std::max(start().y, end().y); //here all x coordinates must be the same
            else
                off = (o.end().y-o.start().y)/(o.end().x-o.start().x)*(start().x-o.start().x) + o.start().y -
                      std::max(start().y, end().y);
            if (off<min_so_far) {
                touchpoint = std::max(start().y, end().y);
                return off;
            }
            return min_so_far;
        }
        if (o.start().x == o.end().x) {
            double tp = (start().y-end().y)/(start().x-end().x)*(o.start().x-start().x) + start().y;
            double off = std::min(o.start().y, o.end().y) - tp;
            if (off<min_so_far) {
                touchpoint = tp;
                return off;
            }
            return min_so_far;
        }
        const double y1 = ((start().y-end().y)/(start().x-end().x)*(x1-start().x) + start().y);
        const double y2 = ((start().y-end().y)/(start().x-end().x)*(x2-start().x) + start().y);
        const double diff1 = (o.end().y-o.start().y)/(o.end().x-o.start().x)*(x1-o.start().x) + o.start().y - y1;
        const double diff2 = (o.end().y-o.start().y)/(o.end().x-o.start().x)*(x2-o.start().x) + o.start().y - y2;
        if (std::min(diff1, diff2)<min_so_far) {
            touchpoint = diff1<diff2 ? y1 : y2;
            return std::min(diff1, diff2);
        }
        return min_so_far;
    }
    if (IsStraight()) {
        if (!o.CreateBoundingBox().x.Overlaps(Range(std::min(start().x, end().x), std::max(start().x, end().x))))
            return min_so_far;
        Edge E1, E2;
        o.Split(E1, E2);
        min_so_far = OffsetBelow(E1, touchpoint, min_so_far);
        min_so_far = OffsetBelow(E1, touchpoint, min_so_far);
        return min_so_far;
    }
    if (o.IsStraight()) {
        if (!CreateBoundingBox().x.Overlaps(Range(std::min(o.start().x, o.end().x), std::max(o.start().x, o.end().x))))
            return min_so_far;
        Edge E1, E2;
        Split(E1, E2);
        min_so_far = E1.OffsetBelow(o, touchpoint, min_so_far);
        min_so_far = E2.OffsetBelow(o, touchpoint, min_so_far);
        return min_so_far;
    }
    if (!CreateBoundingBox().x.Overlaps(o.CreateBoundingBox().x))
        return min_so_far;
    Edge E1, E2, F1, F2;
    Split(E1, E2);
    o.Split(F1, F2);
    min_so_far = E1.OffsetBelow(F1, touchpoint, min_so_far);
    min_so_far = E2.OffsetBelow(F1, touchpoint, min_so_far);
    min_so_far = E1.OffsetBelow(F2, touchpoint, min_so_far);
    min_so_far = E2.OffsetBelow(F2, touchpoint, min_so_far);
    return min_so_far;
}


/** Return the bezier parameters of inflection points in 't' and their number [0..2]
 * @param [out] t The positions of the inflection points.
 * @returns the number of inflection points [0..2]
 * If the edge is straight, it has no inflection points.*/
unsigned Edge::InflectionPoints(std::span<double, 2> t) const
{
    if (IsStraight()) return 0;
    std::array<XY, 4> Cf;
    //calculate Bezier coefficients
    Cf[3] = end() - 3 * c2() + 3 * c1() - start();
    Cf[2] = 3 * (start() - 2 * c1() + c2());
    Cf[1] = 3 * (c1() - start());
    Cf[0] = start();

    //find parameters of quadratic equation
    // a*t^2 + b*t + c = 0
    const double a = 3 * (Cf[2].x *Cf[3].y - Cf[2].y *Cf[3].x);
    const double b = 3 * (Cf[1].x *Cf[3].y - Cf[1].y *Cf[3].x);
    const double c = Cf[1].x *Cf[2].y - Cf[1].y *Cf[2].x;

    //here solve quadratic equations, find t parameters
    //don't forget a lot of special cases like a=0, D<0, D=0, t outside 0..1 range
    const double D = b * b - 4 * a * c;
    if (D<0) return 0;
    if (a==0) {
        //b*t + c = 0  =>  t = -c/b
        if (b==0) return 0;
        t[0] = -c/b;
        goto one_solution;
    }
    if (test_zero(D)) {
        t[0] = -b/2/a;
    one_solution:
        return test_smaller(0, t[0]) && test_smaller(t[0], 1); //if t == 0 or 1, we ignore it.
    }
    unsigned ret = 0;
    t[0] = (-b+sqrt(D))/2/a;
    if (test_smaller(0, t[0]) && test_smaller(t[0], 1))
        ret++;
    t[ret] = (-b-sqrt(D))/2/a;
    return test_smaller(0, t[ret]) && test_smaller(t[ret], 1) ? ret+1 : ret;
}





} //namespace contour
