// SPDX-License-Identifier: LGPL-2.1-or-later

/***************************************************************************
 *   Copyright (c) 2005 Imetric 3D GmbH                                    *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include <algorithm>
#include <boost/core/ignore_unused.hpp>
#include <cmath>
#include <limits>
#include <queue>
#include <utility>


#include <Base/Console.h>
#include <Mod/Mesh/App/WildMagic4/Wm4MeshCurvature.h>

#include "Evaluation.h"
#include "Iterator.h"
#include "MeshKernel.h"
#include "TopoAlgorithm.h"
#include "Triangulation.h"


using namespace MeshCore;

MeshTopoAlgorithm::MeshTopoAlgorithm(MeshKernel& rclM)
    : _rclMesh(rclM)
{}

MeshTopoAlgorithm::~MeshTopoAlgorithm()
{
    if (_needsCleanup) {
        Cleanup();
    }
    EndCache();
}

bool MeshTopoAlgorithm::InsertVertex(FacetIndex ulFacetPos, const Base::Vector3f& rclPoint)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet clNewFacet1, clNewFacet2;

    // insert new point
    PointIndex ulPtCnt = _rclMesh._aclPointArray.size();
    PointIndex ulPtInd = this->GetOrAddIndex(rclPoint);
    FacetIndex ulSize = _rclMesh._aclFacetArray.size();

    if (ulPtInd < ulPtCnt) {
        return false;  // the given point is already part of the mesh => creating new facets would
                       // be an illegal operation
    }

    // adjust the facets
    //
    // first new facet
    clNewFacet1._aulPoints[0] = rclF._aulPoints[1];
    clNewFacet1._aulPoints[1] = rclF._aulPoints[2];
    clNewFacet1._aulPoints[2] = ulPtInd;
    clNewFacet1._aulNeighbours[0] = rclF._aulNeighbours[1];
    clNewFacet1._aulNeighbours[1] = ulSize + 1;
    clNewFacet1._aulNeighbours[2] = ulFacetPos;
    // second new facet
    clNewFacet2._aulPoints[0] = rclF._aulPoints[2];
    clNewFacet2._aulPoints[1] = rclF._aulPoints[0];
    clNewFacet2._aulPoints[2] = ulPtInd;
    clNewFacet2._aulNeighbours[0] = rclF._aulNeighbours[2];
    clNewFacet2._aulNeighbours[1] = ulFacetPos;
    clNewFacet2._aulNeighbours[2] = ulSize;
    // adjust the neighbour facet
    if (rclF._aulNeighbours[1] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[1]].ReplaceNeighbour(ulFacetPos, ulSize);
    }
    if (rclF._aulNeighbours[2] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[2]].ReplaceNeighbour(ulFacetPos, ulSize + 1);
    }
    // original facet
    rclF._aulPoints[2] = ulPtInd;
    rclF._aulNeighbours[1] = ulSize;
    rclF._aulNeighbours[2] = ulSize + 1;

    // insert new facets
    _rclMesh._aclFacetArray.push_back(clNewFacet1);
    _rclMesh._aclFacetArray.push_back(clNewFacet2);

    return true;
}

bool MeshTopoAlgorithm::SnapVertex(FacetIndex ulFacetPos, const Base::Vector3f& rP)
{
    MeshFacet& rFace = _rclMesh._aclFacetArray[ulFacetPos];
    if (!rFace.HasOpenEdge()) {
        return false;
    }
    Base::Vector3f cNo1 = _rclMesh.GetNormal(rFace);
    for (unsigned short i = 0; i < 3; i++) {
        if (rFace._aulNeighbours[i] == FACET_INDEX_MAX) {
            const Base::Vector3f& rPt1 = _rclMesh._aclPointArray[rFace._aulPoints[i]];
            const Base::Vector3f& rPt2 = _rclMesh._aclPointArray[rFace._aulPoints[(i + 1) % 3]];
            Base::Vector3f cNo2 = (rPt2 - rPt1) % cNo1;
            Base::Vector3f cNo3 = (rP - rPt1) % (rPt2 - rPt1);
            float fD2 = Base::DistanceP2(rPt1, rPt2);
            float fTV = (rP - rPt1) * (rPt2 - rPt1);

            // Point is on the edge
            if (cNo3.Length() < std::numeric_limits<float>::epsilon()) {
                return SplitOpenEdge(ulFacetPos, i, rP);
            }
            if ((rP - rPt1) * cNo2 > 0.0F && fD2 >= fTV && fTV >= 0.0F) {
                MeshFacet cTria;
                cTria._aulPoints[0] = this->GetOrAddIndex(rP);
                cTria._aulPoints[1] = rFace._aulPoints[(i + 1) % 3];
                cTria._aulPoints[2] = rFace._aulPoints[i];
                cTria._aulNeighbours[1] = ulFacetPos;
                rFace._aulNeighbours[i] = _rclMesh.CountFacets();
                _rclMesh._aclFacetArray.push_back(cTria);
                return true;
            }
        }
    }

    return false;
}

void MeshTopoAlgorithm::OptimizeTopology(float fMaxAngle)
{
    // For each internal edge get the adjacent facets. When doing an edge swap we must update
    // this structure.
    std::map<std::pair<PointIndex, PointIndex>, std::vector<FacetIndex>> aEdge2Face;
    for (auto pI = _rclMesh._aclFacetArray.begin(); pI != _rclMesh._aclFacetArray.end(); ++pI) {
        for (int i = 0; i < 3; i++) {
            // ignore open edges
            if (pI->_aulNeighbours[i] != FACET_INDEX_MAX) {
                PointIndex ulPt0 = std::min<PointIndex>(pI->_aulPoints[i], pI->_aulPoints[(i + 1) % 3]);
                PointIndex ulPt1 = std::max<PointIndex>(pI->_aulPoints[i], pI->_aulPoints[(i + 1) % 3]);
                aEdge2Face[std::pair<PointIndex, PointIndex>(ulPt0, ulPt1)].push_back(
                    pI - _rclMesh._aclFacetArray.begin()
                );
            }
        }
    }

    // fill up this list with all internal edges and perform swap edges until this list is empty
    std::list<std::pair<PointIndex, PointIndex>> aEdgeList;
    std::map<std::pair<PointIndex, PointIndex>, std::vector<FacetIndex>>::iterator pE;
    for (pE = aEdge2Face.begin(); pE != aEdge2Face.end(); ++pE) {
        if (pE->second.size() == 2) {  // make sure that we really have an internal edge
            aEdgeList.push_back(pE->first);
        }
    }

    // to be sure to avoid an endless loop
    unsigned long uMaxIter = 5 * aEdge2Face.size();

    // Perform a swap edge where needed
    while (!aEdgeList.empty() && uMaxIter > 0) {
        // get the first edge and remove it from the list
        std::pair<PointIndex, PointIndex> aEdge = aEdgeList.front();
        aEdgeList.pop_front();
        uMaxIter--;

        // get the adjacent facets to this edge
        pE = aEdge2Face.find(aEdge);

        // this edge has been removed some iterations before
        if (pE == aEdge2Face.end()) {
            continue;
        }

        // Is swap edge allowed and sensible?
        if (!ShouldSwapEdge(pE->second[0], pE->second[1], fMaxAngle)) {
            continue;
        }

        // ok, here we should perform a swap edge to minimize the maximum angle
        if (/*fMax12 > fMax34*/ true) {
            // swap the edge
            SwapEdge(pE->second[0], pE->second[1]);

            MeshFacet& rF1 = _rclMesh._aclFacetArray[pE->second[0]];
            MeshFacet& rF2 = _rclMesh._aclFacetArray[pE->second[1]];
            unsigned short side1 = rF1.Side(aEdge.first, aEdge.second);
            unsigned short side2 = rF2.Side(aEdge.first, aEdge.second);

            // adjust the edge list
            for (int i = 0; i < 3; i++) {
                std::map<std::pair<PointIndex, PointIndex>, std::vector<FacetIndex>>::iterator it;
                // first facet
                PointIndex ulPt0 = std::min<PointIndex>(rF1._aulPoints[i], rF1._aulPoints[(i + 1) % 3]);
                PointIndex ulPt1 = std::max<PointIndex>(rF1._aulPoints[i], rF1._aulPoints[(i + 1) % 3]);
                it = aEdge2Face.find(std::make_pair(ulPt0, ulPt1));
                if (it != aEdge2Face.end()) {
                    if (it->second[0] == pE->second[1]) {
                        it->second[0] = pE->second[0];
                    }
                    else if (it->second[1] == pE->second[1]) {
                        it->second[1] = pE->second[0];
                    }
                    aEdgeList.push_back(it->first);
                }

                // second facet
                ulPt0 = std::min<PointIndex>(rF2._aulPoints[i], rF2._aulPoints[(i + 1) % 3]);
                ulPt1 = std::max<PointIndex>(rF2._aulPoints[i], rF2._aulPoints[(i + 1) % 3]);
                it = aEdge2Face.find(std::make_pair(ulPt0, ulPt1));
                if (it != aEdge2Face.end()) {
                    if (it->second[0] == pE->second[0]) {
                        it->second[0] = pE->second[1];
                    }
                    else if (it->second[1] == pE->second[0]) {
                        it->second[1] = pE->second[1];
                    }
                    aEdgeList.push_back(it->first);
                }
            }

            // Now we must remove the edge and replace it through the new edge
            PointIndex ulPt0 = std::min<PointIndex>(
                rF1._aulPoints[(side1 + 1) % 3],
                rF2._aulPoints[(side2 + 1) % 3]
            );
            PointIndex ulPt1 = std::max<PointIndex>(
                rF1._aulPoints[(side1 + 1) % 3],
                rF2._aulPoints[(side2 + 1) % 3]
            );
            std::pair<PointIndex, PointIndex> aNewEdge = std::make_pair(ulPt0, ulPt1);
            aEdge2Face[aNewEdge] = pE->second;
            aEdge2Face.erase(pE);
        }
    }
}

