Skip to content

Commit

Permalink
Merge pull request #8528 from fredj/update_earcut
Browse files Browse the repository at this point in the history
Update earcut to v2.2.1
  • Loading branch information
mramato authored Jan 16, 2020
2 parents 619ab84 + 4b3fd9c commit 0a634a2
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Change Log
* Added `Globe.showSkirts` to support the ability to hide terrain skirts when viewing terrain from below the surface. [#8489](https://github.com/AnalyticalGraphicsInc/cesium/pull/8489)
* Fixed `BoundingSphere.projectTo2D` when the bounding sphere’s center is at the origin. [#8482](https://github.com/AnalyticalGraphicsInc/cesium/pull/8482)
* Added `minificationFilter` and `magnificationFilter` options to `Material` to control texture filtering. [#8473](https://github.com/AnalyticalGraphicsInc/cesium/pull/8473)
* Update [earcut](https://github.com/mapbox/earcut) to 2.2.1 [#8528](https://github.com/AnalyticalGraphicsInc/cesium/pull/8528)

##### Fixes :wrench:
* Fixed issue where `RequestScheduler` double-counted image requests made via `createImageBitmap`. [#8162](https://github.com/AnalyticalGraphicsInc/cesium/issues/8162)
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/PolygonPipeline.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import earcut from '../ThirdParty/earcut-2.1.1.js';
import earcut from '../ThirdParty/earcut-2.2.1.js';
import Cartesian2 from './Cartesian2.js';
import Cartesian3 from './Cartesian3.js';
import Cartographic from './Cartographic.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ function earcut(data, holeIndices, dim) {
outerNode = linkedList(data, 0, outerLen, dim, true),
triangles = [];

if (!outerNode) return triangles;
if (!outerNode || outerNode.next === outerNode.prev) return triangles;

var minX, minY, maxX, maxY, x, y, size;
var minX, minY, maxX, maxY, x, y, invSize;

if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);

Expand All @@ -27,11 +27,12 @@ function earcut(data, holeIndices, dim) {
if (y > maxY) maxY = y;
}

// minX, minY and size are later used to transform coords into integers for z-order calculation
size = Math.max(maxX - minX, maxY - minY);
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
invSize = Math.max(maxX - minX, maxY - minY);
invSize = invSize !== 0 ? 1 / invSize : 0;
}

earcutLinked(outerNode, triangles, dim, minX, minY, size);
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);

return triangles;
}
Expand Down Expand Up @@ -67,7 +68,7 @@ function filterPoints(start, end) {
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
removeNode(p);
p = end = p.prev;
if (p === p.next) return null;
if (p === p.next) break;
again = true;

} else {
Expand All @@ -79,11 +80,11 @@ function filterPoints(start, end) {
}

// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
if (!ear) return;

// interlink polygon nodes in z-order
if (!pass && size) indexCurve(ear, minX, minY, size);
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);

var stop = ear,
prev, next;
Expand All @@ -93,15 +94,15 @@ function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
prev = ear.prev;
next = ear.next;

if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) {
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
// cut off the triangle
triangles.push(prev.i / dim);
triangles.push(ear.i / dim);
triangles.push(next.i / dim);

removeNode(ear);

// skipping the next vertice leads to less sliver triangles
// skipping the next vertex leads to less sliver triangles
ear = next.next;
stop = next.next;

Expand All @@ -114,16 +115,16 @@ function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1);
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);

// 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, size, 2);
ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);

// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, size);
splitEarcut(ear, triangles, dim, minX, minY, invSize);
}

break;
Expand Down Expand Up @@ -151,7 +152,7 @@ function isEar(ear) {
return true;
}

