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

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

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

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/
/** @file contour.cpp Implementation of composite contours.
 * @ingroup contour_files
 */
#include <cassert>
#include <cstdlib> //for abs
#include <limits>
#include <unordered_map>
#include "contour.h"

#ifdef _DEBUG
#include "canvas.h"
#endif

#include <cstdint>

//////ContourList/////////////////

namespace contour {

#ifndef NDEBUG
thread_local bool suppress_assert_print = false;
#endif

bool ContourList::operator <(const ContourList &b) const noexcept
{
    if (boundingBox != b.boundingBox) return boundingBox<b.boundingBox;
    if (size() != b.size()) return size()<b.size();
    for (auto i = begin(), j=b.begin(); i!=end(); i++, j++)
        if (!(*i==*j)) return *i<*j;
    return false;
}

bool ContourList::operator ==(const ContourList &b) const noexcept
{
    if (boundingBox != b.boundingBox || size() != b.size()) return false;
    for (auto i = begin(), j=b.begin(); i!=end(); i++, j++)
        if (!(*i==*j)) return false;
    return true;
}

void ContourList::Invert() noexcept
{
    for (auto &c : *this)
        c.Invert();
}

void ContourList::SwapXY() noexcept
{
    for (auto &c : *this)
        c.SwapXY();
    boundingBox.SwapXY();
}

/** Returns the relation of 'c' to the shapes in the list.
 * If 'ignore_holes' is true we ignore holes both in our shapes and in 'c'.
 * Note that the clockwiseness is ignored in RelationTo() calls.*/
EContourRelationType ContourList::RelationTo(const HoledSimpleContour &c, bool ignore_holes) const
{
    if (IsEmpty())
        return c.IsEmpty() ? REL_BOTH_EMPTY : REL_A_IS_EMPTY;
    else if (c.IsEmpty())
        return REL_B_IS_EMPTY;

    if (!boundingBox.Overlaps(c.GetBoundingBox())) return REL_APART;

    EContourRelationType res = front().RelationTo(c, ignore_holes);
    switch (res) {
    default:
    case REL_A_IS_EMPTY:
    case REL_B_IS_EMPTY:
    case REL_BOTH_EMPTY:
    case REL_IN_HOLE_APART:
        _ASSERT(0);
        FALLTHROUGH;
    case REL_OVERLAP:
        return res;
    case REL_B_INSIDE_A:
    case REL_A_INSIDE_B:
    case REL_SAME:
    case REL_APART:
    case REL_B_IN_HOLE_OF_A:
    case REL_A_IN_HOLE_OF_B:
        break;
    }

    for (auto i = ++begin(); i!=end(); i++) {
        const EContourRelationType res2 = i->RelationTo(c, ignore_holes);
        if (res2 == res) continue;
        switch (res2) {
        case REL_A_IS_EMPTY:
        case REL_B_IS_EMPTY:
        case REL_BOTH_EMPTY:
        case REL_IN_HOLE_APART:
            _ASSERT(0);
            FALLTHROUGH;
        case REL_OVERLAP:
            return res;
        case REL_APART:
        case REL_B_IN_HOLE_OF_A:
        case REL_A_IN_HOLE_OF_B:
            if (result_overlap(res)) return REL_OVERLAP;
            else res = REL_IN_HOLE_APART;
            break;
        case REL_B_INSIDE_A:
        case REL_A_INSIDE_B:
        case REL_SAME:
            if (res == REL_SAME) res = res2;
            else return REL_OVERLAP;
            break;
        }
    }
    return res;
}

/** Returns the relation of 'c' to the shapes in the list.
* If 'ignore_holes' is true we ignore holes both in our shapes and in 'c'.
* Note that the clockwiseness is ignored in RelationTo() calls.*/
EContourRelationType ContourList::RelationTo(const ContourList &c, bool ignore_holes) const
{
    if (IsEmpty())
        return c.IsEmpty() ? REL_BOTH_EMPTY : REL_A_IS_EMPTY;
    else if (c.IsEmpty())
        return REL_B_IS_EMPTY;

    if (!boundingBox.Overlaps(c.GetBoundingBox())) return REL_APART;

    EContourRelationType res = c.RelationTo(*begin(), ignore_holes);
    switch (res) {
    default:
    case REL_A_IS_EMPTY:
    case REL_B_IS_EMPTY:
    case REL_BOTH_EMPTY:
        _ASSERT(0);
        FALLTHROUGH;
    case REL_OVERLAP:
        return res;
    case REL_B_INSIDE_A:
    case REL_A_INSIDE_B:
    case REL_SAME:
    case REL_APART:
    case REL_B_IN_HOLE_OF_A:
    case REL_A_IN_HOLE_OF_B:
    case REL_IN_HOLE_APART:
        break;
    }

    auto i = ++begin();
    while (i!=end()) {
        const EContourRelationType res2 = c.RelationTo(*i, ignore_holes);
        if (res2 == res) continue;
        switch (res2) {
        case REL_A_IS_EMPTY:
        case REL_B_IS_EMPTY:
        case REL_BOTH_EMPTY:
            _ASSERT(0);
            FALLTHROUGH;
        case REL_OVERLAP:
            return res;
        case REL_APART:
        case REL_B_IN_HOLE_OF_A:
        case REL_A_IN_HOLE_OF_B:
        case REL_IN_HOLE_APART:
            if (result_overlap(res)) return REL_OVERLAP;
            else res = REL_IN_HOLE_APART;
            break;
        case REL_B_INSIDE_A:
        case REL_A_INSIDE_B:
        case REL_SAME:
            if (res == REL_SAME) res = res2;
            else return REL_OVERLAP;
            break;
        }
        ++i;
    }
    return res;
}


/** Determine the relative vertical distance between our shapes and another one.
 *
 * For detailed explanation and parameters, see
 * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
 */
double ContourList::OffsetBelow(const SimpleContour &below, double &touchpoint, double offset) const
{
    if (offset < below.GetBoundingBox().y.from - GetBoundingBox().y.till) return offset;
    if (!GetBoundingBox().x.Overlaps(below.GetBoundingBox().x)) return offset;
    for (auto &c : *this)
        offset = c.OffsetBelow(below, touchpoint, offset);
    return offset;
}

/** Determine the relative vertical distance between two list of shapes.
 *
 * For detailed explanation and parameters, see
 * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
 */
double ContourList::OffsetBelow(const ContourList &below, double &touchpoint, double offset) const
{
    if (offset < below.GetBoundingBox().y.from - GetBoundingBox().y.till) return offset;
    if (!GetBoundingBox().x.Overlaps(below.GetBoundingBox().x)) return offset;
    for (auto &c : *this)
        for (auto &b : below)
            offset = c.OffsetBelow(b.outline, touchpoint, offset);
    return offset;
}

void ContourList::CairoPath(cairo_t *cr, bool show_hidden) const
{
    for (auto &c : *this)
        c.CairoPath(cr, show_hidden);
}

void ContourList::CairoPath(cairo_t *cr, bool show_hidden, bool clockwiseonly) const
{
    for (auto &c : *this)
        c.CairoPath(cr, show_hidden, clockwiseonly);
}

/** Draw the shape in dashed lines to the path of a cairo context.
 * Needed for backends not supporting dashed lines.
 * If `show_hidden` is false, we skip edges marked not visible.*/
void ContourList::CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden) const
{
    for (auto &c : *this)
        c.CairoPathDashed(cr, pattern, show_hidden);
}

/** Draw the shape in dashed lines to the path of a cairo context.
* Needed for backends not supporting dashed lines.
* If `show_hidden` is false, we skip edges marked not visible.
If `clockwiseonly` is false, we skip counterclockwise contours.*/
void ContourList::CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool clockwiseonly) const
{
    for (auto &c : *this)
        c.CairoPathDashed(cr, pattern, show_hidden, clockwiseonly);
}

/** Calculates the distance between my shapes and 'c' by finding their two closest points.
*
* @param [in] ch The other shape to take the distance from.
* @param dist_so_far We return the distance of the two closest points and the two points themselves.
*            Distance is negative one is inside the other (but not in a hole), zero if partial overlap only (two points equal)
*            'Inside' here ignores clockwiseness. Distance is `CONTOUR_INFINITY` if one of the shapes is empty.
*            Note that `ret` can contain the result of previous searches, we update it if we find two points
*            with a smaller distance (in absolute value).*/
void ContourList::Distance(const HoledSimpleContour &ch, DistanceType &dist_so_far) const noexcept
{
    if (IsEmpty() || ch.IsEmpty()) return;
    if (dist_so_far.IsZero()) return;
    const double bbdist = GetBoundingBox().Distance(ch.GetBoundingBox());
    if (!dist_so_far.ConsiderBB(bbdist)) {
        dist_so_far.MergeInOut(bbdist);
        return;
    }
    for (auto &c : *this) {
        const double bbdist2 = ch.GetBoundingBox().Distance(c.GetBoundingBox());
        if (dist_so_far.ConsiderBB(bbdist2))
            c.Distance(ch, dist_so_far);
        else
            dist_so_far.MergeInOut(bbdist2);
        if (dist_so_far.IsZero())
            break;
    }
}

/** Calculates the distance between two lists of shapes by finding their two closest points.
*
* @param [in] cl The other list to take the distance from.
* @param dist_so_far We return the distance of the two closest points and the two points themselves.
*            Distance is negative one is inside the other (but not in a hole), zero if partial overlap only (two points equal)
*            'Inside' here ignores clockwiseness. Distance is `CONTOUR_INFINITY` if one of the shapes is empty.
*            Note that `ret` can contain the result of previous searches, we update it if we find two points
*            with a smaller distance (in absolute value).*/
void ContourList::Distance(const ContourList &cl, DistanceType &dist_so_far) const noexcept
{
    if (IsEmpty() || cl.IsEmpty()) return;
    if (dist_so_far.IsZero()) return;
    const double bbdist = GetBoundingBox().Distance(cl.GetBoundingBox());
    if (!dist_so_far.ConsiderBB(bbdist)) {
        dist_so_far.MergeInOut(bbdist);
        return;
    }
    dist_so_far.SwapPoints();
    for (auto &c : *this) {
        const double bbdist2 = c.GetBoundingBox().Distance(cl.GetBoundingBox());
        if (dist_so_far.ConsiderBB(bbdist2))
            cl.Distance(c, dist_so_far);
        else
            dist_so_far.MergeInOut(bbdist2);
        if (dist_so_far.IsZero())
            break;
    }
    dist_so_far.SwapPoints();
}

std::string ContourList::Dump(bool precise) const {
    std::string ret = "ContourList::UnsafeMake({";
    for (const HoledSimpleContour& h : *this)
        ret.append(h.Dump(precise)).push_back(',');
    ret.append("})");
    return ret;
}

ContourList ContourList::UnsafeMake(std::vector<HoledSimpleContour>&& v) {
    ContourList ret;
    ret.std::vector<HoledSimpleContour>::swap(v);
    for (const HoledSimpleContour& h : ret)
        ret.boundingBox += h.GetBoundingBox();
    return ret;
}

std::string HoledSimpleContour::Dump(bool precise) const {
    return "HoledSimpleContour::UnsafeMake(" + outline.Dump(precise) + "," + holes.Dump(precise) + ")";
}

HoledSimpleContour HoledSimpleContour::UnsafeMake(SimpleContour&& o, ContourList&& h) {
    HoledSimpleContour ret;
    ret.outline.swap(o);
    ret.holes.swap(h);
    return ret;
}

//ContoursHelper/////////////////////////////////////////////////////////

/** When finding crosspoints, if 1) a new crosspoint between edges E1 and E2
 * is closer than DIST_DETECT_SAME_CP to a previously found cp; and 2) E1 and E2
 * are among the edges of the previous cp; and 3) the distance on the contours
 * between the two crosspoints is smaller on both contours than
 * DIST_DETECT_SAME_CP; then we decide that this is the same CP and do not
 * add E1 and E2 to it again. This is to avoid detecting the same cp twice. */
static constexpr double DIST_DETECT_SAME_CP = 0.01;
/** If two crosspoints (between different edge pairs) are closer than this,
 * we snap them together.*/
static constexpr double DIST_SAME_CP_SNAP = 0.0003;
/** The tolerance used after a Walk to merge subsequent, very similar
 * edges. It cannot be too large, or we may end up with overlapping edges.*/
static constexpr double DIST_MERGE_EDGES_AFTER_WALK = 0.0001;

/** Implements the index of a doubly linked list */
struct link_info
{
    static constexpr size_t no_link = std::numeric_limits<size_t>::max(); ///<The constant to mean "unspecified".
    size_t next = no_link; ///<Index of next element (in ContoursHelper::Rays)
    size_t prev = no_link; ///<Index of previous element (in ContoursHelper::Rays)
    bool IsSane(size_t rays_size, bool can_be_none=true) const noexcept { return ((can_be_none && next==no_link) || next<rays_size) && ((can_be_none && prev==no_link) || prev<rays_size); }
};

constexpr size_t link_info::no_link;

/** Enumerates how an edge overlaps with the other edge around a CP. */
enum class Overlaps : uint8_t {
    Not = 0,       //No overlap at this cp
    Incoming = 1,  //Our edge's incoming ray overlaps with the other edge
    Outgoing = 2,  //Our edge's outgoing ray overlaps with the other edge
    Both = 3       //Our edge overlap with the other edge on both sides of the CP
};
constexpr Overlaps operator +(Overlaps& a, Overlaps b) noexcept { return Overlaps(unsigned(a)|unsigned(b)); }
constexpr Overlaps operator -(Overlaps& a, Overlaps b) noexcept { return Overlaps(unsigned(a)&~unsigned(b)); }
constexpr Overlaps& operator +=(Overlaps& a, Overlaps b) noexcept { return a = Overlaps(unsigned(a)|unsigned(b)); }
constexpr Overlaps& operator -=(Overlaps& a, Overlaps b) noexcept { return a = Overlaps(unsigned(a)&~unsigned(b)); }
constexpr bool HasIncoming(Overlaps a) noexcept { return a==Overlaps::Incoming || a==Overlaps::Both; }
constexpr bool HasOutgoing(Overlaps a) noexcept { return a==Overlaps::Outgoing || a==Overlaps::Both; }

/** Stores data for a ray to aid walking.
 * @nosubgrouping
 * A ray is a half line going to or coming from a crosspoint.
 * For curvy edges, a ray is also curvy. If two edges cross, we have 4 resulting ray.
 * This structure contains a set of members identifying the ray, another set to
 * link it to other rays and a third set to store the evaluation and walk status.*/
struct Ray
{
    /** @name Members for the Linked lists
     * @{ */
    enum list_type {
        IN_CONTOUR,   ///<The strict list linking all rays along a contour
        IN_CP         ///<The strict list linking all rays along a crosspoint in clockwise order
    }; ///<Enumeration describing linked lists for Rays.
    link_info link_in_cp;  ///<The list linking all rays along a crosspoint in clockwise order
    /** @} */
    /** @name Members for Identification
     * @{ */
    const Contour *      main_contour; ///<The original Contour this edge is part of. nullptr for single contour (~untangle) operations.
    const SimpleContour *contour;      ///<The SimpleContour the edge of the ray belongs to.
    double               pos;          ///<The `pos` of the crosspoint on the edge between [0..1). (-1 if it is a vertex and not a crosspoint.)
    RayAngle             angle;        ///<The angle of the ray.
    XY                   xy;           ///<The coordinates of the crosspoint of the ray.
    unsigned             vertex;       ///<The number of the vertex staring the edge the crosspoint is on
    bool                 incoming;     ///<True if the ray is incoming to the cp.
    Overlaps             overlaps;     ///How have we been stored into 'ContoursHelper::RayOverlaps'.
    /** @} */
    /** @name Members for Evaluation and Status
     * Values used during any walk (both untangle and combine)
     * They are mutable so that updating status can be made on const Ray structures.
     * @{ */
    mutable bool   valid = true;     ///<False if this ray have already been included in a contour resulting from a walk.
    mutable size_t switch_to = link_info::no_link; ///<Index of another ray for this cp, where the walk should be continued if arriving on this ray. `no_link` on error.
    /** @} */
    Ray(const XY &point, const Contour *m_c, const SimpleContour *c, size_t v, double p, bool i, Overlaps o) noexcept :
        main_contour(m_c), contour(c), pos(p), xy(point), vertex(v), incoming(i), overlaps(o)
        { _ASSERT(c); }
    void CalcAngle() noexcept {
        if (!contour) return;
        if (!incoming) angle = contour->at(vertex).Angle(false, pos);
        else if (pos) angle = contour->at(vertex).Angle(true, pos);
        else angle = contour->at_prev(vertex).Angle(true, 1);
        _ASSERT(0<=angle.angle && angle.angle<4);
    }
    /** Sets validity to true and erases switch_to. After this you can call EvaluateCrosspoints() again.
     * Keeps coverage_at_0_minus_inf intact - that is the same for all operations.*/
    void Reset() const { valid = true; switch_to = link_info::no_link; }
    /** Check that all links and the switch_to point to something valid.
     * @param [in] rays_size The size of the Rays array.*/
    bool IsSane(size_t rays_size) const noexcept {
        return link_in_cp.IsSane(rays_size, false)
            && (switch_to==std::numeric_limits<size_t>::max() || switch_to<rays_size); }
    /** Calculate the distance of nearby crosspoints on the contour.
     * @returns DBL_MAX for the first param if the two cps are not on the same contour or are
     * not on the same or joining edges. Else it returns how much we need to walk on their
     * contour between them.*/
    constexpr double DistOnContour(const Ray &R) const noexcept {
        if (contour!=R.contour) return DBL_MAX;
        if (vertex==R.vertex)
            return fabs(contour->at(vertex).GetLength(pos, R.pos));
        if (vertex==R.contour->next(R.vertex)) //R is *before* us
            return contour->at(R.vertex).GetLength(R.pos, 1) + contour->at(vertex).GetLength(0, pos);
        else if (vertex==R.contour->prev(R.vertex)) //R is *after* us
            return contour->at(vertex).GetLength(pos, 1) + contour->at(R.vertex).GetLength(0, R.pos);
        return DBL_MAX;
    }
    /** Assuming 'R' is a ray that is on the same contour quite close to us, adjust the
     * 'pos' value of us to be approximately the average of our pos and that of R.
     * If one of the positions is zero, we snap to that.
     * If R is on another edge (because both of us are close to a vertex), we snap to the
     * vertex and may end up on another edge (the next one with pos=0). In this case
     * we return a bool. If true, we have to remove the incoming overlap from the ray
     * (as we have snapped from pos>0 to pos==0) if false, we have to remove outgoing
     * overlap (as we have snapped from pos~~0.999 to pos=0 of the next edge.*/
    constexpr std::optional<bool> AvgPosWith(const Ray& R) noexcept {
        _ASSERT(contour==R.contour);
        if (vertex==R.vertex) {
            if (pos==0 || pos==R.pos) return {};
            pos = R.pos ? (pos + R.pos)/2 : 0.;
            return pos == 0 ? true : std::optional<bool>{};    //Adjust the xy if we have set pos to zero
        } else if (vertex==R.contour->next(R.vertex)) {//R is *before* us
            pos = 0;
            return true;
        } else if (vertex==R.contour->prev(R.vertex)) {//R is *after* us
            vertex = R.vertex;
            pos = 0;
            return false;
        } else {
            _ASSERT(0);
            return {};
        }
    }
};

/** Structure holding our position during walk.
 *
 * We may be either at a vertex or a crosspoint
 */
struct RayPointer {
    size_t index;               ///<Index of a ray. If we are at a vertex this points to a nearby ray on this contour
    size_t vertex = size_t(-1); ///<the number of the vertex if we are at a vertex. Else ignore.
    bool   at_vertex = false;   ///<True if we are at a vertex, false if at a cp.
};

/** To store info for walk backtracking.
 *
 * If we are unable to continue walk (cps are calculated badly)
 * we need to backtrack. We store info for that here.
 */
struct walk_data {
    static bool AreSimilar(const RayAngle& a, const RayAngle& b) noexcept {
        static constexpr RayAngle tolerance{0.01, 0.1}; //This is how much difference we tolerate between rays to assume them "similar"
        return RayAngle::IsCloserAbs(a, b, RayAngle(0, 0), tolerance);
    }
    /** This structure helps finding rays in a CP that are close to each other.
     * We start by one ray on we walk. In case that does not lead to a good
     * result, we will look clockwise and counterclockwise from this first ray
     * and see if there are rays that point to very similar direction.*/
    class Chosen {
        size_t first;       ///<Stores the first ray we attempted walk on. We compare all other rays to this
        size_t clockwise;   ///<The last ray returned in the clockwise direction. Initially equals to 'first'.
        size_t cclockwise;  ///<The last ray returned in the counterclockwise direction. Initially equals to 'first'.
    public:
        explicit Chosen(size_t ray) noexcept : first(ray), clockwise(ray), cclockwise(ray) {}
        void reset_to(size_t ray [[maybe_unused]]) noexcept { cclockwise = clockwise = first; }
        /** Find rays very close to the first we have chosen.
         * We don't select anything that has already been selected.
         * Whatever we select will be noted in clockwise or cclockwise so
         * that we don't select it again later. If there are no more rays
         * similar in angle to 'first', we return link_info::no_link.*/
        size_t select_alternative(const std::vector<Ray>& Rays) {
            if (first==link_info::no_link) return link_info::no_link;
            const size_t candidate_incoming_ray[2] = {
                Rays[clockwise].link_in_cp.next==cclockwise ? link_info::no_link : Rays[clockwise].link_in_cp.next,
                Rays[cclockwise].link_in_cp.prev== clockwise ? link_info::no_link : Rays[cclockwise].link_in_cp.prev
            };
            int i = -1;
            if (candidate_incoming_ray[0]!=link_info::no_link && candidate_incoming_ray[1]!=link_info::no_link)
                i = RayAngle::IsCloserAbs(Rays[first].angle, Rays[candidate_incoming_ray[1]].angle,
                                          Rays[first].angle, Rays[candidate_incoming_ray[0]].angle);
            else if (candidate_incoming_ray[0]!=link_info::no_link)
                i = 0;
            else if (candidate_incoming_ray[1]!=link_info::no_link)
                i = 1;
            if (i<0 || !AreSimilar(Rays[first].angle, Rays[candidate_incoming_ray[i]].angle))
                return link_info::no_link;
            else if (i==0)
                return  clockwise = candidate_incoming_ray[0];
            else
                return cclockwise = candidate_incoming_ray[1];
        }
    };
    size_t result_size;        ///<What was the size of the resulting contour after we have added
    size_t rays_visited_size;  ///<What was the size of the 'rays_visited' array when leaving this CP
    Chosen arriving;             ///<Stores what rays arriving to the CP we have tried
    Chosen departing;            ///<For the currently investigated arriving ray, what departing rays we have already investigated?
    walk_data(size_t rsize, size_t rvsize, size_t ray_in, size_t switch_to) noexcept :
        result_size(rsize), rays_visited_size(rvsize), arriving(ray_in), departing(switch_to) {}
};

struct node;

/** A list of node objects with a bounding box.
* Not entirely safe: we assume users do not expand individual nodes
* in the list in order to keep bounding box valid.
* But this is how the ContoursHelper::InsertContour() is written.*/
class node_list
{
    std::list<node> nodes;
    Block boundingBox;
public:
    node_list() : boundingBox(false) {}
    std::list<node>::iterator begin() { return nodes.begin(); }
    std::list<node>::iterator end() { return nodes.end(); }
    std::list<node>::const_iterator begin() const { return nodes.begin(); }
    std::list<node>::const_iterator end() const { return nodes.end(); }
    node& append(node &&n);
    void splice(std::list<node>::iterator where_to, node_list &from, std::list<node>::iterator what);
    const Block &GetBoundingBox() const { return boundingBox; }
};


/** Node of a tree holding SimpleContours.
 *
 * Used for post-processing after walks to determine which SimpleContour is inside which others.
 */
struct node
{
    SimpleContour contour;    ///<This is part of the result.
    node_list children; ///<These SimpleContours are inside the above member.
    explicit node(SimpleContour &&c, bool invert=false) : contour(invert ? std::move(c.Invert()) : std::move(c)) {}  ///<Move a SimpleContour
    explicit node(const SimpleContour &c, bool invert=false) : contour(invert ? c.CreateInvert() : c) {}        ///<Copy a Simplecontour
};

inline node& node_list::append(node &&n)
{
    boundingBox += n.contour.GetBoundingBox();
    nodes.push_back(std::move(n));
    return nodes.back();
}

inline void node_list::splice(std::list<node>::iterator where_to, node_list &from, std::list<node>::iterator what)
{
    boundingBox += what->contour.GetBoundingBox();
    nodes.splice(where_to, from.nodes, what);
    //We do not change the bounding box of 'from' even if we have removed a contour.
    //1. It is just a performance hit if the bounding box is larger than needed
    //2. We will re-add 'this' to 'from' later and 'this' will include 'what' and by def will have a not smaller bb.
}