// Cosine of the maximum angle in triangle (v1,v2,v3)
static float cos_maxangle(const Base::Vector3f& v1, const Base::Vector3f& v2, const Base::Vector3f& v3)
{
    float a = Base::Distance(v2, v3);
    float b = Base::Distance(v3, v1);
    float c = Base::Distance(v1, v2);
    float A = a * (b * b + c * c - a * a);
    float B = b * (c * c + a * a - b * b);
    float C = c * (a * a + b * b - c * c);
    return 0.5F * std::min<float>(std::min<float>(A, B), C) / (a * b * c);  // min cosine == max angle
}

static float swap_benefit(
    const Base::Vector3f& v1,
    const Base::Vector3f& v2,
    const Base::Vector3f& v3,
    const Base::Vector3f& v4
)
{
    Base::Vector3f n124 = (v4 - v2) % (v1 - v2);
    Base::Vector3f n234 = (v3 - v2) % (v4 - v2);
    if ((n124 * n234) <= 0.0F) {
        return 0.0F;  // avoid normal flip
    }

    return std::max<float>(-cos_maxangle(v1, v2, v3), -cos_maxangle(v1, v3, v4))
        - std::max<float>(-cos_maxangle(v1, v2, v4), -cos_maxangle(v2, v3, v4));
}

float MeshTopoAlgorithm::SwapEdgeBenefit(FacetIndex f, int e) const
{
    const MeshFacetArray& faces = _rclMesh.GetFacets();
    const MeshPointArray& vertices = _rclMesh.GetPoints();

    FacetIndex n = faces[f]._aulNeighbours[e];
    if (n == FACET_INDEX_MAX) {
        return 0.0F;  // border edge
    }

    PointIndex v1 = faces[f]._aulPoints[e];
    PointIndex v2 = faces[f]._aulPoints[(e + 1) % 3];
    PointIndex v3 = faces[f]._aulPoints[(e + 2) % 3];
    unsigned short s = faces[n].Side(faces[f]);
    if (s == std::numeric_limits<unsigned short>::max()) {
        std::cerr << "MeshTopoAlgorithm::SwapEdgeBenefit: error in neighbourhood "
                  << "of faces " << f << " and " << n << std::endl;
        return 0.0F;  // topological error
    }
    PointIndex v4 = faces[n]._aulPoints[(s + 2) % 3];
    if (v3 == v4) {
        std::cerr << "MeshTopoAlgorithm::SwapEdgeBenefit: duplicate faces " << f << " and " << n
                  << std::endl;
        return 0.0F;  // duplicate faces
    }
    return swap_benefit(vertices[v2], vertices[v3], vertices[v1], vertices[v4]);
}

using FaceEdge = std::pair<FacetIndex, int>;  // (face, edge) pair
using FaceEdgePriority = std::pair<float, FaceEdge>;

void MeshTopoAlgorithm::OptimizeTopology()
{
    // Find all edges that can be swapped and insert them into a
    // priority queue
    const MeshFacetArray& faces = _rclMesh.GetFacets();
    FacetIndex nf = _rclMesh.CountFacets();
    std::priority_queue<FaceEdgePriority> todo;
    for (FacetIndex i = 0; i < nf; i++) {
        for (int j = 0; j < 3; j++) {
            float b = SwapEdgeBenefit(i, j);
            if (b > 0.0F) {
                todo.push(std::make_pair(b, std::make_pair(i, j)));
            }
        }
    }

    // Edges are sorted in decreasing order with respect to their benefit
    while (!todo.empty()) {
        FacetIndex f = todo.top().second.first;
        int e = todo.top().second.second;
        todo.pop();
        // Check again if the swap should still be done
        if (SwapEdgeBenefit(f, e) <= 0.0F) {
            continue;
        }
        // OK, swap the edge
        FacetIndex f2 = faces[f]._aulNeighbours[e];
        SwapEdge(f, f2);
        // Insert new edges into queue, if necessary
        for (int j = 0; j < 3; j++) {
            float b = SwapEdgeBenefit(f, j);
            if (b > 0.0F) {
                todo.push(std::make_pair(b, std::make_pair(f, j)));
            }
        }
        for (int j = 0; j < 3; j++) {
            float b = SwapEdgeBenefit(f2, j);
            if (b > 0.0F) {
                todo.push(std::make_pair(b, std::make_pair(f2, j)));
            }
        }
    }
}

void MeshTopoAlgorithm::DelaunayFlip(float fMaxAngle)
{
    // For each internal edge get the adjacent facets.
    std::set<std::pair<FacetIndex, FacetIndex>> aEdge2Face;
    FacetIndex index = 0;
    for (auto pI = _rclMesh._aclFacetArray.begin(); pI != _rclMesh._aclFacetArray.end();
         ++pI, index++) {
        for (FacetIndex nbIndex : pI->_aulNeighbours) {
            // ignore open edges
            if (nbIndex != FACET_INDEX_MAX) {
                FacetIndex ulFt0 = std::min<FacetIndex>(index, nbIndex);
                FacetIndex ulFt1 = std::max<FacetIndex>(index, nbIndex);
                aEdge2Face.insert(std::pair<FacetIndex, FacetIndex>(ulFt0, ulFt1));
            }
        }
    }

    Base::Vector3f center;
    while (!aEdge2Face.empty()) {
        std::set<std::pair<FacetIndex, FacetIndex>>::iterator it = aEdge2Face.begin();
        std::pair<FacetIndex, FacetIndex> edge = *it;
        aEdge2Face.erase(it);
        if (ShouldSwapEdge(edge.first, edge.second, fMaxAngle)) {
            float radius = _rclMesh.GetFacet(edge.first).CenterOfCircumCircle(center);
            radius *= radius;
            const MeshFacet& face_1 = _rclMesh._aclFacetArray[edge.first];
            const MeshFacet& face_2 = _rclMesh._aclFacetArray[edge.second];
            unsigned short side = face_2.Side(edge.first);
            MeshPoint vertex = _rclMesh.GetPoint(face_2._aulPoints[(side + 1) % 3]);
            if (Base::DistanceP2(center, vertex) < radius) {
                SwapEdge(edge.first, edge.second);
                for (int i = 0; i < 3; i++) {
                    if (face_1._aulNeighbours[i] != FACET_INDEX_MAX
                        && face_1._aulNeighbours[i] != edge.second) {
                        FacetIndex ulFt0 = std::min<FacetIndex>(edge.first, face_1._aulNeighbours[i]);
                        FacetIndex ulFt1 = std::max<FacetIndex>(edge.first, face_1._aulNeighbours[i]);
                        aEdge2Face.insert(std::pair<FacetIndex, FacetIndex>(ulFt0, ulFt1));
                    }
                    if (face_2._aulNeighbours[i] != FACET_INDEX_MAX
                        && face_2._aulNeighbours[i] != edge.first) {
                        FacetIndex ulFt0 = std::min<FacetIndex>(edge.second, face_2._aulNeighbours[i]);
                        FacetIndex ulFt1 = std::max<FacetIndex>(edge.second, face_2._aulNeighbours[i]);
                        aEdge2Face.insert(std::pair<FacetIndex, FacetIndex>(ulFt0, ulFt1));
                    }
                }
            }
        }
    }
}

int MeshTopoAlgorithm::DelaunayFlip()
{
    int cnt_swap = 0;
    _rclMesh._aclFacetArray.ResetFlag(MeshFacet::TMP0);
    size_t cnt_facets = _rclMesh._aclFacetArray.size();
    for (size_t i = 0; i < cnt_facets; i++) {
        const MeshFacet& f_face = _rclMesh._aclFacetArray[i];
        if (f_face.IsFlag(MeshFacet::TMP0)) {
            continue;
        }
        for (int j = 0; j < 3; j++) {
            FacetIndex n = f_face._aulNeighbours[j];
            if (n != FACET_INDEX_MAX) {
                const MeshFacet& n_face = _rclMesh._aclFacetArray[n];
                if (n_face.IsFlag(MeshFacet::TMP0)) {
                    continue;
                }
                unsigned short k = n_face.Side(f_face);
                MeshGeomFacet f1 = _rclMesh.GetFacet(f_face);
                MeshGeomFacet f2 = _rclMesh.GetFacet(n_face);
                Base::Vector3f c1, c2, p1, p2;
                p1 = f1._aclPoints[(j + 2) % 3];
                p2 = f2._aclPoints[(k + 2) % 3];
                float r1 = f1.CenterOfCircumCircle(c1);
                r1 = r1 * r1;
                float r2 = f2.CenterOfCircumCircle(c2);
                r2 = r2 * r2;
                float d1 = Base::DistanceP2(c1, p2);
                float d2 = Base::DistanceP2(c2, p1);
                if (d1 < r1 || d2 < r2) {
                    SwapEdge(i, n);
                    cnt_swap++;
                    f_face.SetFlag(MeshFacet::TMP0);
                    n_face.SetFlag(MeshFacet::TMP0);
                }
            }
        }
    }

    return cnt_swap;
}

