Skip to content

Commit

Permalink
Merge pull request #2309 from AnalyticalGraphicsInc/subdivision
Browse files Browse the repository at this point in the history
Optimize Polygon subdivision
  • Loading branch information
pjcozzi committed Dec 5, 2014
2 parents 3db3652 + daea26d commit c71f81c
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Source/Core/PolygonGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
152 changes: 70 additions & 82 deletions Source/Core/PolygonPipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -804,9 +805,18 @@ define([
return randomChop(nodeArray);
};

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();

/**
* 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.
Expand All @@ -815,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.');
}
Expand All @@ -836,135 +849,110 @@ 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 triangles = indices.slice(0);

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]
});
// New positions due to edge splits are appended to the positions list.
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;
}

// New positions due to edge splits are appended to the positions list.
var subdividedPositions = positions.slice(0); // shallow copy!
var subdividedIndices = [];

// Used to make sure shared edges are not split more than once.
var edges = {};

var i;
var radius = ellipsoid.maximumRadius;
var minDistance = 2.0 * radius * Math.sin(granularity * 0.5);
var minDistanceSqrd = minDistance * minDistance;

while (triangles.length > 0) {
var triangle = triangles.dequeue();
var i2 = triangles.pop();
var i1 = triangles.pop();
var i0 = triangles.pop();

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 v0 = subdividedPositions[triangle.i0];
var v1 = subdividedPositions[triangle.i1];
var v2 = subdividedPositions[triangle.i2];
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.angleBetween(v0, v1);
var g1 = Cartesian3.angleBetween(v1, v2);
var g2 = Cartesian3.angleBetween(v2, v0);
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, Math.max(g1, g2));
var max = Math.max(g0, g1, g2);
var edge;
var mid;

if (max > granularity) {
// 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(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,
Expand Down
18 changes: 12 additions & 6 deletions Specs/Core/PolygonPipelineSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand All @@ -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);
Expand Down

0 comments on commit c71f81c

Please sign in to comment.