template <typename T=size_t, T null=size_t(-1)>
requires std::equality_comparable<T> && std::movable<T> && std::move_constructible<T>
class EquivalenceClasses {
    std::vector<T> groups;
public:
    bool contains(T u) const noexcept { _ASSERT(u!=null);  return std::ranges::find(groups, u)!=groups.end(); }
    void reserve(size_t size) { groups.reserve(size); }
    //returns true if we deleted
    bool erase(T u) {
        _ASSERT(u!=null);
        for (size_t x = 0; x<groups.size(); x++)
            if (groups[x]==u) {
                groups.erase(groups.begin()+x);
                if ((x==0 || groups[x-1]==null) && x<groups.size() && groups[x]==null)
                     groups.erase(groups.begin()+x);
                else if (x==groups.size() && x>0 && groups.back()==null)
                     groups.pop_back();
                _ASSERT(!contains(u));
                return true;
            }
        return false;
    }
    void add(const T &u, const T &v) {
        _ASSERT(u!=null);
        _ASSERT(v!=null);
        auto ui = std::ranges::find(groups, u);
        auto vi = std::ranges::find(groups, v);
        if (ui==groups.end()) {
            if (vi==groups.end()) {
                if (groups.size()) groups.push_back(null);
                groups.push_back(u);
                groups.push_back(v);
            } else
                groups.insert(vi, u);
        } else if (vi!=groups.end()) {
            //move the later group into the former
            if (vi<ui) std::swap(vi, ui);
            auto vi2 = vi;
            //find the beginning and end of the group of vi
            while (vi!=groups.begin() && *std::prev(vi)!=null) --vi;
            if (vi<=ui) return; //already in the same group
            while (vi2!=groups.end() && *vi2!=null) ++vi2;
            std::rotate(ui, vi, vi2);
            //Delete terminating -1
            if (vi2!=groups.end())
                groups.erase(vi2);
        } else
            groups.insert(ui, v);
    }
    std::vector<std::span<const T>> GetClasses() const {
        std::vector<std::span<const T>> ret;
        std::optional<size_t> first;
        for (size_t x=0; x<groups.size(); x++)
            if (groups[x]==null) {
                if (first.has_value()) {
                    ret.emplace_back(groups.begin()+*first, x-*first);
                    first.reset();
                }
            } else if (!first.has_value())
                first = x;
        if (first.has_value())
            ret.emplace_back(groups.begin()+*first, groups.end());
        return ret;
    }
};


/** An ID to rays used during FindCrosspoints when only incoming
 * rays are added to Rays.*/
struct RayID {
    size_t ray = size_t(-1);///The ID of a crosspoint/edge pair
    bool incoming = false;  ///Whether we mean the incoming or outgoing ray of this CP/edge pair
    constexpr auto operator<=>(const RayID&) const noexcept = default;
};

//insert-only
template <typename T>
class XY_map {
    struct XY_hash {
        size_t operator()(const XY& xy) const noexcept { return std::hash<double>{}(xy.x) ^ (std::hash<double>{}(xy.y) << 1); }
    };
    std::vector<std::pair<XY, T>> storage;
    size_t elements=0; //only used when hashing
    size_t shift=6;    //only used when hashing
    static inline constexpr size_t cutoff_power = 4;
public:
    class const_iterator {
        std::vector<std::pair<XY, T>>::const_iterator i, end;
        constexpr const_iterator(std::vector<std::pair<XY, T>>::const_iterator i, std::vector<std::pair<XY, T>>::const_iterator end) noexcept : i(i), end(end) {}
        friend XY_map;
    public:
        constexpr auto operator<=>(const const_iterator&) const noexcept = default;
        const_iterator& operator++() noexcept { ++i; while (i!=end && std::isnan(i->first.x)) ++i; return *this; }
        constexpr const std::pair<XY, T>& operator*() noexcept { return *i; }
        constexpr const std::pair<XY, T>* operator->() noexcept { return &*i; }
    };
    bool empty() const noexcept { return storage.empty(); }
    size_t size() const noexcept { if (storage.size() < 1 << cutoff_power) return storage.size(); else return elements; }
    void clear() noexcept { storage.clear(); elements = 0; shift = 6; }
    void reserve(size_t n) { if (n < 1 << cutoff_power) storage.reserve(n); else storage.reserve(n * 2); }
    const_iterator begin() const { return {std::ranges::find_if_not(storage, [](double d) { return std::isnan(d); }, [](const std::pair<XY, T>& p) {return p.first.x; }), storage.end()}; };
    const_iterator end() const { return {storage.end(), storage.end()}; };
    std::pair<T&, bool> emplace(const XY& xy) {
        _ASSERT(!std::isnan(xy.x));
        if (storage.size() < 1<<cutoff_power) {
            bool inserted = false;
            auto i = std::ranges::lower_bound(storage, xy, [](const XY& a, const XY& b) { return a < b; }, [](const std::pair<XY, T>& p)->const XY& { return p.first; });
            if (i == storage.end() || i->first != xy) {
                i = storage.emplace(i);
                i->first = xy;
                inserted = true;
            }
            return {i->second, inserted};
        }
        if (elements == 0) {
            //this is the point when we turn to a hash table
            _ASSERT(storage.size() == 1 << cutoff_power);
            shift = cutoff_power;
            elements = storage.size(); //now we switch to hash
            //We are sure to hit true on the next if
        }
        if (storage.size() < elements * 2) {
            //re-hash. Also works when converting from sorted array to hash
            shift++;
            std::vector<std::pair<XY, T>> new_storage(1<<shift);
            for (auto& e : new_storage)
                e.first.x = std::numeric_limits<double>::quiet_NaN(); //signals empty slot
            const size_t mask = (1 << shift) - 1;
            for (auto& e : storage) {
                size_t idx = XY_hash{}(e.first) & mask;
                while (!std::isnan(new_storage[idx].first.x))
                    idx = (idx + 1) & mask;
                new_storage[idx].first = e.first;
                new_storage[idx].second = std::move(e.second);
            }
            std::swap(storage, new_storage);
        }
        const size_t mask = (1 << shift) - 1;
        size_t idx = XY_hash{}(xy) & mask;
        while (!std::isnan(storage[idx].first.x))
            if (storage[idx].first == xy)
                return {storage[idx].second, false};
            else
                idx = (idx + 1) & mask;
        elements++;
        storage[idx].first = xy;
        return {storage[idx].second, true};
    }
    const T* find(const XY& xy) const noexcept {
        _ASSERT(!std::isnan(xy.x));
        if (storage.size() < 1 << cutoff_power) {
            auto i = std::ranges::lower_bound(storage, xy, [](const XY& a, const XY& b) { return a < b; }, [](const std::pair<XY, T>& p)->const XY& { return p.first; });
            return i == storage.end() || i->first != xy ? nullptr : &i->second;
        }
        const size_t mask = (1 << shift) - 1;
        size_t idx = XY_hash{}(xy) & mask;
        while (!std::isnan(storage[idx].first.x))
            if (storage[idx].first == xy)
                return &storage[idx].second;
            else
                idx = (idx + 1) & mask;
        return nullptr;
    }
};

/** A helper class to implement contour operations and walking.
 * @ingroup contour_internal
 *
 * # Overview of the process
 *
 * This class is instantiated with one or two contours.
 * Note that they can be either const versions or move versions. The latter happens
 * when the operation was initiated on a temporary (like X=Contour(x,y)+B; ) or a
 * changeable reference, such as C+=A; ). We store const pointers to both and a
 * non-const pointer for those we can change (effectively bring to a valid, but
 * undefined state). The difference is important when parts of a contour (specifically
 * a SimpleContour) has no cp ('cp' means crosspoint from now on) with any other.
 * In that case it may (e.g. for unions) need to be copied to
 * to the result. If its origin contour was a move version we do not copy, but move.
 * Note that when one contour is supplied, with a single HoledSimpleContour, it may 
 * not have its bounding box calculated, but set to invalid. This may happen when
 * untangling a SimpleContour (or rather a Path). The returned Contour will have its 
 * bounding box calculated, as that is a class invariant.
 * 
 * The class can also be instantiated with one or two const SimpleContours. This mode
 * of operation only allows calling HasCrossingCPs() and not Do(). This mode of operation
 * is is limited not to have to work this branch into Do().
 * 
 * Note that in order to untangle a SimpleContour, we have to convert the path to a
 * full Contour (see SimpleContour::Expand() and Expand2D()). We have tried to add 
 * SimpleContour support to Do(), but it ended up beeing slower than the current 
 * implementation. Likewise, we have converted [out] Contour& params in Do() and
 * Contour::Operation() to return by value - again, slower.
 *
 * ### Finding crosspoints
 *
 * After instantiation, we search for crosspoints between the edges comprising the two
 * contours. (Or between the edges of a single contour if we were instantiated with one.)
 * Currently this is done with pairwise checking (O(n^2)). For straight edges we could use
 * Bently-Ottoman this is a TODO. See FindCrosspoints().
 *
 * ### Pruning Crosspoints and Rays
 *
 * After we have found all crosspoints, we check if some of them are in fact, the same.
 * This cannot be done by simply comparing the coordinates, as those may be imprecise.
 * If we see that two crosspoints are both between edges A and B and both the positions and
 * the coordinates are very similar, we decide that they are in fact the same CP and drop
 * one of them. We also merge crosspoints that are closer than a threshold (DIST_SAME_CP_SNAP)
 * in the coordinate space. We set up clusters of CPs where any pair is closer than this
 * and merge all CPs in the cluster. Merging means that we set their XY coordinate to the
 * exact same value. After this point on we decide if two rays are in the same CP or not
 * based on exact comparison of the XY coordinates.
 *
 * Note that due to this we allow very minimal overlap between SimpleContours of a
 * Contour (e.g., its holes and outline). If one SimpleContour overlaps another in such
 * a way that all their crosspoints are closer than DIST_SAME_CP_SNAP then they are only
 * considered touching.
 *
 * Then we remove rays that are duplicated. E.g., if an edge of contour C1 goes through a
 * vertex of contour C2, we will have this CP listed twice and have all the rays
 * duplicated. So we need to merge rays, too inside a single CP.
 * Then we calculate the angles of the remaining rays. If two rays overlap, we know it
 * from FindCrosspoints() - in that case it is important to have the exact same angle
 * value for them as they are equally ordered by angle.
 *
 * ### Adjusting rays
 *
 * After this point we check if rays of a CP close in angle are actually ordered right.
 * This happens by walking a bit on them and see which direction they depart. If they
 * lead to the same CP, we can check their ordering there.
 *
 * ### Evaluating crosspoints
 *
 * After we have identified all the crosspoints we evaluate them in the light of the
 * operation (see details at EvaluateCrosspoints()). Evaluation tells us for each
 * ray of a cp, which other ray shall we continue walking, if arrived on the first ray.
 * Evaluation also tells us which ray of which crosspoints can we start a walk from.
 * See the example below.
 * @verbatim
 +-->----+
 |   A   |
 |     +-o-->-+
 |     | |    |
 +--<--x-+    |
       |   B  |
       +----<-+
 @endverbatim
 * Here we have two Contours A and B. Signs (`>` and `<`) show they are clockwise.
 * Neither have holes and both consist of only a single
 * SimpleContour. They have two cps (marked with `o` and `x`). Let us assume we take the union
 * of the two contours. In this case the `o` cp
 * is evaluated as follows. From the north ray, we have to switch to the east ray and vice versa.
 * Also, the east is eligible to start the walk with. (Starting at the north would yield in a
 * counterclockwise result and here we are after a clockwise union. This is because we always
 * start *outwards* from a cp, irrespective of edge direction. Sometimes we have to walk *against*
 * edge direction, e.g., for XOR operations, thus we mostly ignore edge direction on walking.)
 * The other two rays should never be arrived on (they will not be part of the union, since they are
 * inside it), thus these are marked as *error*. Similar for `x` if we arrive on the west ray we shall
 * continue on the south one and vice versa (and we can start with the west one); while the other two
 * represent errors.
 * For the case of the intersection operation, the east and south ray of `o` would be valid ones
 * where for `x` the north and east ones. See EvaluateCrosspoints().
 *
 * ### Walking
 *
 * After evaluation we start walking from one of the eligible rays. (See Walk().) We follow the edges
 * of the SimpleContour we are on until we hit a crosspoint. There we follow the result of the evaluation
 * for the ray we arrived to the crosspoint. Eventually we arrive back to where we started from.
 * (In fact we may terminate the walk after arriving back to another ray of the same cp. To this end,
 * rays in the cp are assigned a *sequence number*. If we arrive back to a ray with the same
 * sequence number we stop the walk. Note that at one crosspoint there may be rays with different
 * sequence numbers. Sometimes we have to continue the walk even if we arrived back to the cp until
 * we get back on the right ray, see the example below at Untangling.)
 * Note that during walk we ignore the original direction of edges. Sometimes we have to walk them
 * in the opposite direction (for XOR, or see the example below for Untangling.)
 *
 * In the meantime we mark all rays we crossed, so that we do not start a walk resulting in this very
 * SimpleContour again. If there are eligible starting rays, we do another walk, as long as we have ones left -
 * each walk generating a separate, non-flipped, disjoint SimpleContour, which however, may be inside one another.
 * See for example the two shapes ('A' forming an upside-down U-shape and 'B' forming a rectangle) below
 * and assume we do an intersection operation.
 * @verbatim
    +-------->-------------+
    |   +-----<-------+  A |
    |   |      H      |    |
  +-1---2------>------3----4---+
  | | M |             | N  |   |
  | +---+      B      +----+   |
  +-------------<--------------+
 @endverbatim
 * Four cps can be find (each having one eligible ray to start the walk, specifically
 * the east one for '1' and '3'; and the south one for '2' and '4').
 * The Contour resulting from the intersection will have two parts, marked with 'M' and 'N'.
 * Let's assume we started from the leftmost crosspoint (marked with `1`).
 * During the walk we cross `2` and get back to '1' having
 * walked the SimpleContour 'M'. In order to obtain 'N' we need to start walking from one of the
 * two remaining eligible rays. Starting from any of the two that walk will give us 'N'.
 *
 * ### Post-processing
 *
 * After completing all the walks we have a set of SimpleContours, whose edges do not cross. Note that
 * even if we hunt for a clockwise union or intersection, some of them can be counterclockwise.
 * Take the above example and assume a union operation. In this case the result will have a hole
 * marked with 'H'. The eligible rays for the same 4 cps are north for '1' and '3'; east for '2'
 * and '4'. Walking from '2' or '3' will walk around 'H' - but in a counterclockwise direction.
 * This is well and shows us 'H' will be a hole.
 *
 * The final step is to determine which of the resulting SimpleContours are contained in which other.
 * We also have to consider those parts of ther original contours that had no cps and therefore
 * were not walked. They are of course disjoint from the result of the walks and may either be added
 * as is, or omitted completely. We determine the containment relationship (which contains which other)
 * and build a tree of `node` structure in Do() and lastly convert the tree to a Contour in ConvertNode().
 *
 * ## Untangling
 *
 * A few words about untangling. In this case we have only a single shape - which is not a
 * valid contour, but we know that its neighboring edges connect. There are two rules on which part the
 * result shall contain.
 *     - *Winding* rule: If the original shape crosses the ray from left-to-right, counts +1.
 *       If the path crosses the ray from right to left, counts -1. (Left and right are determined from the
 *       perspective of looking along the ray from the starting point.)
 *       If the total count is non-zero, the point will be included in the untangled shape.
 *     - *Even-odd* rule: Counts the total number of intersections, without regard to the orientation of the contour.
 *       If the total number of intersections is odd, the point will be included in the untangled shape.
 *
 * See the below example.
 * @verbatim
   +--->---------------+  +--->---------------+   +--->---------------+
   |                   |  |...................|   |...................|
   |      +-->---+     |  |...................|   |......+--<---+.....|
   |      |  B   |     |  |...................|   |......|      |.....|
   ^      +--<---o--<--+  ^.............+--<--+   ^..... +-->---+--<--+
   |  A          |        |...winding...|         |..even-odd...|
   +-------<-----+        +-------<-----+         +-------<-----+
 @endverbatim
 * In case the winding rule, the final shape would contain 'B', where in case of the even-odd rule
 * it would not, see the two shapes on the right, where dots indicate the areas covered.
 * (The counter would be +2 for points inside 'B'.)
 *
 * Note that for the even-odd rule the direction of the edges of 'B' had been reversed. Also note that the crosspoint
 * is walked across two times: once when coming from the east and continuing north and the second
 * time when coming from the west and continuing south. This is one of the occasions when just by getting
 * back to the same crosspoint we shall not stop the walk. Assuming we start in the north direction from
 * this crosspoint (the only one eligible), we shall not stop the walk when we get back from the east.
 * Only when we get back from the west. This is achieved by assigning different sequence numbers
 * to the north/west rays and the east/south rays.
 * Finally note that we could stop the walk every time we get back to the same cp. In the above
 * example for even-odd that would result in a SimpleContour identical to the middle example and
 * a hole in it - that would also be a completely legal representation according to contour library principles.
 * We just do it the former way.
 *
 * Basically the process is very similar to the above one. We take crosspoints, evaluate and walk.
 * However, the crosspoints are taken between the edges of the single shape.
 *
 * # Data Structures
 *
 * The data structure is a bit exotic here.
 * We have Ray. They represent half lines going outwards from or inwards to crosspoints.
 * In fact they can be curvy, for curvy edges.
 * When two edges cross (and form a cp), there are 4 rays: two incoming and two outgoing
 * (as edges are directed). Rays have an *angle* which is the clockwise angle from the positive x axis
 * and the outgoing tangent of the ray. The angle of the ray does not consider whether the ray is incoming
 * or outgoing. Thus for the examples above east rays (both incoming and outgoing) have angle of zero,
 * south rays the angle of 90 degrees, west rays 180 degrees and north rays 270 degrees. (Note that we
 * store the 'false angle' not the precise one as we just use it for sorting.) This angle is
 * used to sort the rays around a crosspoint in clockwise order. To be able to sort curvy edges with the
 * same tangent, we also store the curviness of the ange, which is the inverse of the curve radius at the
 * crosspoint (thus zero for straight lines). See RayAngle.
 *
 * Rays are stored in a big vector (ContoursHelper::Rays) and are arranged in 2 in-place, doubly linked, circular lists.
 * This enables us to move both along the edges of a SimpleContour during walking and also to rotate around crosspoints easily.
 * For each SimpleContour that is part of the operation we have a circular list that indexes rays according
 * to their order along the contour, by increasing <edge_no, pos_on_the_edge, incoming>.
 * (incoming rays are earlier in the list than their outgoing pair). The index of the first element for each
 * SimpleContour is stored in the ContoursHelper::contour_heads map.
 *
 * There is a second, in-place, doubly linked, circular list for Rays, which lists rays associated with a
 * crosspoint in clockwise order. In fact, they are ordered by <false angle, curve angle> and the cpdata is
 * the ray closest to (but clockwise after) <0, -inf>. E.g., for a simple crosspoint of two edges crossing,
 * we will have 4 rays in this circular list, two for each edge. The heads of these lists are stored in
 * ContoursHelper::cp_heads, mapping a coordinate (XY) to an index in ContoursHelper::Rays (size_t);
 * (Note that by the time we set these list indices up, the coordinate of each Ray of the same crosspoint will
 * already be normalized to the exact same value, so we can determine if two Rays belong to the same cp, by
 * simply comparing their coordinates.
 *
 * Each Ray structure contains two link_info members with `prev` and `next` members for the two in-place lists.
 * If `next==prev`, we are alone in our circular lists, but that shall happen only during the setup, as each
 * SimpleContour and crosspoint has more than one ray.
 *
 * ### Ray groups
 *
 * Rays are ordered clockwise around a crosspoint in their list. In this ordering we ignore
 * whether the ray is incoming or outgoing, we take its angle as if it were outgoing -
 * horizontal right is angle 0 and angle grows clockwise (in a space where the y coordinate grows
 * downward). We use fake angles here with curvature values, see RayAngle.
 *
 * A ray group is a set of rays around a cp with exactly the same angle and curvature angle.
 * This means that they lie exactly on each other. See the three examples below.
 * @verbatim
   +-->-----o=====o----+    +-->--o=====o->---+        +--->--+
   |        |  B  |    |    |  A  |     |     |        |  A   |
   |    A   +--<--+    |    +--<--x-----+     |     +--o======o--+
   +-----<-------------+          |        B  |     |      B     |
                                  +------<----+     +-----<------+
   @endverbatim
 * Here the two shapes have some parts of their edges common. In the first two case the common
 * segments share the same direction, but in the third they are opposite. In all cases the
 * crosspoints marked by `o` have three ray groups. Two of the ray groups have a single ray,
 * whereas a third one has two.
 *
 * What follows from the above definition is that if we have to continue
 * the walk along one of the rays of a ray group, we can (at least in theory) pick any of them.
 * The crosspoint we hit next will have to be on all such rays. This is unfortunately not always
 * true, due to numeric imprecision.
 * For this we use ClosestNextCP() to see which of the edges in a ray group has its next crosspoint
 * closest to the cp of the ray group and use that ray. Also, we have the idea of *backtracking*.
 *
 * ### Backtracking
 *
 * During walk, we record a stack of decisions we made, that is selection between ray group members.
 * If later in the walk we end up in a ray with error, we backtrack and try other members of the
 * ray group (and even rays that are not exactly the same angle, but very similar).
 * See Walk() and struct walk_data for details.
 */
class ContoursHelper {
protected:
    struct contour_range {
        const SimpleContour* contour;
        size_t begin;
        size_t end;
    };

    struct cp_data {
                size_t head;
        mutable int    coverage_at_0_minus_inf = std::numeric_limits<int>::max(); ///<Only at cp heads at untangle operation: it holds the coverage at the RayAngle<0, -inf> direction from the cp. If very large, we do not know
    };

    const Contour* const C1 = nullptr, * const C2 = nullptr;      ///<The contours we process.
          Contour* const C1_nc = nullptr, * const C2_nc = nullptr;///<If the contours we process are non const, these point to the same as C1 and C2, else null.
    const bool           clockwise_C1 = false, clockwise_C2 = false;///<Store the clockwiseness status of C1 and C2 (save, as C1 and C2 may be destroyed). Note that it is never needed for uni-contour operations (when C2==nullptr). So in those cases it is garbage. This is most important for expand - we avoid calculating these.
    const SimpleContour* const SC1 = nullptr;                     ///If we process SimpleContours these are the ones we do. This is also used to check sanity of SimpleContours
    const SimpleContour* const SC2 = nullptr;                     ///If we process SimpleContours these are the ones we do.
    std::vector<Ray>      Rays;                                   ///<All the rays of an operation for all cps
    std::vector<contour_range> contour_heads;                     ///The cpdata ray of the linked list of rays of each contour
    XY_map<cp_data> cp_heads;                                     ///The cpdata ray of the linked list of rays of each crosspoint
    std::vector<std::pair<RayID, RayID>> RayOverlaps;             ///<All the ray pairs that overlap. Used only during FindCrosspoints() & PruneCrosspoints()
    double max_cp_adjust_sqr = 0;                                 ///<Square of the largest distance we have moved a crosspoint in PruneCrosspoints() (maintained only in debug mode)
    mutable std::vector<size_t> StartRays;                        ///<Indexes of rays where walks can start
    mutable std::optional<Contour::EOperationType> evaluated_for; ///<The operation the crosspoints were evaluate for (for debugging)

private:
    auto find_contour_range(const SimpleContour *c) const noexcept {
        return std::ranges::find(contour_heads, c, &contour_range::contour);
    }
    /** Compare two rays based on their contour.
     * Ordering is deterministic, but in some cases in untangle it may depend on
     * pointer values. */
    std::strong_ordering RayCompareBy_Contour(size_t r1, size_t r2) const noexcept {
        if (Rays[r1].main_contour!=Rays[r2].main_contour) return Rays[r1].main_contour==C1 ? std::strong_ordering::less : std::strong_ordering::greater;
        if (Rays[r1].contour == Rays[r2].contour) return std::strong_ordering::equal;
        if (SC1 && SC2) return Rays[r1].contour==SC1 ? std::strong_ordering::less : std::strong_ordering::greater;
        if (auto c = Rays[r1].contour->operator<=>(*Rays[r2].contour); c!=0) return c;
        //Two contours with same content, same main contour, but different pointers, must be untangle
        //fall back to pointer value: here we can be implementation dependent - the two contours are equivalent
        //for all intents and purposes, so we can order them whichever way (and get the same result) until
        //that ordering is consistently the same.
        //Admittedly SimpleContour::operator<=> is a bit expensive to call especially when returns equal, but we
        //will have this case only rarely.
        return std::uintptr_t(Rays[r1].contour) < std::uintptr_t(Rays[r2].contour) ? std::strong_ordering::less : std::strong_ordering::greater;
    }
    /** Compare two rays based on (vertex, pos, incoming) triplets.
     * If vertex & pos is equal, we put incoming as smaller than outgoing.
     * Returns true if r1<r2. */
    bool RayCompareBy_VertexPosIncoming (size_t r1, size_t r2) const noexcept
        {return Rays[r1].vertex == Rays[r2].vertex ? test_equal(Rays[r1].pos, Rays[r2].pos) ? Rays[r1].incoming == Rays[r2].incoming ? false : Rays[r1].incoming : Rays[r1].pos<Rays[r2].pos : Rays[r1].vertex<Rays[r2].vertex;}
    /** Compare two rays based on (vertex, pos, incoming) triplets.
     * Do not snap the two 'pos' together, use hard == to check equality
     * If vertex & pos is equal, we put incoming as smaller than outgoing.
     * Returns true if r1<r2. */
    bool RayCompareBy_VertexPosIncomingHard(size_t r1, size_t r2) const noexcept
        {return Rays[r1].vertex == Rays[r2].vertex ? Rays[r1].pos == Rays[r2].pos ? Rays[r1].incoming == Rays[r2].incoming ? false : Rays[r1].incoming : Rays[r1].pos<Rays[r2].pos : Rays[r1].vertex<Rays[r2].vertex;}
    /** True if contour,vertex,pos are exact equal.*/
    bool RayCompareBy_ContourVertexPosHard(size_t r1, size_t r2) const noexcept
        { return Rays[r1].contour==Rays[r2].contour && Rays[r1].vertex==Rays[r2].vertex && Rays[r1].pos==Rays[r2].pos; }
    /** Compare two rays based on (angle, contour, vertex, pos, incoming) sextets.
     * Returns true if r1<r2. */
    bool RayCompareBy_Angle(size_t r1, size_t r2) const noexcept {
        //if both angle and curvature have the same relation use that
        if ((Rays[r1].angle.angle<=Rays[r2].angle.angle) && (Rays[r1].angle.curve<=Rays[r2].angle.curve))
            return Rays[r1].angle.angle<Rays[r2].angle.angle || Rays[r1].angle.curve<Rays[r2].angle.curve;
        if ((Rays[r1].angle.angle>=Rays[r2].angle.angle) && (Rays[r1].angle.curve>=Rays[r2].angle.curve))
            return false;
        //else on similarity use some other criteria to break ties
        if (Rays[r1].angle.IsSimilar(Rays[r2].angle)) return Rays[r1].contour == Rays[r2].contour ? RayCompareBy_VertexPosIncoming(r1, r2) : RayCompareBy_Contour(r1, r2)<0;
        return Rays[r1].angle.Smaller(Rays[r2].angle);
    }
    /** Tells us if (after sanitization in FindCrosspoints()) two rays are in the same ray group or not.*/
    bool SameRayGroup(size_t r1, size_t r2) const noexcept
        { return Rays[r1].angle.ExactEqual(Rays[r2].angle); }

protected:
    /** What shall be the bounding box of the resulting Contour be for this operation.
     * @returns True, if the BB should be almost this big, False if it shall be at most this big.
     * We may also return an invalid Block, meaning we cannot tell. Only used for C1!=null && C2!=null.*/
    std::pair<bool, Block> BoundingBoxEstimateFor(Contour::EOperationType type) const noexcept;
    /**Compares two rays for the purpose of inserting them into a strict list.
     * If 'hard' is true, we use hard == when comparing edge positions. No effect on CP list (comparing angles)*/
    template <Ray::list_type type>
    bool RayCompare(size_t r1, size_t r2) const noexcept {return type==Ray::IN_CP ? RayCompareBy_Angle(r1,r2) : RayCompareBy_VertexPosIncomingHard(r1, r2);}
    /** Links a ray `index` to the list of rays for a crosspoint. */
    void InsertToCPList(size_t index) noexcept;
    /** Swap this ray with the following in the cp list. We also
     * swap their 'angle' so that they remain ordered by angle.
     * This is used when two angles are close to each other.
     * 'cpdata' is the cpdata of this cp's strict list, supplied so
     * that we do not need to search for it.
     * @returns the new cp cpdata (may have changed), which we have
     * already updated in 'cp_heads'.*/
    size_t SwapWithNextInCPList(size_t index, size_t cp_head) noexcept;
    /** Finds the cpdata of this ray */
    size_t FindCPHead(size_t r) const noexcept;
    size_t next_in_contour(size_t r) const noexcept;
    size_t prev_in_contour(size_t r) const noexcept;
    /** Get the index of the ray following this ray to the next crosspoint along its contour. */
    size_t follow_in_contour(size_t r) const noexcept { return Rays[r].incoming ? prev_in_contour(r) : next_in_contour(r); }