void MeshTopoAlgorithm::AdjustEdgesToCurvatureDirection()
{
    std::vector<Wm4::Vector3<float>> aPnts;
    MeshPointIterator cPIt(_rclMesh);
    aPnts.reserve(_rclMesh.CountPoints());
    for (cPIt.Init(); cPIt.More(); cPIt.Next()) {
        aPnts.emplace_back(cPIt->x, cPIt->y, cPIt->z);
    }

    // get all point connections
    std::vector<int> aIdx;
    const MeshFacetArray& raFts = _rclMesh.GetFacets();
    aIdx.reserve(3 * raFts.size());

    // Build map of edges to the referencing facets
    FacetIndex k = 0;
    std::map<std::pair<PointIndex, PointIndex>, std::list<FacetIndex>> aclEdgeMap;
    for (auto jt = raFts.begin(); jt != raFts.end(); ++jt, k++) {
        for (int i = 0; i < 3; i++) {
            PointIndex ulT0 = jt->_aulPoints[i];
            PointIndex ulT1 = jt->_aulPoints[(i + 1) % 3];
            PointIndex ulP0 = std::min<PointIndex>(ulT0, ulT1);
            PointIndex ulP1 = std::max<PointIndex>(ulT0, ulT1);
            aclEdgeMap[std::make_pair(ulP0, ulP1)].push_front(k);
            aIdx.push_back(static_cast<int>(jt->_aulPoints[i]));
        }
    }

    // compute vertex based curvatures
    Wm4::MeshCurvature<float> meshCurv(
        static_cast<int>(_rclMesh.CountPoints()),
        aPnts.data(),
        static_cast<int>(_rclMesh.CountFacets()),
        aIdx.data()
    );

    // get curvature information now
    const Wm4::Vector3<float>* aMaxCurvDir = meshCurv.GetMaxDirections();
    const Wm4::Vector3<float>* aMinCurvDir = meshCurv.GetMinDirections();
    const float* aMaxCurv = meshCurv.GetMaxCurvatures();
    const float* aMinCurv = meshCurv.GetMinCurvatures();

    raFts.ResetFlag(MeshFacet::VISIT);
    const MeshPointArray& raPts = _rclMesh.GetPoints();
    for (auto& kt : aclEdgeMap) {
        if (kt.second.size() == 2) {
            PointIndex uPt1 = kt.first.first;
            PointIndex uPt2 = kt.first.second;
            FacetIndex uFt1 = kt.second.front();
            FacetIndex uFt2 = kt.second.back();

            const MeshFacet& rFace1 = raFts[uFt1];
            const MeshFacet& rFace2 = raFts[uFt2];
            if (rFace1.IsFlag(MeshFacet::VISIT) || rFace2.IsFlag(MeshFacet::VISIT)) {
                continue;
            }

            PointIndex uPt3 {}, uPt4 {};
            unsigned short side = rFace1.Side(uPt1, uPt2);
            uPt3 = rFace1._aulPoints[(side + 2) % 3];
            side = rFace2.Side(uPt1, uPt2);
            uPt4 = rFace2._aulPoints[(side + 2) % 3];

            Wm4::Vector3<float> dir;
            float fActCurvature {};
            if (std::fabs(aMinCurv[uPt1]) > std::fabs(aMaxCurv[uPt1])) {
                fActCurvature = aMinCurv[uPt1];
                dir = aMaxCurvDir[uPt1];
            }
            else {
                fActCurvature = aMaxCurv[uPt1];
                dir = aMinCurvDir[uPt1];
            }

            Base::Vector3f cMinDir(dir.X(), dir.Y(), dir.Z());
            Base::Vector3f cEdgeDir1 = raPts[uPt1] - raPts[uPt2];
            Base::Vector3f cEdgeDir2 = raPts[uPt3] - raPts[uPt4];
            cMinDir.Normalize();
            cEdgeDir1.Normalize();
            cEdgeDir2.Normalize();

            // get the plane and calculate the distance to the fourth point
            MeshGeomFacet cPlane(raPts[uPt1], raPts[uPt2], raPts[uPt3]);
            // positive or negative distance
            float fDist = raPts[uPt4].DistanceToPlane(cPlane._aclPoints[0], cPlane.GetNormal());

            float fLength12 = Base::Distance(raPts[uPt1], raPts[uPt2]);
            float fLength34 = Base::Distance(raPts[uPt3], raPts[uPt4]);
            if (fabs(cEdgeDir1 * cMinDir) < fabs(cEdgeDir2 * cMinDir)) {
                if (IsSwapEdgeLegal(uFt1, uFt2) && fLength34 < 1.05F * fLength12
                    && fActCurvature * fDist > 0.0F) {
                    SwapEdge(uFt1, uFt2);
                    rFace1.SetFlag(MeshFacet::VISIT);
                    rFace2.SetFlag(MeshFacet::VISIT);
                }
            }
        }
    }
}

bool MeshTopoAlgorithm::InsertVertexAndSwapEdge(
    FacetIndex ulFacetPos,
    const Base::Vector3f& rclPoint,
    float fMaxAngle
)
{
    if (!InsertVertex(ulFacetPos, rclPoint)) {
        return false;
    }

    // get the created elements
    FacetIndex ulF1Ind = _rclMesh._aclFacetArray.size() - 2;
    FacetIndex ulF2Ind = _rclMesh._aclFacetArray.size() - 1;
    MeshFacet& rclF1 = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclF2 = _rclMesh._aclFacetArray[ulF1Ind];
    MeshFacet& rclF3 = _rclMesh._aclFacetArray[ulF2Ind];

    // first facet
    for (FacetIndex uNeighbour : rclF1._aulNeighbours) {
        if (uNeighbour != FACET_INDEX_MAX && uNeighbour != ulF1Ind && uNeighbour != ulF2Ind) {
            if (ShouldSwapEdge(ulFacetPos, uNeighbour, fMaxAngle)) {
                SwapEdge(ulFacetPos, uNeighbour);
                break;
            }
        }
    }
    for (FacetIndex uNeighbour : rclF2._aulNeighbours) {
        // second facet
        if (uNeighbour != FACET_INDEX_MAX && uNeighbour != ulFacetPos && uNeighbour != ulF2Ind) {
            if (ShouldSwapEdge(ulF1Ind, uNeighbour, fMaxAngle)) {
                SwapEdge(ulF1Ind, uNeighbour);
                break;
            }
        }
    }

    // third facet
    for (FacetIndex uNeighbour : rclF3._aulNeighbours) {
        if (uNeighbour != FACET_INDEX_MAX && uNeighbour != ulFacetPos && uNeighbour != ulF1Ind) {
            if (ShouldSwapEdge(ulF2Ind, uNeighbour, fMaxAngle)) {
                SwapEdge(ulF2Ind, uNeighbour);
                break;
            }
        }
    }

    return true;
}

bool MeshTopoAlgorithm::IsSwapEdgeLegal(FacetIndex ulFacetPos, FacetIndex ulNeighbour) const
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uFSide = rclF.Side(rclN);
    unsigned short uNSide = rclN.Side(rclF);

    constexpr auto max = std::numeric_limits<unsigned short>::max();
    if (uFSide == max || uNSide == max) {
        return false;  // not neighbours
    }

    Base::Vector3f cP1 = _rclMesh._aclPointArray[rclF._aulPoints[uFSide]];
    Base::Vector3f cP2 = _rclMesh._aclPointArray[rclF._aulPoints[(uFSide + 1) % 3]];
    Base::Vector3f cP3 = _rclMesh._aclPointArray[rclF._aulPoints[(uFSide + 2) % 3]];
    Base::Vector3f cP4 = _rclMesh._aclPointArray[rclN._aulPoints[(uNSide + 2) % 3]];

    // do not allow one to create degenerated triangles
    MeshGeomFacet cT3(cP4, cP3, cP1);
    if (cT3.IsDegenerated(MeshDefinitions::_fMinPointDistanceP2)) {
        return false;
    }
    MeshGeomFacet cT4(cP3, cP4, cP2);
    if (cT4.IsDegenerated(MeshDefinitions::_fMinPointDistanceP2)) {
        return false;
    }

    // We must make sure that the two adjacent triangles builds a convex polygon, otherwise
    // the swap edge operation is illegal
    Base::Vector3f cU = cP2 - cP1;
    Base::Vector3f cV = cP4 - cP3;
    // build a helper plane through cP1 that must separate cP3 and cP4
    Base::Vector3f cN1 = (cU % cV) % cU;
    if (((cP3 - cP1) * cN1) * ((cP4 - cP1) * cN1) >= 0.0F) {
        return false;  // not convex
    }
    // build a helper plane through cP3 that must separate cP1 and cP2
    Base::Vector3f cN2 = (cU % cV) % cV;
    if (((cP1 - cP3) * cN2) * ((cP2 - cP3) * cN2) >= 0.0F) {
        return false;  // not convex
    }

    return true;
}

bool MeshTopoAlgorithm::ShouldSwapEdge(FacetIndex ulFacetPos, FacetIndex ulNeighbour, float fMaxAngle) const
{
    if (!IsSwapEdgeLegal(ulFacetPos, ulNeighbour)) {
        return false;
    }

    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uFSide = rclF.Side(rclN);
    unsigned short uNSide = rclN.Side(rclF);

    Base::Vector3f cP1 = _rclMesh._aclPointArray[rclF._aulPoints[uFSide]];
    Base::Vector3f cP2 = _rclMesh._aclPointArray[rclF._aulPoints[(uFSide + 1) % 3]];
    Base::Vector3f cP3 = _rclMesh._aclPointArray[rclF._aulPoints[(uFSide + 2) % 3]];
    Base::Vector3f cP4 = _rclMesh._aclPointArray[rclN._aulPoints[(uNSide + 2) % 3]];

    MeshGeomFacet cT1(cP1, cP2, cP3);
    float fMax1 = cT1.MaximumAngle();
    MeshGeomFacet cT2(cP2, cP1, cP4);
    float fMax2 = cT2.MaximumAngle();
    MeshGeomFacet cT3(cP4, cP3, cP1);
    float fMax3 = cT3.MaximumAngle();
    MeshGeomFacet cT4(cP3, cP4, cP2);
    float fMax4 = cT4.MaximumAngle();

    // get the angle between the triangles
    Base::Vector3f cN1 = cT1.GetNormal();
    Base::Vector3f cN2 = cT2.GetNormal();
    if (cN1.GetAngle(cN2) > fMaxAngle) {
        return false;
    }

    float fMax12 = std::max<float>(fMax1, fMax2);
    float fMax34 = std::max<float>(fMax3, fMax4);

    return fMax12 > fMax34;
}

