Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix bug #74 #91

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 74 additions & 25 deletions src/earcut.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function earcut(data, holeIndices, dim) {

var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
outerNode = linkedList(data, 0, outerLen, dim, true),
outerNode = linkedList(data, 0, outerLen, dim, true, null),
triangles = [];

if (!outerNode) return triangles;
Expand Down Expand Up @@ -42,19 +42,27 @@ function earcut(data, holeIndices, dim) {
return triangles;
}

// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
// Create a circular doubly linked list from polygon points in the specified winding order.
// holeId is used to identify which polygons(holes)the points belong to.
function linkedList(data, start, end, dim, clockwise, holeId) {
var i, last;

if (clockwise === (signedArea(data, start, end, dim) > 0)) {
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
for (i = start; i < end; i += dim) {
last = insertNode(i, data[i], data[i + 1], last);
last.holeId = holeId;
}
} else {
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
for (i = end - dim; i >= start; i -= dim) {
last = insertNode(i, data[i], data[i + 1], last);
last.holeId = holeId;
}
}

if (last && equals(last, last.next)) {
removeNode(last);
last = last.next;
last.holeId = holeId;
}

return last;
Expand All @@ -70,12 +78,39 @@ function filterPoints(start, end) {
do {
again = false;

if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
var prevHole = p.prev.holeId;
var currentHole = p.holeId;
var nextHole = p.next.holeId;

var toRemove = false;

if (!p.steiner) {
if (equals(p, p.next) || equals(p.prev, p)) {
toRemove = true;
} else if (!prevHole || !nextHole || prevHole === nextHole || prevHole !== currentHole) {
// When `p.prev, p & p.next` are collinear,
// If `p.prev, p & p.next` are on holes (not outer edge) ,
// And `p.prev & p` are on the same hole ,
// Then do NOT remove `p` .

// In other words,
// When `p.prev, p & p.next` are collinear,
// If `p.prev, p & p.next` are on the outer edge,
// Or `p.prev & p` are on different holes ,
// Then do REMOVE `p`
if (area(p.prev, p, p.next) === 0) {
toRemove = true;
}
}
}

if (toRemove) {
removeNode(p);
p = end = p.prev;
if (p === p.next) break;
if (p === p.next) {
break;
}
again = true;

} else {
p = p.next;
}
Expand Down Expand Up @@ -122,12 +157,12 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);

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

// as a last resort, try splitting the remaining polygon into two
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, invSize);
}
Expand All @@ -143,14 +178,18 @@ function isEar(ear) {
b = ear,
c = ear.next;

if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
if (area(a, b, c) >= 0) {
return false; // reflex, can't be an ear
}

// now make sure we don't have other points inside the potential ear
var p = ear.next.next;

while (p !== ear.prev) {
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
area(p.prev, p, p.next) >= 0) {
return false;
}
p = p.next;
}

Expand Down Expand Up @@ -251,12 +290,14 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
var queue = [],
i, len, start, end, list;
i, len, start, end, list, holeId;

for (i = 0, len = holeIndices.length; i < len; i++) {
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
list = linkedList(data, start, end, dim, false);

holeId = i + 1; // ensure holeId != 0 ;
list = linkedList(data, start, end, dim, false, holeId);
if (list === list.next) list.steiner = true;
queue.push(getLeftmost(list));
}
Expand Down Expand Up @@ -328,7 +369,7 @@ function findHoleBridge(hole, outerNode) {

while (p !== stop) {
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)) {
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {

tan = Math.abs(hy - p.y) / (hx - p.x); // tangential

Expand Down Expand Up @@ -447,14 +488,15 @@ function getLeftmost(start) {
// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}

// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
var rs = a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
return rs;
}

// signed area of a triangle
Expand All @@ -472,15 +514,15 @@ function intersects(p1, q1, p2, 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;
area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}

// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
var p = a;
do {
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects(p, p.next, a, b)) return true;
intersects(p, p.next, a, b)) return true;
p = p.next;
} while (p !== a);

Expand All @@ -502,7 +544,7 @@ function middleInside(a, b) {
py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p !== a);
Expand All @@ -518,6 +560,9 @@ function splitPolygon(a, b) {
an = a.next,
bp = b.prev;

a2.holeId = a.holeId;
b2.holeId = b.holeId;

a.next = b;
b.prev = a;

Expand Down Expand Up @@ -621,9 +666,13 @@ function signedArea(data, start, end, dim) {

// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
earcut.flatten = function (data) {
var dim = data[0][0].length,
result = {vertices: [], holes: [], dimensions: dim},
holeIndex = 0;
var dim = data[0][0].length;
var result = {
vertices: [],
holes: [],
dimensions: dim
};
var holeIndex = 0;

for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
Expand Down