    /** After walking this will be called for all SimpleContour elements of the
     * input. This function decides if the selected SimpleContour shall be picked
     * or not.*/
    bool PickAfterWalk(const SimpleContour *c) const noexcept;
    void AddRayOverlap(size_t r1, bool incoming1, size_t r2, bool incoming2);
    void RemoveRayOverlap(size_t ray, bool incoming);
    void AddCrosspoint(XY xy, const Contour *m_c1, const SimpleContour *c1, size_t v1, double p1, Overlaps overlap1, const Contour *m_c2, const SimpleContour *c2, size_t v2, double p2, Overlaps overlap2);
    /**Find the crosspoints between the edges of `i`, adds them to 'Rays' and returns the number of crosspoints found.*/
    void FindCrosspointsHelper(const SimpleContour *i);
    /**Find the crosspoints between the edges of `i`, adds them to 'Rays' and returns the number of crosspoints found.*/
    void FindCrosspointsHelper(const HoledSimpleContour *i);
    /**Find the crosspoints between the edges of shapes in `c`, adds them to 'Rays' and returns the number of crosspoints found.
     * If "cwh_included" is false we do not test individual HoledSimpleContour in the list for self-crossing,
     * just pairwise touch between the list elements */
    void FindCrosspointsHelper(const ContourList *c, bool cwh_included);
    /**Find the crosspoints between the edges of `c`, adds them to 'Rays' and returns the number of crosspoints found.
     * If "cwh_included" is false we do not test individual HoledSimpleContour for self-crossing,
     * just pairwise touch between the HoledSimpleContour this Contour consists of.*/
    void FindCrosspointsHelper(const Contour *c, bool cwh_included);
    /**Finds the crosspoints between `c1` and `c2`, adds them to 'Rays' and returns the number of crosspoints.
     * `m_c1` and `m_c2` are the main Contour of them, respectively. */
    void FindCrosspointsHelper(const Contour *m_c1, const SimpleContour *c1, const Contour *m_c2, const SimpleContour *c2);
    /**Finds the crosspoints between `c1` and `c2`, adds them to 'Rays' and returns the number of crosspoints.
     * `m_c1` and `m_c2` are the main Contour of them, respectively. */
    void FindCrosspointsHelper(const Contour *m_c1, const HoledSimpleContour *c1, const Contour *m_c2, const HoledSimpleContour *c2);
    /**Finds the crosspoints between `cwh` and `cs`, adds them to 'Rays' and returns the number of crosspoints.
     * `m_c1` and `m_c2` are the main Contour of them, respectively. */
    void FindCrosspointsHelper(const Contour *m_c1, const HoledSimpleContour *cwh, const Contour *m_c2, const ContourList *cs);
    /**Finds the crosspoints between `c1` and `c2`, adds them to 'Rays' and returns the number of crosspoints.
     * `m_c1` and `m_c2` are the main Contour of them, respectively. */
    void FindCrosspointsHelper(const Contour *m_c1, const ContourList *c1, const Contour *m_c2, const ContourList *c2);
    /**Finds the crosspoints between C1 and C2 (or SC1 and SC2) or just in C1 (or in SC2). Adds 2 incoming rays for each to 'Rays'.*/
    void FindCrosspoints();
    void PruneCrosspoints();
    void SanitizeRays();
    bool OperationWithAnEmpty(Contour::EOperationType type, bool clockwise) const;
    bool IsCoverageToInclude(int cov, Contour::EOperationType type) const;
    size_t FindRayGroupEnd(size_t from, int &coverage, size_t abort_at1, size_t abort_at2) const;
    bool GoToCoverage(size_t &from, size_t &to, int &cov_now, Contour::EOperationType type, bool start, size_t abort_at) const;
    int  CalcCoverageHelper(const XY &xy, size_t cp_head, const HoledSimpleContour *cwh) const;
    int  CalcCoverageHelper2(const XY& xy, size_t cp_head, const HoledSimpleContour* cwh) const;
    int  CalcCoverageHelper(size_t cp_head) const;
    int  CalcCoverageHelper(const SimpleContour *sc) const;
    size_t ClosestNextCP(size_t from, size_t to) const;
    void ResetCrosspoints() const;    //make them valid and set switch_action to ERROR
    void EvaluateCrosspoints(Contour::EOperationType type) const; //Fills in switch actions and StartRays
    //helpers for walking
    static void AppendDuringWalk(Path &edges, const Edge &edge);
    void Advance(RayPointer &p, bool forward) const;
    SimpleContour Walk(RayPointer start) const;

    //helper for post-processing
    template <typename range>
    static bool CheckContainementForOperation(const SimpleContour& c, const range& other);
    int CoverageInNodeList(const node_list& list, const SimpleContour& c) const;
    node *InsertContour(node_list *list, node &&n) const;
    void InsertIfNotInRays(node_list* list, const HoledSimpleContour* c, HoledSimpleContour* c_nc,
                           const Contour *C_other, Contour::EOperationType type) const;
    void ConvertNode(Contour::EOperationType type, node_list &&list, Contour &result, bool positive) const;
    void ConvertNode(Contour::EOperationType type, node_list &&list, ContourList &result, bool positive) const;

    //Can only be used for HasCrossingCPs()
    explicit ContoursHelper(const SimpleContour& sc1, const SimpleContour& sc2) noexcept : SC1(&sc1), SC2(&sc2) {}
    explicit ContoursHelper(const SimpleContour& sc1) noexcept : SC1(&sc1) {}
    [[nodiscard]] bool HasCrossingCPs();
public:
    //external interface
    explicit ContoursHelper(const Contour& c) noexcept : C1(&c) {}
    explicit ContoursHelper(Contour&& c) noexcept : C1(&c), C1_nc(&c) {}
    explicit ContoursHelper(const Contour& c1, const Contour& c2) noexcept : C1(&c1), C2(&c2), clockwise_C1(c1.GetClockWise()), clockwise_C2(c2.GetClockWise()) {}
    explicit ContoursHelper(const Contour& c1, Contour&& c2) noexcept : C1(&c1), C2(&c2), C2_nc(&c2), clockwise_C1(c1.GetClockWise()), clockwise_C2(c2.GetClockWise()) {}
    explicit ContoursHelper(Contour&& c1, const Contour& c2) noexcept : C1(&c1), C2(&c2), C1_nc(&c1), clockwise_C1(c1.GetClockWise()), clockwise_C2(c2.GetClockWise()) {}
    explicit ContoursHelper(Contour&& c1, Contour&& c2) noexcept : C1(&c1), C2(&c2), C1_nc(&c1), C2_nc(&c2), clockwise_C1(c1.GetClockWise()), clockwise_C2(c2.GetClockWise()) {}
    void Do(Contour::EOperationType type, Contour &result);
    [[nodiscard]] static bool HasCrossingCPs(const SimpleContour& sc1) { return ContoursHelper(sc1).HasCrossingCPs(); }
    [[nodiscard]] static bool HasCrossingCPs(const SimpleContour& sc1, const SimpleContour& sc2) { return ContoursHelper(sc1, sc2).HasCrossingCPs(); }

    void Dump();
    [[nodiscard]] bool IsSane() const noexcept;
};

//return true if we inserted a new element
void ContoursHelper::InsertToCPList(size_t index) noexcept
{
    const auto [cpdata, inserted] = cp_heads.emplace(Rays[index].xy);
    if (inserted) {
        //initialize new list
        cpdata.head = index;
        Rays[index].link_in_cp = {index, index};
        return;
    }
    size_t now = cpdata.head; //The CP head now
    //search until now is smaller and we have a valid "next"
    while (RayCompare<Ray::IN_CP>(now, index) && Rays[now].link_in_cp.next != cpdata.head)
        now = Rays[now].link_in_cp.next;
    if (RayCompare<Ray::IN_CP>(now, index)) {
        //Not found, link in 'index' after 'now' and 'before 'cpdata'
        //Rays[now] < Rays[index]: insert after (at the end, but a circular buffer, so does not matter)
        Rays[now].link_in_cp.next = index;
        Rays[cpdata.head].link_in_cp.prev = index;
        Rays[index].link_in_cp.prev = now;
        Rays[index].link_in_cp.next = cpdata.head; //this was the original "next" of "now"
        return;
    }
    //Now we know that Rays[now]>=Rays[index]

    //ok Rays[index]<Rays[now], insert before "now"
    size_t prev = Rays[now].link_in_cp.prev;
    Rays[now].link_in_cp.prev = index;
    Rays[prev].link_in_cp.next = index;
    Rays[index].link_in_cp.prev = prev;
    Rays[index].link_in_cp.next = now;
    //check if we insert to the beginning of the list
    if (cpdata.head == now)
        cpdata.head = index;
}

size_t ContoursHelper::SwapWithNextInCPList(size_t index, size_t cp_head) noexcept
{
    _ASSERT(index!=link_info::no_link);
    _ASSERT(FindCPHead(index)==cp_head); //uses resources only in debug builds.
    const size_t next = Rays[index].link_in_cp.next;
    _ASSERT(next!=index);
    _ASSERT(next!=link_info::no_link);
    if (index==next)
        return cp_head; // only one ray in this cp.
    //Swap angles, so that they remain ordered
    std::swap(Rays[index].angle, Rays[next].angle);
    //Calc new cp cpdata
    size_t new_cp_head = cp_head;
    if (cp_head==index) new_cp_head = next;
    else if (cp_head==next) new_cp_head = index;
    if (new_cp_head != cp_head) {//OK, change cp head, as well
        const auto [cpdata, inserted] = cp_heads.emplace(Rays[index].xy);
        _ASSERT(!inserted);
        cpdata.head = new_cp_head;
    }

    //re-link order in this cp.
    Rays[Rays[index].link_in_cp.prev].link_in_cp.next = next;
    Rays[Rays[next].link_in_cp.next].link_in_cp.prev = index;
    Rays[next].link_in_cp.prev = Rays[index].link_in_cp.prev;
    Rays[index].link_in_cp.next = Rays[next].link_in_cp.next;
    Rays[index].link_in_cp.prev = next;
    Rays[next].link_in_cp.next = index;
    return new_cp_head;
}

size_t ContoursHelper::FindCPHead(size_t r) const noexcept
{
    if (r == link_info::no_link) {
        _ASSERT(0);
        return r;
    }
    if (const auto p = cp_heads.find(Rays[r].xy))
        return p->head;
    _ASSERT(0);
    return r;
}

size_t ContoursHelper::next_in_contour(size_t r) const noexcept {
    _ASSERT(r < Rays.size());
    if (r == Rays.size() - 1) return contour_heads.back().begin;
    if (Rays[r].contour == Rays[r+1].contour) return r+1;
    const auto itr = find_contour_range(Rays[r].contour);
    _ASSERT(itr != contour_heads.end());
    return itr->begin;
}

size_t ContoursHelper::prev_in_contour(size_t r) const noexcept {
    _ASSERT(r < Rays.size());
    if (r==0) return contour_heads.front().end-1;
    if (Rays[r].contour == Rays[r-1].contour) return r-1;
    const auto itr = find_contour_range(Rays[r].contour);
    _ASSERT(itr != contour_heads.end());
    return itr->end-1;
}

/* It returns true if 'c' has not been used in a walk and does not cross
 * any other SimpleContour and has no overlapping edges either (based on
 * the crosspoints in Rays).*/
bool ContoursHelper::PickAfterWalk(const SimpleContour *c) const noexcept
{
    if (Rays.empty()) return true;
    if (c->IsEmpty()) return false;
    const auto itr = find_contour_range(c);
    if (itr==contour_heads.end())
        return true; //c has no crosspoints at all, it must be a sane SimpleContour not touching anything.
    for (size_t u = itr->begin; u<itr->end; u++) {
        //If this ray is an incoming ray and both it and the previous (outgoing) ray
        //of the contour are invalid then we have walked on this segment of the contour.
        //This means we should not pick this contour.
        if (!Rays[u].valid && Rays[u].incoming && !Rays[prev_in_contour(u)].valid)
            return false;
        if (Rays[u].overlaps!=Overlaps::Not) return false;
        //Circle the CP to detect any of the following
        // - we have more than 2 rays in this cp (self-cross for untangle)
        // - we cross any other contour
        // - one of our ray overlaps (with us or another contour): we detect this from the 'overlaps' flag above
        //Do this only for incoming rays so that each cp is circled only once
        if (Rays[u].incoming) {
            for (size_t v = Rays[u].link_in_cp.next, u2 = link_info::no_link; v!=u; v = Rays[v].link_in_cp.next)
                if (Rays[v].contour==c) {
                    if (u2!=link_info::no_link) return false; //this is the third ray of us.
                    u2 = v;
                } else if (u2!=link_info::no_link)
                    //check if the contour+edge of 'v' is between our incoming and outgoing rays, as well
                    //If 'v' or 'v2' overlaps with 'u' or 'u2' that is also a cause for fail, so we don't check that
                    for (size_t v2 = Rays[u].link_in_cp.next; v2!=u2; v2 = Rays[v2].link_in_cp.next)
                        if (Rays[v2].contour==Rays[v].contour && Rays[v2].vertex==Rays[v].vertex)
                            return false;
        }
    }
    return true;
}

/** Marks two rays as overlapping.*/
void ContoursHelper::AddRayOverlap(size_t r1, bool incoming1, size_t r2, bool incoming2) {
    RayOverlaps.emplace_back(RayID{r1, incoming1}, RayID{r2, incoming2});
    Rays[r1].overlaps += incoming1 ? Overlaps::Incoming : Overlaps::Outgoing;
    Rays[r2].overlaps += incoming2 ? Overlaps::Incoming : Overlaps::Outgoing;

}

/** Remove a ray from overlapping any other.
 * This is needed when we snap a CP from the middle of an edge to a vertex.*/
void ContoursHelper::RemoveRayOverlap(size_t ray, bool incoming) {
    if ((incoming ? HasIncoming : HasOutgoing)(Rays[ray].overlaps)) {
        Rays[ray].overlaps -= incoming ? Overlaps::Incoming : Overlaps::Outgoing;
        std::erase_if(RayOverlaps, [id = RayID{ray,incoming}](const std::pair<RayID, RayID>& p) { return p.first==id || p.second==id; });
    }
}


/** Adds two incoming rays for a crosspoint.
 *
 * This shall be called for all cps, when found.
 * In this function we avoid incoming rays with pos==0, we use the previous edge and pos==1.
 * We expect the position to never equal to 1 (those should be reported as position 0 at
 * the next edge).
 * This function does only create the incoming rays without calculating the angles.
 * - Outgoing rays are the same, except the 'incoming' flag (and the angle)
 * - The 'pos' member may change when merging crosspoints so angle calculation comes later.
 * @param [in] xy The coordinate of the crosspoint. We take it by value to be able to snap it
 *                to another, already existing crosspoint.
 * @param [in] m_c1 True if the main Contour of c1 is clockwise.
 * @param [in] c1 One of the SimpleContour the cp belongs to.
 * @param [in] v1 The index of the edge of c1 the cp is on.
 * @param [in] p1 The pos of the crosspoint on edge v1.
 * @param [in] overlap1 Shows where does this edge overlap with c2/v2.
 *             If None 'overlap2' must also be None.
 * @param [in] m_c2 True if the main Contour of c2 is clockwise.
 * @param [in] c2 The other of the SimpleContour the cp belongs to (can be the same as c1 at untangle).
 * @param [in] v2 The index of the edge of c2 the cp is on.
 * @param [in] p2 The pos of the crosspoint on edge v2.
 * @param [in] overlap2 Shows where does this edge overlap with c1/v1.
 *             If None, 'overlap1' must also be None. */
void ContoursHelper::AddCrosspoint(XY xy,
                                   const Contour *m_c1, const SimpleContour *c1, size_t v1, double p1, Overlaps overlap1,
                                   const Contour *m_c2, const SimpleContour *c2, size_t v2, double p2, Overlaps overlap2)
{
    _ASSERT(c2);
    _ASSERT(p1<1 && p2<1 && p1>=0 && p2>=0);
    _ASSERT(c1->at(v1).Match(xy, p1));
    _ASSERT(c2->at(v2).Match(xy, p2));
    _ASSERT((overlap1==Overlaps::Not) == (overlap2==Overlaps::Not));

    const size_t i1 = Rays.size();   Rays.emplace_back(xy, m_c1, c1, v1, p1, true, overlap1);
    const size_t i2 = Rays.size();   Rays.emplace_back(xy, m_c2, c2, v2, p2, true, overlap2);

    if (overlap1!=Overlaps::Not) {
        RayOverlaps.emplace_back(RayID{i1, overlap1==Overlaps::Incoming},
                                 RayID{i2, overlap2==Overlaps::Incoming});

    }
}


//finds all crosspoints between all edges of this SimpleContour (except vertices)
void ContoursHelper::FindCrosspointsHelper(const SimpleContour *i)
{
    XY r[Edge::MAX_CP];
    double one_pos[Edge::MAX_CP], two_pos[Edge::MAX_CP];
    //We need to check edges subsequent to each other, since crazy circles may cross at places
    //more than their endpoint. Assumedly Crossing() will not return pos==1 crosspoints
    //so we will not get normal vertices back
    for (size_t u1 = 1; u1<i->size(); u1++) {
        const Block T = i->at(u1).IsStraight() ? Block(i->at(u1).GetStart(), i->at(u1).GetEnd()) : i->at(u1).GetBezierHullBlock();
        for (size_t u2 = 0; u2<u1; u2++) {
            const Block O = i->at(u2).IsStraight() ? Block(i->at(u2).GetStart(), i->at(u2).GetEnd()) : i->at(u2).GetBezierHullBlock();
            if (!HullOverlap(O, T, i->next(u2) == u1)) continue;
            const int n = i->at(u2).CrossingWithOverlap(i->at(u1), i->next(u2)==u1, r, two_pos, one_pos); //n<0 means overlap
            for (int k=0; k<abs(n);k++) {
                //main_clockwise values are dummy
                //_ASSERT(i->at(u1).Pos2Point(one_pos[k]).test_equal(r[k]));
                //_ASSERT(i->at(u2).Pos2Point(two_pos[k]).test_equal(r[k]));
                _ASSERT(i->at(u1).Pos2Point(one_pos[k]).DistanceSqr(r[k])<0.1);
                _ASSERT(i->at(u2).Pos2Point(two_pos[k]).DistanceSqr(r[k])<0.1);
                //skip crosspoints that are vertices: one edge joins the next
                if (one_pos[k]==1 && two_pos[k]==0 && i->next(u1)==u2) continue;
                if (one_pos[k]==0 && two_pos[k]==1 && i->next(u2)==u1) continue;
                //change vertex crosspoints which are detected at the _end_
                //of an edge to be expressed as being at the beginning of the
                //following edge.
                //This is to avoid a vertex cp to be added twice (in case it is detected
                //again for the following edge).
                AddCrosspoint(r[k], nullptr, &*i, one_pos[k]==1 ? i->next(u1) : u1,
                                                  one_pos[k]==1 ? 0 : one_pos[k],
                                                 n>=0 ? Overlaps::Not : one_pos[k]>one_pos[1-k] ? Overlaps::Incoming : Overlaps::Outgoing,
                                    nullptr, &*i, two_pos[k]==1 ? i->next(u2) : u2,
                                                  two_pos[k]==1 ? 0 : two_pos[k],
                                                 n>=0 ? Overlaps::Not : two_pos[k]>two_pos[1-k] ? Overlaps::Incoming : Overlaps::Outgoing);
            }
        }
    }
    for (size_t u1 = 1; u1<i->size(); u1++) {
        const unsigned n = i->at(u1).SelfCrossing(r, one_pos, two_pos);
        _ASSERT(n<=1);
        if (n==1) // && !(one_pos))
            AddCrosspoint(r[0], nullptr, &*i, one_pos[0]==1 ? i->next(u1) : u1,
                                              one_pos[0]==1 ? 0 : one_pos[0], Overlaps::Not,
                                nullptr, &*i, two_pos[0]==1 ? i->next(u1) : u1,
                                              two_pos[0]==1 ? 0 : two_pos[0], Overlaps::Not);
    }
}

//finds all crosspoints between all simplecontours found in this tree.
void ContoursHelper::FindCrosspointsHelper(const HoledSimpleContour *i)
{
    //Find intersections of the main contour with itself
    FindCrosspointsHelper(&i->outline);
    //Now see where the main contour meets its holes
    for (const auto &h : i->holes)
        //main_clockwise values are dummy
        FindCrosspointsHelper(nullptr, &i->outline, nullptr, &h.outline);
    FindCrosspointsHelper(&i->holes, true);
}

//Finds all crosspoints between any two contours and among the edges of each
//If "cwh_included" is false we do not test individual contours in the list,
//just pairwise touch between the list elements
void ContoursHelper::FindCrosspointsHelper(const ContourList *c, bool cwh_included)
{
    //We ignore the holes, just calculate
    for (auto i = c->begin(); i!=c->end(); i++) {
        //see where i crosses itself, including its holes
        if (cwh_included)
            FindCrosspointsHelper(&*i);
        auto j = i;
        //now see where i crosses others (including holes)
        for (++j; j!=c->end(); j++) {
            if (i->GetBoundingBox().Overlaps(j->GetBoundingBox()))
                //main_clockwise values are dummy if we do a single contour operation
                //but if we do multi-contour, we shall use the clockwiseness of these
                FindCrosspointsHelper(nullptr, &*i, nullptr, &*j);
        }
    }
}

//Finds all crosspoints between any two contours and among the edges of each
//If "cwh_included" is false we do not test individual contours in the list,
//just pairwise touch between the list elements
inline void ContoursHelper::FindCrosspointsHelper(const Contour *c, bool cwh_included)
{
    if (cwh_included)
        FindCrosspointsHelper(&c->first);
    if (c->further.size()) {
        FindCrosspointsHelper(nullptr, &c->first, nullptr, &c->further);
        FindCrosspointsHelper(&c->further, cwh_included);
    }
}


//finds all crosspoints between the two arguments, considering holes, too
void ContoursHelper::FindCrosspointsHelper(const Contour *m_c1, const SimpleContour *i1,
                                           const Contour* m_c2, const SimpleContour* i2)
{
    XY r[Edge::MAX_CP];
    double one_pos[Edge::MAX_CP], two_pos[Edge::MAX_CP];
    for (size_t u1 = 0; u1<i1->size(); u1++) {
        const Block T = i1->at(u1).IsStraight() ? Block(i1->at(u1).GetStart(), i1->at(u1).GetEnd()) : i1->at(u1).GetBezierHullBlock();
        for (size_t u2 = 0; u2<i2->size(); u2++) {
            const Block O = i2->at(u2).IsStraight() ? Block(i2->at(u2).GetStart(), i2->at(u2).GetEnd()) : i2->at(u2).GetBezierHullBlock();
            if (!HullOverlap(T, O, false)) continue;
            const int n = i1->at(u1).CrossingWithOverlap(i2->at(u2), false, r, one_pos, two_pos);
            for (int k = 0; k<abs(n); k++)
                //change vertex crosspoints which are detected at the _end_
                //of an edge to be expressed as being at the beginning of the
                //following edge.
                //This is to avoid a vertex cp to be added twice (in case it is detected
                //again for the following edge).
                AddCrosspoint(r[k], m_c1, i1, one_pos[k]==1 ? i1->next(u1) : u1,
                                              one_pos[k]==1 ? 0 : one_pos[k],
                                             n>=0 ? Overlaps::Not : one_pos[k]>one_pos[1-k] ? Overlaps::Incoming : Overlaps::Outgoing,
                                    m_c2, i2, two_pos[k]==1 ? i2->next(u2) : u2,
                                              two_pos[k]==1 ? 0 : two_pos[k],
                                             n>=0 ? Overlaps::Not : two_pos[k]>two_pos[1-k] ? Overlaps::Incoming : Overlaps::Outgoing);
        }
    }
}