void MeshTopoAlgorithm::SwapEdge(FacetIndex ulFacetPos, FacetIndex ulNeighbour)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uFSide = rclF.Side(rclN);
    unsigned short uNSide = rclN.Side(rclF);

    constexpr auto max = std::numeric_limits<unsigned short>::max();
    if (uFSide == max || uNSide == max) {
        return;  // not neighbours
    }

    // adjust the neighbourhood
    if (rclF._aulNeighbours[(uFSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[(uFSide + 1) % 3]].ReplaceNeighbour(
            ulFacetPos,
            ulNeighbour
        );
    }
    if (rclN._aulNeighbours[(uNSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 1) % 3]].ReplaceNeighbour(
            ulNeighbour,
            ulFacetPos
        );
    }

    // swap the point and neighbour indices
    rclF._aulPoints[(uFSide + 1) % 3] = rclN._aulPoints[(uNSide + 2) % 3];
    rclN._aulPoints[(uNSide + 1) % 3] = rclF._aulPoints[(uFSide + 2) % 3];
    rclF._aulNeighbours[uFSide] = rclN._aulNeighbours[(uNSide + 1) % 3];
    rclN._aulNeighbours[uNSide] = rclF._aulNeighbours[(uFSide + 1) % 3];
    rclF._aulNeighbours[(uFSide + 1) % 3] = ulNeighbour;
    rclN._aulNeighbours[(uNSide + 1) % 3] = ulFacetPos;
}

bool MeshTopoAlgorithm::SplitEdge(FacetIndex ulFacetPos, FacetIndex ulNeighbour, const Base::Vector3f& rP)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uFSide = rclF.Side(rclN);
    unsigned short uNSide = rclN.Side(rclF);

    constexpr auto max = std::numeric_limits<unsigned short>::max();
    if (uFSide == max || uNSide == max) {
        return false;  // not neighbours
    }

    PointIndex uPtCnt = _rclMesh._aclPointArray.size();
    PointIndex uPtInd = this->GetOrAddIndex(rP);
    FacetIndex ulSize = _rclMesh._aclFacetArray.size();

    // the given point is already part of the mesh => creating new facets would
    // be an illegal operation
    if (uPtInd < uPtCnt) {
        return false;
    }

    // adjust the neighbourhood
    if (rclF._aulNeighbours[(uFSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[(uFSide + 1) % 3]].ReplaceNeighbour(
            ulFacetPos,
            ulSize
        );
    }
    if (rclN._aulNeighbours[(uNSide + 2) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 2) % 3]].ReplaceNeighbour(
            ulNeighbour,
            ulSize + 1
        );
    }

    MeshFacet cNew1, cNew2;
    cNew1._aulPoints[0] = uPtInd;
    cNew1._aulPoints[1] = rclF._aulPoints[(uFSide + 1) % 3];
    cNew1._aulPoints[2] = rclF._aulPoints[(uFSide + 2) % 3];
    cNew1._aulNeighbours[0] = ulSize + 1;
    cNew1._aulNeighbours[1] = rclF._aulNeighbours[(uFSide + 1) % 3];
    cNew1._aulNeighbours[2] = ulFacetPos;

    cNew2._aulPoints[0] = rclN._aulPoints[uNSide];
    cNew2._aulPoints[1] = uPtInd;
    cNew2._aulPoints[2] = rclN._aulPoints[(uNSide + 2) % 3];
    cNew2._aulNeighbours[0] = ulSize;
    cNew2._aulNeighbours[1] = ulNeighbour;
    cNew2._aulNeighbours[2] = rclN._aulNeighbours[(uNSide + 2) % 3];

    // adjust the facets
    rclF._aulPoints[(uFSide + 1) % 3] = uPtInd;
    rclF._aulNeighbours[(uFSide + 1) % 3] = ulSize;
    rclN._aulPoints[uNSide] = uPtInd;
    rclN._aulNeighbours[(uNSide + 2) % 3] = ulSize + 1;

    // insert new facets
    _rclMesh._aclFacetArray.push_back(cNew1);
    _rclMesh._aclFacetArray.push_back(cNew2);

    return true;
}

bool MeshTopoAlgorithm::SplitOpenEdge(
    FacetIndex ulFacetPos,
    unsigned short uSide,
    const Base::Vector3f& rP
)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    if (rclF._aulNeighbours[uSide] != FACET_INDEX_MAX) {
        return false;  // not open
    }

    PointIndex uPtCnt = _rclMesh._aclPointArray.size();
    PointIndex uPtInd = this->GetOrAddIndex(rP);
    FacetIndex ulSize = _rclMesh._aclFacetArray.size();

    if (uPtInd < uPtCnt) {
        return false;  // the given point is already part of the mesh => creating new facets would
                       // be an illegal operation
    }

    // adjust the neighbourhood
    if (rclF._aulNeighbours[(uSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[(uSide + 1) % 3]].ReplaceNeighbour(
            ulFacetPos,
            ulSize
        );
    }

    MeshFacet cNew;
    cNew._aulPoints[0] = uPtInd;
    cNew._aulPoints[1] = rclF._aulPoints[(uSide + 1) % 3];
    cNew._aulPoints[2] = rclF._aulPoints[(uSide + 2) % 3];
    cNew._aulNeighbours[0] = FACET_INDEX_MAX;
    cNew._aulNeighbours[1] = rclF._aulNeighbours[(uSide + 1) % 3];
    cNew._aulNeighbours[2] = ulFacetPos;

    // adjust the facets
    rclF._aulPoints[(uSide + 1) % 3] = uPtInd;
    rclF._aulNeighbours[(uSide + 1) % 3] = ulSize;

    // insert new facets
    _rclMesh._aclFacetArray.push_back(cNew);
    return true;
}

bool MeshTopoAlgorithm::Vertex_Less::operator()(const Base::Vector3f& u, const Base::Vector3f& v) const
{
    if (std::fabs(u.x - v.x) > std::numeric_limits<float>::epsilon()) {
        return u.x < v.x;
    }
    if (std::fabs(u.y - v.y) > std::numeric_limits<float>::epsilon()) {
        return u.y < v.y;
    }
    if (std::fabs(u.z - v.z) > std::numeric_limits<float>::epsilon()) {
        return u.z < v.z;
    }
    return false;
}

void MeshTopoAlgorithm::BeginCache()
{
    delete _cache;
    _cache = new tCache();
    PointIndex nbPoints = _rclMesh._aclPointArray.size();
    for (unsigned int pntCpt = 0; pntCpt < nbPoints; ++pntCpt) {
        _cache->insert(std::make_pair(_rclMesh._aclPointArray[pntCpt], pntCpt));
    }
}

void MeshTopoAlgorithm::EndCache()
{
    if (_cache) {
        _cache->clear();
        delete _cache;
        _cache = nullptr;
    }
}

PointIndex MeshTopoAlgorithm::GetOrAddIndex(const MeshPoint& rclPoint)
{
    if (!_cache) {
        return _rclMesh._aclPointArray.GetOrAddIndex(rclPoint);
    }

    unsigned long sz = _rclMesh._aclPointArray.size();
    std::pair<tCache::iterator, bool> retval = _cache->insert(std::make_pair(rclPoint, sz));
    if (retval.second) {
        _rclMesh._aclPointArray.push_back(rclPoint);
    }
    return retval.first->second;
}

std::vector<FacetIndex> MeshTopoAlgorithm::GetFacetsToPoint(FacetIndex uFacetPos, PointIndex uPointPos) const
{
    // get all facets this point is referenced by
    std::list<FacetIndex> aReference;
    aReference.push_back(uFacetPos);
    std::set<FacetIndex> aRefFacet;
    while (!aReference.empty()) {
        FacetIndex uIndex = aReference.front();
        aReference.pop_front();
        aRefFacet.insert(uIndex);
        MeshFacet& rFace = _rclMesh._aclFacetArray[uIndex];
        for (int i = 0; i < 3; i++) {
            if (rFace._aulPoints[i] == uPointPos) {
                if (rFace._aulNeighbours[i] != FACET_INDEX_MAX) {
                    if (aRefFacet.find(rFace._aulNeighbours[i]) == aRefFacet.end()) {
                        aReference.push_back(rFace._aulNeighbours[i]);
                    }
                }
                if (rFace._aulNeighbours[(i + 2) % 3] != FACET_INDEX_MAX) {
                    if (aRefFacet.find(rFace._aulNeighbours[(i + 2) % 3]) == aRefFacet.end()) {
                        aReference.push_back(rFace._aulNeighbours[(i + 2) % 3]);
                    }
                }
                break;
            }
        }
    }

    // copy the items
    std::vector<FacetIndex> aRefs;
    aRefs.insert(aRefs.end(), aRefFacet.begin(), aRefFacet.end());
    return aRefs;
}

void MeshTopoAlgorithm::Cleanup()
{
    _rclMesh.RemoveInvalids();
    _needsCleanup = false;
}

