diff --git a/CHANGES.md b/CHANGES.md index b36df8b3ac74..26bd08e39621 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index bad46968aedb..e1521c9c72d0 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -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'; diff --git a/Source/ThirdParty/earcut-2.1.1.js b/Source/ThirdParty/earcut-2.2.1.js similarity index 76% rename from Source/ThirdParty/earcut-2.1.1.js rename to Source/ThirdParty/earcut-2.2.1.js index 25d406043a0b..91629f55db52 100644 --- a/Source/ThirdParty/earcut-2.1.1.js +++ b/Source/ThirdParty/earcut-2.2.1.js @@ -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); @@ -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; } @@ -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 { @@ -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; @@ -93,7 +94,7 @@ 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); @@ -101,7 +102,7 @@ function earcutLinked(ear, triangles, dim, minX, minY, size, pass) { 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; @@ -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; @@ -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; @@ -165,22 +166,26 @@ 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) && @@ -188,6 +193,14 @@ function isEarHashed(ear, minX, minY, size) { 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; } @@ -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 { @@ -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; @@ -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; @@ -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; @@ -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; @@ -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--; @@ -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; @@ -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); @@ -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 @@ -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 @@ -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); @@ -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;