//finds all crosspoints between the two arguments, considering holes, too
void ContoursHelper::FindCrosspointsHelper(const Contour *m_c1, const HoledSimpleContour *i1,
                                           const Contour* m_c2, const HoledSimpleContour* i2)
{
    //see where the two main covers meet
    FindCrosspointsHelper(m_c1, &i1->outline, m_c2, &i2->outline);
    //now see how the holes cross the other's main cover
    for (auto i = i1->holes.begin(); i!=i1->holes.end(); i++)
        if (i->GetBoundingBox().Overlaps(i2->GetBoundingBox()))
            FindCrosspointsHelper(m_c1, &*i, m_c2, i2);
    for (auto i = i2->holes.begin(); i!=i2->holes.end(); i++)
        if (i->GetBoundingBox().Overlaps(i1->GetBoundingBox()))
            FindCrosspointsHelper(m_c1, i1, m_c2, &*i);
    //And now see how the holes meet each other
    if (i1->holes.GetBoundingBox().Overlaps(i2->holes.GetBoundingBox()))
        FindCrosspointsHelper(m_c1, &i1->holes, m_c2, &i2->holes);
}

//finds all crosspoints between the two arguments, considering holes, too
void ContoursHelper::FindCrosspointsHelper(const Contour *m_c1, const HoledSimpleContour *cwh,
                                           const Contour* m_c2, const ContourList* cs)
{
    for (const HoledSimpleContour &hc : *cs)
        FindCrosspointsHelper(m_c1, cwh, m_c2, &hc);
}


void ContoursHelper::FindCrosspointsHelper(const Contour *m_c1, const ContourList *c1,
                                           const Contour* m_c2, const ContourList* c2)
{
    for (const HoledSimpleContour& hc1 : *c1)
        for (const HoledSimpleContour& hc2 : *c2)
            //Search for cps only if the two main contour overlaps
            if (hc1.GetBoundingBox().Overlaps(hc2.GetBoundingBox()))
                FindCrosspointsHelper(m_c1, &hc1, m_c2, &hc2);
}

/** This checks if two rays with these angles should be further
 * checked for ordering.
 * We check it if
 * - the two angles are very similar (<1e-4) irrespective of curvature.
 * - the two angles are somewhat similar (<1e-2) and the ordering of the
 *   angles and curves are different, thus it is expected that the two
 *   edges will cross nearby.*/
constexpr bool ShouldVerify(const RayAngle &a, const RayAngle &b) noexcept {
    _ASSERT(0<=a.angle && a.angle<4);
    _ASSERT(0<=b.angle && b.angle<4);
    const double diff_angle = constexpr_fabs(a.angle - b.angle);
    if (diff_angle<1e-4 || (4-1e-4)<diff_angle) return true;
    if (a.curve==b.curve) return false;
    if (1e-2<diff_angle && diff_angle<(4-1e-2)) return false;
    const bool a_angle_is_smaller = b.angle-a.angle<-2 || (0<b.angle-a.angle && b.angle-a.angle<2);
    return a_angle_is_smaller!=(a.curve<b.curve);
}



void ContoursHelper::FindCrosspoints() {
    //Reset stuff
    Rays.clear();
    contour_heads.clear();
    cp_heads.clear();

    Rays.reserve(200);
    if (C2 && C1) {
        if (C1->GetBoundingBox().Overlaps(C2->GetBoundingBox())) {
            FindCrosspointsHelper(C1, &C1->first, C2, &C2->first);
            if (C2->further.GetBoundingBox().Overlaps(C1->GetBoundingBox()))
                FindCrosspointsHelper(C1, &C1->first, C2, &C2->further);
            if (C1->further.GetBoundingBox().Overlaps(C2->GetBoundingBox()))
                FindCrosspointsHelper(C2, &C2->first, C1, &C1->further);
            if (C2->further.GetBoundingBox().Overlaps(C1->further.GetBoundingBox()))
                FindCrosspointsHelper(C1, &C1->further, C2, &C2->further);
            //Here we also need to test if there are HoledSimpleContour:s inside C1 and C2 that touch
            //FindCrosspointsHelper(C1, false);
            //FindCrosspointsHelper(C2, false);
        }
    } else if (C1) {
        FindCrosspointsHelper(C1, true);
    } else if (SC1 && SC2) {
        FindCrosspointsHelper(nullptr, SC1, nullptr, SC2);
    } else if (SC1)
        FindCrosspointsHelper(SC1);
    if (Rays.empty()) return;

#ifdef _DEBUG
    if (0) {
        contour::debug::Snapshot(EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5,
                                 ColorType(255, 0, 0), (*C1)[0].Outline(), ColorType(0, 0, 255), C2 ? (*C2)[0].Outline() : SimpleContour());
        std::string r = C1->Dump(true);
        std::string a = C2 ? C2->Dump(true) : "";
    }
#endif
}

/** Merge crosspoints that are suspiciously close together
 * Criteria to merge
 * 1. the coordinate is closer than DIST_SAME_CP_SNAP
 * 2. one of the rays is an exact match of contour,edge,pos (even pos matches exactly)
 *   => In the 1-2 case above we merge all rays where contour,edge is the same and pos differs
 *      only in POS_DIST_DETECT_SAME_CP. We also set the xy to the average of the coordinates.
 * 3. the coordinate is closer than DIST_DETECT_SAME_CP (which is looser than the above);
 *    AND all 4 rays match in the two CP with at most POS_DIST_DETECT_SAME_CP difference in pos;
 *    AND one CP is from overlap and the other is not. In this case we keep only the one with
 *    the overlap and completely drop the other.
 *    This is to cater for the case of crosspoints at the vertex of an edge present in both
 *    contours.*/
void ContoursHelper::PruneCrosspoints() {
    //At this point all crosspoints stored so far has 2 rays in Rays: the two incoming ones.
    //First take a look at case 3.
    for (unsigned u = 0; u < Rays.size(); u += 2)
        for (unsigned v = u + 2; v < Rays.size(); v += 2) {
            if (Rays[v].contour == nullptr) continue;
            if (Rays[u].xy.DistanceSqr(Rays[v].xy) > DIST_DETECT_SAME_CP * DIST_DETECT_SAME_CP) continue;
            //If the 2 cps (u and v) are between the same edges, this will contain
            //the differences between the position values of the respective edges.
            //E.g., if the two edges are A (ray u) and B (rays u+1) then
            //pos_dist[0] will contain the difference between u's and v's position
            //of the incoming ray on edge A, and pos[2-3] on edge B. Else DBL_MAX
            std::array<double, 2> pos_dist = {DBL_MAX, DBL_MAX};
            std::array<size_t, 2> umatch;
            for (unsigned uu = u; uu < u + 2; uu++)
                for (unsigned vv = v; vv < v + 2; vv++) {
                    if (Rays[uu].contour != Rays[vv].contour) continue;
                    const unsigned first_edge = uu < u + 1 ? 0 : 1;
                    const double diff = Rays[uu].DistOnContour(Rays[vv]);
                    if (diff < pos_dist[first_edge]) {
                        pos_dist[first_edge] = diff;
                        umatch[first_edge] = vv;
                    }
                }
            if (pos_dist[0] < DIST_DETECT_SAME_CP && pos_dist[1] < DIST_DETECT_SAME_CP) {
                //Here we merge u with umatch[0] and u+1 with umatch[1]
                unsigned at_vertex = 0;
                for (unsigned x = 0; x < 2; x++) {
                    //Average out the positions (for both, since we don't know which one will we remove)
                    const std::optional<bool> vertex_incoming = Rays[u + x].AvgPosWith(Rays[umatch[x]]);
                    if (vertex_incoming.has_value()) {
                        at_vertex |= x + 1;
                        RemoveRayOverlap(u + x, *vertex_incoming);
                        if (Rays[umatch[x]].vertex != Rays[u + x].vertex) {
                            //we have moved to the next vertex, remove outgoing overlap
                            _ASSERT(Rays[u + x].pos == 0);
                            RemoveRayOverlap(umatch[x], false);
                        } else if (Rays[umatch[x]].pos != 0 && Rays[u + x].pos == 0) {
                            //we have snapped to the beginning of the edge, remove incoming overlap
                            RemoveRayOverlap(umatch[x], true);
                        }
                    }
                    Rays[umatch[x]].vertex = Rays[u + x].vertex;
                    Rays[umatch[x]].pos = Rays[u + x].pos;
                    //If one of them is already (and remained) part of an overlap group, we make
                    //the other be part of it, too.
                    const Overlaps combined = Rays[umatch[x]].overlaps + Rays[u + x].overlaps;
                    if (HasIncoming(combined)) AddRayOverlap(u + x, true, umatch[x], true);
                    if (HasOutgoing(combined)) AddRayOverlap(u + x, false, umatch[x], false);
                }
                //at_vertex contains 1 and/or 2 (binary or-ed) if the first or second cp was snapped to the
                //vertex. If so, we adjust the coordinates of these crosspoints to the vertex (or the avg
                //of the two vertices).
                const unsigned del = Rays[u].overlaps != Overlaps::Not ? v : u; //Delete the not overlapping one, or all the same if both overlap
                const unsigned keep = Rays[u].overlaps == Overlaps::Not ? u : v; //Keep the other
                if (at_vertex) {
                    const XY new_xy = [&] {
                        switch (at_vertex) {
                        case 1: return Rays[u].contour->at(Rays[u].vertex).GetStart();
                        case 2: return Rays[u + 1].contour->at(Rays[u + 1].vertex).GetStart();
                        default:
                        case 3: return (Rays[u].contour->at(Rays[u].vertex).GetStart()
                                        + Rays[u + 1].contour->at(Rays[u + 1].vertex).GetStart()) / 2;
                        }
                        }();
#ifndef NDEBUG
                        max_cp_adjust_sqr = std::max(max_cp_adjust_sqr, Rays[keep].xy.DistanceSqr(new_xy));
                        max_cp_adjust_sqr = std::max(max_cp_adjust_sqr, Rays[keep + 1].xy.DistanceSqr(new_xy));
#endif // !NDEBUG
                        Rays[keep].xy = Rays[keep + 1].xy = new_xy;
                }
                //We don't actually delete as we have indices of Rays[] stored in RayOverlaps
                Rays[del].contour = nullptr;
                Rays[del + 1].contour = nullptr;
                if (del == u) break; //we have deleted 'u' no need to compare it to anything
            }
        }
    //Now check merge rule 1-2
    EquivalenceClasses merge_groups;
    for (unsigned u = 0; u < Rays.size(); u += 2)
        if (Rays[u].contour) {
            for (unsigned v = u + 2; v < Rays.size(); v += 2)
                if (!Rays[v].contour) continue;
                else if (const double distsqr = Rays[u].xy.DistanceSqr(Rays[v].xy); distsqr > 1) continue; //fast path to exclude most cases
                else if (distsqr < DIST_SAME_CP_SNAP * DIST_SAME_CP_SNAP)
                    merge_groups.add(u, v);
                else //xy coordinate is not close enough (but may still be close): check edge positions
                    for (unsigned uu = u; uu < u + 2; uu++)
                        if (std::any_of(Rays.begin() + v, Rays.begin() + v + 2, [&R = Rays[uu]](const Ray& S)
                                        { return R.contour == S.contour && R.vertex == S.vertex && R.pos == S.pos; })) {
                            merge_groups.add(u, v);
                            break;
                        }
        }
    //Now cps to merge are in merge_groups
    for (auto group : merge_groups.GetClasses()) {
        _ASSERT(!group.empty());
        //1. Calculate the average of the coordinates
        XY xy(0, 0);
        for (size_t g : group)
            xy += Rays[g].xy;
        xy = xy / group.size();
        //2. Snap all rays of the CPs to the same xy coordinate
        //THis will cause them to be inserted to the same CP list
        for (size_t g : group)
            for (size_t u = g; u < g + 2; u++)
                if (Rays[u].contour) {
#ifndef NDEBUG
                    max_cp_adjust_sqr = std::max(max_cp_adjust_sqr, Rays[u].xy.DistanceSqr(xy));
#endif // !NDEBUG
                    Rays[u].xy = xy;
                }
        //3. Mark duplicate rays - ones that have
        //- contour,edge,pos,incoming quite similar
        for (unsigned gu = 0; gu < group.size(); gu++)
            for (unsigned gv = gu + 1; gv < group.size(); gv++)
                for (unsigned uu = group[gu]; uu < group[gu] + 2; uu++)
                    for (unsigned vv = group[gv]; vv < group[gv] + 2; vv++)
                        if (Rays[uu].contour != Rays[vv].contour) continue;
                        else if (Rays[uu].contour == nullptr) continue;
                        else if (Rays[uu].DistOnContour(Rays[vv]) > DIST_DETECT_SAME_CP) continue;
                        else {
                            Rays[uu].AvgPosWith(Rays[vv]);
                            if (HasIncoming(Rays[vv].overlaps))
                                AddRayOverlap(uu, true, vv, true);
                            if (HasOutgoing(Rays[vv].overlaps))
                                AddRayOverlap(uu, false, vv, false);
                            Rays[vv].contour = nullptr; //delete
                        }
    }
    //Now add the outgoing rays, calculate angles
    //Keep removed edges, so that CPs have rigidly two rays.
    const size_t size = Rays.size();
    Rays.reserve(2 * size);
    for (size_t u = 0; u < size; u += 2) {
        for (size_t v = 0; v < 2; v++) {
            Rays[u + v].CalcAngle();
            Rays.push_back(Rays[u + v]);
            Rays.back().incoming = false;
            Rays.back().CalcAngle();
        }
    }
    EquivalenceClasses < RayID, RayID{} > overlap_groups;
    overlap_groups.reserve(RayOverlaps.size() * 3);
    for (auto& [id1, id2] : RayOverlaps)
        overlap_groups.add(id1, id2);
    RayOverlaps.clear();
    //Now average the ray angles for overlapping rays
    //This snaps them to the exact same value and will make them
    //a ray group. We omit the removed rays as they have no angle
    for (auto group : overlap_groups.GetClasses()) {
        if (group.size() < 2) continue;
        std::optional<RayAngle> r;
        for (auto [ray, incoming] : group)
            if (const size_t x = ray + (incoming ? 0 : size); Rays[x].contour == nullptr) continue;
            //This is not equal averaging, as later values have more weight
            //But we only have a pairwise Avg, and I am lazy to write one with weights.
            else if (r) r = Avg(*r, Rays[x].angle);
            else r = Rays[x].angle;
        if (r)
            for (auto& [ray, incoming] : group)
                if (const size_t x = ray + (incoming ? 0 : size); Rays[x].contour)
                    Rays[x].angle = *r;
    }

    //Remove the marked edges.
    std::erase_if(Rays, [](const Ray& R) {return R.contour == nullptr; });
    _ASSERT((std::ranges::stable_sort(Rays, [](const Ray& A, const Ray& B) { return A.xy < B.xy; }), true)); //sort CPs in debug mode for easier read

    //Shortcut heuristics for binary operations: if we have one CP only,
    //that must be a touchpoint, so we can ignore it.
    if (Rays.size() <= 4 && (C2 || SC2))
        Rays.clear();

    if (Rays.empty()) return;

    //Create in-place linked list
    //Since we will have few contours, we sort the array, according to <contour, vertex, pos, incoming>
    //and assume that rays along a contour simply follow one another in the Rays vector.
    //Since we do not use the RayOverlaps array at and beyond this point, so no reference is made to
    //Rays indices, so we can reshuffle the array.
    //When it comes to the CP list, we simply add them one-by one. There we assume that one CP will have few
    //entires, so linear search-and-insert inside a CP list will be quick.
    std::ranges::sort(Rays, {}, [](const Ray& r) { return std::tuple(reinterpret_cast<std::uintptr_t>(r.contour), r.vertex, r.pos, !r.incoming); });
    const SimpleContour* current = nullptr;
    cp_heads.reserve(Rays.size() / 4);
    for (size_t index = 0; index < Rays.size(); index++) {
        if (current != Rays[index].contour) {
            if (index) contour_heads.back().end = index;
            current = Rays[index].contour;
            contour_heads.emplace_back(current, index);
        }
        InsertToCPList(index);
    }
    contour_heads.back().end = Rays.size();
}

/** Go through neighboring rays of a CP and check that they are in the right order.*/
void ContoursHelper::SanitizeRays() {
    //Go through the crosspoints to see if the rays meaningfully connect them
    for (const auto &[xy, cpdata] : cp_heads) {
        size_t cp_head = cpdata.head;
        //Walk through all ray groups and for rays that have very similar angles
        //test if the order of the angles is indeed good.
        for (size_t orig[2] = {cp_head, Rays[cp_head].link_in_cp.next}
                 ; orig[1] != cp_head
                 ; orig[0] = orig[1], orig[1] = Rays[orig[1]].link_in_cp.next) {
            const size_t next_cp[2] = { follow_in_contour(orig[0]), follow_in_contour(orig[1]) };
            if (Rays[next_cp[0]].link_in_cp.prev == next_cp[1])
                //if the two rays (orig[0/1]) lead to the same cp and the corresponding rays there
                //(next_cp[0/1]) are in the right order, there is nothing to do. This is a special
                //case for performance.
                continue;
            bool need_swap =
                Rays[next_cp[0]].link_in_cp.next == next_cp[1]
                // Here we have the simple and special case of two neighboring crosspoints connect
                // by two edges where the order of the two edges is opposite in the two crosspoints.
                // (And the two rays in the two crosspoints are both neighboring, so it is really a
                // limited case we handle here as a quick fix.)
                && C2                                                   //For untangle operations we cannot assume such nice edge ordering
                && Rays[orig[0]].contour != Rays[orig[1]].contour       //We only do it if the two contours are different
                && !Rays[orig[0]].angle.ExactEqual(Rays[orig[1]].angle) //Keep ordering within a ray group
                // We need to swap either us orig[0] with orig[1] or next_cp[0] with next_cp[1].
                // Swap the one with the smaller angle difference: here we only swap if orig[0] and orig[1]
                // needs to be swapped, else we will swap when processing the other cp.
                && RayAngle::IsCloserAbs(Rays[orig[0]].angle, Rays[orig[1]].angle,
                                         Rays[next_cp[0]].angle, Rays[next_cp[1]].angle)
                // Finally, do not swap rays that are not at all similar
                && Rays[orig[0]].angle.IsABitSimilar(Rays[orig[1]].angle);
            if (!need_swap && ShouldVerify(Rays[orig[0]].angle, Rays[orig[1]].angle)) {
                //1. Test if the next cp is the same on both rays then the angles are consistent
                if (Rays[next_cp[0]].xy == Rays[next_cp[1]].xy &&
                    Rays[next_cp[0]].angle.IsSimilar(Rays[next_cp[1]].angle)) {
                    need_swap = Rays[next_cp[0]].angle.Smaller(Rays[next_cp[1]].angle);
                    //If they are indeed very similar, make them equal
                    if (Rays[orig[0]].angle.IsSimilar(Rays[orig[1]].angle))
                        Rays[orig[0]].angle = Rays[orig[1]].angle =
                            Avg(Rays[orig[0]].angle, Rays[orig[0]].angle);
                    //fallthrough to swap, if needed
                } else {
                    //2. Else start walking away from the CP and find out which way the two contours depart
                    struct Pos : PathPos
                    {
                        const SimpleContour *c;
                        bool walk_forward;
                    } p[2];
                    for (unsigned u = 0; u<2; u++)
                        p[u] = { PathPos{Rays[orig[u]].vertex, Rays[orig[u]].pos}, Rays[orig[u]].contour, !Rays[orig[u]].incoming };
                    int p0_to_p1 = 0; //-1: p0 is smaller, +1: p1 is larger, 0: inconclusive
                    do { //goal of this loop is to set p0_to_p1
                        //Here the two positions/rays in p[0] and p[1] has the same angle
                        //We assume the rays overlap
                        //Follow the rays till the closest vertex
                        double dist[2];
                        double new_pos[2];
                        for (unsigned u = 0; u<2; u++) {
                            if (p[u].walk_forward) {
                                if (p[u].pos==1) {
                                    p[u].edge = p[u].c->next(p[u].edge);
                                    p[u].pos = 0;
                                    new_pos[u] = 1;
                                    dist[u] = p[u].c->at(p[u].edge).GetLength();
                                } else {
                                    dist[u] = p[u].c->at(p[u].edge).GetLength(p[u].pos, 1);
                                    new_pos[u] = 1;
                                }
                            } else {
                                if (p[u].pos==0) {
                                    p[u].edge = p[u].c->prev(p[u].edge);
                                    p[u].pos = 1;
                                    new_pos[u] = 0;
                                    dist[u] = p[u].c->at(p[u].edge).GetLength();
                                } else {
                                    dist[u] = p[u].c->at(p[u].edge).GetLength(0, p[u].pos);
                                    new_pos[u] = 0;
                                }
                            }
                        }
                        //Now p[u] contains the original points (with p[u].pos!=1); new_pos[u] is the pos at the next vertex,
                        //and dist contains the distance from p[u]->new_pos[u] (always positive)
                        //Also: new_pos[u] is understood at the terms of edge p[u].edge
                        //Next: walk back on the edge we have walked further so that we are as far from the
                        //original cp as in the edge where we have walked less.
                        const bool larger = dist[0]<dist[1];
                        if (p[larger].walk_forward) {
                            p[larger].pos = p[larger].c->at(p[larger].edge).MovePos2(p[larger].pos, dist[!larger]);
                            p[!larger].pos = new_pos[!larger];
                        } else {
                            p[larger].pos = p[larger].c->at(p[larger].edge).MovePos2(p[larger].pos, -dist[!larger]);
                            p[!larger].pos = new_pos[!larger];
                        }

                        //Test if we have bypassed the next_cp[0,1]
                        //We have bypassed them if we are on the same edge, but a smaller/larger pos (depending
                        //on the walk direction). It cannot happen that we jump over them without being on
                        //the same edge for at least one iteration.
                        bool bypassed = false;
                        for (unsigned u = 0; u<2; u++)
                            if (p[u].walk_forward)
                                bypassed |= p[u].edge==Rays[next_cp[u]].vertex && p[u].pos>=Rays[next_cp[u]].pos;
                            else
                                bypassed |= p[u].edge==Rays[next_cp[u]].vertex && p[u].pos<=Rays[next_cp[u]].pos;
                        if (bypassed)
                            break; //Here the two contours do not depart before the next cp. Give up essentially.

                        //If the two new points are far enough, we can test their clockwisedness
                        XY new_xy[2] = {p[0].c->at(p[0].edge).Pos2Point(p[0].pos), p[1].c->at(p[1].edge).Pos2Point(p[1].pos)};
                        if (new_xy[0].DistanceSqr(new_xy[1])>=1) {
                            new_xy[0] -= Rays[orig[0]].xy;
                            new_xy[1] -= Rays[orig[0]].xy;
                        } else {
                            //if not, test the angles at them
                            //Note that if we have walked to a vertex on one of the curves, we shall
                            //avoid calculating a tangent point based on the following edge (or the
                            //previous if we walk backwards), as that may point to a very different direction.
                            //Thus here, we effectively calculate the angle of arriving to this point, trying to 
                            //see if the edges diverged before this point.
                            //Note that in a vertex we reset curve - effectively comparing the angle of a tangent
                            RayAngle a[2];
                            for (unsigned u = 0; u<2; u++)
                                if (p[u].walk_forward) {
                                    if (p[u].pos < 1) a[u] = p[u].c->at(p[u].edge).Angle(false, p[u].pos);
                                    else a[u] = {p[u].c->at(p[u].edge).Angle(true, p[u].pos).Opposite().angle, 0};
                                } else {
                                    if (p[u].pos > 0) a[u] = p[u].c->at(p[u].edge).Angle(true, p[u].pos);
                                    else a[u] = {p[u].c->at(p[u].edge).Angle(false, p[u].pos).Opposite().angle, 0};
                                }
                            if (a[0].IsSimilar(a[1])) {
                                //even the angles/curves are similar, test if we are at a vertex
                                if (!(p[0].walk_forward && p[0].pos == 1) && !(!p[0].walk_forward && p[0].pos == 0) &&
                                    !(p[1].walk_forward && p[1].pos == 1) && !(!p[1].walk_forward && p[1].pos == 0))
                                    continue; //not at a vertex, continue
                                //Calculate the forward tangent after the vertex - the other tangent is good
                                for (unsigned u = 0; u < 2; u++) 
                                    if (p[u].walk_forward) {
                                        if (p[u].pos == 1) a[u] = p[u].c->at_next(p[u].edge).Angle(false, 0);
                                    } else {
                                        if (p[u].pos == 0) a[u] = p[u].c->at_prev(p[u].edge).Angle(true, 1);
                                    }
                                if (a[0].IsSimilar(a[1])) 
                                    continue; //the angles are still similar after the vertex, continue;
                             }
                            //OK, angles are different, start moving in 0.1-pixel increments
                            //We stop if we are 0.1 apart - but we always stop when reaching the
                            //next crosspoint on one of the contours.
                            constexpr double increment = 0.1;
                            const PathPos ncp[2] = { {Rays[next_cp[0]].vertex, Rays[next_cp[0]].pos}, {Rays[next_cp[1]].vertex, Rays[next_cp[1]].pos} };
                            for (int _ = 0; _ < 100; _++) {
                                bool bypassed = false; //set when we bypass the next crosspoint on one of the contours. In that case we give up.
                                const PathPos old_p[2] = { p[0], p[1] };
                                for (unsigned u = 0; u < 2 && !bypassed; u++) {
                                    double len = p[u].walk_forward ? increment : -increment;
                                    static_cast<PathPos&>(p[u]) = p[u].c->GetEdges().MovePos(len, old_p[u], false, true);
                                    _ASSERT(len == 0);
                                    bypassed |= p[u].walk_forward ? between_warp(old_p[u], ncp[u], p[u]) : between_warp(p[u], ncp[u], old_p[u]);
                                    new_xy[u] = p[u].c->at(p[u].edge).Pos2Point(p[u].pos);
                                }
                                if (bypassed || new_xy[0].DistanceSqr(new_xy[1]) >= increment) {
                                    new_xy[0] -= Rays[orig[0]].xy;
                                    new_xy[1] -= Rays[orig[0]].xy;
                                    break;
                                }
                            }
                        }
                        switch (triangle_dir(XY(0, 0), new_xy[0], new_xy[1])) {
                            case ETriangleDirType::CLOCKWISE:        p0_to_p1 = -1; break;
                            case ETriangleDirType::COUNTERCLOCKWISE: p0_to_p1 = +1; break;
                            default: p0_to_p1 = 0; break;
                        }
                        //finally we found where the two contours depart
                        break;
                    } while (1);
                    //Here p0_to_p1 is -1 if p[0] leaves cclockwise to p[1] (smaller in angle)
                    //+1 if opposite and 0 if we could not determine their relation.
                    if (p0_to_p1 && Rays[orig[0]].angle.ExactEqual(Rays[orig[1]].angle))
                        Rays[orig[p0_to_p1==-1]].angle.curve += 1e-4;
                    need_swap = p0_to_p1 == +1;
                }
            }
            if (need_swap) {
                cp_head = SwapWithNextInCPList(orig[0], cp_head);
                //continue from this index
                orig[1] = Rays[orig[0]].link_in_cp.next;
                //orig[1] changed and the loop condition would not catch the end
                if (orig[1]==cp_head) break;
            }
        }
    }
}