bool MeshTopoAlgorithm::CollapseVertex(const VertexCollapse& vc)
{
    if (vc._circumFacets.size() != vc._circumPoints.size()) {
        return false;
    }

    if (vc._circumFacets.size() != 3) {
        return false;
    }

    if (!_rclMesh._aclPointArray[vc._point].IsValid()) {
        return false;  // the point is marked invalid from a previous run
    }

    MeshFacet& rFace1 = _rclMesh._aclFacetArray[vc._circumFacets[0]];
    MeshFacet& rFace2 = _rclMesh._aclFacetArray[vc._circumFacets[1]];
    MeshFacet& rFace3 = _rclMesh._aclFacetArray[vc._circumFacets[2]];

    // get the point that is not shared by rFace1
    PointIndex ptIndex = POINT_INDEX_MAX;
    std::vector<PointIndex>::const_iterator it;
    for (it = vc._circumPoints.begin(); it != vc._circumPoints.end(); ++it) {
        if (!rFace1.HasPoint(*it)) {
            ptIndex = *it;
            break;
        }
    }

    if (ptIndex == POINT_INDEX_MAX) {
        return false;
    }

    FacetIndex neighbour1 = FACET_INDEX_MAX;
    FacetIndex neighbour2 = FACET_INDEX_MAX;

    const std::vector<FacetIndex>& faces = vc._circumFacets;
    // get neighbours that are not part of the faces to be removed
    for (int i = 0; i < 3; i++) {
        if (std::ranges::find(faces, rFace2._aulNeighbours[i]) == faces.end()) {
            neighbour1 = rFace2._aulNeighbours[i];
        }
        if (std::ranges::find(faces, rFace3._aulNeighbours[i]) == faces.end()) {
            neighbour2 = rFace3._aulNeighbours[i];
        }
    }

    // adjust point and neighbour indices
    rFace1.Transpose(vc._point, ptIndex);
    rFace1.ReplaceNeighbour(vc._circumFacets[1], neighbour1);
    rFace1.ReplaceNeighbour(vc._circumFacets[2], neighbour2);

    if (neighbour1 != FACET_INDEX_MAX) {
        MeshFacet& rFace4 = _rclMesh._aclFacetArray[neighbour1];
        rFace4.ReplaceNeighbour(vc._circumFacets[1], vc._circumFacets[0]);
    }
    if (neighbour2 != FACET_INDEX_MAX) {
        MeshFacet& rFace5 = _rclMesh._aclFacetArray[neighbour2];
        rFace5.ReplaceNeighbour(vc._circumFacets[2], vc._circumFacets[0]);
    }

    // the two facets and the point can be marked for removal
    rFace2.SetInvalid();
    rFace3.SetInvalid();
    _rclMesh._aclPointArray[vc._point].SetInvalid();

    _needsCleanup = true;

    return true;
}

bool MeshTopoAlgorithm::CollapseEdge(FacetIndex ulFacetPos, FacetIndex ulNeighbour)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uFSide = rclF.Side(rclN);
    unsigned short uNSide = rclN.Side(rclF);

    constexpr auto max = std::numeric_limits<unsigned short>::max();
    if (uFSide == max || uNSide == max) {
        return false;  // not neighbours
    }

    if (!rclF.IsValid() || !rclN.IsValid()) {
        return false;  // the facets are marked invalid from a previous run
    }

    // get the point index we want to remove
    PointIndex ulPointPos = rclF._aulPoints[uFSide];
    PointIndex ulPointNew = rclN._aulPoints[uNSide];

    // get all facets this point is referenced by
    std::vector<FacetIndex> aRefs = GetFacetsToPoint(ulFacetPos, ulPointPos);
    for (FacetIndex it : aRefs) {
        MeshFacet& rFace = _rclMesh._aclFacetArray[it];
        rFace.Transpose(ulPointPos, ulPointNew);
    }

    // set the new neighbourhood
    if (rclF._aulNeighbours[(uFSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[(uFSide + 1) % 3]].ReplaceNeighbour(
            ulFacetPos,
            rclF._aulNeighbours[(uFSide + 2) % 3]
        );
    }
    if (rclF._aulNeighbours[(uFSide + 2) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclF._aulNeighbours[(uFSide + 2) % 3]].ReplaceNeighbour(
            ulFacetPos,
            rclF._aulNeighbours[(uFSide + 1) % 3]
        );
    }
    if (rclN._aulNeighbours[(uNSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 1) % 3]].ReplaceNeighbour(
            ulNeighbour,
            rclN._aulNeighbours[(uNSide + 2) % 3]
        );
    }
    if (rclN._aulNeighbours[(uNSide + 2) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 2) % 3]].ReplaceNeighbour(
            ulNeighbour,
            rclN._aulNeighbours[(uNSide + 1) % 3]
        );
    }

    // isolate the both facets and the point
    rclF._aulNeighbours[0] = FACET_INDEX_MAX;
    rclF._aulNeighbours[1] = FACET_INDEX_MAX;
    rclF._aulNeighbours[2] = FACET_INDEX_MAX;
    rclF.SetInvalid();
    rclN._aulNeighbours[0] = FACET_INDEX_MAX;
    rclN._aulNeighbours[1] = FACET_INDEX_MAX;
    rclN._aulNeighbours[2] = FACET_INDEX_MAX;
    rclN.SetInvalid();
    _rclMesh._aclPointArray[ulPointPos].SetInvalid();

    _needsCleanup = true;

    return true;
}

bool MeshTopoAlgorithm::IsCollapseEdgeLegal(const EdgeCollapse& ec) const
{
    // http://stackoverflow.com/a/27049418/148668
    // Check connectivity
    //
    std::vector<PointIndex> commonPoints;
    std::set_intersection(
        ec._adjacentFrom.begin(),
        ec._adjacentFrom.end(),
        ec._adjacentTo.begin(),
        ec._adjacentTo.end(),
        std::back_insert_iterator<std::vector<PointIndex>>(commonPoints)
    );
    if (commonPoints.size() > 2) {
        return false;
    }

    // Check geometry
    std::vector<FacetIndex>::const_iterator it;
    for (it = ec._changeFacets.begin(); it != ec._changeFacets.end(); ++it) {
        MeshFacet f = _rclMesh._aclFacetArray[*it];
        if (!f.IsValid()) {
            return false;
        }

        // ignore the facet(s) at this edge
        if (f.HasPoint(ec._fromPoint) && f.HasPoint(ec._toPoint)) {
            continue;
        }

        MeshGeomFacet tria1 = _rclMesh.GetFacet(f);
        f.Transpose(ec._fromPoint, ec._toPoint);
        MeshGeomFacet tria2 = _rclMesh.GetFacet(f);

        if (tria1.GetNormal() * tria2.GetNormal() < 0.0F) {
            return false;
        }
    }

    // If the data structure is valid and the algorithm works as expected
    // it should never happen to reject the edge-collapse here!
    for (it = ec._removeFacets.begin(); it != ec._removeFacets.end(); ++it) {
        MeshFacet f = _rclMesh._aclFacetArray[*it];
        if (!f.IsValid()) {
            return false;
        }
    }

    if (!_rclMesh._aclPointArray[ec._fromPoint].IsValid()) {
        return false;
    }

    if (!_rclMesh._aclPointArray[ec._toPoint].IsValid()) {
        return false;
    }

    return true;
}

bool MeshTopoAlgorithm::CollapseEdge(const EdgeCollapse& ec)
{
    std::vector<FacetIndex>::const_iterator it;
    for (it = ec._removeFacets.begin(); it != ec._removeFacets.end(); ++it) {
        MeshFacet& f = _rclMesh._aclFacetArray[*it];
        f.SetInvalid();

        // adjust the neighbourhood
        std::vector<FacetIndex> neighbours;
        for (FacetIndex nbIndex : f._aulNeighbours) {
            // get the neighbours of the facet that won't be invalidated
            if (nbIndex != FACET_INDEX_MAX) {
                if (std::ranges::find(ec._removeFacets, nbIndex) == ec._removeFacets.end()) {
                    neighbours.push_back(nbIndex);
                }
            }
        }

        if (neighbours.size() == 2) {
            MeshFacet& n1 = _rclMesh._aclFacetArray[neighbours[0]];
            n1.ReplaceNeighbour(*it, neighbours[1]);
            MeshFacet& n2 = _rclMesh._aclFacetArray[neighbours[1]];
            n2.ReplaceNeighbour(*it, neighbours[0]);
        }
        else if (neighbours.size() == 1) {
            MeshFacet& n1 = _rclMesh._aclFacetArray[neighbours[0]];
            n1.ReplaceNeighbour(*it, FACET_INDEX_MAX);
        }
    }

    for (it = ec._changeFacets.begin(); it != ec._changeFacets.end(); ++it) {
        MeshFacet& f = _rclMesh._aclFacetArray[*it];
        f.Transpose(ec._fromPoint, ec._toPoint);
    }

    _rclMesh._aclPointArray[ec._fromPoint].SetInvalid();

    _needsCleanup = true;
    return true;
}

bool MeshTopoAlgorithm::CollapseFacet(FacetIndex ulFacetPos)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];
    if (!rclF.IsValid()) {
        return false;  // the facet is marked invalid from a previous run
    }

    // get the point index we want to remove
    PointIndex ulPointInd0 = rclF._aulPoints[0];
    PointIndex ulPointInd1 = rclF._aulPoints[1];
    PointIndex ulPointInd2 = rclF._aulPoints[2];

    // move the vertex to the gravity center
    Base::Vector3f cCenter = _rclMesh.GetGravityPoint(rclF);
    _rclMesh._aclPointArray[ulPointInd0] = cCenter;

    // set the new point indices for all facets that share one of the points to be deleted
    std::vector<FacetIndex> aRefs = GetFacetsToPoint(ulFacetPos, ulPointInd1);
    for (FacetIndex it : aRefs) {
        MeshFacet& rFace = _rclMesh._aclFacetArray[it];
        rFace.Transpose(ulPointInd1, ulPointInd0);
    }

    aRefs = GetFacetsToPoint(ulFacetPos, ulPointInd2);
    for (FacetIndex it : aRefs) {
        MeshFacet& rFace = _rclMesh._aclFacetArray[it];
        rFace.Transpose(ulPointInd2, ulPointInd0);
    }

    // set the neighbourhood of the circumjacent facets
    for (FacetIndex nbIndex : rclF._aulNeighbours) {
        if (nbIndex == FACET_INDEX_MAX) {
            continue;
        }
        MeshFacet& rclN = _rclMesh._aclFacetArray[nbIndex];
        unsigned short uNSide = rclN.Side(rclF);

        if (rclN._aulNeighbours[(uNSide + 1) % 3] != FACET_INDEX_MAX) {
            _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 1) % 3]].ReplaceNeighbour(
                nbIndex,
                rclN._aulNeighbours[(uNSide + 2) % 3]
            );
        }
        if (rclN._aulNeighbours[(uNSide + 2) % 3] != FACET_INDEX_MAX) {
            _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 2) % 3]].ReplaceNeighbour(
                nbIndex,
                rclN._aulNeighbours[(uNSide + 1) % 3]
            );
        }

        // Isolate the neighbours from the topology
        rclN._aulNeighbours[0] = FACET_INDEX_MAX;
        rclN._aulNeighbours[1] = FACET_INDEX_MAX;
        rclN._aulNeighbours[2] = FACET_INDEX_MAX;
        rclN.SetInvalid();
    }

    // Isolate this facet and make two of its points invalid
    rclF._aulNeighbours[0] = FACET_INDEX_MAX;
    rclF._aulNeighbours[1] = FACET_INDEX_MAX;
    rclF._aulNeighbours[2] = FACET_INDEX_MAX;
    rclF.SetInvalid();
    _rclMesh._aclPointArray[ulPointInd1].SetInvalid();
    _rclMesh._aclPointArray[ulPointInd2].SetInvalid();

    _needsCleanup = true;

    return true;
}

