From 949a5b25c2dbe83b2ba657b4d2bff284d9e16ed1 Mon Sep 17 00:00:00 2001
From: rinkk <karsten.rink@ufz.de>
Date: Fri, 28 May 2021 18:01:49 +0200
Subject: [PATCH] [FileIO] Adding EarClipping triangulation ... again

---
 Applications/FileIO/Legacy/createSurface.cpp |  52 ++++
 Applications/FileIO/Legacy/createSurface.h   |  10 +-
 GeoLib/EarClippingTriangulation.cpp          | 306 +++++++++++++++++++
 GeoLib/EarClippingTriangulation.h            |  66 ++++
 GeoLib/Polygon.cpp                           |  14 +
 GeoLib/Polygon.h                             |   3 +
 6 files changed, 449 insertions(+), 2 deletions(-)
 create mode 100644 GeoLib/EarClippingTriangulation.cpp
 create mode 100644 GeoLib/EarClippingTriangulation.h

diff --git a/Applications/FileIO/Legacy/createSurface.cpp b/Applications/FileIO/Legacy/createSurface.cpp
index bd956f30260..c0d6c6b62b5 100644
--- a/Applications/FileIO/Legacy/createSurface.cpp
+++ b/Applications/FileIO/Legacy/createSurface.cpp
@@ -17,11 +17,13 @@
 #include "Applications/FileIO/Gmsh/GMSHInterface.h"
 #include "BaseLib/Logging.h"
 #include "BaseLib/StringTools.h"
+#include "GeoLib/EarClippingTriangulation.h"
 #include "GeoLib/GEOObjects.h"
 #include "GeoLib/Point.h"
 #include "GeoLib/Polygon.h"
 #include "GeoLib/Polyline.h"
 #include "GeoLib/Surface.h"
+#include "GeoLib/Triangle.h"
 #include "MeshLib/IO/readMeshFromFile.h"
 #include "MeshLib/Mesh.h"
 #include "MeshLib/convertMeshToGeo.h"
@@ -122,4 +124,54 @@ bool createSurface(GeoLib::Polyline const& ply,
     return true;
 }
 
+GeoLib::Surface* createSurfaceWithEarClipping(GeoLib::Polyline const& line)
+{
+    if (!line.isClosed())
+    {
+        WARN("Error in Surface::createSurface() - Polyline is not closed.");
+        return nullptr;
+    }
+
+    if (line.getNumberOfPoints() <= 2)
+    {
+        WARN(
+            "Error in Surface::createSurface() - Polyline consists of less "
+            "than three points and therefore cannot be triangulated.");
+        return nullptr;
+    }
+
+    // create empty surface
+    GeoLib::Surface* sfc(new GeoLib::Surface(line.getPointsVec()));
+    GeoLib::Polygon* polygon(new GeoLib::Polygon(line));
+    std::list<GeoLib::Polygon*> const& list_of_simple_polygons =
+        polygon->computeListOfSimplePolygons();
+
+    for (std::list<GeoLib::Polygon*>::const_iterator simple_polygon_it(
+             list_of_simple_polygons.begin());
+         simple_polygon_it != list_of_simple_polygons.end();
+         ++simple_polygon_it)
+    {
+        std::list<GeoLib::Triangle> triangles;
+        GeoLib::EarClippingTriangulation(*simple_polygon_it, triangles);
+
+        // add Triangles to Surface
+        std::list<GeoLib::Triangle>::const_iterator it(triangles.begin());
+        while (it != triangles.end())
+        {
+            sfc->addTriangle((*it)[0], (*it)[1], (*it)[2]);
+            ++it;
+        }
+    }
+    // delete polygon;
+    if (sfc->getNumberOfTriangles() == 0)
+    {
+        WARN(
+            "Surface::createSurface(): Triangulation does not contain any "
+            "triangle.");
+        delete sfc;
+        return nullptr;
+    }
+    return sfc;
+}
+
 }  // namespace FileIO