/**After this you can call again EvaluateCrosspoints with a different operation*/
void ContoursHelper::ResetCrosspoints() const
{
    StartRays.clear();
    for (const Ray &r : Rays)
        r.Reset();
    evaluated_for.reset();
}

/** Returns wether to include a part of a shape.
 *
 * Specifically :
 * - If union, pick the angle ranges covered by at least one contour
 * - If intersect, pick angle ranges covered by all of the contours
 * - If xor, we pick ranges of odd coverage
 * - for untangle we formulate winding rules as they say
 *
 * Note that a clockwise surface has coverage 1 inside and zero outside
 * a counterclockwise surface has coverage -1 inside and zero outside
 * thus we have the following non-intuitive results (`+` is union, `*`
 * is intersection, 'hole' is counterclockwise, 'surface' is clockwise')
 * - hole1 + hole2 = inv(inv(hole1) + inv(hole2))
 * - hole1 * hole  = inv(inv(hole1) * inv(hole2))
 * - hole * surface = 0
 * - surface - surface = surface1 + inv(surface2) and not surface1 * inv(surface2)
 * @param [in] cov The total cumulative coverage at the area. E.g., if two positive (clockwise)
 *                 surface cover an area, coverage there is +2.
 * @param [in] type The type of operation we perform.
 * @returns True if the area shall be included in the result of the operation.*/
inline bool ContoursHelper::IsCoverageToInclude(int cov, Contour::EOperationType type) const
{
    switch (type) {
    default: _ASSERT(0); FALLTHROUGH;
    case Contour::POSITIVE_INTERSECT:   return cov>=2;
    case Contour::NEGATIVE_INTERSECT:   return cov<=-2;
    case Contour::POSITIVE_UNION:       return cov>=1;
    case Contour::NEGATIVE_UNION:       return cov<=-1;
    case Contour::POSITIVE_XOR:         return cov==1;
    case Contour::NEGATIVE_XOR:         return cov==-1;
    case Contour::WINDING_RULE_NONZERO: return cov!=0;
    case Contour::WINDING_RULE_EVENODD: return abs(cov)%2==1; //here we can have cov<0 & we may keep it
    case Contour::EXPAND_POSITIVE:      return cov>0;
    case Contour::EXPAND_NEGATIVE:      return cov<0;
    }
}

/** Returns the ray after the current ray-group.
 *
 * A ray group is a set of rays of the same cp where RayAngle is the same.
 * This functions steps ahead in the per cp list until it hits a ray with a different RayAngle.
 * @param [in] from The index of the ray we start the search from.
 * @param coverage When calling it shall be the coverage of the area just before from.
 *                 On return it will contain the coverage just before the returned ray.
 * @param [in] abort_at1 Stop searching if hitting this ray. Return this as result (with coverage set accordingly)
 * @param [in] abort_at2 Stop searching if hitting this ray. Return this as result (with coverage set accordingly)
 * @returns The index of the ray following the ray group of from.*/
inline size_t ContoursHelper::FindRayGroupEnd(size_t from, int &coverage, size_t abort_at1, size_t abort_at2) const
{
    size_t to = from;
    do {
        if (Rays[to].incoming) coverage--;
        else coverage++;
        to = Rays[to].link_in_cp.next;
    } while (to != from && to!=abort_at1 && to!=abort_at2 && SameRayGroup(from, to));
    return to;
}

/** Walk around a cp to a ray group where inclusion becomes true or false.
 *
 * If `start` is true, go to the ray group *after* which coverage becomes eligible for inclusion
 *   (that is, for intersects >=no of contours, for union >=1 and for xor odd; see IsCoverageToInclude()).
 * Else go to the ray group *after* which coverage becomes ineligible.
 * @param from At calling this is the beginning of the search. At return this is the first element of the
 *             ray group returned.
 * @param [out] to This is the first element after the ray group returned.
 * @param cov_now The coverage just before ray "from" at calling. The coverage just before "to" is returned.
 * @param [in] type The type of operation.
 * @param [in] start If true we search for a ray group after which coverage becomes eligible, else ineligible.
 * @param [in] abort_at Stop search here. 'to' will be set to this (and 'cov_now' is set appropriately).
 * @return True if coverage is eligible/ineligible just before to as requested by 'start'. */
bool ContoursHelper::GoToCoverage(size_t &from, size_t &to, int &cov_now, Contour::EOperationType type, bool start, size_t abort_at) const
{
    const size_t started = to = from;
    do {
        from = to;
        to = FindRayGroupEnd(from, cov_now, started, abort_at);
    } while (to!=started && to!=abort_at && start != IsCoverageToInclude(cov_now, type));
    return start == IsCoverageToInclude(cov_now, type);
}

/** Calculates the coverage of a HoledSimpleContour close to a point in the <0, -inf> direction assuming an untangle operation.
 *
 * Holes are considered just like the outline.
 * @param [in] xy The coordinates of the point
 * @param [in] cpdata If the point is a cp in 'Rays' the cpdata ray for the crosspoint.
 *                     Else link_info::no_link.
 * @param [in] cwh The shape for which coverage is calculated.
 * @returns The coverage just a bit right-up from the cp, just before a hypothetical ray with RayAngle of <0,-inf>.*/
int ContoursHelper::CalcCoverageHelper(const XY &xy, size_t cp_head, const HoledSimpleContour *cwh) const
{
    _ASSERT(cp_head == link_info::no_link || Rays[cp_head].xy == xy);
    //For each edge, we see if it crosses the (xy.x, xy.y)->(inf, xy.y) line
    //if it crosses clockwise: +1 cov
    //if it crosses cclockwise: -1 cov
    //if it is an ellipse that crosses twice and not in xy:
    //    change cov according to cps that ar strictly left of xy
    //if it is an ellipse that touches the line: no change in cov
    //horizontal lines through x are ignored: we seek coverage just before (0;-inf)

    //For beziers, we check if this particular edge is part of the crosspoint.
    //If so, we ignore it outright to save processing (and avoid problems from numerical
    //imprecision)
    int ret = 0;
    for (size_t e=0; e<cwh->outline.size();e++) {
        if (cp_head != link_info::no_link && !cwh->outline[e].IsStraight()) {
            //for beziers search the crosspoint if we are part of this cp
            size_t u = cp_head;
            //XXX ToDo: We cannot FULLY ignore these edges, as they may be curvy
            //and cross they line more than once....
            do {
                if (Rays[u].contour == &cwh->outline) {
                    //We ignore an edge if the cp is on it...
                    //...or the cp is a vertex and the endpoint of the edge is it.
                    if ((Rays[u].vertex == e) ||
                        (Rays[u].vertex == cwh->outline.next(e) && Rays[u].pos==0)) {
                        u = link_info::no_link;
                        break;
                    }
                }
                u = Rays[u].link_in_cp.next;
            } while (u!=cp_head);
            //if the cp is on 'e' we do nothing
            if (u!=cp_head)
                continue;
        }
        double x[3], pos[3];
        int fw[3];
        Edge tmp(cwh->outline[e]);
        tmp.SwapXY();
        const int num = tmp.CrossingVertical(xy.y, x, pos, fw);
        //do nothing for -1 or 0 returns
        for (int f=0; f<num; f++)
            if (test_smaller(xy.x, x[f])) {
                if (fw[f]==+1) ret++;    //fw is inverse due to Edge::SwapXY above
                else if (fw[f]==-1) ret--;
                //do nothing for touchpoints
            }
    }
    //now consider the holes
    for (auto i = cwh->holes.begin(); i!=cwh->holes.end(); i++)
        ret += CalcCoverageHelper(xy, cp_head, &*i);
    return ret;
}

/** Calculates the coverage of a HoledSimpleContour close to a point in the <0, -inf> direction assuming an untangle operation.
*
* Holes are considered just like the outline.
* @param [in] xy The coordinates of the point
* @param [in] cpdata If the point is a cp in 'Rays' the cpdata ray for the crosspoint.
*                     Else link_info::no_link.
* @param [in] cwh The shape for which coverage is calculated.
* @returns The coverage just a bit right-up from the cp, just before a hypothetical ray with RayAngle of <0,-inf>.*/
int ContoursHelper::CalcCoverageHelper2(const XY &xy, size_t cp_head, const HoledSimpleContour *cwh) const
{
    _ASSERT(cp_head == link_info::no_link || Rays[cp_head].xy == xy);
    //For each edge, we see if it crosses the (xy.x, xy.y)->(inf, xy.y) line
    //if it crosses clockwise: +1 cov
    //if it crosses cclockwise: -1 cov
    //if it is an ellipse that crosses twice and not in xy:
    //    change cov according to cps that ar strictly left of xy
    //if it is an ellipse that touches the line: no change in cov
    //horizontal lines through x are ignored: we seek coverage just before (0;-inf)

    //For beziers, we check if this particular edge is part of the crosspoint.
    //If so, we ignore it outright to save processing (and avoid problems from numerical
    //imprecision)
    int ret = 0;
    for (size_t e = 0; e<cwh->outline.size(); e++) {
        size_t u = cp_head;
        if (cp_head != link_info::no_link) {
            //for beziers search the crosspoint if we are part of this cp
            do {
                if (Rays[u].contour == &cwh->outline) {
                    //We ignore an edge if the cp is on it...
                    //...or the cp is a vertex and the endpoint of the edge is it.
                    if ((Rays[u].vertex == e) ||
                        (Rays[u].vertex == cwh->outline.next(e) && Rays[u].pos==0)) {
                        u = link_info::no_link;
                        break;
                    }
                }
                u = Rays[u].link_in_cp.next;
            } while (u!=cp_head);
        }
        ret += cwh->outline[e].CrossingHorizontalCPEvaluate(xy, u!=cp_head);
    }
    //now consider the holes
    for (auto i = cwh->holes.begin(); i!=cwh->holes.end(); i++)
        ret += CalcCoverageHelper2(xy, cp_head, &*i);
    return ret;
}

/** Calculates the coverage close to a cp in the <0, -inf> direction.
 *
 * Holes are considered just like the outline. We may have binary operations, not just untangle.
 * @param [in] cpdata The index of a ray at the cpdata of the per-cp strict list.
 * @returns The coverage just a bit right-up from the cp, just before a hypothetical ray with RayAngle of <0,-inf>.*/
int ContoursHelper::CalcCoverageHelper(size_t cp_head) const
{
    int ret = CalcCoverageHelper2(Rays[cp_head].xy, cp_head, &C1->first);
        for (auto i = C1->further.begin(); i!=C1->further.end(); i++)
            ret += CalcCoverageHelper2(Rays[cp_head].xy, cp_head, &*i);
        if (C2!=nullptr) {
            ret += CalcCoverageHelper2(Rays[cp_head].xy, cp_head, &C2->first);
            for (auto i = C2->further.begin(); i!=C2->further.end(); i++)
                ret += CalcCoverageHelper2(Rays[cp_head].xy, cp_head, &*i);
        }
    //And now add coverage for curves with tangent of zero
    size_t ray_no = cp_head;
    while (Rays[ray_no].angle.angle==0 && Rays[ray_no].angle.curve<0) {
        if (Rays[ray_no].incoming) ret++;
        else ret--;
        ray_no = Rays[ray_no].link_in_cp.next;
        if (ray_no == cp_head) break;
    }
    return ret;
}

/** Calculates the coverage inside a SimpleContour assuming it has no crosspoints.
 *
 * We assume C2==nullptr, an untangle operation.
 * This is called at the end of an untangle, when 'sc' has no crosspoints. In this case
 * 1. sc itself is untangled, thus there is a valid area it encloses.
 * 2. it touches nothing, thus the coverage in it is meaningful to talk about.
 * 3. We can calculate its clockwisedness.
 *
 * @param [in] sc The shape for which coverage is calculated.
 * @returns The coverage inside sc considering C1 and sc itself.*/
int ContoursHelper::CalcCoverageHelper(const SimpleContour *sc) const
{
    const XY &xy = sc->at(0).GetStart();
    int ret =  CalcCoverageHelper2(xy, link_info::no_link, &C1->first);
    for (auto i = C1->further.begin(); i!=C1->further.end(); i++)
        ret += CalcCoverageHelper2(xy, link_info::no_link, &*i);
    const RayAngle prev = sc->at(sc->size()-1).Angle(true, 1);
    const RayAngle next = sc->at(0).Angle(false, 0);
    //if the clockwise order is next->0->prev, then the horizontal line is inside
    //if sc is clockwise, then we are OK. Also vice versa.
    //Otherwise we need to add sc's coverage
    if ((prev.Smaller(next)) != sc->GetClockWise()) {
        if (sc->GetClockWise()) ret ++;
        else ret--;
    }
    return ret;
}


/** Determine which ray of a ray group has its following crosspoint closest to the cp of the ray group.
 *
 * Used to select between elements of a ray group during walking.
 * We assume rays in [from, to) actually form a ray group.
 * For performance reasons the caller should check that ray group has more than one member.
 * (Otherwise there is no ambiguity on which ray to pick.)
 * @param [in] from The first element of the ray group.
 * @param [in] to The element following the last element of the ray group.
 * @returns The index of the ray inside the ray group on which the following cp is closest to the cp of the ray group.*/
size_t ContoursHelper::ClosestNextCP(size_t from, size_t to) const
{
    double dist = MaxVal(dist);
    size_t closest=from;
    for (size_t i = from; i!=to; i = Rays[i].link_in_cp.next) {
        RayPointer p(i);
        Advance (p, !Rays[i].incoming);
        const XY &xy = p.at_vertex ? Rays[i].contour->at(p.vertex).GetStart() : Rays[p.index].xy;
        const double d = Rays[i].xy.Distance(xy);
        if (d < dist) {
            dist = d;
            closest = i;
        }
    }
    return closest;
}

/** Go through all the rays and for each one determine which other ray to switch to.
 *
 * During walking in order to see which ray to switch to at a crosspoint, we use the concept of
 * *coverage*. A clockwise surface has coverage 1 inside and zero outside.
 * A counterclockwise surface has coverage -1 inside and zero outside.
 * Coverage values add up, thus if a point is covered by two clockwise surfaces,
 * we say that the coverage at that point is +2.
 *
 * See the examples below. We show two edges for each example, with two rays per edge.
 * @verbatim
       (A1)
        |                       0
        ^  +1        (X1)--->-----o===>===(X2) & (Y2)
     0  |                         |
(B1)----o---->--(B2)      +1      ^  +2
    +1  |  +2                     |
       (A2)                      (Y1)
   @endverbatim
 * We show the coverage at the respected areas. During crosspoint evaluation, as we go through the rays
 * we always maintain the coverage *just before* a given ray (meaning a bit counterclockwise from it).
 * Thus, for example the coverage for ray Y1 is +2 and for B2 it is +1. (Incoming/outgoing property is
 * ignored when talking about the angle of a ray.)
 * We can thus say that as we process rays in clockwise order outgoing rays *increase* the coverage,
 * whereas incoming edges *decrease*. So if we know the coverage just before any ray it is easy to
 * calculate it for any place around a cp.
 *
 * Thus the evaluation starts by determining the coverage just before the smallest possible RayAngle value,
 * which is angle zero (tangent is horizontal to the right) and curvature -inf, which is a counterclockwise
 * curve with infinitely small radius. Depending on the operation this can be calculated as below.
 *
 * - If we make operations on sane and untangled shapes
 *   we can be sure that if we walk along an edge, to our left is the outside of the
 *   surface and to our right is the inside of the surface.
 *   Thus (knowing if the shape is clockwise or counterclockwise) we can detemine the coverage outside
 *   (always zero) and inside (+1 or -1). So we merely check each edge
 *   that is part of this cp and determine if the point in question is to its left or right, giving us
 *   the coverage from the contour of that shape. We sum up and are done. This is done inside
 *   EvaluateCrosspoints().
 *
 * - For untangle operations we are in a more difficult situation, since we have no well behaving contours
 *   and it is impossible to locally tell the coverage just by looking at the vicinity of a cp. Thus
 *   here we have to make a vertical cut of the (untangled) shape and count how many edges cross the
 *   vertical line drawn at the cp above the cp (with smaller y coordinate) and count how many left-to-right
 *   and right-to-left crossings we have and thereby determining the coverage. This is more expensive.
 *   This is done in CalcCoverageHelper().
 *
 * After we have determined the coverage at RayAngle (0, -inf), we use IsCoverageToInclude() to can see
 * if this area shall be included in the result or not. We then walk around the cp in clockwise fashion
 * updating the coverage at each edge, until we see a change in the inclusion. This then enables us to pair rays along the contour of the
 * result shape. This will be stored in the 'switch_to' member of the Ray.
 *
 * This function also fills in StartRays with incoming rays that are at the contour of the result.
 */