void MeshTopoAlgorithm::SplitFacet(
    FacetIndex ulFacetPos,
    const Base::Vector3f& rP1,
    const Base::Vector3f& rP2
)
{
    float fEps = MESH_MIN_EDGE_LEN;
    MeshFacet& rFace = _rclMesh._aclFacetArray[ulFacetPos];
    MeshPoint& rVertex0 = _rclMesh._aclPointArray[rFace._aulPoints[0]];
    MeshPoint& rVertex1 = _rclMesh._aclPointArray[rFace._aulPoints[1]];
    MeshPoint& rVertex2 = _rclMesh._aclPointArray[rFace._aulPoints[2]];

    auto pointIndex = [=](const Base::Vector3f& rP) {
        unsigned short equalP = std::numeric_limits<unsigned short>::max();
        if (Base::Distance(rVertex0, rP) < fEps) {
            equalP = 0;
        }
        else if (Base::Distance(rVertex1, rP) < fEps) {
            equalP = 1;
        }
        else if (Base::Distance(rVertex2, rP) < fEps) {
            equalP = 2;
        }
        return equalP;
    };

    unsigned short equalP1 = pointIndex(rP1);
    unsigned short equalP2 = pointIndex(rP2);

    constexpr auto max = std::numeric_limits<unsigned short>::max();
    // both points are coincident with the corner points
    if (equalP1 != max && equalP2 != max) {
        return;  // must not split the facet
    }

    if (equalP1 != max) {
        // get the edge to the second given point and perform a split edge operation
        SplitFacetOnOneEdge(ulFacetPos, rP2);
    }
    else if (equalP2 != max) {
        // get the edge to the first given point and perform a split edge operation
        SplitFacetOnOneEdge(ulFacetPos, rP1);
    }
    else {
        SplitFacetOnTwoEdges(ulFacetPos, rP1, rP2);
    }
}

void MeshTopoAlgorithm::SplitFacetOnOneEdge(FacetIndex ulFacetPos, const Base::Vector3f& rP)
{
    float fMinDist = std::numeric_limits<float>::max();
    unsigned short iEdgeNo = std::numeric_limits<unsigned short>::max();
    MeshFacet& rFace = _rclMesh._aclFacetArray[ulFacetPos];

    for (unsigned short i = 0; i < 3; i++) {
        Base::Vector3f cBase(_rclMesh._aclPointArray[rFace._aulPoints[i]]);
        Base::Vector3f cEnd(_rclMesh._aclPointArray[rFace._aulPoints[(i + 1) % 3]]);
        Base::Vector3f cDir = cEnd - cBase;

        float fDist = rP.DistanceToLine(cBase, cDir);
        if (fDist < fMinDist) {
            fMinDist = fDist;
            iEdgeNo = i;
        }
    }

    if (fMinDist < 0.05F) {
        if (rFace._aulNeighbours[iEdgeNo] != FACET_INDEX_MAX) {
            SplitEdge(ulFacetPos, rFace._aulNeighbours[iEdgeNo], rP);
        }
        else {
            SplitOpenEdge(ulFacetPos, iEdgeNo, rP);
        }
    }
}

void MeshTopoAlgorithm::SplitFacetOnTwoEdges(
    FacetIndex ulFacetPos,
    const Base::Vector3f& rP1,
    const Base::Vector3f& rP2
)
{
    // search for the matching edges
    unsigned short iEdgeNo1 = std::numeric_limits<unsigned short>::max();
    unsigned short iEdgeNo2 = std::numeric_limits<unsigned short>::max();
    float fMinDist1 = std::numeric_limits<float>::max();
    float fMinDist2 = std::numeric_limits<float>::max();
    MeshFacet& rFace = _rclMesh._aclFacetArray[ulFacetPos];

    for (unsigned short i = 0; i < 3; i++) {
        Base::Vector3f cBase(_rclMesh._aclPointArray[rFace._aulPoints[i]]);
        Base::Vector3f cEnd(_rclMesh._aclPointArray[rFace._aulPoints[(i + 1) % 3]]);
        Base::Vector3f cDir = cEnd - cBase;

        float fDist = rP1.DistanceToLine(cBase, cDir);
        if (fDist < fMinDist1) {
            fMinDist1 = fDist;
            iEdgeNo1 = i;
        }
        fDist = rP2.DistanceToLine(cBase, cDir);
        if (fDist < fMinDist2) {
            fMinDist2 = fDist;
            iEdgeNo2 = i;
        }
    }

    if (iEdgeNo1 == iEdgeNo2 || fMinDist1 >= 0.05F || fMinDist2 >= 0.05F) {
        return;  // no valid configuration
    }

    // make first point lying on the previous edge
    Base::Vector3f cP1 = rP1;
    Base::Vector3f cP2 = rP2;
    if ((iEdgeNo2 + 1) % 3 == iEdgeNo1) {
        std::swap(iEdgeNo1, iEdgeNo2);
        std::swap(cP1, cP2);
    }

    // insert new points
    PointIndex cntPts1 = this->GetOrAddIndex(cP1);
    PointIndex cntPts2 = this->GetOrAddIndex(cP2);
    FacetIndex cntFts = _rclMesh.CountFacets();

    unsigned short v0 = (iEdgeNo2 + 1) % 3;
    unsigned short v1 = iEdgeNo1;
    unsigned short v2 = iEdgeNo2;

    PointIndex p0 = rFace._aulPoints[v0];
    PointIndex p1 = rFace._aulPoints[v1];
    PointIndex p2 = rFace._aulPoints[v2];

    FacetIndex n0 = rFace._aulNeighbours[v0];
    FacetIndex n1 = rFace._aulNeighbours[v1];
    FacetIndex n2 = rFace._aulNeighbours[v2];

    // Modify and add facets
    //
    rFace._aulPoints[v0] = cntPts2;
    rFace._aulPoints[v1] = cntPts1;
    rFace._aulNeighbours[v0] = cntFts + 1;

    float dist1 = Base::DistanceP2(_rclMesh._aclPointArray[p0], cP1);
    float dist2 = Base::DistanceP2(_rclMesh._aclPointArray[p1], cP2);

    if (dist1 > dist2) {
        AddFacet(p0, p1, cntPts2, n0, cntFts + 1, n2);
        AddFacet(p1, cntPts1, cntPts2, n1, ulFacetPos, cntFts);
    }
    else {
        AddFacet(p0, p1, cntPts1, n0, n1, cntFts + 1);
        AddFacet(p0, cntPts1, cntPts2, cntFts, ulFacetPos, n2);
    }

    std::vector<FacetIndex> fixIndices;
    fixIndices.push_back(ulFacetPos);

    if (n0 != FACET_INDEX_MAX) {
        fixIndices.push_back(n0);
    }

    // split up the neighbour facets
    if (n1 != FACET_INDEX_MAX) {
        fixIndices.push_back(n1);
        MeshFacet& rN = _rclMesh._aclFacetArray[n1];
        for (FacetIndex nbIndex : rN._aulNeighbours) {
            fixIndices.push_back(nbIndex);
        }
        SplitFacet(n1, p1, p2, cntPts1);
    }

    if (n2 != FACET_INDEX_MAX) {
        fixIndices.push_back(n2);
        MeshFacet& rN = _rclMesh._aclFacetArray[n2];
        for (FacetIndex nbIndex : rN._aulNeighbours) {
            fixIndices.push_back(nbIndex);
        }
        SplitFacet(n2, p2, p0, cntPts2);
    }

    FacetIndex cntFts2 = _rclMesh.CountFacets();
    for (FacetIndex i = cntFts; i < cntFts2; i++) {
        fixIndices.push_back(i);
    }

    std::sort(fixIndices.begin(), fixIndices.end());
    fixIndices.erase(std::unique(fixIndices.begin(), fixIndices.end()), fixIndices.end());
    HarmonizeNeighbours(fixIndices);
}

void MeshTopoAlgorithm::SplitFacet(FacetIndex ulFacetPos, PointIndex P1, PointIndex P2, PointIndex Pn)
{
    MeshFacet& rFace = _rclMesh._aclFacetArray[ulFacetPos];
    unsigned short side = rFace.Side(P1, P2);
    if (side != std::numeric_limits<unsigned short>::max()) {
        PointIndex V1 = rFace._aulPoints[(side + 1) % 3];
        PointIndex V2 = rFace._aulPoints[(side + 2) % 3];
        FacetIndex size = _rclMesh._aclFacetArray.size();

        rFace._aulPoints[(side + 1) % 3] = Pn;
        FacetIndex N1 = rFace._aulNeighbours[(side + 1) % 3];
        if (N1 != FACET_INDEX_MAX) {
            _rclMesh._aclFacetArray[N1].ReplaceNeighbour(ulFacetPos, size);
        }

        rFace._aulNeighbours[(side + 1) % 3] = ulFacetPos;
        AddFacet(Pn, V1, V2);
    }
}

