Skip to content

Commit

Permalink
Fix a ton of robustness issues (parity with earcut v2.2.0) (#76)
Browse files Browse the repository at this point in the history
* fix a ton of robustness issues

* add a missing dep to hopefully fix travis

* upgrade to clang-6

* xenial

* update readme [skip ci]
  • Loading branch information
mourner authored Sep 18, 2019
1 parent 6e4db47 commit 0d0897a
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 22 deletions.
8 changes: 3 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,13 @@ matrix:
- BUILDTYPE=Debug
- MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0"
compiler: clang
dist: xenial
addons:
apt:
sources:
- llvm-toolchain-trusty-5.0
packages:
- *shared_linux_packages
- clang-5.0

# clang3.8 release
- os: linux
env:
Expand Down Expand Up @@ -115,10 +114,9 @@ matrix:
osx_image: xcode8
env: BUILDTYPE=Release
compiler: gcc

# osx clang
- os: osx
osx_image: xcode8
env: BUILDTYPE=Release
compiler: clang

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,4 @@ Import the project from https://github.com/mapbox/earcut.hpp.git and you should

## Status

This is currently based on [earcut 2.1.5](https://github.com/mapbox/earcut#215-feb-5-2019).
This is currently based on [earcut 2.2.0](https://github.com/mapbox/earcut#220-sep-18-2019).
64 changes: 51 additions & 13 deletions include/mapbox/earcut.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Earcut {
template <typename Polygon> Node* eliminateHoles(const Polygon& points, Node* outerNode);
void eliminateHole(Node* hole, Node* outerNode);
Node* findHoleBridge(Node* hole, Node* outerNode);
bool sectorContainsSector(const Node* m, const Node* p);
void indexCurve(Node* start);
Node* sortLinked(Node* list);
int32_t zOrder(const double x_, const double y_);
Expand All @@ -74,6 +75,8 @@ class Earcut {
double area(const Node* p, const Node* q, const Node* r) const;
bool equals(const Node* p1, const Node* p2);
bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2);
bool onSegment(const Node* p, const Node* q, const Node* r);
int sign(double val);
bool intersectsPolygon(const Node* a, const Node* b);
bool locallyInside(const Node* a, const Node* b);
bool middleInside(const Node* a, const Node* b);
Expand Down Expand Up @@ -289,7 +292,7 @@ void Earcut<N>::earcutLinked(Node* ear, int pass) {

// if this didn't work, try curing all small self-intersections locally
else if (pass == 1) {
ear = cureLocalIntersections(ear);
ear = cureLocalIntersections(filterPoints(ear));
earcutLinked(ear, 2);

// as a last resort, try splitting the remaining polygon into two
Expand Down Expand Up @@ -386,7 +389,7 @@ Earcut<N>::cureLocalIntersections(Node* start) {
p = p->next;
} while (p != start);

return p;
return filterPoints(p);
}

// try splitting polygon into two and triangulate them independently
Expand Down Expand Up @@ -482,7 +485,7 @@ Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {

if (!m) return 0;

if (hx == qx) return m->prev;
if (hx == qx) return m; // hole touches outer segment; pick leftmost endpoint

// look for points inside the triangle of hole Vertex, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
Expand All @@ -492,28 +495,38 @@ Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {
double tanMin = std::numeric_limits<double>::infinity();
double tanCur = 0;

p = m->next;
p = m;
double mx = m->x;
double my = m->y;

while (p != stop) {
do {
if (hx >= p->x && p->x >= mx && hx != p->x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) {

tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential

if ((tanCur < tanMin || (tanCur == tanMin && p->x > m->x)) && locallyInside(p, hole)) {
if (locallyInside(p, hole) &&
(tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
m = p;
tanMin = tanCur;
}
}

p = p->next;
}
} while (p != stop);

return m;
}

// whether sector in vertex m contains sector in vertex p in the same coordinates
template <typename N>
bool Earcut<N>::sectorContainsSector(const Node* m, const Node* p) {
return (
(area(m->prev, m, p->prev) < 0 || area(p->prev, m, m->next) < 0) &&
(area(m->prev, m, p->next) < 0 || area(p->next, m, m->next) < 0)
);
}

// interlink polygon nodes in z-order
template <typename N>
void Earcut<N>::indexCurve(Node* start) {
Expand Down Expand Up @@ -648,8 +661,10 @@ bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by, doub
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
template <typename N>
bool Earcut<N>::isValidDiagonal(Node* a, Node* b) {
return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges
((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
(area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors
(equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case
}

// signed area of a triangle
Expand All @@ -667,10 +682,33 @@ bool Earcut<N>::equals(const Node* p1, const Node* p2) {
// check if two segments intersect
template <typename N>
bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) {
if ((equals(p1, q1) && equals(p2, q2)) ||
(equals(p1, q2) && equals(p2, q1))) return true;
return (area(p1, q1, p2) > 0) != (area(p1, q1, q2) > 0) &&
(area(p2, q2, p1) > 0) != (area(p2, q2, q1) > 0);
int o1 = sign(area(p1, q1, p2));
int o2 = sign(area(p1, q1, q2));
int o3 = sign(area(p2, q2, p1));
int o4 = sign(area(p2, q2, q1));

if (o1 != o2 && o3 != o4) return true; // general case

if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2

return false;
}

// for collinear points p, q, r, check if point q lies on segment pr
template <typename N>
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
return q->x <= std::max<double>(p->x, r->x) &&
q->x >= std::min<double>(p->x, r->x) &&
q->y <= std::max<double>(p->y, r->y) &&
q->y >= std::min<double>(p->y, r->y);
}

template <typename N>
int Earcut<N>::sign(double val) {
return (0.0 < val) - (val < 0.0);
}

// check if a polygon diagonal intersects any polygon segments
Expand Down
3 changes: 2 additions & 1 deletion test/convert_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ fs.readdirSync(base).filter(function (name) {
"issue45": 0.094,
"empty_square": Infinity,
"issue83": Infinity,
"issue107": Infinity
"issue107": Infinity,
"issue119": 0.04
};
var expectedLibtessDeviation = libtessDeviationMap[id];
if (!expectedLibtessDeviation) expectedLibtessDeviation = 0.000001;
Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/boxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> boxy("boxy", 57, 1e-14, 0.000001, {
{{3432,2779},{3432,2794},{3450,2794},{3450,2825},{3413,2825},{3413,2856},{3395,2856},{3395,2871},{3377,2871},{3377,2856},{3359,2856},{3359,2840},{3341,2840},{3341,2871},{3322,2871},{3322,2887},{3249,2887},{3249,2871},{3268,2871},{3268,2840},{3304,2840},{3304,2825},{3322,2825},{3322,2810},{3304,2810},{3304,2794},{3322,2794},{3322,2779},{3341,2779},{3341,2733},{3359,2733},{3359,2687},{3395,2687},{3395,2702},{3432,2702},{3432,2717},{3450,2717},{3450,2733},{3486,2733},{3486,2748},{3468,2748},{3468,2763},{3450,2763},{3450,2779},{3432,2779}},
{{3359,2794},{3341,2794},{3341,2810},{3395,2810},{3395,2794},{3377,2794},{3377,2779},{3359,2779},{3359,2794}},
{{3432,2779},{3432,2748},{3413,2748},{3413,2779},{3432,2779}},
{{3377,2779},{3395,2779},{3395,2748},{3377,2748},{3377,2779}},
{{3377,2717},{3395,2717},{3395,2702},{3377,2702},{3377,2717}},
});

}
}
13 changes: 13 additions & 0 deletions test/fixtures/collinear_diagonal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> collinear_diagonal("collinear_diagonal", 14, 1e-14, 0.000001, {
{{3468,1913},{3486,1884},{3413,1869},{3322,1869},{3413,1854},{3413,1869},{3486,1869},{3486,1884},{3504,1884},{3504,1869},{3432,1869},{3432,1854},{3395,1854},{3432,1839},{3432,1854},{3450,1839},{3341,1839},{3341,1825},{3195,1825},{3341,1810},{3341,1825},{3450,1825},{3523,1854},{3523,1913}},
});

}
}
13 changes: 13 additions & 0 deletions test/fixtures/hourglass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> hourglass("hourglass", 2, 1e-14, 0.000001, {
{{7,18},{7,15},{5,15},{7,13},{7,15},{17,17}},
});

}
}
17 changes: 17 additions & 0 deletions test/fixtures/issue119.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> issue119("issue119", 18, 1e-14, 0.04, {
{{2,12},{2,20},{25,20},{25,12}},
{{7,18},{7,15},{5,15}},
{{19,18},{19,17},{17,17}},
{{19,17},{21,17},{19,16}},
{{7,15},{9,15},{7,13}},
});

}
}
20 changes: 20 additions & 0 deletions test/fixtures/rain.cpp

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions test/fixtures/touching2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> touching2("touching2", 8, 1e-14, 0.000001, {
{{120,2031},{92,2368},{94,2200},{33,2119},{42,2112},{53,2068}},
{{44,2104},{79,2132},{88,2115},{44,2104}},
});

}
}
15 changes: 15 additions & 0 deletions test/fixtures/touching3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file is auto-generated, manual changes will be lost if the code is regenerated.

#include "geometries.hpp"

namespace mapbox {
namespace fixtures {

static const Fixture<short> touching3("touching3", 15, 1e-14, 0.000001, {
{{1241,887},{1257,891},{1248,904},{1232,911},{1212,911},{1207,911},{1209,900},{1219,898},{1225,907},{1241,887}},
{{1212,902},{1212,911},{1219,909},{1212,902}},
{{1248,891},{1239,896},{1246,898},{1248,891}},
});

}
}
2 changes: 1 addition & 1 deletion test/fixtures/water_huge.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/fixtures/water_huge2.cpp

Large diffs are not rendered by default.

0 comments on commit 0d0897a

Please sign in to comment.