//Copy the processed crosspoints from the set to the bycont, where they are sorted by <contour,edge,pos>.
//(each contour has its own bycont).
//Fill in startrays with incoming rays marked as can_start_here
void ContoursHelper::EvaluateCrosspoints(Contour::EOperationType type) const
{
    evaluated_for = type;
    if (Rays.size()==0) return;
    StartRays.clear();
    StartRays.reserve(Rays.size()/4);

    //if we do untangle for a single contour (such as with SimpleContour::CreateExpand),
    //we try to save on calls to CalcCoverageHelper(), since that involves
    //a full cycling of the edges and a calculation of vertical crossing.
    //Instead we first walk around the single contour and calculate the coverage
    //around each crosspoint. There may be multiple contours, we do this only if
    //there is only one - only this way can we guarantee that the starting point
    //is not inside another contour.
    //We skip this step if we have already done so. (A repeated call to EvaluateCrosspoints(),
    //something we do not normally do, but are prepared for.).
    if (contour_heads.size()==1 && !cp_heads.empty() &&
        cp_heads.begin()->second.coverage_at_0_minus_inf == std::numeric_limits<int>::max()) {
        //Start from the leftmost point of the contour.
        const SimpleContour *c = contour_heads.front().contour;
        size_t edge = 0;
        auto xy_pos = c->at(0).XMaxExtreme();
        for (size_t e = 1; e<c->size(); e++) {
            auto xy_pos2 = c->at(e).XMaxExtreme();
            if (xy_pos.first.x < xy_pos2.first.x) {
                edge = e;
                xy_pos = xy_pos2;
            }
        }
        //eliminate pos == 1
        if (xy_pos.second==1) {
            xy_pos.second = 0;
            edge = c->next(edge);
        }
        //Walk around the contour to the extreme found above.
        //There we know that rightwards there is coverage zero.
        //We know that cps in the in_contour list are ordered by <edge_no,pos,incoming>,
        //find the cp that is *after* the extreme.
        //If the extreme is beyond all cps, we will get back to the cpdata - no problem.
        size_t u = contour_heads.front().begin; //head ray of the contour
        for (; u < contour_heads.front().end; u++) {
            if (Rays[u].vertex > edge) goto found;
            if (Rays[u].vertex == edge &&
                !test_smaller(Rays[u].pos, xy_pos.second)) goto found;
        }
        u = contour_heads.front().begin;
    found:
        _ASSERT(Rays[u].incoming);

        //Now walk along each cp in the contour and calculate the requested coverage
        //The variable 'coverage' will hold the coverage on the "outside" side of the
        //edges as we go along. For edges going downward, this is the right side,
        //for edges going up, this is the left side.
        int coverage = 0;
        const size_t started_with = u;
        //Special case: If the rightmost point lies ~exactly on a crosspoint
        if (Rays[u].vertex == edge && test_equal(Rays[u].pos, xy_pos.second)) {
            //We know coverage is zero right of this cp (nothing of the contour is right of us)
            const auto head_itr = cp_heads.find(Rays[u].xy);
            _ASSERT(head_itr);
            u = head_itr->head;
            head_itr->coverage_at_0_minus_inf = 0;
            const size_t cp_head = u; //save for later
            //Now find any valid outgoing ray in the cp and calculate the coverage on its
            //right (outside) side. If the CP head is outgoing, we are OK, the coverage is 0.
            //If the CP head is incoming, we start walking around the CP to find an outgoing
            //edge. In the meantime, we count the number of incoming edges we stepped over
            //coverage will be this much less at the outside of the outgoing edge we find.
            //Here is an example, where this is relevant
            //   1--------2            In this untangle operation
            //   |         \           the input path goes by numbered vertices.
            //   |          \          The edge 3->4 overlaps with 6->7 and likewise
            //   |     7,4===3,6       4->5 overlaps with 7->8. In this case the rightmost
            //   |      ||  /          point of the path is in vertex 3 (=6). The first ray
            //   |      || /           of this CP is the 5->6 (closest to, but after the X axis).
            //   |      ||/            It is now stored in 'u'. But we cannot start walking on it
            //   9-----8,5             as it is an incoming ray. So in the cycle below we find
            //                         an outgoing one. We find 3->4 (or maybe 6->7), but the
            //                         coverage at the left side of 3->4 (marked with an asterisk)
            //                         is not zero, but -1 (because we have stepped over 5->6
            //                         when going clockwise around 3.
            while (Rays[u].incoming) {
                u = Rays[u].link_in_cp.next;
                coverage--;
            }
            //If the outgoing edge we found is part of a ray group we need the coverage before
            //the entire ray group. Undo the decrements done for the incoming edges of the ray group.
            //Note that the first outgoing edge may be the head ray itself or may be in the same ray
            //group as the head ray.
            if (u != cp_head) {
                if (SameRayGroup(u, cp_head))
                    coverage = 0;
                else
                    for (size_t v = Rays[u].link_in_cp.prev; v != cp_head; v = Rays[v].link_in_cp.prev)
                        if (SameRayGroup(u, v)) coverage++;
                        else break;
            }
            u = next_in_contour(u);
            if (cp_heads.size() == 1)
                goto done_with_coverage_at_0_minus_inf;
        } else {
            //Here the rightmost point of the contour is not at a crosspoint.
            //If the contour at the extreme goes upwards, then "outside" is really inside a
            //counterclockwise enclosure and we need to start with -1. If it goes down,
            //then the "outside" is really to the right (where there is nothing) and we need
            //to start from zero.
            RayAngle prev = xy_pos.second==0 ? c->at_prev(edge).Angle(true, 1) : c->at(edge).Angle(true, xy_pos.second);
            RayAngle next = c->at(edge).Angle(false, xy_pos.second); //pos is never 1
            _ASSERT(prev.angle>=0 && next.angle>=0); //-1 signals an error
            //If the two angles equal, we have a spike and cannot determine the clockwiseness.
            //In this case we give up this way of calculating the coverage.
            if (prev.IsSimilar(next))
                goto cannot_do_coverage_at_0_minus_inf;
            if (test_equal(prev.angle, next.angle)) {
                //This is a cusp. In this case the 'curve' member of 'prev' and 'next' may be imprecise
                //and we cannot rely on them.

                XY r[Edge::MAX_CP];
                double p_pos[Edge::MAX_CP], n_pos[Edge::MAX_CP];
                unsigned num;
                if (xy_pos.second==0) num = c->at_prev(edge).Crossing(c->at(edge), true, r, p_pos, n_pos);
                else num = c->at(edge).SelfCrossing(r, p_pos, n_pos);
                //If the two edges do not cross, just compare the angle between their endpoint...
                if (num==0) {
                    prev.angle = angle_to_horizontal(xy_pos.first, (xy_pos.second==0 ? c->at_prev(edge) : c->at(edge)).GetStart());
                    next.angle = angle_to_horizontal(xy_pos.first, c->at(edge).GetEnd());
                } else {
                    //else find the closest crosspoint and compare the angle halfway there
                    _ASSERT(xy_pos.second==0); //should not be a self-crossing edge on a well-behaving contour
                    ptrdiff_t l = std::min_element(n_pos, n_pos+num) - n_pos;
                    _ASSERT(l == std::max_element(p_pos, p_pos+num) - p_pos);
                    const XY p_xy = c->at_prev(edge).Split((1+p_pos[l])/2);
                    const XY n_xy = c->at(edge).Split((0+n_pos[l])/2);
                    prev.angle = angle_to_horizontal(xy_pos.first, p_xy);
                    next.angle = angle_to_horizontal(xy_pos.first, n_xy);
                }
            }
            //This is now two (hopefully well) comparable angles
            coverage = prev.Smaller(next) ? -1 : 0;
        }
        do {
            _ASSERT(Rays[u].incoming);
            //We arrive on ray 'u' and coverage is 'coverage' on our external side (left side)
            //Find what is the coverage at the <0, -inf> direction
            //We walk around till we hit the cpdata, which has the lowest angle (just after
            //<0,-inf>
            int loc_cov = coverage;
            size_t uu = Rays[u].link_in_cp.next;
            const auto u_cp_head = cp_heads.find(Rays[u].xy);
            _ASSERT(u_cp_head);
            while (uu!=link_info::no_link && uu!=u_cp_head->head) {
                //Rays[u] may be part of a ray group (rays with equal angles)
                //In this case all of them has the same coverage (=='coverage')
                //on their outside, so we do not modify 'loc_cov' if the
                //angle is the same as that of the ray we came in (Ray[u])
                if (!SameRayGroup(u, uu))
                    loc_cov += Rays[uu].incoming ? -1 : +1;
                uu = Rays[uu].link_in_cp.next;
            };
            //uu now points to the cp cpdata
            if (u_cp_head->coverage_at_0_minus_inf!=std::numeric_limits<int>::max() &&
                u_cp_head->coverage_at_0_minus_inf!=loc_cov) {
                //if we have calculated this before, they should equal.
                //Since they don't we must have missed finding a crosspoint.
                //In this case we give up this way of calculating the coverage and rely on
                //CalcCoverageHelper() calls.
                //Invalidate coverages calculated so far.
                for (auto &[xy, cp_head]: cp_heads)
                    cp_head.coverage_at_0_minus_inf = std::numeric_limits<int>::max();
                goto cannot_do_coverage_at_0_minus_inf;
            }
            u_cp_head->coverage_at_0_minus_inf = loc_cov;

            const size_t v = next_in_contour(u); //the outgoing ray corresponding to us
            _ASSERT_PRINT(!Rays[v].incoming, *C1, C2 ? *C2 : Contour(), "Non-incoming ray in EvaluateCrosspoints");
            _ASSERT((Rays[v].vertex == Rays[u].vertex && Rays[v].pos == Rays[u].pos) ||
                (Rays[v].pos == 0 && Rays[u].pos == 1));
            //Walk around the crosspoint till we get to the corresponding outgoing ray
            //Here if Ray[u] is part of a ray group, we skip elements in that ray group
            //If the outgoing ray, that we search is also part of a ray group, we skip that too.
            //If OTOH there is a ray group in between, we count for all of them
            //To this end we set up a 'raygroup_cov' variable, where we count coverage we have not
            //yet added to 'coverage' because it was part of a ray group.
            //When the ray group ends, we don't add it at the beginning nor at the end, but in between
            int raygroup_cov = 0;
            for (size_t uuu = Rays[u].link_in_cp.next; uuu!=link_info::no_link && uuu!=v; uuu = Rays[uuu].link_in_cp.next) {
                if (!SameRayGroup(u, uuu))
                    raygroup_cov += Rays[uuu].incoming ? -1 : +1; //skip the first ray group
                if (!SameRayGroup(Rays[uuu].link_in_cp.next, uuu)) {
                    //At the end of a ray group - add what we collected
                    //(next will be a different angle)
                    coverage += raygroup_cov;
                    raygroup_cov = 0;
                }
            }
            u = next_in_contour(v); //the incoming ray at the next cp around the contour
        } while (u!=link_info::no_link && u!=started_with);
    }

done_with_coverage_at_0_minus_inf:
cannot_do_coverage_at_0_minus_inf:
    //Now we either have all 'coverage_at_0_minus_inf' field of the head rays calculated or not.
    //If not we will calculate the coverage at angle(0, -inf) at each crosspoint the hard way.
    //Anyway, we cycle the crosspoints and evaluate them.
    std::vector<const SimpleContour*> ignore;
    ignore.reserve(8);
    //Do the cycling in deterministic order, so that our tests ar not flaky
    std::vector<std::pair<XY, cp_data>> ordered_cp_heads;
    ordered_cp_heads.reserve(cp_heads.size());
    for (auto& xy_data : cp_heads)
        ordered_cp_heads.emplace_back(xy_data);
    std::ranges::sort(ordered_cp_heads, [](const XY& a, const XY& b) { return a < b; }, & std::pair<XY, cp_data>::first);
    for (const auto &[xy, cpdata]: ordered_cp_heads) {
        //for union coverage is sufficient if any one contour covers an area.
        //for intersects all of them have to cover it.
        //for xor, an odd number needs to cover it
        //
        //Example:      1\    ->2,,     .=area covered by contour 1
        //              ..\  /,,,,,     ,=area covered by contour 2
        //              ...\|,,,,,,     :=area covered by contour 3 and 2
        //              ....o----->3
        //              .../|\:::::     For unions one (clockwise) range will be 2(outgoing)->2(incoming)
        //              ../ |,\::::     and another is from 1(out)->1(in).
        //             1<- 2|,,\3::     For intersects only 3(out)->3(in) range will be had.
        //
        // An x(out)->y(in) range selected will result in y(in) be designated a cp switching to edge x.
        // Finally, some terminology. A <ray group> is a set of rays (incoming/outgoing mixed) with the same angle.
        // (On the example above each ray is a separate ray group of its own.)
        // Supposedly these rays have another common crosspoint besides the current one (since they lie on one another).


        //For well formed contours any intersect is at the edges of a contour,
        //thus the sections which are covered by no contour at this crosspoint
        //(like 1(in)->2(out) below) have total coverage of zero.
        //For untangles, we can be in the interior of a contour, thus sections
        //not covered by any contour in this cp may have nonzero coverage.
        //Thus we need to calculate what is the coverage around us.

        int coverage_before_r;
        if (C2==nullptr)  { //we do untangle
            //For untangle we go through all the edges and see, how many times are this
            //particular cp being circled around to find what is the coverage here
            //If we could calculate the coverage for the crosspoints, use that
            if (cpdata.coverage_at_0_minus_inf == std::numeric_limits<int>::max())
                coverage_before_r = CalcCoverageHelper(cpdata.head);
            else {
                coverage_before_r = cpdata.coverage_at_0_minus_inf;
//                _ASSERT(coverage_before_r == CalcCoverageHelper(cpdata));
            }
        } else {
            //Here we have simplecontours of one or more contours touch or cross.
            //Simplecontours of the same contour cannot cross, since contours are well-formed,
            //but they may touch.
            //Below we calculate the combined coverage at <0, -inf> direction from the
            //crosspoint. Each contour contributes with +1, 0 or -1 to this coverage,
            //irrespective of how many of its simplecontours are present at this crosspoint.
            //The contribution of each contour to the coverage can be determined from the
            //leftmost edge-pair of each contour.
            //We start going around the cp clockwise, find the first ray for each contour
            //and determine the contribution of that contour to the coverage.
            //We build on the fact that we do operation between max two main contours.
            //
            //For each main Contour we note here if the <0,-inf> point is inside a clockwise or
            //a counterclockwise SimpleContour belonging to this main_contour. It may be that
            //this point is inside *both* a clockwise and counterclockwise SimpleContour of the
            //same main_contour, e.g., the situation below:
            //  +----------------+   - is the outer clockwise outline of the (main)Contour, and
            //  |                |   the .s represent a circle hole touching the Contour outline,
            //  |        .....   |   where * is the crosspoint. In this case the coverage at the
            //  |         . .    |   <0,-inf> dir around this crosspoint is zero (we are in a hole).
            //  +----------*-----+
            //
            //  +----------------+   If the CP (with another SimpleContour from another Contour) is
            //  |                |   inside the outline (not on its edge) we only have a single
            //  |        .....   |   SimpleContour for this (main) Contour for it (a counterclockwise
            //  |         . .    |   hole, but the coverage is still 0 at <0,-inf>.
            //  |          *     |
            //  +--o-------------+   If, OTHO the crosspoint is on a clockwise SimpleContour ('o')
            //                       only, then the coverage at <0,-inf> is one.
            //
            // Thus the algorithm is simple: start walking around the crosspoint and find the
            // first ray of appropriate direction (incoming for clockwise main Contours and outgoing
            // for cclockwise main Contours).
            // It may happen that due to CP merging we have two exactly equal angle rays from two
            // SimpleContours of the same Contour. For this reason, we note which SimpleContour we
            // have found first and what was its angle. If the next ray from this main Contour is
            // from the same ray Group, we check how the two SimpleContours in question relate to
            // each other.
            // Note: we only consider the first ray of SimpleContours.
            size_t ray_no = cpdata.head;
            const SimpleContour* had[2] = {nullptr, nullptr};
            RayAngle angles[2];
            bool done[2] = {false, false};
            int cov[2] = {0,0};
            ignore.clear();
            do {
                //If an edge's incoming ray has smaller angle than the outgoing, then the
                //dir before <0, -inf> is included in a clockwise simplecontour.
                //This happens if we find an incoming ray for the contour first.
                //Coverage is +1 there if the above is TRUE and the main contour is clockwise
                // (irrespective of the clockwiseness of the simplecontour of the edge)
                //Coverage is -1 there if the above is FALSE, the main contour is cclockwise
                // (irrespective of the clockwiseness of the simplecontour of the edge)
                //Else coverage by this contour is zero (either outside of it or in a hole).
                if (std::ranges::find(ignore, Rays[ray_no].contour)==ignore.end()) {
                    ignore.push_back(Rays[ray_no].contour);
                    _ASSERT(Rays[ray_no].main_contour);
                    const int i = Rays[ray_no].main_contour==C2;
                    if (had[i]) {
                        //Here we are sure to have angles[i] initialized, because had[i] is set.
                        if (angles[i].ExactEqual(Rays[ray_no].angle)) {  // cppcheck-suppress uninitvar
                            _ASSERT(had[i]!=Rays[ray_no].contour);
                            switch (had[i]->CheckContainmentForOperation(*Rays[ray_no].contour)) {
                            case EContourRelationType::REL_A_INSIDE_B:
                                had[i] = nullptr;
                                cov[i] = 0;
                                break; //fallthrough to using the current Ray to decide.
                            case EContourRelationType::REL_B_INSIDE_A:
                                break;
                            case EContourRelationType::REL_APART:
                                //This may happen if the main contour consist of SimpleContours very close (but not overlapping)
                                //with parallel edges. In this case we need to consider both SimpleContours in the coverage
                                had[i] = nullptr;
                                break; //fallthrough to add the coverage deduced from the current ray to the one existing
                            default:
                                _ASSERT_PRINT(0, *C1, C2 ? *C2 : Contour(),
                                              "Unexpected containment on CP eval. Op:"+to_string(type)+" XY:"
                                              +Rays[ray_no].xy.Dump(false)+" rel:"+std::to_string(int(had[i]->CheckContainmentForOperation(*Rays[ray_no].contour)))
                                              +" S1:"+had[i]->Dump(true)+" S2:"+Rays[ray_no].contour->Dump(true));
                            }
                        } else
                            done[i] = true;
                    }
                    if (had[i]==nullptr) {
                        if (Rays[ray_no].incoming == Rays[ray_no].main_contour->GetClockWise())
                            cov[i] += Rays[ray_no].incoming ? +1 : -1;  //use += only for a potential previous REL_APART branch
                        had[i] = Rays[ray_no].contour;
                        angles[i] = Rays[ray_no].angle;
                    }
                }
                ray_no = Rays[ray_no].link_in_cp.next;
            } while (ray_no != cpdata.head && (!done[0] || !done[1]));
            coverage_before_r = cov[0]+cov[1];
        }

        size_t r = cpdata.head;
        size_t tmp = r;
        //coverage_before_r actually shows coverage before the first ray group
        //find first ray group after we shall not include. (coverage is not according to the requirement)
        if (GoToCoverage(tmp, r, coverage_before_r, type, false, tmp)) {
            const size_t original_started_at_ray = r;
            const size_t orig_start_size = StartRays.size();
            while(1) {
                //find first ray group (between "start_from" and "start_to" after which coverage is above requirement
                size_t start_from = r, start_to;
                if (!GoToCoverage(start_from, start_to, coverage_before_r, type, true, original_started_at_ray))
                    break; //OK, we are done - either no coverage fulfills the req, or we processed all rays (hitting "started_at_ray")
                size_t end_from = start_to, end_to;
                if (!GoToCoverage(end_from, end_to, coverage_before_r, type, false, original_started_at_ray)) {
                    _ASSERT(0); //we must find one
                }
                r = end_to; //next cycle will use this

                //Now we know that coverage is like required between ray groups <start_from, start_to> and <end_from, end_to>
                //Now we set the "switch_to" members of both ray groups to the "from" of the other (could be any)
                //and leave the "switch" to of ray groups in between on "error"
                //(An optimization can be that if a ray in <start_from, start_to> continues as one ray in <end_from, end_to>
                // then we just set that ray to IGNORE. Now such situations will result in an added vertex, later removed by
                // Edge::CheckAndCombine().)
                for (size_t i = start_from; i!=start_to; i = Rays[i].link_in_cp.next)
                    Rays[i].switch_to = end_from;
                for (size_t i = end_from; i!=end_to; i = Rays[i].link_in_cp.next)
                    Rays[i].switch_to = start_from;

                //Finally add a ray to start_rays
                StartRays.push_back(end_from);

                //if we made a full scan stop now
                if (original_started_at_ray == r)
                    break;
            } //while through regions of sufficient coverage

            //Test for a special case:
            //A touching crosspoint, where a bezier touches a line or two beziers touch, without
            //actually crossing each other. In this case we strive to keep the contour together
            //in order to avoid spiky vertices.
            //This is important only if these edges are all at the border of included and not
            //included coverage (so that they are included in the walk). If this happens inside
            //a contour and would not be part of the final result we do not care.
            //We start by testing a prerequisite: If we have created two start rays, only then
            //do we start investigating.
            if (StartRays.size()==orig_start_size+2) {
                //OK, we may have a thing here. See if
                //a) we have 4 rays in the cp
                //b) all are along the same line (same angle +-2)
                unsigned no_rays = 1;
                size_t rays[4] = {cpdata.head};
                const double angle = Rays[cpdata.head].angle.angle;
                const double angle2 = angle<2 ? angle+2 : angle-2;
                for (size_t x = Rays[cpdata.head].link_in_cp.next; x != cpdata.head; x = Rays[x].link_in_cp.next)
                    if (!test_equal(Rays[x].angle.angle, angle) && !test_equal(Rays[x].angle.angle, angle2))
                        goto done_checking;  //angles do not align: this is not the special case
                    else if (no_rays==4)
                        goto done_checking; //too many rays: this is not the special case
                    else
                        rays[no_rays++] = x;

                //OK, now we shall see if two of the rays go in one and two of the rays go in the other dir
                const size_t same = test_equal(Rays[rays[0]].angle.angle, Rays[rays[1]].angle.angle) ? 0 : 3;
                if (!test_equal(Rays[rays[same]].angle.angle, Rays[rays[(same+1)%4]].angle.angle))
                    goto done_checking;
                if (!test_equal(Rays[rays[(same+2)%4]].angle.angle, Rays[rays[(same+3)%4]].angle.angle))
                    goto done_checking;
                if (!test_equal(angle2, Rays[rays[(same+2)%4]].angle.angle))
                    goto done_checking;
                //Now we know this is the special case.
                //Index 'same' and 'same+1' (modulo 4) of 'rays' point to the same direction and
                //'same+2' and 'same+3' also point to the same dir; and these two dirs are opposite.
                //If the two rays going the same dir are not switching to each other, the existing
                //arrangement will keep the contours together and we need not do anything.
                if (Rays[rays[ same     ]].switch_to != rays[(same+1)%4]) goto done_checking;
                if (Rays[rays[(same+2)%4]].switch_to != rays[(same+3)%4]) goto done_checking;
                _ASSERT(Rays[rays[(same+1)%4]].switch_to == rays[same]);
                _ASSERT(Rays[rays[(same+3)%4]].switch_to == rays[(same+2)%4]);
                //Ok, we need to rearrange
                //instead of switching from same<->same+1 and same+2<->same+3, we shall
                //switch from same<->same+3 and same+1<->same+2.
                Rays[rays[same]].switch_to = rays[(same+3)%4];
                Rays[rays[(same+3)%4]].switch_to = rays[same];
                Rays[rays[(same+1)%4]].switch_to = rays[(same+2)%4];
                Rays[rays[(same+2)%4]].switch_to = rays[(same+1)%4];
                StartRays.resize(orig_start_size);
                StartRays.push_back(rays[same]);
                StartRays.push_back(rays[(same+1)%4]);
            }
        done_checking: {}
        } //else never happens -> this is a crosspoint not needed, all switch_action will remain ERROR
    } /* for cycle through the crosspoints */
}

/** Do one step of the walking.
 *
 * This function does not actually switch from one contour to another.
 * For vertices it simply goes to along the next edge up to the first
 * cp or the ending vertex. For the first cp, it stops at the incoming
 * ray of that cp.
 * For cps it leaves the cp along the ray pointed by `p` and finds the next
 * cp on that edge (or the ending vertex of the edge if no cp).
 * Actual switching is done in Walk() from which this is called.
 *
 * @param p The current position of the walk. Can be a vertex or a crosspoint.
 * @param [in] forward True if we walk the same direction as edges go or opposite.
*/
void ContoursHelper::Advance(RayPointer &p, bool forward) const
{
    if (p.at_vertex) {  //we are at a vertex
        if (forward) {
            //Check if upcoming edge has a crosspoint
            //Note: either we have a cp mid-edge (pos!=0) or at the start point of the edge.
            //In the latter case the cp is recorded with pos==0 for the next edge
            if ((Rays[p.index].vertex == p.vertex) ||
                (Rays[p.index].pos == 0.0 && Rays[p.index].vertex == Rays[p.index].contour->next(p.vertex))) {
                _ASSERT_PRINT(Rays[p.index].incoming, *C1, C2 ? *C2 : Contour(), "Next ray is incoming. Op:"+(evaluated_for ? to_string(*evaluated_for) : "not set"));
                p.at_vertex = false;
                //p.index remains, p.vertex is ignored
            } else { //no cp on upcoming edge, go to next vertex
                p.vertex = Rays[p.index].contour->next(p.vertex);  //we are at the contour pointed by the ray in p
            }
        } else {
            //move to previous vertex
            p.vertex = Rays[p.index].contour->prev(p.vertex);
            if (Rays[p.index].vertex == p.vertex) { //Ok, upcoming (previous) vertex has a cp
                _ASSERT_PRINT(!Rays[p.index].incoming, *C1, C2 ? *C2 : Contour(), "Next ray is not incoming. Op:"+(evaluated_for ? to_string(*evaluated_for) : "not set"));
                p.at_vertex = false;
            }
            //else we use the updated p.vertex
        }
    } else { //we are at a crosspoint
        _ASSERT(Rays[p.index].incoming != forward); //when forward we must be at an outgoing (and vice versa)
        p.vertex = Rays[p.index].vertex;
        const double pos = Rays[p.index].pos;
        p.index = forward ? next_in_contour(p.index) : prev_in_contour(p.index);
        if (p.vertex != Rays[p.index].vertex || (pos < Rays[p.index].pos)!=forward) {
            //If next cp is on a different edge or on the same edge but wrong direction
            //we switch to vertex (In the latter case next cp will happen once we have
            //circled the whole contour and got back to this edge)
            if (forward) {
                p.vertex = Rays[p.index].contour->next(p.vertex);
                //However, if the next cp is exactly on the start of the next edge
                //(p.vertex(the next edge) == Rays[p.index].vertex (the edge of the next cp)
                // AND (Rays[p.index].pos==0 (the next cp is at the start of the edge)
                // then we remain in cp mode
                p.at_vertex = p.vertex != Rays[p.index].vertex || Rays[p.index].pos>0;
            } else
                p.at_vertex = true;
        }
        //else we remained on the same edge, use updated p.index
    }
}

/** Appends an edge and makes checks. Used during walking.
*
* Appends an edge to the (yet incomplete, not closed) shape.
* Checks that we do not append a degenerate (start==end) edge.
* Checks if the edge to append is a direct continuation of the last edge, in which case
* we merge the two. */
void ContoursHelper::AppendDuringWalk(Path &edges, const Edge &edge)
{
    //if the last point equals to the start point of edge, skip the last edge added
    XY start = edge.GetStart();
    if (edges.size()) {
        if (edge.GetStart().test_equal(edges.back().GetStart()) && edges.back().IsStraight()) {
            start = edges.back().GetStart();
            edges.pop_back();
        }
    }
    edges.push_back(edge);
    edges.back().SetStartOnly(start);
};


/** Perform a walk operation on evaluated crosspoints.
 *
 * Walk around the contours starting from start point and follow the
 * switch_action for each ray. We invalidate rays visited.
 * @param [in] start The (incoming) ray to start from.
 * @returns The resulting SimpleContour.
 */