function isEarHashed(ear, minX, minY, size) {
function isEarHashed(ear, minX, minY, invSize) {
var a = ear.prev,
b = ear,
c = ear.next;
Expand All @@ -165,29 +166,41 @@ function isEarHashed(ear, minX, minY, size) {
maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);

// z-order range for the current triangle bbox;
var minZ = zOrder(minTX, minTY, minX, minY, size),
maxZ = zOrder(maxTX, maxTY, minX, minY, size);
var minZ = zOrder(minTX, minTY, minX, minY, invSize),
maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);

// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
var p = ear.prevZ,
n = ear.nextZ;

while (p && p.z <= maxZ) {
// look for points inside the triangle in both directions
while (p && p.z >= minZ && n && n.z <= maxZ) {
if (p !== ear.prev && p !== ear.next &&
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;
p = p.nextZ;
}
p = p.prevZ;

// then look for points in decreasing z-order
p = ear.prevZ;
if (n !== ear.prev && n !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}

// look for remaining points in decreasing z-order
while (p && p.z >= minZ) {
if (p !== ear.prev && p !== ear.next &&
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;
p = p.prevZ;
}

// look for remaining points in increasing z-order
while (n && n.z <= maxZ) {
if (n !== ear.prev && n !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}

return true;
}

Expand All @@ -213,11 +226,11 @@ function cureLocalIntersections(start, triangles, dim) {
p = p.next;
} while (p !== start);

return p;
return filterPoints(p);
}

// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, size) {
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
// look for a valid diagonal that divides the polygon into two
var a = start;
do {
Expand All @@ -232,8 +245,8 @@ function splitEarcut(start, triangles, dim, minX, minY, size) {
c = filterPoints(c, c.next);

// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, size);
earcutLinked(c, triangles, dim, minX, minY, size);
earcutLinked(a, triangles, dim, minX, minY, invSize);
earcutLinked(c, triangles, dim, minX, minY, invSize);
return;
}
b = b.next;
Expand Down Expand Up @@ -290,7 +303,7 @@ function findHoleBridge(hole, outerNode) {
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do {
if (hy <= p.y && hy >= p.next.y) {
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx) {
qx = x;
Expand All @@ -306,7 +319,7 @@ function findHoleBridge(hole, outerNode) {

if (!m) return null;

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

// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
Expand All @@ -318,31 +331,37 @@ function findHoleBridge(hole, outerNode) {
tanMin = Infinity,
tan;

p = m.next;
p = m;

while (p !== stop) {
if (hx >= p.x && p.x >= mx &&
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)) {

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

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

p = p.next;
}
} while (p !== stop);

return m;
}

// whether sector in vertex m contains sector in vertex p in the same coordinates
function sectorContainsSector(m, p) {
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
}

// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, size) {
function indexCurve(start, minX, minY, invSize) {
var p = start;
do {
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size);
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
Expand Down Expand Up @@ -375,20 +394,11 @@ function sortLinked(list) {
q = q.nextZ;
if (!q) break;
}

qSize = inSize;

while (pSize > 0 || (qSize > 0 && q)) {

if (pSize === 0) {
e = q;
q = q.nextZ;
qSize--;
} else if (qSize === 0 || !q) {
e = p;
p = p.nextZ;
pSize--;
} else if (p.z <= q.z) {
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
e = p;
p = p.nextZ;
pSize--;
Expand Down Expand Up @@ -416,11 +426,11 @@ function sortLinked(list) {
return list;
}

// z-order of a point given coords and size of the data bounding box
function zOrder(x, y, minX, minY, size) {
// z-order of a point given coords and inverse of the longer side of data bbox
function zOrder(x, y, minX, minY, invSize) {
// coords are transformed into non-negative 15-bit integer range
x = 32767 * (x - minX) / size;
y = 32767 * (y - minY) / size;
x = 32767 * (x - minX) * invSize;
y = 32767 * (y - minY) * invSize;

x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
Expand All @@ -440,7 +450,7 @@ function getLeftmost(start) {
var p = start,
leftmost = start;
do {
if (p.x < leftmost.x) leftmost = p;
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
p = p.next;
} while (p !== start);

Expand All @@ -456,8 +466,10 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {

// 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);
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) || area(a, b.prev, b)) || // 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 @@ -472,10 +484,28 @@ function equals(p1, p2) {

// check if two segments intersect
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;
var o1 = sign(area(p1, q1, p2));
var o2 = sign(area(p1, q1, q2));
var o3 = sign(area(p2, q2, p1));
var 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
function onSegment(p, q, r) {
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
}

function sign(num) {
return num > 0 ? 1 : num < 0 ? -1 : 0;
}

// check if a polygon diagonal intersects any polygon segments
Expand Down Expand Up @@ -504,7 +534,8 @@ function middleInside(a, b) {
px = (a.x + b.x) / 2,
py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
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))
inside = !inside;
p = p.next;
} while (p !== a);
Expand Down Expand Up @@ -561,14 +592,14 @@ function removeNode(p) {
}

function Node(i, x, y) {
// vertice index in coordinates array
// vertex index in coordinates array
this.i = i;

// vertex coordinates
this.x = x;
this.y = y;

// previous and next vertice nodes in a polygon ring
// previous and next vertex nodes in a polygon ring
this.prev = null;
this.next = null;

Expand Down

0 comments on commit 0a634a2

Please sign in to comment.