diff --git a/Applications/FileIO/Legacy/createSurface.h b/Applications/FileIO/Legacy/createSurface.h
index 24a0347cd45..8b3fd65c8e6 100644
--- a/Applications/FileIO/Legacy/createSurface.h
+++ b/Applications/FileIO/Legacy/createSurface.h
@@ -15,12 +15,13 @@
 namespace GeoLib
 {
 class Polyline;
+class Surface;
 class GEOObjects;
-}
+}  // namespace GeoLib
 
 namespace FileIO
 {
-/// Creates a plane surface from the given polyline. The polyline have to be
+/// Creates a plane surface from the given polyline. The polyline has to be
 /// closed, i.e. the first and the last point have to be the identical. The
 /// triangulation of the polyline is done by the finite element meshing tool
 /// Gmsh. Finally, the resulting mesh is converted into a GeoLib::Surface which
@@ -30,4 +31,9 @@ bool createSurface(GeoLib::Polyline const& ply,
                    GeoLib::GEOObjects& geometries,
                    std::string const& geometry_name,
                    std::string const& gmsh_binary);
+
+/// Creates a plane surface from the given polyline. The polyline has to be
+/// closed, i.e. the first and the last point have to be the identical. The
+/// triangulation of the polyline is done via a simple ear clipping algorithm.
+GeoLib::Surface* createSurfaceWithEarClipping(GeoLib::Polyline const& line);
 }  // namespace FileIO