SimpleContour ContoursHelper::Walk(RayPointer start) const
{
    //Some info on how we insert edges to the 'edges' local variable.
    //Note that for bezier curves it is an expensive operation to find the parameter value (position)
    //of a point (given by XY coordinates) on the curve and chop the rest away. In contrast
    //if you know the parameter of the point you want to chop away it is fast. Since we have
    //already got all position values for all crosspoints anyway in FindCrosspoints(), we
    //use them to speed up chopping. Therefore there is a meticulous maintenance of 'pos' values
    //through the code and we use ASSERTs to check that coordinates and pos values match.
    //
    //Example: we have two shapes A and B. Assuming we do A-B (so B is counterclockwise).
    // +------------------+    Assume we start the walk from the asterisk on edge (1).
    // |                  |    This crosspoint lies on position 0.75 on edge #1 and
    // |       +--+       |    So we append (1) and set its start to the coordinate of
    // |      (2) |       B    the CP (the pos value is available as Ray::pos).
    // |       |  |       |    We register that the next CP on the contour of 'B' walking
    // | +-(3)-o--x---+   |    counterclockwise will be the left CP of (2) and (3) ('o') and
    // | |     |  |   |   |    store the index of the arriving ray in 'current.index'.
    // +-*-(1)-+  +---+---+    (This is the ray coming to the CP from the "south" direction.)
    //   |            |        Then we arrive at the vertex at the right end of (1).
    //   +-------A----+        We see that we have not yet reached the edge of 'current.index'
    //                         so we append (2) to the 'edges' and do not trim its start, since
    // it is known to perfectly connect to (1). (All input paths are connected.)
    // Then we move on and realize we have reached the crosspoint (o), more specifically its
    // ray in 'current.index'. Then we know we need to trim the last added edge (2) to make
    // it shorter. The pos is available in Rays[current.index].pos. Then we select which
    // ray to swatch to from the current incoming ray. If EvaluateCrosspoints() figured this
    // CP out correctly then it should be the ray pointing right (stored in Rays[current.index].switch_to).
    // Then we append (3) identified by Rays[switch_to].vertex and chop its start to
    // set it to the coordinate of the crosspoint Rays[switch_to].xy and using the pos in
    // Rays[switch_to].pos.
    // The next cycle will take us to the next CP ('x'), where we will have to chop the end
    // of (3) away. However, the position stored in Ray::pos corresponds to the full length of
    // the edge, whereas we have a chopped version of (3) appended as the last edge.
    // So we need to adjust Ray::pos in this case with the start pos - maintained in the
    // 'prev_startpos' local variable.
    //
    // In short: we always add edges trimmed at the start, not trimmed at the end; and we
    // trim edges only when we arrive at a CP.

    // A few words on backtracking.
    // Sometimes we have two rays that are very similar in their angle. Angle computation has
    // limited numerical precision, so when we order these rays we may make a mistake. Also,
    // we merge rays on overlap and may adjust the angle badly. There may also be more than one
    // ray with the very same angle (called a ray group) from overlapping edges. All in all we
    // may make a bad call on which ray to switch to, when evaluating the ﻿crosspoint. Then,
    // if we follow a bad ray, the walk will likely fail with hitting a ray that has no 'switch_to'
    // value. In these case we step back and check if the last CP had a ray with a very similar
    // angle to the one we selected and try walking on that. If that walk fails again, we
    // go back to the CP and eventually try all the rays with very similar angles than the one
    // we originally selected. If none of them leads to a good result, we check if the ray we
    // have *arrived on* at the crosspoint has rays similar to it and if those have valid
    // switch to rays.

    std::vector<size_t> rays_visited; //should allocate these as thread local
    std::vector<walk_data> wdata;  //should allocate these as thread local
    Path edges;
    rays_visited.reserve(200);
    wdata.reserve(200);
    edges.reserve(200);
#ifndef NDEBUG
    const double tolerance = sqrt(max_cp_adjust_sqr)+0.1;
#endif // !NDEBUG

    //"current" will point to a valid arriving ray at a crosspoint
    _ASSERT(start.index<Rays.size() && !start.at_vertex && Rays[start.index].valid);
    RayPointer current = start;

    //do a walk from the current crosspoint, until we get back here
    bool forward = Rays[current.index].incoming;
    double prev_startpos=0;
    while (true) {
        //here "current" points to an arriving ray
        if (current.at_vertex) { //we are at a vertex
            if (forward)
                AppendDuringWalk(edges, Rays[current.index].contour->at(current.vertex)); //just append the whole edge
            else {
                //Append the inverse of the edge
                Edge edge(Rays[current.index].contour->at_prev(current.vertex));
                edge.Invert();
                AppendDuringWalk(edges, edge);
            }
            prev_startpos = 0;
            //no need to modify "current" here. A vertex is treated as both an arriving and departing ray
        } else { //we are at a crosspoint, not a vertex
            const Ray &ray = Rays[current.index];  //ray on which we arrive
            _ASSERT(forward == ray.incoming);
            //The position to use as ending position for the last added edge
            double terminating_pos = ray.incoming ? ray.pos : 1-ray.pos;
            //If the cp is in a vertex (denoted always by pos==0), use the last added edge to its original end
            if (terminating_pos==0)
                terminating_pos = 1;
            const double prev_endpos = 1-(1-terminating_pos)/(1-prev_startpos);
            //Make the new edge connect to the last one precisely, by trimming the last one
            //Use 'Ign', as we may have merged two crosspoints and t 'p' may not correspond to
            //the crosspoint coordinates well enough. But we assert that the diff is small
            while (edges.size() && edges.back().GetEnd()!=ray.xy) {
                _ASSERT_PRINT(edges.back().Pos2Point(prev_endpos).Distance(ray.xy)<0.5, *C1, C2 ? *C2 : Contour(),
                              "EGDES mismatch "+edges.Dump(false)+" xy:"+ray.xy.Dump(false)+" d:"+std::to_string(edges.back().Pos2Point(prev_endpos).Distance(ray.xy)));
                edges.back().SetEndIgn(ray.xy, prev_endpos);
                if (edges.back().IsDot())
                    edges.pop_back();
            }
            //Mark the arriving ray as done. Note it, so that if we need to backtrack, we can re-validate.
            if (ray.valid) {
                rays_visited.push_back(current.index);
                ray.valid = false;
            }
            size_t switch_to = ray.switch_to;
            wdata.emplace_back(edges.size(), rays_visited.size(), current.index, switch_to);
            while (switch_to == link_info::no_link || !Rays[switch_to].valid) { //hm nowhere to switch or already been there
                //backtrack to last position
                _ASSERT(edges.size()>=wdata.back().result_size);
                switch_to = wdata.back().departing.select_alternative(Rays);
                if (switch_to==link_info::no_link) {
                    //We have not found a good departing ray.
                    //See if we may select another arriving ray.
                    do {
                        current.index = wdata.back().arriving.select_alternative(Rays);
                    } while (current.index != link_info::no_link && !Rays[current.index].valid);
                    if (current.index != link_info::no_link) { //OK valid arriving ray close to the original found
                        _ASSERT(Rays[current.index].valid);
                        rays_visited.push_back(current.index);
                        Rays[current.index].valid = false;
                        wdata.back().rays_visited_size = rays_visited.size();
                        switch_to = Rays[current.index].switch_to;
                        wdata.back().departing.reset_to(switch_to);
                    } else { //No (remaining) close enough alternative to the arriving ray - backtrack
                        wdata.pop_back();
                        if (wdata.empty()) {//cannot backtrack any more: give up
                            //revalidate the start ray (it may be needed when we start from another start ray)
                            Rays[start.index].valid = true;
                            return {};
                        }
                        edges.resize(wdata.back().result_size);  //Discard parts added since this crosspoint
                        //Enable rays we have visited during the walk we now backtrack
                        while (rays_visited.size()>wdata.back().rays_visited_size) {
                            Rays[rays_visited.back()].valid = true;
                            rays_visited.pop_back();
                        }
                        //switch_to is left as link_info::no_link here, so while loop will continue & select a new departing ray
                    }
                    continue;
                }
            }
            //'ray' and 'current.index' cannot be used from here, as we may have switched to
            //a different arriving ray above during a backtrack step.

            //Now switch to the departing ray
            current.index = switch_to;
            _ASSERT(Rays[current.index].valid);
            rays_visited.push_back(current.index);
            Rays[current.index].valid = false;
            const Ray &next_ray = Rays[current.index];
            forward = !next_ray.incoming;  //fw may change if we need to walk on an incoming ray
            //Append a point
            double my_startpos;
            Edge edge(next_ray.contour->at(next_ray.vertex));
            if (forward)
                my_startpos = next_ray.pos;
             else {
                edge.Invert();
                my_startpos = 1 - next_ray.pos;
             }
            _ASSERT_PRINT(edge.Match(next_ray.xy, my_startpos, tolerance), *C1, C2 ? *C2 : Contour(),
                          "Startpos: "+std::to_string(my_startpos)+" xy:"+next_ray.xy.Dump(false)+" edge:"+edge.Dump(false));
            edge.SetStartIgn(next_ray.xy, my_startpos);
            AppendDuringWalk(edges, edge);
            prev_startpos = my_startpos;
        }
        //Now find the next cp and corresponding arriving ray
        Advance(current, forward);
        //See if we are done
        if (current.at_vertex) continue; //we can only end in a CP
        if (current.index==start.index) break; //we got back where we started from
        if (Rays[start.index].xy!=Rays[current.index].xy) continue; //not on the starting CP
        //if we have arrived back to the starting CP, we ought to come on the starting ray.
        //however, due to imprecision, we may come at another arriving ray that is very similar
        //in angle. We shall accept that one, too.
        if (walk_data::AreSimilar(Rays[current.index].angle, Rays[start.index].angle)) break;
    }

    if (edges.size()>=1 && edges.front().GetStart()!=edges.back().GetEnd()) {
        const double terminating_pos = Rays[current.index].incoming ? Rays[current.index].pos : 1-Rays[current.index].pos;
        if (terminating_pos) {
            //Same as in AppendDuringWalk() use 'Ign', as we may have merged two crosspoints and front().GetStart()
            //may not correspond to the crosspoint coordinates well enough. But we assert that the diff is small
            const double pos = 1-(1-terminating_pos)/(1-prev_startpos);
            _ASSERT_PRINT(edges.back().Pos2Point(pos).Distance(edges.front().GetStart())<0.7, *C1, C2 ? *C2 : Contour(),
                          "Bad pos after Walk. POINT: "+std::to_string(edges.back().Pos2Point(pos).Distance(edges.front().GetStart()))+
                          " E:"+edges.back().Dump(false)+" F:"+edges.front().Dump(false)+" term:"+std::to_string(terminating_pos)+
                          " prev:"+std::to_string(prev_startpos)+" p:"+std::to_string(pos));
            edges.back().SetEndIgn(edges.front().GetStart(), pos);
        } else
            edges.back().SetEndOnly(edges.front().GetStart());
    }
    if (edges.back().IsDot()) {
        edges.pop_back();
        if (edges.size())
            edges.back().SetEndOnly(edges.front().GetStart());
    }
    _ASSERT(edges.IsClosed());
    if (edges.size()==0) return {};
    if (edges.size()==1 && (edges.front().IsStraight() || edges.front().IsDot() || edges.front().GetStart()!=edges.front().GetEnd())) return {};
    if (edges.size()==2 && edges.front().IsStraight() && edges.back().IsStraight()) return {};

    _ASSERT(edges.IsClosed());
    //A quick bounding box "upper bound" to detect really small outcomes.
    Block bb(false);
    //Remove empty edges - which we get if a crosspoint falls exactly on pos==0 of an edge
    //and we do not use the edge.
    //We do not maintain endpoints any more
    //Also try to combine edges to save on them.
    //Since the last edge was deleted we try to combine with our previous one
    for (size_t i = 0; i<edges.size(); /*nope*/)
        if (edges[i].GetStart() == edges[i].GetEnd()) {
            edges.erase(edges.begin()+i);
        } else if (edges[i].IsDot(DIST_SAME_CP_SNAP)) {
            edges.at_next(i).SetStartOnly(edges.at_prev(i).GetEnd());
            edges.erase(edges.begin()+i);
        } else if (edges.at_prev(i).CheckAndCombine(edges[i], DIST_MERGE_EDGES_AFTER_WALK)) {
            edges.erase(edges.begin()+i);
            if (i>0) i--; //check the previous node again - it may have become a dot
        } else {
            bb.x += edges[i].GetHullXRange();
            bb.y += edges[i].GetHullYRange();
            i++;
        }
    //Sanity checks
    if (edges.size()==0)
        return {};
    _ASSERT(edges.IsClosed());
    //Drop sub-pixel contours
    if (bb.x.Spans()<1 && bb.y.Spans()<1)
        return {};
    //Drop contours of two straight edges. These are clearly degenerate.
    if (edges.size()==2 && edges[0].IsStraight()&& edges[1].IsStraight())
        return {};

    SimpleContour ret = SimpleContour::UnsafeMake(Path(edges)); //this is a copy to keep memory allocation low.
    _ASSERT_PRINT(ret.IsSane(), *C1, C2 ? *C2 : Contour(), "Not sane after Walk. Op:"+(evaluated_for ? to_string(*evaluated_for) : "not set"));
    return ret;
}

/** Finds the combined coverage of elements in "list" at the area covered by "c"
 * We assume c does not overlap with any element. */
int ContoursHelper::CoverageInNodeList(const node_list &list, const SimpleContour &c) const
{
    for (auto i=list.begin(); i!=list.end(); i++)
        switch(i->contour.CheckContainmentForOperation(c)) {
        case REL_OVERLAP:
        case REL_A_IS_EMPTY:
        case REL_B_IS_EMPTY:
        case REL_BOTH_EMPTY:
        case REL_SAME:
        case REL_A_IN_HOLE_OF_B:
        case REL_B_IN_HOLE_OF_A:
        case REL_IN_HOLE_APART:
            _ASSERT(0); //should not happen
            FALLTHROUGH;
        case REL_A_INSIDE_B:
        case REL_APART:     //c is outside "i->contour" check further elements in list
            continue;
        case REL_B_INSIDE_A: //"c" is inside "i->contour", delve deeper
            return CoverageInNodeList(i->children, c) + (i->contour.GetClockWise() ? +1 : -1);
        }
    return 0;
}


/** Inserts a node into the containment tree.
 *
 * We first find where to insert, then we insert.
 * @param list The tree to insert to.
 * @param n The node to insert.
 * @returns the resulting inserted node.*/
node * ContoursHelper::InsertContour(node_list *list, node &&n) const
{
    if (n.contour.size()==0) return nullptr;
    if (list->GetBoundingBox().Overlaps(n.contour.GetBoundingBox()))
        for (auto i = list->begin(); i!=list->end(); /*nope*/) {
            //fast path
            if (!i->contour.GetBoundingBox().Overlaps(n.contour.GetBoundingBox())) {
                i++;
                continue;
            }
            const EContourRelationType res = i->contour.CheckContainmentForOperation(n.contour);
            _ASSERT_PRINT(res != REL_OVERLAP, *C1, C2 ? *C2 : Contour(),
                          "Overlap at insert\nSub1:" + i->contour.Dump(false) + "\nSub2:"+n.contour.Dump(false));
            switch (res) {
            default:
            case REL_A_IS_EMPTY:
            case REL_BOTH_EMPTY:  //nodes already inserted cannot be empty
            case REL_OVERLAP:     //at this point we cannot have overlap, that was eliminated by walking or we did not find crosspoints
                _ASSERT(0);
                FALLTHROUGH;
            case REL_A_INSIDE_B:
                //move "i" from the "list" to "n.children"
                n.children.splice(n.children.end(), *list, i++);
                //continue checking
                break;
            case REL_B_IS_EMPTY:
            case REL_SAME:
                return &*i; //easy, do nothing
            case REL_B_INSIDE_A:
                //insert into the children of "i" instead;
                return InsertContour(&i->children, std::move(n));
            case REL_APART:
                i++;
                break; //continue checking the remainder of the list
            }
        }
    //at this point we are APART with all elements in "list"
    return &list->append(std::move(n));
}

/** Returns true if 'c' is inside 'other', but not in one of its holes,
 * false if it is apart or in one of the holes.
 * We ignore clockwisedness.*/
template <typename range>
bool ContoursHelper::CheckContainementForOperation(const SimpleContour& c, const range& other) {
    for (const HoledSimpleContour& o : other)
        switch (o.Outline().CheckContainmentForOperation(c)) {
        case EContourRelationType::REL_A_IS_EMPTY:
        case EContourRelationType::REL_BOTH_EMPTY:
        case EContourRelationType::REL_B_IN_HOLE_OF_A:
        case EContourRelationType::REL_A_INSIDE_B:
        case EContourRelationType::REL_APART:
            continue;
        case EContourRelationType::REL_A_IN_HOLE_OF_B:
        case EContourRelationType::REL_B_IS_EMPTY:
        case EContourRelationType::REL_IN_HOLE_APART:
            _ASSERT(0); //these are technically not possible
            continue;
        case EContourRelationType::REL_OVERLAP:
        case EContourRelationType::REL_SAME:
            _ASSERT(0); //these should not happen
            FALLTHROUGH;
        case EContourRelationType::REL_B_INSIDE_A:
            return !CheckContainementForOperation(c, o.Holes());
        }
    return false;
}

/** Insert a shape that had no crosspoints into the post-processing tree.
 *
 * If the SimpleContour in "c->outline" has no crosspoints and
 * it should be included due to the operation, this is called and we insert it into the tree.
 * This is done by finding where to insert (which shape already in the contour is it inside).
 * Plus we go on and process its holes, too
 * @param list The tree to insert to
 * @param c The shape to insert
 * @param [in] const_c True if `c` should be copied, false if we can move.
 * @param [in] C_other One of C1 or C2 - the other than the one `c` is from.
 * @param [in] type The operation we perform.
 */
void ContoursHelper::InsertIfNotInRays(node_list* list, const HoledSimpleContour* c, HoledSimpleContour* c_nc,
                                       const Contour *C_other, Contour::EOperationType type) const
{
    if (PickAfterWalk(&c->outline)) {
        //If we have crosspoints for this contour, but those are not used during a walk,
        //we add it. It may happen when we have a touchpoints only, that are not used during a walk.
        int coverage_in_c;
        if (C2==nullptr)
            coverage_in_c = CalcCoverageHelper(&c->outline);
        else {
            //In order to avoid checking ray crossing for all contours, we calculate
            //coverage within "c" using the assumptions on C1 and C2 being nice.
            //Now, it may be that C_other is already empty, if we have already moved all its
            //content to "list". In that case we have to traverse the "list"
            const bool clockwise_us = C1==C_other ? clockwise_C2 : clockwise_C1;
            const bool clockwise_other = C1==C_other ? clockwise_C1 : clockwise_C2;
            if (C_other->IsEmpty()) {
                const int cov_from_other = CoverageInNodeList(*list, c->outline);
                //if our whole surface is clockwise, then coverage inside "c" us can be +1 or 0
                //if our whole surface is ccl then 0 or -1
                const int cov_from_us = clockwise_us ? (c->GetClockWise() ? 1 : 0) : (c->GetClockWise() ? 0 : -1);
                coverage_in_c = cov_from_us + cov_from_other;
            } else {
                const bool are_apart = !CheckContainementForOperation(c->Outline(), *C_other);
                //if we are outside the other, coverage from that is zero
                //if we are inside, then coverage is either +1 or -1 depending on dir
                const int cov_from_other = are_apart ?  0 : clockwise_other ? 1 : -1;
                //if our whole surface is clockwise, then coverage inside "c" us can be +1 or 0
                //if our whole surface is ccl then 0 or -1
                const int cov_from_us = clockwise_us ? (c->GetClockWise() ? 1 : 0) : (c->GetClockWise() ? 0 : -1);
                coverage_in_c = cov_from_us + cov_from_other;
            }
        }
        const int coverage_outside_c = coverage_in_c + (c->GetClockWise() ? -1 : +1);
        if (IsCoverageToInclude(coverage_in_c, type) !=
            IsCoverageToInclude(coverage_outside_c, type)) {
            //place into the tree
            //If the contour is not const, we can destroy it.
            //The below move does only destroy the outline of c, not the holes.
            node* n = InsertContour(list, c_nc
                                    ? node(std::move(c_nc->outline))
                                    : node(c->outline));
            _ASSERT(n);
            //use its children list to insert any holes it may have (fallthrough to the below 'for' cycle)
            list = &n->children;
        }
    } //else we do not insert the contour "c" (already taken into account with Walk()s) and use the original list for holes
    for (unsigned u = 0; u<c->holes.size(); u++)
        InsertIfNotInRays(list, &c->holes[u], c_nc ? &c_nc->holes[u] : nullptr, C_other, type);
}

/** Converts a post-processing tree to a class Contour.
 *
 * We simply walk the top level entries of the tree (a forest really) and convert them to
 * HoledSimpleContour objects and we add them to 'result'
 * @param [in] type The type of operation we do.
 * @param list The tree we process. This will be emptied.
 * @param [out] result The result.
 * @param [in] positive True if we want to have a clockwise Contour at the end.
 */
void ContoursHelper::ConvertNode(Contour::EOperationType type, node_list &&list, Contour &result, bool positive) const
{
    for (auto &n : list) {
        if (n.contour.GetClockWise() != positive)
            n.contour.Invert();
        HoledSimpleContour c(std::move(n.contour));
        ConvertNode(type, std::move(n.children), c.holes, !positive);
        result.append(std::move(c));
    }
}

/** Converts a post-processing tree to a class ContourList.
 *
 * We simply walk the top level entries of the tree (a forest really) and convert them to
 * HoledSimpleContour objects and we add them to 'result'
 * @param [in] type The type of operation we do.
 * @param list The tree we process. This will be emptied.
 * @param [out] result The result.
 * @param [in] positive True if we want to have a clockwise Contour at the end.
 */
void ContoursHelper::ConvertNode(Contour::EOperationType type, node_list &&list, ContourList &result, bool positive) const
{
    for (auto &n : list) {
        if (n.contour.GetClockWise() != positive)
            n.contour.Invert();
        HoledSimpleContour c(std::move(n.contour));
        ConvertNode(type, std::move(n.children), c.holes, !positive);
        result.append(std::move(c));
    }
}

//returns true: bb shall be approx this big, false: this big or smaller.
std::pair<bool, Block> ContoursHelper::BoundingBoxEstimateFor(Contour::EOperationType type) const noexcept {
    if (C1 && C2) switch (type) {
    case contour::Contour::POSITIVE_UNION:
        return { C1->GetClockWise() &&  C2->GetClockWise(), C1->GetBoundingBox() + C2->GetBoundingBox()};
    case contour::Contour::NEGATIVE_UNION:
        return {!C1->GetClockWise() && !C2->GetClockWise(), C1->GetBoundingBox() + C2->GetBoundingBox()};
    case contour::Contour::POSITIVE_INTERSECT:
    case contour::Contour::NEGATIVE_INTERSECT:
        return {false, C1->GetBoundingBox() * C2->GetBoundingBox()};
    case contour::Contour::POSITIVE_XOR:
    case contour::Contour::NEGATIVE_XOR:
        return {false, C1->GetBoundingBox() + C2->GetBoundingBox()};
    case contour::Contour::WINDING_RULE_NONZERO:
    case contour::Contour::WINDING_RULE_EVENODD:
    case contour::Contour::EXPAND_POSITIVE:
    case contour::Contour::EXPAND_NEGATIVE:
    default:
        break;
    }
    return {false, Block(false)};//invalid
}


/** Returns what to do if performing an operation with an empty contour.
 *
 * E.g., if A+B is done and B is empty. In this case either we have to return
 * A or an empty contour. (E.g., for unions we need to return A, for intersections
 * we need to return an empty contour.)
 * @param type The type of operation we do.
 * @param clockwise The clockwiseness of the non-empty shape.
 * @returns True if the non-empty contour shall be returned, false if an empty one. */
inline bool ContoursHelper::OperationWithAnEmpty(Contour::EOperationType type, bool clockwise) const
{
    //if the sole non-empty contour is to be included, we return this
    return IsCoverageToInclude(clockwise ? 1 : 0, type);
}


/** The main function for carrying out the operation.
 *
 * @param [in] type The type of operation we do.
 * @param [out] result The resulting Contour.
 */
void ContoursHelper::Do(Contour::EOperationType type, Contour &result)
{
    _ASSERT(SC1 == nullptr);
    _ASSERT(C1);
    //"result" may be the identical to "C1" or "C2"
    if (C2==nullptr) {
        //We do some form of untangle
        _ASSERT(type != Contour::POSITIVE_INTERSECT);
        _ASSERT(type != Contour::POSITIVE_UNION);
        _ASSERT(type != Contour::POSITIVE_XOR);
        _ASSERT(type != Contour::NEGATIVE_INTERSECT);
        _ASSERT(type != Contour::NEGATIVE_UNION);
        _ASSERT(type != Contour::NEGATIVE_XOR);
        if (C1->IsEmpty()) {
            result.clear();
            return;
        }
        FindCrosspoints();
        //fast path: if single surface, no holes and no crosspoints
        if (C1->further.empty() && C1->first.holes.empty() && Rays.empty()) {
            if (!C1_nc) result = *C1;
            else if (&result != C1_nc) //avoid self move assignment
                result = std::move(*C1_nc);
            //If the result is not to be included in this operation, delete
            if (!IsCoverageToInclude(result.GetClockWise() ? 1 : -1, type))
                result.clear();
            return;
        }
    } else {
        //We do a binary operation, do trivial cases
        if (C1->IsEmpty()) {
            if (C2->IsEmpty() || !OperationWithAnEmpty(type, C2->GetClockWise())) {
                result.clear();
                return;
            }
            if (!C2_nc) result = *C2;
            else if (&result != C2_nc) //avoid self move assignment
                result = std::move(*C2_nc);
            return;
        }
        if (C2->IsEmpty()) {
            if (OperationWithAnEmpty(type, C1->GetClockWise())) {
                if (!C1_nc) result = *C1;
                else if (&result != C1_nc) //avoid self move assignment
                    result = std::move(*C1_nc);
            } else
                result.clear();
            return;
        }
        _ASSERT(C1->IsSane() && C2->IsSane());
        FindCrosspoints();
    }
    PruneCrosspoints();
    SanitizeRays();
    EvaluateCrosspoints(type); // Process each cp and determine if it is relevant to us or not
#ifndef NDEBUG
    const auto [est_roughly_equal, est_bb] = BoundingBoxEstimateFor(type);
    const Contour sC1 = C1 ? Contour(*C1) : Contour(), sC2 = C2 ? Contour(*C2) : Contour();
#endif
    node_list list; //this will be the root(s) of the post-processing tree (forest).
    //Walk while we have eligible starting points
    for (size_t u : StartRays)
        if (Rays[u].valid)
            InsertContour(&list, node(Walk(RayPointer{u})));
    //result of the walk(s) are in "list"

    //Now we need to consider those contours from the original set
    //for which we found no crosspoints (may be all of them, none of them or in between)
    //We shall always demand a positive surface, except if both input are hole
    InsertIfNotInRays(&list, &C1->first, C1_nc ? &C1_nc->first : nullptr, C2, type);
    for (unsigned u = 0; u<C1->further.size(); u++)
        InsertIfNotInRays(&list, &C1->further[u], C1_nc ? &C1_nc->further[u] : nullptr, C2, type);
    if (C2) {
        InsertIfNotInRays(&list, &C2->first, C2_nc ? &C2_nc->first : nullptr, C1, type);
        for (unsigned u = 0; u<C2->further.size(); u++)
            InsertIfNotInRays(&list, &C2->further[u], C2_nc ? &C2_nc->further[u] : nullptr, C1, type);
    }
    //Remove anything the caller left in "result".
    result.clear();
    //Note result may be the same as C1 or C2.
    //Clean up moved-from operands, so that they are Sane
    //(and have no empty elements in ContourLists, etc.)
    if (C1_nc) C1_nc->clear();
    if (C2_nc) C2_nc->clear();
    //Convert the tree of nodes to a Contour object. Make it positive unless
    //we do a binary operation on holes
    ConvertNode(type, std::move(list), result, Contour::is_positive(type));
#ifndef NDEBUG
    _ASSERT_PRINT(result.IsSane(), sC1, sC2, "Not sane after Do. Op:"+(evaluated_for ? to_string(*evaluated_for) : "not set"));
    if (!est_bb.IsInvalid()) {
        if (const Block& bb = result.GetBoundingBox();  est_roughly_equal)
            _ASSERT_PRINT(fabs(est_bb.x.from-bb.x.from)+fabs(est_bb.y.from-bb.y.from)+fabs(est_bb.x.till-bb.x.till)+fabs(est_bb.y.till-bb.y.till)<1,
                          sC1, sC2, "Not equal BB after Do. Op:"+to_string(type) + " "+ bb.Dump(false)+" instead of "+est_bb.Dump(false)+"\nContour R="+result.Dump(true)+";");
        else //smaller
            _ASSERT_PRINT(std::max(0., est_bb.x.from-bb.x.from)+std::max(0., est_bb.y.from-bb.y.from)+std::max(0., bb.x.till-est_bb.x.till)+std::max(0., bb.y.till-est_bb.y.till)<1,
                          sC1, sC2, "Not smaller BB after Do. Op:"+to_string(type) + " "+ bb.Dump(false)+" instead of "+est_bb.Dump(false)+"\nContour R"+result.Dump(true)+";");
    }
#endif
    ResetCrosspoints(); //A subsequent call to "Do" can work OK, again
}

/** Returns if any of the crosspoints is of crossing-type */
bool ContoursHelper::HasCrossingCPs() {
    FindCrosspoints();
    size_t cross = 0;
    for (const auto &[xy, cp_head] : cp_heads) {
        size_t started = cp_head.head;
        size_t r1 = started;
        size_t r2 = Rays[r1].link_in_cp.next;
        if (!RayCompareBy_ContourVertexPosHard(r1, r2)) {
            started = r1 = r2;
            r2 = Rays[r1].link_in_cp.next;
        }
        do {
            if (!RayCompareBy_ContourVertexPosHard(r1, r2))
                goto cross;
            r1 = Rays[r2].link_in_cp.next; //step 2
            r2 = Rays[r1].link_in_cp.next;
        } while (r1!=started && r2!=started);
        continue;
    cross:
        if (++cross>=2) return true;
    }
    return false;
}

/** Used when assembling a Contour from individual SimpleContours resulting from
 * Walks. There we merge crosspoints and therefore tolerate some minimal overlap.
 * As a result, if the result of a (cheap) containment check fails, we do a full
 * crosspoint search (which merges crosspoints the same way as before a walk),
 * and decide based on that - by calling RelationTo().*/
