From af4575a499bb888988ddb3541c82dc9621864973 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 3 Dec 2014 17:16:00 -0500 Subject: [PATCH 1/4] Clean up memory allocations in subdivision. --- Source/Core/PolygonPipeline.js | 124 +++++++++++++-------------------- 1 file changed, 49 insertions(+), 75 deletions(-) diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 4ed905156902..17e1bf15f9da 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -804,6 +804,11 @@ define([ return randomChop(nodeArray); }; + var subdivisionV0Scratch = new Cartesian3(); + var subdivisionV1Scratch = new Cartesian3(); + var subdivisionV2Scratch = new Cartesian3(); + var subdivisionMidScratch = new Cartesian3(); + /** * Subdivides positions and raises points to the surface of the ellipsoid. * @@ -836,135 +841,104 @@ define([ } //>>includeEnd('debug'); - // Use a queue for triangles that need (or might need) to be subdivided. - var triangles = new Queue(); - + // triangles that need (or might need) to be subdivided. var indicesLength = indices.length; - for ( var j = 0; j < indicesLength; j += 3) { - triangles.enqueue({ - i0 : indices[j], - i1 : indices[j + 1], - i2 : indices[j + 2] - }); + var triangles = new Array(indicesLength); + for ( var j = 0; j < indicesLength; ++j) { + triangles[j] = indices[j]; } // New positions due to edge splits are appended to the positions list. - var subdividedPositions = positions.slice(0); // shallow copy! + var i; + var length = positions.length; + var subdividedPositions = new Array(length * 3); + var q = 0; + for (i = 0; i < length; i++) { + var item = positions[i]; + subdividedPositions[q++] = item.x; + subdividedPositions[q++] = item.y; + subdividedPositions[q++] = item.z; + } + var subdividedIndices = []; // Used to make sure shared edges are not split more than once. var edges = {}; - var i; while (triangles.length > 0) { - var triangle = triangles.dequeue(); + var i2 = triangles.pop(); + var i1 = triangles.pop(); + var i0 = triangles.pop(); - var v0 = subdividedPositions[triangle.i0]; - var v1 = subdividedPositions[triangle.i1]; - var v2 = subdividedPositions[triangle.i2]; + var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch); + var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch); + var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch); var g0 = Cartesian3.angleBetween(v0, v1); var g1 = Cartesian3.angleBetween(v1, v2); var g2 = Cartesian3.angleBetween(v2, v0); - var max = Math.max(g0, Math.max(g1, g2)); + var max = Math.max(g0, g1, g2); var edge; var mid; if (max > granularity) { if (g0 === max) { - edge = Math.min(triangle.i0, triangle.i1).toString() + ' ' + Math.max(triangle.i0, triangle.i1).toString(); + edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1); i = edges[edge]; if (!defined(i)) { - mid = Cartesian3.add(v0, v1, new Cartesian3()); + mid = Cartesian3.add(v0, v1, subdivisionMidScratch); Cartesian3.multiplyByScalar(mid, 0.5, mid); - subdividedPositions.push(mid); - i = subdividedPositions.length - 1; + subdividedPositions.push(mid.x, mid.y, mid.z); + i = subdividedPositions.length / 3 - 1; edges[edge] = i; } - triangles.enqueue({ - i0 : triangle.i0, - i1 : i, - i2 : triangle.i2 - }); - triangles.enqueue({ - i0 : i, - i1 : triangle.i1, - i2 : triangle.i2 - }); + triangles.push(i0, i, i2); + triangles.push(i, i1, i2); } else if (g1 === max) { - edge = Math.min(triangle.i1, triangle.i2).toString() + ' ' + Math.max(triangle.i1, triangle.i2).toString(); + edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2); i = edges[edge]; if (!defined(i)) { - mid = Cartesian3.add(v1, v2, new Cartesian3()); + mid = Cartesian3.add(v1, v2, subdivisionMidScratch); Cartesian3.multiplyByScalar(mid, 0.5, mid); - subdividedPositions.push(mid); - i = subdividedPositions.length - 1; + subdividedPositions.push(mid.x, mid.y, mid.z); + i = subdividedPositions.length / 3 - 1; edges[edge] = i; } - triangles.enqueue({ - i0 : triangle.i1, - i1 : i, - i2 : triangle.i0 - }); - triangles.enqueue({ - i0 : i, - i1 : triangle.i2, - i2 : triangle.i0 - }); + triangles.push(i1, i, i0); + triangles.push(i, i2, i0); } else if (g2 === max) { - edge = Math.min(triangle.i2, triangle.i0).toString() + ' ' + Math.max(triangle.i2, triangle.i0).toString(); + edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0); i = edges[edge]; if (!defined(i)) { - mid = Cartesian3.add(v2, v0, new Cartesian3()); + mid = Cartesian3.add(v2, v0, subdivisionMidScratch); Cartesian3.multiplyByScalar(mid, 0.5, mid); - subdividedPositions.push(mid); - i = subdividedPositions.length - 1; + subdividedPositions.push(mid.x, mid.y, mid.z); + i = subdividedPositions.length / 3 - 1; edges[edge] = i; } - triangles.enqueue({ - i0 : triangle.i2, - i1 : i, - i2 : triangle.i1 - }); - triangles.enqueue({ - i0 : i, - i1 : triangle.i0, - i2 : triangle.i1 - }); + triangles.push(i2, i, i1); + triangles.push(i, i0, i1); } } else { - subdividedIndices.push(triangle.i0); - subdividedIndices.push(triangle.i1); - subdividedIndices.push(triangle.i2); + subdividedIndices.push(i0); + subdividedIndices.push(i1); + subdividedIndices.push(i2); } } - // PERFORMANCE_IDEA Rather that waste time re-iterating the entire set of positions - // here, all of the above code can be refactored to flatten as values are added - // Removing the need for this for loop. - var length = subdividedPositions.length; - var flattenedPositions = new Array(length * 3); - var q = 0; - for (i = 0; i < length; i++) { - var item = subdividedPositions[i]; - flattenedPositions[q++] = item.x; - flattenedPositions[q++] = item.y; - flattenedPositions[q++] = item.z; - } - return new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : flattenedPositions + values : subdividedPositions }) }, indices : subdividedIndices, From 35fb679beb5da4a17fda9dbe547861eaf174ed1d Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 3 Dec 2014 19:47:47 -0500 Subject: [PATCH 2/4] Optimize subdivision by comparing chord lengths instead of vector angles (avoids many inverse trig calls). --- Source/Core/PolygonPipeline.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 17e1bf15f9da..01aa57604816 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -807,6 +807,9 @@ define([ var subdivisionV0Scratch = new Cartesian3(); var subdivisionV1Scratch = new Cartesian3(); var subdivisionV2Scratch = new Cartesian3(); + var subdivisionS0Scratch = new Cartesian3(); + var subdivisionS1Scratch = new Cartesian3(); + var subdivisionS2Scratch = new Cartesian3(); var subdivisionMidScratch = new Cartesian3(); /** @@ -865,6 +868,11 @@ define([ // Used to make sure shared edges are not split more than once. var edges = {}; + // TODO: Add ellipsoid parameter. Properly deprecate. + var radius = 6378137.0; + var minDistance = 2.0 * radius * Math.sin(granularity * 0.5); + var minDistanceSqrd = minDistance * minDistance; + while (triangles.length > 0) { var i2 = triangles.pop(); var i1 = triangles.pop(); @@ -874,15 +882,19 @@ define([ var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch); var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch); - var g0 = Cartesian3.angleBetween(v0, v1); - var g1 = Cartesian3.angleBetween(v1, v2); - var g2 = Cartesian3.angleBetween(v2, v0); + var s0 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v0, subdivisionS0Scratch), radius, subdivisionS0Scratch); + var s1 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v1, subdivisionS1Scratch), radius, subdivisionS1Scratch); + var s2 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v2, subdivisionS2Scratch), radius, subdivisionS2Scratch); + + var g0 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s0, s1, subdivisionMidScratch)); + var g1 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s1, s2, subdivisionMidScratch)); + var g2 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s2, s0, subdivisionMidScratch)); var max = Math.max(g0, g1, g2); var edge; var mid; - if (max > granularity) { + if (max > minDistanceSqrd) { if (g0 === max) { edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1); From ae0d5e95e9dbddf3e573fbe4088d6673f4d3d4e7 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Thu, 4 Dec 2014 13:22:20 -0500 Subject: [PATCH 3/4] Add ellipsoid parameter to subdivision for chord length (PolygonPipeline is private). --- Source/Core/PolygonGeometry.js | 2 +- Source/Core/PolygonPipeline.js | 12 ++++++++---- Specs/Core/PolygonPipelineSpec.js | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 4560dd7c022f..c319e06ea5b6 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -109,7 +109,7 @@ define([ var geo; if (!perPositionHeight) { - geo = PolygonPipeline.computeSubdivision(positions, indices, granularity); + geo = PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); } else { var length = positions.length; var flattenedPositions = new Array(length * 3); diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 01aa57604816..7afe103e0667 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -704,6 +704,7 @@ define([ * @private */ var PolygonPipeline = {}; + /** * Cleans up a simple polygon by removing duplicate adjacent positions and making * the first position not equal the last position. @@ -773,7 +774,7 @@ define([ }; /** - * Triangulate a polygon + * Triangulate a polygon. * * @param {Cartesian2[]} positions - Cartesian2 array containing the vertices of the polygon * @returns {Number[]} - Index array representing triangles that fill the polygon @@ -815,6 +816,7 @@ define([ /** * Subdivides positions and raises points to the surface of the ellipsoid. * + * @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on. * @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon. * @param {Number[]} indices An array of indices that determines the triangles in the polygon. * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. @@ -823,10 +825,13 @@ define([ * @exception {DeveloperError} The number of indices must be divisable by three. * @exception {DeveloperError} Granularity must be greater than zero. */ - PolygonPipeline.computeSubdivision = function(positions, indices, granularity) { + PolygonPipeline.computeSubdivision = function(ellipsoid, positions, indices, granularity) { granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE); //>>includeStart('debug', pragmas.debug); + if (!defined(ellipsoid)) { + throw new DeveloperError('ellipsoid is required.'); + } if (!defined(positions)) { throw new DeveloperError('positions is required.'); } @@ -868,8 +873,7 @@ define([ // Used to make sure shared edges are not split more than once. var edges = {}; - // TODO: Add ellipsoid parameter. Properly deprecate. - var radius = 6378137.0; + var radius = ellipsoid.maximumRadius; var minDistance = 2.0 * radius * Math.sin(granularity * 0.5); var minDistanceSqrd = minDistance * minDistance; diff --git a/Specs/Core/PolygonPipelineSpec.js b/Specs/Core/PolygonPipelineSpec.js index c8ae5b9ad2b4..de9a72421339 100644 --- a/Specs/Core/PolygonPipelineSpec.js +++ b/Specs/Core/PolygonPipelineSpec.js @@ -295,33 +295,39 @@ defineSuite([ /////////////////////////////////////////////////////////////////////// - it('computeSubdivision throws without positions', function() { + it('computeSubdivision throws without ellipsoid', function() { expect(function() { PolygonPipeline.computeSubdivision(); }).toThrowDeveloperError(); }); + it('computeSubdivision throws without positions', function() { + expect(function() { + PolygonPipeline.computeSubdivision(Ellipsoid.WGS84); + }).toThrowDeveloperError(); + }); + it('computeSubdivision throws without indices', function() { expect(function() { - PolygonPipeline.computeSubdivision([]); + PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, []); }).toThrowDeveloperError(); }); it('computeSubdivision throws with less than 3 indices', function() { expect(function() { - PolygonPipeline.computeSubdivision([], [1, 2]); + PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2]); }).toThrowDeveloperError(); }); it('computeSubdivision throws without a multiple of 3 indices', function() { expect(function() { - PolygonPipeline.computeSubdivision([], [1, 2, 3, 4]); + PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2, 3, 4]); }).toThrowDeveloperError(); }); it('computeSubdivision throws with negative granularity', function() { expect(function() { - PolygonPipeline.computeSubdivision([], [1, 2, 3], -1.0); + PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2, 3], -1.0); }).toThrowDeveloperError(); }); @@ -332,7 +338,7 @@ defineSuite([ new Cartesian3(90.0, 0.0, 0.0) ]; var indices = [0, 1, 2]; - var subdivision = PolygonPipeline.computeSubdivision(positions, indices, 60.0); + var subdivision = PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, positions, indices, 60.0); expect(subdivision.attributes.position.values[0]).toEqual(0.0); expect(subdivision.attributes.position.values[1]).toEqual(0.0); From 98eafc86d496f9add56157d7260f500cf28194a4 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Thu, 4 Dec 2014 16:14:32 -0500 Subject: [PATCH 4/4] Tweak subdivision. --- Source/Core/PolygonPipeline.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 7afe103e0667..9224410ebc80 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -850,11 +850,7 @@ define([ //>>includeEnd('debug'); // triangles that need (or might need) to be subdivided. - var indicesLength = indices.length; - var triangles = new Array(indicesLength); - for ( var j = 0; j < indicesLength; ++j) { - triangles[j] = indices[j]; - } + var triangles = indices.slice(0); // New positions due to edge splits are appended to the positions list. var i; @@ -898,6 +894,8 @@ define([ var edge; var mid; + // if the max length squared of a triangle edge is greater than the chord length of squared + // of the granularity, subdivide the triangle if (max > minDistanceSqrd) { if (g0 === max) { edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);