void MeshTopoAlgorithm::AddFacet(PointIndex P1, PointIndex P2, PointIndex P3)
{
    MeshFacet facet;
    facet._aulPoints[0] = P1;
    facet._aulPoints[1] = P2;
    facet._aulPoints[2] = P3;

    _rclMesh._aclFacetArray.push_back(facet);
}

void MeshTopoAlgorithm::AddFacet(
    PointIndex P1,
    PointIndex P2,
    PointIndex P3,
    FacetIndex N1,
    FacetIndex N2,
    FacetIndex N3
)
{
    MeshFacet facet;
    facet._aulPoints[0] = P1;
    facet._aulPoints[1] = P2;
    facet._aulPoints[2] = P3;
    facet._aulNeighbours[0] = N1;
    facet._aulNeighbours[1] = N2;
    facet._aulNeighbours[2] = N3;

    _rclMesh._aclFacetArray.push_back(facet);
}

void MeshTopoAlgorithm::HarmonizeNeighbours(const std::vector<FacetIndex>& ulFacets)
{
    for (FacetIndex it : ulFacets) {
        for (FacetIndex jt : ulFacets) {
            HarmonizeNeighbours(it, jt);
        }
    }
}

void MeshTopoAlgorithm::HarmonizeNeighbours(FacetIndex facet1, FacetIndex facet2)
{
    if (facet1 == facet2) {
        return;
    }

    MeshFacet& rFace1 = _rclMesh._aclFacetArray[facet1];
    MeshFacet& rFace2 = _rclMesh._aclFacetArray[facet2];

    unsigned short side = rFace1.Side(rFace2);
    if (side != std::numeric_limits<unsigned short>::max()) {
        rFace1._aulNeighbours[side] = facet2;
    }

    side = rFace2.Side(rFace1);
    if (side != std::numeric_limits<unsigned short>::max()) {
        rFace2._aulNeighbours[side] = facet1;
    }
}

void MeshTopoAlgorithm::SplitNeighbourFacet(
    FacetIndex ulFacetPos,
    unsigned short uFSide,
    const Base::Vector3f& rPoint
)
{
    MeshFacet& rclF = _rclMesh._aclFacetArray[ulFacetPos];

    FacetIndex ulNeighbour = rclF._aulNeighbours[uFSide];
    MeshFacet& rclN = _rclMesh._aclFacetArray[ulNeighbour];

    unsigned short uNSide = rclN.Side(rclF);

    PointIndex uPtInd = this->GetOrAddIndex(rPoint);
    FacetIndex ulSize = _rclMesh._aclFacetArray.size();

    // adjust the neighbourhood
    if (rclN._aulNeighbours[(uNSide + 1) % 3] != FACET_INDEX_MAX) {
        _rclMesh._aclFacetArray[rclN._aulNeighbours[(uNSide + 1) % 3]].ReplaceNeighbour(
            ulNeighbour,
            ulSize
        );
    }

    MeshFacet cNew;
    cNew._aulPoints[0] = uPtInd;
    cNew._aulPoints[1] = rclN._aulPoints[(uNSide + 1) % 3];
    cNew._aulPoints[2] = rclN._aulPoints[(uNSide + 2) % 3];
    cNew._aulNeighbours[0] = ulFacetPos;
    cNew._aulNeighbours[1] = rclN._aulNeighbours[(uNSide + 1) % 3];
    cNew._aulNeighbours[2] = ulNeighbour;

    // adjust the facet
    rclN._aulPoints[(uNSide + 1) % 3] = uPtInd;
    rclN._aulNeighbours[(uNSide + 1) % 3] = ulSize;

    // insert new facet
    _rclMesh._aclFacetArray.push_back(cNew);
}

#if 0
  // create 3 new facets
  MeshGeomFacet clFacet;

  // facet [P1, Ei+1, P2]
  clFacet._aclPoints[0] = cP1;
  clFacet._aclPoints[1] = _rclMesh._aclPointArray[rFace._aulPoints[(iEdgeNo1+1)%3]];
  clFacet._aclPoints[2] = cP2;
  clFacet.CalcNormal();
  _aclNewFacets.push_back(clFacet);
  // facet [P2, Ei+2, Ei]
  clFacet._aclPoints[0] = cP2;
  clFacet._aclPoints[1] = _rclMesh._aclPointArray[rFace._aulPoints[(iEdgeNo1+2)%3]];
  clFacet._aclPoints[2] = _rclMesh._aclPointArray[rFace._aulPoints[iEdgeNo1]];
  clFacet.CalcNormal();
  _aclNewFacets.push_back(clFacet);
  // facet [P2, Ei, P1]
  clFacet._aclPoints[0] = cP2;
  clFacet._aclPoints[1] = _rclMesh._aclPointArray[rFace._aulPoints[iEdgeNo1]];
  clFacet._aclPoints[2] = cP1;
  clFacet.CalcNormal();
  _aclNewFacets.push_back(clFacet);
#endif

bool MeshTopoAlgorithm::RemoveDegeneratedFacet(FacetIndex index)
{
    if (index >= _rclMesh._aclFacetArray.size()) {
        return false;
    }
    MeshFacet& rFace = _rclMesh._aclFacetArray[index];

    // coincident corners (either topological or geometrical)
    for (int i = 0; i < 3; i++) {
        const MeshPoint& rE0 = _rclMesh._aclPointArray[rFace._aulPoints[i]];
        const MeshPoint& rE1 = _rclMesh._aclPointArray[rFace._aulPoints[(i + 1) % 3]];
        if (rE0 == rE1) {
            FacetIndex uN1 = rFace._aulNeighbours[(i + 1) % 3];
            FacetIndex uN2 = rFace._aulNeighbours[(i + 2) % 3];
            if (uN2 != FACET_INDEX_MAX) {
                _rclMesh._aclFacetArray[uN2].ReplaceNeighbour(index, uN1);
            }
            if (uN1 != FACET_INDEX_MAX) {
                _rclMesh._aclFacetArray[uN1].ReplaceNeighbour(index, uN2);
            }

            // isolate the face and remove it
            rFace._aulNeighbours[0] = FACET_INDEX_MAX;
            rFace._aulNeighbours[1] = FACET_INDEX_MAX;
            rFace._aulNeighbours[2] = FACET_INDEX_MAX;
            _rclMesh.DeleteFacet(index);
            return true;
        }
    }

    // We have a facet of the form
    // P0 +----+------+P2
    //         P1
    for (int j = 0; j < 3; j++) {
        Base::Vector3f cVec1 = _rclMesh._aclPointArray[rFace._aulPoints[(j + 1) % 3]]
            - _rclMesh._aclPointArray[rFace._aulPoints[j]];
        Base::Vector3f cVec2 = _rclMesh._aclPointArray[rFace._aulPoints[(j + 2) % 3]]
            - _rclMesh._aclPointArray[rFace._aulPoints[j]];

        // adjust the neighbourhoods and point indices
        if (cVec1 * cVec2 < 0.0F) {
            FacetIndex uN1 = rFace._aulNeighbours[(j + 1) % 3];
            if (uN1 != FACET_INDEX_MAX) {
                // get the neighbour and common edge side
                MeshFacet& rNb = _rclMesh._aclFacetArray[uN1];
                unsigned short side = rNb.Side(index);

                // bend the point indices
                rFace._aulPoints[(j + 2) % 3] = rNb._aulPoints[(side + 2) % 3];
                rNb._aulPoints[(side + 1) % 3] = rFace._aulPoints[j];

                // set correct neighbourhood
                FacetIndex uN2 = rFace._aulNeighbours[(j + 2) % 3];
                rNb._aulNeighbours[side] = uN2;
                if (uN2 != FACET_INDEX_MAX) {
                    _rclMesh._aclFacetArray[uN2].ReplaceNeighbour(index, uN1);
                }
                FacetIndex uN3 = rNb._aulNeighbours[(side + 1) % 3];
                rFace._aulNeighbours[(j + 1) % 3] = uN3;
                if (uN3 != FACET_INDEX_MAX) {
                    _rclMesh._aclFacetArray[uN3].ReplaceNeighbour(uN1, index);
                }
                rNb._aulNeighbours[(side + 1) % 3] = index;
                rFace._aulNeighbours[(j + 2) % 3] = uN1;
            }
            else {
                _rclMesh.DeleteFacet(index);
            }

            return true;
        }
    }

    return false;
}

bool MeshTopoAlgorithm::RemoveCorruptedFacet(FacetIndex index)
{
    if (index >= _rclMesh._aclFacetArray.size()) {
        return false;
    }
    MeshFacet& rFace = _rclMesh._aclFacetArray[index];

    // coincident corners (topological)
    for (int i = 0; i < 3; i++) {
        if (rFace._aulPoints[i] == rFace._aulPoints[(i + 1) % 3]) {
            FacetIndex uN1 = rFace._aulNeighbours[(i + 1) % 3];
            FacetIndex uN2 = rFace._aulNeighbours[(i + 2) % 3];
            if (uN2 != FACET_INDEX_MAX) {
                _rclMesh._aclFacetArray[uN2].ReplaceNeighbour(index, uN1);
            }
            if (uN1 != FACET_INDEX_MAX) {
                _rclMesh._aclFacetArray[uN1].ReplaceNeighbour(index, uN2);
            }

            // isolate the face and remove it
            rFace._aulNeighbours[0] = FACET_INDEX_MAX;
            rFace._aulNeighbours[1] = FACET_INDEX_MAX;
            rFace._aulNeighbours[2] = FACET_INDEX_MAX;
            _rclMesh.DeleteFacet(index);
            return true;
        }
    }

    return false;
}