EContourRelationType SimpleContour::CheckContainmentForOperation(const SimpleContour& other) const {
    //fast path
    if (other.IsEmpty())
        return IsEmpty() ? REL_BOTH_EMPTY : REL_B_IS_EMPTY;
    if (IsEmpty())
        return REL_A_IS_EMPTY;
    if (GetBoundingBox().HasZeroOverlap(other.GetBoundingBox()))
        return REL_APART;

    const auto Eval = [](const SimpleContour& A, const SimpleContour& B, int threshold, int allow) {
        const auto Eval = [threshold, allow](const SimpleContour& A, const SimpleContour& B) {
            if (fabs(A.GetArea())>fabs(B.GetArea())*1.01) return REL_OVERLAP; //means either APART or B_IN_A
            switch (A.CheckContainmentHelper(B, threshold, allow)) {
            case REL_SAME:       if (fabs(A.GetArea())*0.99 < fabs(B.GetArea()) && fabs(B.GetArea())<fabs(A.GetArea())*1.01) return REL_SAME; else break;
            case REL_A_INSIDE_B: if (fabs(A.GetArea())<fabs(B.GetArea())*1.01) return REL_A_INSIDE_B; else break;
            default: _ASSERT(0); FALLTHROUGH;
            case REL_OVERLAP: return REL_OVERLAP; //means either APART or B_IN_A
            }
            return REL_IN_HOLE_APART; //means undecided
        };
        const EContourRelationType rel[2] = {Eval(A, B), switch_side(Eval(B, A))};
        if (rel[0]==rel[1]) {
            if (rel[0]==REL_SAME) return REL_SAME;
            if (rel[0]==REL_OVERLAP) return REL_APART;
        } else if (rel[0]==REL_OVERLAP || rel[1]==REL_OVERLAP) {
            const size_t o = rel[0]==REL_OVERLAP ? 1 : 0;
            if (rel[o]==REL_A_INSIDE_B) return REL_A_INSIDE_B;
            if (rel[o]==REL_B_INSIDE_A) return REL_B_INSIDE_A;
        }
        return REL_IN_HOLE_APART; //means undecided
    };
    //Try finding the relation.
    //Note that we test only one vertex. In unlucky situations this may lead to incorrect results.
    //E.g., if two contours overlap just a bit so that A has a vertex inside B and we accidentally
    //evaluate that. There we see A_INSIDE_B. Then we evaluate a point from B, which lies outside A,
    //so we get REL_OVERLAP (meaning either REL_APART or REL_A_INSIDE_B). And voila, we have
    //REL_A_INSIDE_B confirmed, whereas the correct answer is REL_APART. We catch this error
    //if the area of the two do not allow A_INSIDE_B, but not if they are roughly of similar size.
    //The only way to avoid this, would be to test more vertices - but that would increase the cost.
    //So for now, we keep it as is. If we find a conflict, we do a full CP evaluation to test if they
    //overlap. That is expensive, but seldom triggered.
    if (const EContourRelationType r = Eval(*this, other, 1, 0); r!=REL_IN_HOLE_APART)
        return r;
    //Inconclusive, test if we overlap by searching for crosspoints
    if (ContoursHelper::HasCrossingCPs(*this, other)) return REL_OVERLAP; //This returns false if we overlap, but very little

    //Try finding the relation on a majority basis. It is also not perfect. It may be that many
    //vertices (with small edges) are inside the other contour, but the area outside the other contour
    //is much larger than the one inside, despite the difference in the number of vertices. Eh.
    const size_t m = std::max(size(), other.size());
    if (const EContourRelationType r = Eval(*this, other, m, m); r!=REL_IN_HOLE_APART)
        return r;
    return REL_OVERLAP; //We give up this way.
}


/** Dump all contours to a text file and draw them, too. */
void ContoursHelper::Dump()
{
#ifdef _DEBUG
    //if (C2==nullptr)
    //    contour::debug::Snapshot(EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5, *C1);
    //else
    //    contour::debug::Snapshot(EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5,
    //                             ColorType(1,0,0), *C1, ColorType(0, 0, 1), *C2);
#endif
}

bool ContoursHelper::IsSane() const noexcept {
    if (Rays.empty())
        return StartRays.empty()
        && contour_heads.empty()
        && cp_heads.empty();
    return contour_heads.size() && cp_heads.size()
        && std::ranges::all_of(Rays, [s = Rays.size()](const Ray& r) { return r.IsSane(s); })
        && std::ranges::all_of(StartRays, [s = Rays.size()](size_t r) {return r<s; });
}


/** Checks if all edges connect, the shape is closed, no edges
 * are degenerated to a dot, the shape has an actual area,
 * and that no two edges cross*/
bool SimpleContour::IsSane() const {
    if (size()==0) return true;
    if (size()==1) {
        if (at(0).IsStraight()) return false;
        if (at(0).IsDot()) return false;
        if (at(0).GetStart().test_equal(at(0).GetEnd())) return true;
        return false;
    }
    if (size()==2 && at(0).IsStraight() && at(1).IsStraight())
        return false;
    for (size_t u = 0; u<size(); u++)
        if (at(u).IsDot())
            return false;
        else if (at(u).GetEnd() != at_next(u).GetStart())
            return false;
    return !ContoursHelper::HasCrossingCPs(*this);
}

/** Calculates the distance between a point and the list of contours by finding our closest point.
* @param [in] o The point to take the distance from.
* @param [out] ret We return the point on one of our contours closes to `o`.
* @return The distance, negative if `o` is inside one of the contours. `CONTOUR_INFINITY` if we are empty. */
double ContourList::Distance(const XY &o, XY &ret) const noexcept
{
    double d = CONTOUR_INFINITY;
    for (auto i = begin(); i!=end(); i++) {
        XY tmp;
        double dd = i->Distance(o, tmp);
        if (fabs(dd)<fabs(d)) {
            d = dd;
            ret = tmp;
            if (d==0) return 0;
        }
    }
    return d;
}

/** Calculates the distance between a point and our shapes by finding our closest point and returns two tangent points.
* Same as Distance(const XY &o, XY &ret), but in addition we return two tangent points
* from the tangent of the shape at `ret`. See @ref contour for a description of tangents.
*
* @param [in] o The point to take the distance from.
* @param [out] ret We return the point on our contour closes to `o`.
* @param [out] t1 The forward tangent point.
* @param [out] t2 The backward tangent point.
* @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty.*/
double ContourList::DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const
{
    double d = CONTOUR_INFINITY;
    for (auto i = begin(); i!=end(); i++) {
        XY tmp, _1, _2;
        double dd = i->DistanceWithTangents(o, tmp, _1, _2);
        if (fabs(dd)<fabs(d)) {
            d = -dd;
            ret = tmp;
            t1 = _1;
            t2 = _2;
            if (d==0) return 0;
        }
    }
    return d;
}


/////////////////////HoledSimpleContour

/** Checks if all edges connect, the outline and holes are closed, no edges
* are degenerated to a dot, the shape has an actual area, etc.*/
bool HoledSimpleContour::IsSane(bool shouldbehole) const
{
    if (!outline.IsSane()) return false;
    if (outline.GetClockWise()==shouldbehole) return false;
    for (auto &h :holes)
        if (h.IsEmpty())
            return false;
        else if (!h.IsSane(!shouldbehole))
            return false;
        else if (outline.RelationTo(h.Outline())!=REL_B_INSIDE_A)
            return false;
    return true;
}

void HoledSimpleContour::Expand(EExpandType type4positive, EExpandType type4negative, double gap, Contour &res,
                              double miter_limit_positive, double miter_limit_negative) const
{
    if (outline.size()==0) return;
    if (gap==0) {res = *this; return;}
    res = outline.CreateExpand(outline.GetClockWise() && gap>0 ? type4positive : type4negative, gap,
                               outline.GetClockWise() && gap>0 ? miter_limit_positive : miter_limit_negative);
    if (holes.size()==0 || res.IsEmpty()) return;
    for (const auto &h : holes) {
        Contour tmp;
        h.Expand(type4positive, type4negative, gap, tmp, miter_limit_positive, miter_limit_negative);
        //in case "i" is an actual holes, it is are already inversed, adding is the right op
        res.Operation(GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION, res, std::move(tmp));
    }
}

Contour HoledSimpleContour::CreateExpand2D(const XY &gap) const
{
    if (outline.size()==0) return Contour();
    if (test_zero(gap.x) && test_zero(gap.y)) return Contour{ *this };
    Contour res = outline.CreateExpand2D(gap);
    if (holes.size() && !res.IsEmpty())
        for (const auto & h: holes)
            //in case "i" is an actual holes, it is are already inversed, adding is the right op
            res.Operation(GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION, res, h.CreateExpand2D(gap));
    return res;
}

EContourRelationType HoledSimpleContour::RelationTo(const HoledSimpleContour &c, bool ignore_holes) const
{
    const EContourRelationType res = outline.RelationTo(c.outline);
    if (ignore_holes) return res;
    switch (res) {
    default:
    case REL_A_IN_HOLE_OF_B:
    case REL_B_IN_HOLE_OF_A:
    case REL_IN_HOLE_APART:
        _ASSERT(0);
        FALLTHROUGH;
    case REL_OVERLAP:
    case REL_A_IS_EMPTY:
    case REL_B_IS_EMPTY:
    case REL_BOTH_EMPTY:
    case REL_APART:
        return res;
    case REL_SAME:
        if (holes.RelationTo(c.holes, ignore_holes) == REL_SAME) return REL_SAME;
        return REL_OVERLAP;
    case REL_A_INSIDE_B:
        switch (c.holes.RelationTo(outline, ignore_holes)) {
        case REL_A_IS_EMPTY:
            return REL_A_INSIDE_B;
        case REL_B_INSIDE_A:
        case REL_SAME:
            return REL_A_IN_HOLE_OF_B;
        case REL_A_INSIDE_B:
        case REL_OVERLAP:
            return REL_OVERLAP;
        case REL_APART:
        case REL_B_IN_HOLE_OF_A:
            return REL_A_INSIDE_B;
        case REL_A_IN_HOLE_OF_B:
        case REL_B_IS_EMPTY:
        case REL_BOTH_EMPTY:
        case REL_IN_HOLE_APART:
        default:
            _ASSERT(0);
            return REL_OVERLAP;
        }
    case REL_B_INSIDE_A:
        switch (holes.RelationTo(c.outline, ignore_holes)) {
        case REL_A_IS_EMPTY:
            return REL_B_INSIDE_A;
        case REL_B_INSIDE_A:
        case REL_SAME:
            return REL_B_IN_HOLE_OF_A;
        case REL_A_INSIDE_B:
        case REL_OVERLAP:
            return REL_OVERLAP;
        case REL_APART:
        case REL_B_IN_HOLE_OF_A:
            return REL_B_INSIDE_A;
        case REL_A_IN_HOLE_OF_B:
        case REL_B_IS_EMPTY:
        case REL_BOTH_EMPTY:
        case REL_IN_HOLE_APART:
        default:
            _ASSERT(0);
            return REL_OVERLAP;
        }
    }
}


/** Calculates the distance between two shapes by finding their two closest points.
*
* @param [in] c The other shape to take the distance from.
* @param dist_so_far We return the distance of the two closest points and the two points themselves.
*            Distance is negative one is inside the other (but not in a hole), zero if partial overlap only (two points equal)
*            'Inside' here ignores clockwiseness. Distance is `CONTOUR_INFINITY` if one of the shapes is empty.
*            Note that `ret` can contain the result of previous searches, we update it if we find two points
*            with a smaller distance (in absolute value).*/
void HoledSimpleContour::Distance(const HoledSimpleContour &c, DistanceType &dist_so_far) const noexcept
{
    if (dist_so_far.IsZero()) return;
    DistanceType d = dist_so_far;
    d.ClearInOut();
    outline.Distance(c.outline, d);
    if (d.was_inside) { //one outline is inside another one, consider holes
        //see which one is in the other
        DistanceType temp = d;
        temp.ClearInOut();
        if (GetBoundingBox().GetArea() < c.GetBoundingBox().GetArea()) {
            //we are inside, see if we are in the holes of 'c'
            if (!c.holes.IsEmpty()) {
                temp.SwapPoints();
                c.holes.Distance(*this, temp);
                temp.SwapPoints();
            }
        } else {
            if (!holes.IsEmpty())
                holes.Distance(c, temp);
        }
        temp.SwapInOut();
        d.Merge(temp);
    }
    dist_so_far.Merge(d);
}

/////////////////////////////////////////  Contour implementation


Contour & Contour::Untangle(Contour &&tmp, EForceClockwise force_clockwise, bool winding)
{
    Operation(winding ? Contour::WINDING_RULE_NONZERO : Contour::WINDING_RULE_EVENODD, std::move(tmp));
    if (!GetClockWise()) switch (force_clockwise) {
    case EForceClockwise::INVERT_IF_NEEDED: invert_dont_check(); break;
    case EForceClockwise::REMOVE_COUNTERCLOCKWISE: clear(); break;
    default: break;
    }
    return *this;
}

Contour & Contour::assign(const Path & p, bool positive, ECloseType close)
{
    clear();
    Contour tmp;
    for (auto &e: p.ConvertToClosed(close)) {
        SimpleContour s;
        s.assign_dont_check(std::move(static_cast<Path&>(e)));
        tmp.append_dont_check(s);
    }
    Operation(positive ? Contour::EXPAND_POSITIVE : Contour::EXPAND_NEGATIVE, std::move(tmp));
    return *this;
}

Contour & Contour::assign(Path && p, bool positive, ECloseType close)
{
    clear();
    Contour tmp;
    //First see if p is a single closed shape
    for (size_t u = 0; u<p.size(); u++)
        if (p.at(u).GetEnd() != p.at_next(u).GetStart())
            //no, we need to work this through
            return assign(const_cast<const Path&>(p), positive, close);
    //OK, a single round thingy, we can move
    tmp.first.outline.assign_dont_check(std::move(p));
    Operation(positive ? Contour::EXPAND_POSITIVE : Contour::EXPAND_NEGATIVE, std::move(tmp));
    return *this;
}

Contour &Contour::assign_dont_check(Path &&p, ECloseType close)
{
    //First see if p is a single closed shape
    for (size_t u = 0; u<p.size(); u++)
        if (p.at(u).GetEnd() != p.at_next(u).GetStart())
            return assign_dont_check(p, close); //the non-move version
    //OK, fully closed - we can assign the edges directly
    SimpleContour tmp;
    tmp.assign_dont_check(std::move(p));
    clear();
    append_dont_check(std::move(tmp));
    return *this;
}

/** Checks if all edges connect, the shape is closed, no edges
* are degenerated to a dot, the shape has an actual area, etc.*/
bool Contour::IsSane() const
{
    if (!first.IsSane(!first.GetClockWise())) return false;
    for (auto i = further.begin(); i!=further.end(); i++)
        if (i->IsEmpty()) return false;
        else if (!i->IsSane(!i->GetClockWise())) return false;
    //check pairwise that the outlines do not overlap
    for (auto i = begin(); i!=end(); ++i)
        for (auto j = std::next(i); j!=end(); ++j)
            if (ContoursHelper::HasCrossingCPs(i->Outline(), j->Outline())
                || i->Outline().CheckContainmentForOperation(j->Outline())!=REL_APART)
                return false;
    return true;
}


/** Remove the uth component from us.
 * If we don't have as many components, silently no-op.
 * Our bounding box will not be updated.*/
Contour & Contour::Remove(unsigned u)
{
    if (further.size()>=u) {
        if (u)
            further.Remove(u-1);
        else if (further.size()) {
            first = std::move(further.front());
            further.Remove(0);
        } else
            clear();
    }
    return *this;
}

/** Remove the all but the uth component from us.
 * If we don't have as many components, silently empties us.*/
Contour &Contour::RemoveAllBut(unsigned u) {
    if (further.size()>=u) {
        if (u)
            first = std::move(further[u-1]);
        further.clear();
    } else
        clear();
    return *this;
}


/** Transform a generic 2D transformation. */

Contour &Contour::Transform(const TRMatrix & M) noexcept
{
    if (M.IsDegenerate())
        clear();
    else if (!M.IsIdentity()) {
        if (M.IsFlipping()) {
            first.InvertingTransform(M);
            further.InvertingTransform(M);
        } else {
            first.NonInvertingTransform(M);
            further.NonInvertingTransform(M);
        }
    }
    return *this;
}

/** Create a transformed version of us.*/
Contour Contour::CreateTransformed(const TRMatrix & M) const
{
    if (M.IsIdentity())
        return *this;
    Contour ret;
    if (M.IsDegenerate())
        return ret;
    if (M.IsFlipping()) {
        ret.first = first.CreateInvertingTransformed(M);
        ret.further = further.CreateInvertingTransformed(M);
    } else {
        ret.first = first.CreateNonInvertingTransformed(M);
        ret.further = further.CreateNonInvertingTransformed(M);
    }
    return ret;
}

void Contour::Invert()
{
    Contour tmp(*this);
    tmp.first.Invert();
    if (tmp.further.size())
        tmp.further.Invert();
    Operation(tmp.GetClockWise() ? EXPAND_POSITIVE : EXPAND_NEGATIVE, std::move(tmp));
}

void Contour::Operation(EOperationType type, const Contour &c1)
{
    ContoursHelper h(c1);
    h.Do(type, *this);

}

void Contour::Operation(EOperationType type, Contour &&c1)
{
    ContoursHelper h(std::move(c1));
    h.Do(type, *this);
}

void Contour::Operation(EOperationType type, const Contour &c1, const Contour &c2)
{
    ContoursHelper h(c1, c2);
    h.Do(type, *this);
}


void Contour::Operation(EOperationType type, const Contour &c1, Contour &&c2)
{
    ContoursHelper h(c1, std::move(c2));
    h.Do(type, *this);
}

void Contour::Operation(EOperationType type, Contour &&c1, Contour &&c2)
{
    ContoursHelper h(std::move(c1), std::move(c2));
    h.Do(type, *this);
}

void Contour::Expand(EExpandType type4positive, EExpandType type4negative, double gap, Contour &res,
                     double miter_limit_positive, double miter_limit_negative) const
{
    first.Expand(type4positive, type4negative, gap, res, miter_limit_positive, miter_limit_negative);
    if (further.size()==0) return;
    Contour tmp;
    for (auto i = further.begin(); i!=further.end(); i++) {
        i->Expand(type4positive, type4negative, gap, tmp, miter_limit_positive, miter_limit_negative);
        res += std::move(tmp);
        tmp.clear();
    }
    _ASSERT(IsSane());
}

Contour Contour::CreateExpand2D(const XY &gap) const
{
    if (test_zero(gap.x) && test_zero(gap.y)) return *this;
    Contour res = first.CreateExpand2D(gap);
    for (auto &f : further)
        res += f.CreateExpand2D(gap);
    return res;
}


//if "ignore holes" is true, it can only return
//A_IS_EMPTY, B_IS_EMPTY, BOTH_EMPTY, A_INSIDE_B, B_INSIDE_A, SAME, APART, OVERLAP
//if false, these additional values may come:
//A_IN_HOLE_OF_B, B_IN_HOLE_OF_A, IN_HOLE_APART
//(latter meaning no overlap, but some parts of A is in holes and outside of B)
EContourRelationType Contour::RelationTo(const Contour &c, bool ignore_holes) const
{
    if (IsEmpty())
        return c.IsEmpty() ? REL_BOTH_EMPTY : REL_A_IS_EMPTY;
    else if (c.IsEmpty())
        return REL_B_IS_EMPTY;
    if (further.IsEmpty() && c.further.IsEmpty()) return first.RelationTo(c.first, ignore_holes);
    if (!GetBoundingBox().Overlaps(c.GetBoundingBox())) return REL_APART;
    //Here we admittedly hack.
    //To avoid writing code again, we append the "first" to "further" and compare
    //two ContourLists re-using the code there.
    //Then we restore "further" by deleting the appended cwh and restoring the bounding box
    Contour &c1 = const_cast<Contour&>(*this);
    Contour &c2 = const_cast<Contour&>(c);
    EContourRelationType res;
    if (further.IsEmpty()) {
        const Block bb(c2.further.boundingBox);
        c2.further.append(c2.first);
        res = first.RelationTo(c2.further, ignore_holes);
        c2.further.pop_back();
        c2.further.boundingBox = bb;
    } else if (c2.further.IsEmpty()) {
        const Block bb(c1.further.boundingBox);
        c1.further.append(c1.first);
        res = c1.further.RelationTo(c.first, ignore_holes);
        c1.further.pop_back();
        c1.further.boundingBox = bb;
    } else {
        const Block bb(c1.further.boundingBox);
        const Block b2(c2.further.boundingBox);
        c1.further.push_back(c1.first);
        c2.further.push_back(c2.first);
        res = c1.further.RelationTo(c2.further, ignore_holes);
        c1.further.pop_back();
        c2.further.pop_back();
        c1.further.boundingBox = bb;
        c2.further.boundingBox = b2;
    }
    return res;
}

/** Merges the result of two tangent results, taking the widest.
 * 'was' and 'was2' signifies if the tangents are valid, or not.*/
inline bool MergeTangentFroms(bool was, XY clockwise[2], XY cclockwise[2], bool was2, const XY c[2], const XY cc[2])
{
    if (was && was2) {
        clockwise[1]  = minmax_clockwise(clockwise[0], c[1], clockwise[1], true);
        clockwise[0]  = minmax_clockwise(clockwise[1], c[0], clockwise[0], false);
        cclockwise[0] = minmax_clockwise(cclockwise[1], cc[0], cclockwise[0], true);
        cclockwise[1] = minmax_clockwise(cclockwise[0], cc[1], cclockwise[1], false);
        //clockwise[1]  = minmax_clockwise(clockwise[0], c[1], clockwise[1], true);
        //clockwise[0]  = minmax_clockwise(clockwise[1], c[0], clockwise[0], false);
    } else if (was2) {
        clockwise[1]  = c[1];
        clockwise[0]  = c[0];
        cclockwise[0] = cc[0];
        cclockwise[1] = cc[1];
    }
    return was || was2;
}


bool Contour::TangentFrom(const Contour &from, XY clockwise[2], XY cclockwise[2]) const
{
    if (IsEmpty() || from.IsEmpty()) return false;
    bool was = first.outline.TangentFrom(from.first.outline, clockwise, cclockwise);
    XY c[2], cc[2];
    for (auto i = from.further.begin(); i!=from.further.end(); i++) {
        const bool was2 = first.outline.TangentFrom(i->outline, c, cc);
        was = MergeTangentFroms(was, clockwise, cclockwise, was2, c, cc);
    }
    for (auto j = further.begin(); j!=further.end(); j++) {
        const bool was2 = j->outline.TangentFrom(from.first.outline, c, cc);
        was = MergeTangentFroms(was, clockwise, cclockwise, was2, c, cc);
        for (auto i = from.further.begin(); i!=from.further.end(); i++) {
            const bool was3 = j->outline.TangentFrom(i->outline, c, cc);
            was = MergeTangentFroms(was, clockwise, cclockwise, was3, c, cc);
        }
    }
    return was;
}

Range Contour::Cut(const XY &A, const XY &B) const
{
    Range ret = GetBoundingBox().Cut(A, B); //also tests for A==B or invalid bb, which catches empty "this"
    if (ret.IsInvalid()) return ret;
    const Edge s(A+(B-A)*(ret.from-0.1), A+(B-A)*(ret.till+0.1));
    ret = Cut(s);
    if (ret.IsInvalid()) return ret;
    Range ret2;
    if (fabs(A.x-B.x) > fabs(A.y-B.y)) {
        const double p1 = s.GetStart().x + (s.GetEnd().x-s.GetStart().x)*ret.from;
        const double p2 = s.GetStart().x + (s.GetEnd().x-s.GetStart().x)*ret.till;
        //p1 and p2 are now the x coordinate of the two CP, convert to "pos" on A-B
        ret2.from = (p1-A.x)/(B.x-A.x);
        ret2.till = (p2-A.x)/(B.x-A.x);
    } else { //do it in y coordinates
        const double p1 = s.GetStart().y + (s.GetEnd().y-s.GetStart().y)*ret.from;
        const double p2 = s.GetStart().y + (s.GetEnd().y-s.GetStart().y)*ret.till;
        //p1 and p2 are now the y coordinate of the two CP, convert to "pos" on A-B
        ret2.from = (p1-A.y)/(B.y-A.y);
        ret2.till = (p2-A.y)/(B.y-A.y);
    }
    return ret2;
}


Range Contour::CutWithTangent(const XY &A, const XY &B, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const
{
    Range ret = GetBoundingBox().Cut(A, B); //also tests for A==B or invalid bb, which catches empty "this"
    if (ret.IsInvalid()) return ret;
    const Edge s(A+(B-A)*ret.from, A+(B-A)*(ret.till+0.1));
    ret = CutWithTangent(s, from, till);
    if (ret.IsInvalid()) return ret;
    Range ret2;
    if (fabs(A.x-B.x) > fabs(A.y-B.y)) {
        const double p1 = s.GetStart().x + (s.GetEnd().x-s.GetStart().x)*ret.from;
        const double p2 = s.GetStart().x + (s.GetEnd().x-s.GetStart().x)*ret.till;
        //p1 and p2 are now the x coordinate of the two CP, convert to "pos" on A-B
        ret2.from = (p1-A.x)/(B.x-A.x);
        ret2.till = (p2-A.x)/(B.x-A.x);
    } else { //do it in y coordinates
        const double p1 = s.GetStart().y + (s.GetEnd().y-s.GetStart().y)*ret.from;
        const double p2 = s.GetStart().y + (s.GetEnd().y-s.GetStart().y)*ret.till;
        //p1 and p2 are now the y coordinate of the two CP, convert to "pos" on A-B
        ret2.from = (p1-A.y)/(B.y-A.y);
        ret2.till = (p2-A.y)/(B.y-A.y);
    }
    return ret2;
}

/** Creates a C++ representation of this contour.
    * Calls Edge->Dump(precise).
    * @param [in] precise When set, we emit the hex representation of doubles, else a human readable one.
    * @returns the full string.*/
std::string Contour::Dump(bool precise) const {
    return "Contour::UnsafeMake(" + first.Dump(precise) + "," + further.Dump(precise) + ")";
}
Contour Contour::UnsafeMake(HoledSimpleContour&& fi, ContourList&& fu) {
    Contour ret;
    ret.first.swap(fi);
    ret.further.swap(fu);
    return ret;
}

} //namespace