diff --git a/GeoLib/EarClippingTriangulation.cpp b/GeoLib/EarClippingTriangulation.cpp
new file mode 100644
index 00000000000..3b1bdcf1007
--- /dev/null
+++ b/GeoLib/EarClippingTriangulation.cpp
@@ -0,0 +1,306 @@
+/**
+ * \file
+ * \author Thomas Fischer
+ * \date   2011-02-23
+ * \brief  Implementation of the EarClippingTriangulation class.
+ *
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "EarClippingTriangulation.h"
+
+//#include "BaseLib/uniqueInsert.h"
+
+#include "MathLib/GeometricBasics.h"
+
+#include "Polygon.h"
+#include "Triangle.h"
+#include "Point.h"
+
+template <typename Container>
+void uniquePushBack(Container& container,
+                    typename Container::value_type const& element)
+{
+    if (std::find(container.begin(), container.end(), element) ==
+        container.end())
+        container.push_back(element);
+}
+
+namespace GeoLib
+{
+EarClippingTriangulation::EarClippingTriangulation(const GeoLib::Polygon* polygon,
+        std::list<GeoLib::Triangle> &triangles, bool rot)
+{
+    copyPolygonPoints (polygon);
+
+    if (rot) {
+        rotatePointsToXY (_pnts);
+        ensureCWOrientation ();
+    }
+
+    initVertexList ();
+    initLists ();
+    clipEars ();
+
+    std::vector<GeoLib::Point*> const& ref_pnts_vec (polygon->getPointsVec());
+    std::list<GeoLib::Triangle>::const_iterator it (_triangles.begin());
+    if (_original_orient == GeoLib::CW) {
+        while (it != _triangles.end()) {
+            const std::size_t i0 (polygon->getPointID ((*it)[0]));
+            const std::size_t i1 (polygon->getPointID ((*it)[1]));
+            const std::size_t i2 (polygon->getPointID ((*it)[2]));
+            triangles.push_back (GeoLib::Triangle (ref_pnts_vec, i0, i1, i2));
+            ++it;
+        }
+    } else {
+        std::size_t n_pnts (_pnts.size() - 1);
+        while (it != _triangles.end()) {
+            const std::size_t i0 (polygon->getPointID (n_pnts - (*it)[0]));
+            const std::size_t i1 (polygon->getPointID (n_pnts - (*it)[1]));
+            const std::size_t i2 (polygon->getPointID (n_pnts - (*it)[2]));
+            triangles.push_back (GeoLib::Triangle (ref_pnts_vec, i0, i1, i2));
+            ++it;
+        }
+    }
+}
+
+EarClippingTriangulation::~EarClippingTriangulation()
+{
+    const std::size_t n_pnts (_pnts.size());
+    for (std::size_t k(0); k<n_pnts; k++) {
+        delete _pnts[k];
+    }
+}
+
+void EarClippingTriangulation::copyPolygonPoints (const GeoLib::Polygon* polygon)
+{
+    // copy points - last point is identical to the first
+    std::size_t n_pnts (polygon->getNumberOfPoints() - 1);
+    for (std::size_t k(0); k < n_pnts; k++) {
+        _pnts.push_back (new GeoLib::Point (*(polygon->getPoint(k))));
+    }
+}
+
+void EarClippingTriangulation::ensureCWOrientation ()
+{
+    std::size_t n_pnts (_pnts.size());
+    // get the left most upper point
+    std::size_t min_x_max_y_idx (0); // for orientation check
+    for (std::size_t k(0); k<n_pnts; k++) {
+        if ((*(_pnts[k]))[0] <= (*(_pnts[min_x_max_y_idx]))[0]) {
+            if ((*(_pnts[k]))[0] < (*(_pnts[min_x_max_y_idx]))[0]) {
+                min_x_max_y_idx = k;
+            } else {
+                if ((*(_pnts[k]))[1] > (*(_pnts[min_x_max_y_idx]))[1]) {
+                    min_x_max_y_idx = k;
+                }
+            }
+        }
+    }
+    // determine orientation
+    if (0 < min_x_max_y_idx && min_x_max_y_idx < n_pnts-1) {
+        _original_orient = GeoLib::getOrientation (
+            *_pnts[min_x_max_y_idx-1], *_pnts[min_x_max_y_idx], *_pnts[min_x_max_y_idx+1]);
+    } else {
+        if (0 == min_x_max_y_idx) {
+            _original_orient = GeoLib::getOrientation (*_pnts[n_pnts-1], *_pnts[0], *_pnts[1]);
+        } else {
+            _original_orient = GeoLib::getOrientation (*_pnts[n_pnts-2], *_pnts[n_pnts-1], *_pnts[0]);
+        }
+    }
+    if (_original_orient == GeoLib::CCW) {
+        // switch orientation
+        for (std::size_t k(0); k<n_pnts/2; k++) {
+            std::swap (_pnts[k], _pnts[n_pnts-1-k]);
+        }
+    }
+}
+
+bool EarClippingTriangulation::isEar(std::size_t v0, std::size_t v1, std::size_t v2) const
+{
+    for (std::list<std::size_t>::const_iterator it (_vertex_list.begin ());
+        it != _vertex_list.end(); ++it) {
+        if (*it != v0 && *it != v1 && *it != v2) {
+            if (MathLib::isPointInTriangle (*_pnts[*it], *_pnts[v0], *_pnts[v1], *_pnts[v2])) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+void EarClippingTriangulation::initVertexList ()
+{
+    std::size_t n_pnts (_pnts.size());
+    for (std::size_t k(0); k<n_pnts; k++)
+        _vertex_list.push_back (k);
+}
+
+void EarClippingTriangulation::initLists ()
+{
+    // go through points checking ccw, cw or collinear order and identifying ears
+    std::list<std::size_t>::iterator it (_vertex_list.begin()), prev(_vertex_list.end()), next;
+    --prev;
+    next = it;
+    ++next;
+    GeoLib::Orientation orientation;
+    bool first_run(true); // saves special handling of the last case with identical code
+    while (_vertex_list.size() >= 3 && first_run) {
+        if (next == _vertex_list.end()) {
+            first_run = false;
+            next = _vertex_list.begin();
+        }
+        orientation  = getOrientation (*_pnts[*prev], *_pnts[*it], *_pnts[*next]);
+        if (orientation == GeoLib::COLLINEAR) {
+            WARN(
+                "EarClippingTriangulation::initLists(): collinear points "
+                "({:f}, {:f}, {:f}), ({:f}, {:f}, {:f}), ({:f}, {:f}, {:f})",
+                    (*_pnts[*prev])[0], (*_pnts[*prev])[1], (*_pnts[*prev])[2],
+                    (*_pnts[*it])[0], (*_pnts[*it])[1], (*_pnts[*it])[2],
+                    (*_pnts[*next])[0], (*_pnts[*next])[1], (*_pnts[*next])[2]);
+            it = _vertex_list.erase (it);
+            ++next;
+        } else {
+            if (orientation == GeoLib::CW) {
+                _convex_vertex_list.push_back (*it);
+                if (isEar (*prev, *it, *next))
+                    _ear_list.push_back (*it);
+            }
+            prev = it;
+            it = next;
+            ++next;
+        }
+    }
+}
+
+void EarClippingTriangulation::clipEars()
+{
+    std::list<std::size_t>::iterator it, prev, next;
+    // *** clip an ear
+    while (_vertex_list.size() > 3) {
+        // pop ear from list
+        std::size_t ear = _ear_list.front();
+        _ear_list.pop_front();
+        // remove ear tip from _convex_vertex_list
+        _convex_vertex_list.remove(ear);
+
+        // remove ear from vertex_list, apply changes to _ear_list, _convex_vertex_list
+        bool nfound(true);
+        it = _vertex_list.begin();
+        prev = _vertex_list.end();
+        --prev;
+        while (nfound && it != _vertex_list.end()) {
+            if (*it == ear) {
+                nfound = false;
+                it = _vertex_list.erase(it); // remove ear tip
+                next = it;
+                if (next == _vertex_list.end()) {
+                    next = _vertex_list.begin();
+                    prev = _vertex_list.end();
+                    --prev;
+                }
+                // add triangle
+                _triangles.push_back(GeoLib::Triangle(_pnts, *prev, *next, ear));
+
+                // check the orientation of prevprev, prev, next
+                std::list<std::size_t>::iterator prevprev;
+                if (prev == _vertex_list.begin()) {
+                    prevprev = _vertex_list.end();
+                } else {
+                    prevprev = prev;
+                }
+                --prevprev;
+
+                // apply changes to _convex_vertex_list and _ear_list looking "backward"
+                GeoLib::Orientation orientation = GeoLib::getOrientation(*_pnts[*prevprev], *_pnts[*prev],
+                        *_pnts[*next]);
+                if (orientation == GeoLib::CW) {
+                    uniquePushBack(_convex_vertex_list, *prev);
+                    // prev is convex
+                    if (isEar(*prevprev, *prev, *next)) {
+                        // prev is an ear tip
+                        uniquePushBack(_ear_list, *prev);
+                    } else {
+                        // if necessary remove prev
+                        _ear_list.remove(*prev);
+                    }
+                } else {
+                    // prev is not convex => reflex or collinear
+                    _convex_vertex_list.remove(*prev);
+                    _ear_list.remove(*prev);
+                    if (orientation == GeoLib::COLLINEAR) {
+                        prev = _vertex_list.erase(prev);
+                        if (prev == _vertex_list.begin()) {
+                            prev = _vertex_list.end();
+                            --prev;
+                        } else {
+                            --prev;
+                        }
+                    }
+                }
+
+                // check the orientation of prev, next, nextnext
+                std::list<std::size_t>::iterator nextnext,
+                        help_it(_vertex_list.end());
+                --help_it;
+                if (next == help_it) {
+                    nextnext = _vertex_list.begin();
+                } else {
+                    nextnext = next;
+                    ++nextnext;
+                }
+
+                // apply changes to _convex_vertex_list and _ear_list looking "forward"
+                orientation = getOrientation(*_pnts[*prev], *_pnts[*next],
+                        *_pnts[*nextnext]);
+                if (orientation == GeoLib::CW) {
+                    uniquePushBack(_convex_vertex_list, *next);
+                    // next is convex
+                    if (isEar(*prev, *next, *nextnext)) {
+                        // next is an ear tip
+                        uniquePushBack(_ear_list, *next);
+                    } else {
+                        // if necessary remove *next
+                        _ear_list.remove(*next);
+                    }
+                } else {
+                    // next is not convex => reflex or collinear
+                    _convex_vertex_list.remove(*next);
+                    _ear_list.remove(*next);
+                    if (orientation == GeoLib::COLLINEAR) {
+                        next = _vertex_list.erase(next);
+                        if (next == _vertex_list.end())
+                            next = _vertex_list.begin();
+                    }
+                }
+            } else {
+                prev = it;
+                ++it;
+            }
+        }
+
+    }
+
+    // add last triangle
+    next = _vertex_list.begin();
+    prev = next;
+    ++next;
+    if (next == _vertex_list.end())
+        return;
+    it = next;
+    ++next;
+    if (next == _vertex_list.end())
+        return;
+
+    if (getOrientation(*_pnts[*prev], *_pnts[*it], *_pnts[*next]) == GeoLib::CCW)
+        _triangles.push_back(GeoLib::Triangle(_pnts, *prev, *it, *next));
+    else
+        _triangles.push_back(GeoLib::Triangle(_pnts, *prev, *next, *it));
+}
+
+} // end namespace GeoLib
diff --git a/GeoLib/EarClippingTriangulation.h b/GeoLib/EarClippingTriangulation.h
new file mode 100644
index 00000000000..4eec2a09ba3
--- /dev/null
+++ b/GeoLib/EarClippingTriangulation.h
@@ -0,0 +1,66 @@
+/**
+ * \file
+ * \author Thomas Fischer
+ * \date   2011-02-23
+ * \brief  Definition of the EarClippingTriangulation class.
+ *
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef EARCLIPPINGTRIANGULATION_H_
+#define EARCLIPPINGTRIANGULATION_H_
+
+#include <list>
+#include <vector>
+
+#include "AnalyticalGeometry.h"
+
+namespace GeoLib
+{
+
+class Polygon;
+class Triangle;
+
+class EarClippingTriangulation
+{
+public:
+    EarClippingTriangulation(const GeoLib::Polygon* ply,
+                             std::list<GeoLib::Triangle> &triangles,
+                             bool rot = true);
+    virtual ~EarClippingTriangulation();
+private:
+    /**
+     * copies the points of the polygon to the vector _pnts
+     */
+    inline void copyPolygonPoints (const GeoLib::Polygon* polygon);
+    inline void ensureCWOrientation ();
+
+    inline bool isEar(std::size_t v0, std::size_t v1, std::size_t v2) const;
+
+    inline void initVertexList ();
+    inline void initLists ();
+    inline void clipEars ();
+
+    /**
+     * a copy of the polygon points
+     */
+    std::vector<GeoLib::Point*> _pnts;
+    std::list<std::size_t> _vertex_list;
+    std::list<std::size_t> _convex_vertex_list;
+    std::list<std::size_t> _ear_list;
+
+    /**
+     * triangles of the triangulation (maybe in the wrong orientation)
+     */
+    std::list<GeoLib::Triangle> _triangles;
+
+    GeoLib::Orientation _original_orient;
+};
+} // end namespace GeoLib
+
+#endif /* EARCLIPPINGTRIANGULATION_H_ */
diff --git a/GeoLib/Polygon.cpp b/GeoLib/Polygon.cpp
index c74b550fc0f..dbcdaf8043a 100644
--- a/GeoLib/Polygon.cpp
+++ b/GeoLib/Polygon.cpp
@@ -615,4 +615,18 @@ bool operator==(Polygon const& lhs, Polygon const& rhs)
     return false;
 }
 
+std::list<Polygon*> const& Polygon::computeListOfSimplePolygons()
+{
+    splitPolygonAtPoint(_simple_polygon_list.begin());
+    splitPolygonAtIntersection(_simple_polygon_list.begin());
+
+    for (std::list<GeoLib::Polygon*>::iterator it(_simple_polygon_list.begin());
+         it != _simple_polygon_list.end();
+         ++it)
+    {
+        (*it)->initialise();
+    }
+    return _simple_polygon_list;
+}
+
 }  // end namespace GeoLib
diff --git a/GeoLib/Polygon.h b/GeoLib/Polygon.h
index f0a5d8b11e3..09f4ad3bc8e 100644
--- a/GeoLib/Polygon.h
+++ b/GeoLib/Polygon.h
@@ -100,6 +100,9 @@ public:
                                              GeoLib::Point& intersection_pnt,
                                              std::size_t& seg_num) const;
 
+    /// Subdivides a self-intersecting polygon into a list of non-intersecting shapes.
+    std::list<Polygon*> const& computeListOfSimplePolygons();
+
     friend bool operator==(Polygon const& lhs, Polygon const& rhs);
 private:
     /**
-- 
GitLab