void MeshTopoAlgorithm::FillupHoles(
    unsigned long length,
    int level,
    AbstractPolygonTriangulator& cTria,
    std::list<std::vector<PointIndex>>& aFailed
)
{
    // get the mesh boundaries as an array of point indices
    std::list<std::vector<PointIndex>> aBorders, aFillBorders;
    MeshAlgorithm cAlgo(_rclMesh);
    cAlgo.GetMeshBorders(aBorders);

    // split boundary loops if needed
    cAlgo.SplitBoundaryLoops(aBorders);

    for (const auto& aBorder : aBorders) {
        if (aBorder.size() - 1 <= length) {  // ignore boundary with too many edges
            aFillBorders.push_back(aBorder);
        }
    }

    if (!aFillBorders.empty()) {
        FillupHoles(level, cTria, aFillBorders, aFailed);
    }
}

void MeshTopoAlgorithm::FillupHoles(
    int level,
    AbstractPolygonTriangulator& cTria,
    const std::list<std::vector<PointIndex>>& aBorders,
    std::list<std::vector<PointIndex>>& aFailed
)
{
    // get the facets to a point
    MeshRefPointToFacets cPt2Fac(_rclMesh);
    MeshAlgorithm cAlgo(_rclMesh);

    MeshFacetArray newFacets;
    MeshPointArray newPoints;
    unsigned long numberOfOldPoints = _rclMesh._aclPointArray.size();
    for (const auto& aBorder : aBorders) {
        MeshFacetArray cFacets;
        MeshPointArray cPoints;
        std::vector<PointIndex> bound = aBorder;
        if (cAlgo.FillupHole(bound, cTria, cFacets, cPoints, level, &cPt2Fac)) {
            if (bound.front() == bound.back()) {
                bound.pop_back();
            }
            // the triangulation may produce additional points which we must take into account when
            // appending to the mesh
            if (cPoints.size() > bound.size()) {
                unsigned long countBoundaryPoints = bound.size();
                unsigned long countDifference = cPoints.size() - countBoundaryPoints;
                MeshPointArray::_TIterator pt = cPoints.begin() + countBoundaryPoints;
                for (unsigned long i = 0; i < countDifference; i++, pt++) {
                    bound.push_back(numberOfOldPoints++);
                    newPoints.push_back(*pt);
                }
            }
            if (cTria.NeedsReindexing()) {
                for (auto& cFacet : cFacets) {
                    cFacet._aulPoints[0] = bound[cFacet._aulPoints[0]];
                    cFacet._aulPoints[1] = bound[cFacet._aulPoints[1]];
                    cFacet._aulPoints[2] = bound[cFacet._aulPoints[2]];
                    newFacets.push_back(cFacet);
                }
            }
            else {
                for (auto& cFacet : cFacets) {
                    newFacets.push_back(cFacet);
                }
            }
        }
        else {
            aFailed.push_back(aBorder);
        }
    }

    // insert new points and faces into the mesh structure
    _rclMesh._aclPointArray.insert(_rclMesh._aclPointArray.end(), newPoints.begin(), newPoints.end());
    for (const auto& newPoint : newPoints) {
        _rclMesh._clBoundBox.Add(newPoint);
    }
    if (!newFacets.empty()) {
        // Do some checks for invalid point indices
        MeshFacetArray addFacets;
        addFacets.reserve(newFacets.size());
        unsigned long ctPoints = _rclMesh.CountPoints();
        for (auto& newFacet : newFacets) {
            if (newFacet._aulPoints[0] >= ctPoints || newFacet._aulPoints[1] >= ctPoints
                || newFacet._aulPoints[2] >= ctPoints) {
                Base::Console().log(
                    "Ignore invalid face <%d, %d, %d> (%d vertices)\n",
                    newFacet._aulPoints[0],
                    newFacet._aulPoints[1],
                    newFacet._aulPoints[2],
                    ctPoints
                );
            }
            else {
                addFacets.push_back(newFacet);
            }
        }
        _rclMesh.AddFacets(addFacets, true);
    }
}

void MeshTopoAlgorithm::FindHoles(unsigned long length, std::list<std::vector<PointIndex>>& aBorders) const
{
    std::list<std::vector<PointIndex>> border;
    MeshAlgorithm cAlgo(_rclMesh);
    cAlgo.GetMeshBorders(border);
    for (const auto& it : border) {
        if (it.size() <= length) {
            aBorders.push_back(it);
        }
    }
}

void MeshTopoAlgorithm::FindComponents(unsigned long count, std::vector<FacetIndex>& findIndices)
{
    std::vector<std::vector<FacetIndex>> segments;
    MeshComponents comp(_rclMesh);
    comp.SearchForComponents(MeshComponents::OverEdge, segments);

    for (const auto& segment : segments) {
        if (segment.size() <= count) {
            findIndices.insert(findIndices.end(), segment.begin(), segment.end());
        }
    }
}

void MeshTopoAlgorithm::RemoveComponents(unsigned long count)
{
    std::vector<FacetIndex> removeFacets;
    FindComponents(count, removeFacets);
    if (!removeFacets.empty()) {
        _rclMesh.DeleteFacets(removeFacets);
    }
}

void MeshTopoAlgorithm::HarmonizeNormals()
{
    std::vector<FacetIndex> uIndices = MeshEvalOrientation(_rclMesh).GetIndices();
    for (FacetIndex index : uIndices) {
        _rclMesh._aclFacetArray[index].FlipNormal();
    }
}

void MeshTopoAlgorithm::FlipNormals()
{
    for (auto i = _rclMesh._aclFacetArray.begin(); i < _rclMesh._aclFacetArray.end(); ++i) {
        i->FlipNormal();
    }
}

// ---------------------------------------------------------------------------

/**
 * Some important formulas:
 *
 * Ne = 3Nv - Nb + 3B + 6(G-R)
 * Nt = 2Nv - Nb + 2B + 4(G-R)
 *
 * Ne <= 3Nv + 6(G-R)
 * Nt <= 2Nv + 4(G-R)
 *
 * Ne ~ 3Nv, Nv >> G, Nv >> R
 * Nt ~ 2Nv, Nv >> G, Nv >> R
 *
 * Ne = #Edges
 * Nt = #Facets
 * Nv = #Vertices
 * Nb = #Boundary vertices
 * B  = #Boundaries
 * G  = Genus (Number of holes)
 * R  = #components
 *
 * See also http://max-limper.de/publications/Euler/
 */

MeshComponents::MeshComponents(const MeshKernel& rclMesh)
    : _rclMesh(rclMesh)
{}

void MeshComponents::SearchForComponents(TMode tMode, std::vector<std::vector<FacetIndex>>& aclT) const
{
    // all facets
    std::vector<FacetIndex> aulAllFacets(_rclMesh.CountFacets());
    FacetIndex k = 0;
    for (FacetIndex& aulAllFacet : aulAllFacets) {
        aulAllFacet = k++;
    }

    SearchForComponents(tMode, aulAllFacets, aclT);
}

void MeshComponents::SearchForComponents(
    TMode tMode,
    const std::vector<FacetIndex>& aSegment,
    std::vector<std::vector<FacetIndex>>& aclT
) const
{
    FacetIndex ulStartFacet {};

    if (_rclMesh.CountFacets() == 0) {
        return;
    }

    // reset VISIT flags
    MeshAlgorithm cAlgo(_rclMesh);
    cAlgo.SetFacetFlag(MeshFacet::VISIT);
    cAlgo.ResetFacetsFlag(aSegment, MeshFacet::VISIT);

    const MeshFacetArray& rFAry = _rclMesh.GetFacets();
    MeshFacetArray::_TConstIterator iTri = rFAry.begin();
    MeshFacetArray::_TConstIterator iBeg = rFAry.begin();
    MeshFacetArray::_TConstIterator iEnd = rFAry.end();

    // start from the first not visited facet
    unsigned long ulVisited = cAlgo.CountFacetFlag(MeshFacet::VISIT);
    MeshIsNotFlag<MeshFacet> flag;
    iTri = std::find_if(iTri, iEnd, [flag](const MeshFacet& f) { return flag(f, MeshFacet::VISIT); });
    ulStartFacet = iTri - iBeg;

    // visitor
    std::vector<FacetIndex> aclComponent;
    std::vector<std::vector<FacetIndex>> aclConnectComp;
    MeshTopFacetVisitor clFVisitor(aclComponent);

    while (ulStartFacet != FACET_INDEX_MAX) {
        // collect all facets of a component
        aclComponent.clear();
        if (tMode == OverEdge) {
            ulVisited += _rclMesh.VisitNeighbourFacets(clFVisitor, ulStartFacet);
        }
        else if (tMode == OverPoint) {
            ulVisited += _rclMesh.VisitNeighbourFacetsOverCorners(clFVisitor, ulStartFacet);
        }

        // get also start facet
        aclComponent.push_back(ulStartFacet);
        aclConnectComp.push_back(aclComponent);

        // if the mesh consists of several topologic independent components
        // We can search from position 'iTri' on because all elements _before_ are already visited
        // what we know from the previous iteration.
        iTri = std::find_if(iTri, iEnd, [flag](const MeshFacet& f) {
            return flag(f, MeshFacet::VISIT);
        });

        if (iTri < iEnd) {
            ulStartFacet = iTri - iBeg;
        }
        else {
            ulStartFacet = FACET_INDEX_MAX;
        }
    }

    // sort components by size (descending order)
    std::sort(aclConnectComp.begin(), aclConnectComp.end(), CNofFacetsCompare());
    aclT = aclConnectComp;
    boost::ignore_unused(ulVisited);
}
