From 36db66026fdff27f878ee9b493a38d89c07227a3 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 31 Jul 2013 12:08:05 -0400 Subject: [PATCH 01/25] box outline --- Source/Core/BoxGeometryOutline.js | 203 ++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 Source/Core/BoxGeometryOutline.js diff --git a/Source/Core/BoxGeometryOutline.js b/Source/Core/BoxGeometryOutline.js new file mode 100644 index 000000000000..fc9554661046 --- /dev/null +++ b/Source/Core/BoxGeometryOutline.js @@ -0,0 +1,203 @@ +/*global define*/ +define([ + './DeveloperError', + './Cartesian3', + './ComponentDatatype', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat' + ], function( + DeveloperError, + Cartesian3, + ComponentDatatype, + PrimitiveType, + defaultValue, + BoundingSphere, + GeometryAttribute, + GeometryAttributes, + VertexFormat) { + "use strict"; + + var diffScratch = new Cartesian3(); + + /** + * A {@link Geometry} that represents vertices and indices for a cube centered at the origin. + * + * @alias BoxGeometryOutline + * @constructor + * + * @param {Cartesian3} options.minimumCorner The minimum x, y, and z coordinates of the box. + * @param {Cartesian3} options.maximumCorner The maximum x, y, and z coordinates of the box. + * + * @exception {DeveloperError} options.minimumCorner is required. + * @exception {DeveloperError} options.maximumCorner is required. + * + * @example + * var box = new BoxGeometryOutline({ + * maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + * minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + * }); + */ + var BoxGeometryOutline = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var min = options.minimumCorner; + var max = options.maximumCorner; + + if (typeof min === 'undefined') { + throw new DeveloperError('options.minimumCorner is required.'); + } + + if (typeof max === 'undefined') { + throw new DeveloperError('options.maximumCorner is required'); + } + + var attributes = new GeometryAttributes(); + var indices; + var positions; + + // Positions only - no need to duplicate corner points + positions = new Float64Array(8 * 3); + + positions[0] = min.x; + positions[1] = min.y; + positions[2] = min.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = min.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = min.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = min.z; + positions[12] = min.x; + positions[13] = min.y; + positions[14] = max.z; + positions[15] = max.x; + positions[16] = min.y; + positions[17] = max.z; + positions[18] = max.x; + positions[19] = max.y; + positions[20] = max.z; + positions[21] = min.x; + positions[22] = max.y; + positions[23] = max.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + indices = new Uint16Array(12 * 2); + + // top + indices[0] = 4; + indices[1] = 5; + indices[2] = 5; + indices[3] = 6; + indices[4] = 6; + indices[5] = 7; + indices[6] = 7; + indices[7] = 4; + + // bottom + indices[8] = 0; + indices[9] = 1; + indices[10] = 1; + indices[11] = 2; + indices[12] = 2; + indices[13] = 3; + indices[14] = 3; + indices[15] = 0; + + // left + indices[16] = 0; + indices[17] = 4; + indices[18] = 1; + indices[19] = 5; + + //right + indices[20] = 2; + indices[21] = 6; + indices[22] = 3; + indices[23] = 7; + + var diff = Cartesian3.subtract(max, min, diffScratch); + var radius = diff.magnitude() * 0.5; + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = new BoundingSphere(Cartesian3.ZERO, radius); + }; + + /** + * Creates vertices and indices for a cube centered at the origin given its dimensions. + * @memberof BoxGeometryOutline + * + * @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the Cartesian3, respectively. + * + * @exception {DeveloperError} options.dimensions is required. + * @exception {DeveloperError} All dimensions components must be greater than or equal to zero. + * + * @example + * var box = BoxGeometryOutline.fromDimensions({ + * dimensions : new Cartesian3(500000.0, 500000.0, 500000.0) + * }); + */ + BoxGeometryOutline.fromDimensions = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var dimensions = options.dimensions; + if (typeof dimensions === 'undefined') { + throw new DeveloperError('options.dimensions is required.'); + } + + if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) { + throw new DeveloperError('All dimensions components must be greater than or equal to zero.'); + } + + var corner = dimensions.multiplyByScalar(0.5); + var min = corner.negate(); + var max = corner; + + var newOptions = { + minimumCorner : min, + maximumCorner : max + }; + return new BoxGeometryOutline(newOptions); + }; + + return BoxGeometryOutline; +}); \ No newline at end of file From c5b06ba16e7cf3a970bdaba7f154a43194eb7a6c Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 31 Jul 2013 12:57:08 -0400 Subject: [PATCH 02/25] box outline tests --- Source/Core/BoxGeometryOutline.js | 4 +- Specs/Core/BoxGeometryOutlineSpec.js | 61 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 Specs/Core/BoxGeometryOutlineSpec.js diff --git a/Source/Core/BoxGeometryOutline.js b/Source/Core/BoxGeometryOutline.js index fc9554661046..efe494e0a761 100644 --- a/Source/Core/BoxGeometryOutline.js +++ b/Source/Core/BoxGeometryOutline.js @@ -24,7 +24,7 @@ define([ var diffScratch = new Cartesian3(); /** - * A {@link Geometry} that represents vertices and indices for a cube centered at the origin. + * A {@link Geometry} that represents vertices and indices for the edges of a cube centered at the origin. * * @alias BoxGeometryOutline * @constructor @@ -163,7 +163,7 @@ define([ }; /** - * Creates vertices and indices for a cube centered at the origin given its dimensions. + * Creates vertices and indices for the edges of a cube centered at the origin given its dimensions. * @memberof BoxGeometryOutline * * @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the Cartesian3, respectively. diff --git a/Specs/Core/BoxGeometryOutlineSpec.js b/Specs/Core/BoxGeometryOutlineSpec.js new file mode 100644 index 000000000000..0fc9a02ea2d3 --- /dev/null +++ b/Specs/Core/BoxGeometryOutlineSpec.js @@ -0,0 +1,61 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/BoxGeometryOutline', + 'Core/VertexFormat', + 'Core/Cartesian3' + ], function( + BoxGeometryOutline, + VertexFormat, + Cartesian3) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws without minimum corner', function() { + expect(function() { + return new BoxGeometryOutline({ + maximumCorner : new Cartesian3() + }); + }).toThrow(); + }); + + it('constructor throws without maximum corner', function() { + expect(function() { + return new BoxGeometryOutline({ + minimumCorner : new Cartesian3() + }); + }).toThrow(); + }); + + it('constructor creates optimized number of positions for VertexFormat.POSITIONS_ONLY', function() { + var m = new BoxGeometryOutline({ + minimumCorner : new Cartesian3(-1, -2, -3), + maximumCorner : new Cartesian3(1, 2, 3) + }); + + expect(m.attributes.position.values.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(12 * 2); + }); + + it('fromDimensions throws without dimensions', function() { + expect(function() { + return BoxGeometryOutline.fromDimensions(); + }).toThrow(); + }); + + it('fromDimensions throws with negative dimensions', function() { + expect(function() { + return BoxGeometryOutline.fromDimensions({ + dimensions : new Cartesian3(1, 2, -1) + }); + }).toThrow(); + }); + + it('fromDimensions', function() { + var m = BoxGeometryOutline.fromDimensions({ + dimensions : new Cartesian3(1, 2, 3) + }); + + expect(m.attributes.position.values.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(12 * 2); + }); +}); \ No newline at end of file From 85397b1079a5bbc1002fc4bed208bfd482ca1043 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 31 Jul 2013 14:10:43 -0400 Subject: [PATCH 03/25] cylinder outline --- Source/Core/CylinderGeometryOutline.js | 189 ++++++++++++++++++++++ Specs/Core/CylinderGeometryOutlineSpec.js | 123 ++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 Source/Core/CylinderGeometryOutline.js create mode 100644 Specs/Core/CylinderGeometryOutlineSpec.js diff --git a/Source/Core/CylinderGeometryOutline.js b/Source/Core/CylinderGeometryOutline.js new file mode 100644 index 000000000000..a87c3a45a7eb --- /dev/null +++ b/Source/Core/CylinderGeometryOutline.js @@ -0,0 +1,189 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError', + './Cartesian2', + './Cartesian3', + './Math', + './ComponentDatatype', + './IndexDatatype', + './PrimitiveType', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat' + ], function( + defaultValue, + DeveloperError, + Cartesian2, + Cartesian3, + CesiumMath, + ComponentDatatype, + IndexDatatype, + PrimitiveType, + BoundingSphere, + GeometryAttribute, + GeometryAttributes, + VertexFormat) { + "use strict"; + + var radiusScratch = new Cartesian2(); + + /** + * A {@link Geometry} that represents vertices and indices for the edges of cylinder. + * + * @alias CylinderGeometryOutline + * @constructor + * + * @param {Number} options.length The length of the cylinder + * @param {Number} options.topRadius The radius of the top of the cylinder + * @param {Number} options.bottomRadius The radius of the bottom of the cylinder + * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder + * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the cylinder + * + * @exception {DeveloperError} options.length must be greater than 0 + * @exception {DeveloperError} options.topRadius must be greater than 0 + * @exception {DeveloperError} options.bottomRadius must be greater than 0 + * @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0 + * @exception {DeveloperError} options.slices must be greater that 3 + * + * @example + * // create cylinder geometry + * var cylinder = new Cesium.CylinderGeometryOutline({ + * length: 200000, + * topRadius: 80000, + * bottomRadius: 200000, + * }); + * + */ + var CylinderGeometryOutline = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var length = options.length; + if (typeof length === 'undefined' || length <= 0) { + throw new DeveloperError('options.length must be greater than 0'); + } + var topRadius = options.topRadius; + if (typeof topRadius === 'undefined' || topRadius < 0) { + throw new DeveloperError('options.topRadius must be greater than 0'); + } + var bottomRadius = options.bottomRadius; + if (typeof bottomRadius === 'undefined' || bottomRadius < 0) { + throw new DeveloperError('options.bottomRadius must be greater than 0'); + } + if (bottomRadius === 0 && topRadius === 0) { + throw new DeveloperError('bottomRadius and topRadius cannot both equal 0'); + } + + var slices = defaultValue(options.slices, 100); + if (slices < 3) { + throw new DeveloperError('options.slices must be greater that 3'); + } + + var numLengthLines = defaultValue(options.numLengthLines, 10); + if (numLengthLines < 0) { + throw new DeveloperError('options.numLengthLines cannot be less than zero'); + } + + var topZ = length * 0.5; + var bottomZ = -topZ; + + var numVertices = slices * 2; + + var positions = new Float64Array(numVertices * 3); + + var i; + var index = 0; + + for (i = 0; i < slices; i++) { + var angle = i / slices * CesiumMath.TWO_PI; + var x = Math.cos(angle); + var y = Math.sin(angle); + var bottomX = x * bottomRadius; + var bottomY = y * bottomRadius; + var topX = x * topRadius; + var topY = y * topRadius; + + positions[index + slices*3] = topX; + positions[index + slices*3 + 1] = topY; + positions[index + slices*3 + 2] = topZ; + + positions[index++] = bottomX; + positions[index++] = bottomY; + positions[index++] = bottomZ; + } + var numIndices = slices * 2; + var numSide; + if (numLengthLines > 0) { + var numSideLines = Math.min(numLengthLines, slices); + numSide = Math.round(slices/numSideLines); + numIndices += numSideLines; + } + + var indices = IndexDatatype.createTypedArray(numVertices, numIndices*2); + index = 0; + + for (i = 0; i < slices-1; i++) { + indices[index++] = i; + indices[index++] = i+1; + indices[index++] = i + slices; + indices[index++] = i + 1 + slices; + } + indices[index++] = slices-1; + indices[index++] = 0; + indices[index++] = slices + slices - 1; + indices[index++] = slices; + + if (numLengthLines > 0) { + for (i = 0; i < slices; i+= numSide){ + indices[index++] = i; + indices[index++] = i + slices; + } + } + + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype: ComponentDatatype.DOUBLE, + componentsPerAttribute: 3, + values: new Float64Array(positions) + }); + + radiusScratch.x = length * 0.5; + radiusScratch.y = Math.max(bottomRadius, topRadius); + + var boundingSphere = new BoundingSphere(Cartesian3.ZERO, radiusScratch.magnitude()); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type Object + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = boundingSphere; + }; + + return CylinderGeometryOutline; +}); diff --git a/Specs/Core/CylinderGeometryOutlineSpec.js b/Specs/Core/CylinderGeometryOutlineSpec.js new file mode 100644 index 000000000000..5732c9c46721 --- /dev/null +++ b/Specs/Core/CylinderGeometryOutlineSpec.js @@ -0,0 +1,123 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/CylinderGeometryOutline', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + CylinderGeometryOutline, + Cartesian3, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws with no length', function() { + expect(function() { + return new CylinderGeometryOutline({}); + }).toThrow(); + }); + + it('constructor throws with length less than 0', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: -1 + }); + }).toThrow(); + }); + + it('constructor throws with no topRadius', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1 + }); + }).toThrow(); + }); + + it('constructor throws with topRadius less than 0', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: -1 + }); + }).toThrow(); + }); + + it('constructor throws with no bottomRadius', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: 1 + }); + }).toThrow(); + }); + + it('constructor throws with bottomRadius less than 0', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: 1, + bottomRadius: -1 + }); + }).toThrow(); + }); + + it('constructor throws if top and bottom radius are 0', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: 0, + bottomRadius: 0 + }); + }).toThrow(); + }); + + it('constructor throws if slices is less than 3', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: 1, + bottomRadius: 1, + slices: 2 + }); + }).toThrow(); + }); + + it('constructor throws if numLengthLines is less than 0', function() { + expect(function() { + return new CylinderGeometryOutline({ + length: 1, + topRadius: 1, + bottomRadius: 1, + numLengthLines: -4 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var m = new CylinderGeometryOutline({ + length: 1, + topRadius: 1, + bottomRadius: 1, + slices: 3 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 3 * 2); + expect(m.indices.length).toEqual(9 * 2); + }); + + it('computes positions with no lines along the length', function() { + var m = new CylinderGeometryOutline({ + length: 1, + topRadius: 1, + bottomRadius: 1, + slices: 3, + numLengthLines: 0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 3 * 2); + expect(m.indices.length).toEqual(6 * 2); + }); +}); \ No newline at end of file From 8ef955c2e433a6959093b9d146d0b594fa7b47ab Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 2 Aug 2013 10:49:19 -0400 Subject: [PATCH 04/25] outline polygon --- ...ometryOutline.js => BoxOutlineGeometry.js} | 0 ...yOutline.js => CylinderOutlineGeometry.js} | 0 Source/Core/PolygonOutlineGeometry.js | 498 ++++++++++++++++++ 3 files changed, 498 insertions(+) rename Source/Core/{BoxGeometryOutline.js => BoxOutlineGeometry.js} (100%) rename Source/Core/{CylinderGeometryOutline.js => CylinderOutlineGeometry.js} (100%) create mode 100644 Source/Core/PolygonOutlineGeometry.js diff --git a/Source/Core/BoxGeometryOutline.js b/Source/Core/BoxOutlineGeometry.js similarity index 100% rename from Source/Core/BoxGeometryOutline.js rename to Source/Core/BoxOutlineGeometry.js diff --git a/Source/Core/CylinderGeometryOutline.js b/Source/Core/CylinderOutlineGeometry.js similarity index 100% rename from Source/Core/CylinderGeometryOutline.js rename to Source/Core/CylinderOutlineGeometry.js diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js new file mode 100644 index 000000000000..4491afc7a1e0 --- /dev/null +++ b/Source/Core/PolygonOutlineGeometry.js @@ -0,0 +1,498 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingRectangle', + './BoundingSphere', + './Cartesian2', + './Cartesian3', + './Cartesian4', + './ComponentDatatype', + './DeveloperError', + './Ellipsoid', + './EllipsoidTangentPlane', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './GeometryInstance', + './GeometryPipeline', + './IndexDatatype', + './Intersect', + './Math', + './Matrix3', + './PolygonPipeline', + './PrimitiveType', + './Quaternion', + './Queue', + './VertexFormat', + './WindingOrder' + ], function( + defaultValue, + BoundingRectangle, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartesian4, + ComponentDatatype, + DeveloperError, + Ellipsoid, + EllipsoidTangentPlane, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryInstance, + GeometryPipeline, + IndexDatatype, + Intersect, + CesiumMath, + Matrix3, + PolygonPipeline, + PrimitiveType, + Quaternion, + Queue, + VertexFormat, + WindingOrder) { + "use strict"; + var createGeometryFromPositionsPositions = []; + + var distanceScratch = new Cartesian3(); + function getPointAtDistance(p0, p1, distance, length) { + distanceScratch = p1.subtract(p0, distanceScratch); + distanceScratch = distanceScratch.multiplyByScalar(distance/length, distanceScratch); + distanceScratch = p0.add(distanceScratch, distanceScratch); + return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; + } + + function subdivideLine(p0, p1, granularity) { + var length = Cartesian3.distance(p0, p1); + var angleBetween = Cartesian3.angleBetween(p0, p1); + var n = angleBetween/granularity; + var countDivide = Math.ceil(Math.log(n)/Math.log(2)); + if (countDivide < 1) { + countDivide = 0; + } + var numVertices = Math.pow(2, countDivide); + + var distanceBetweenVertices = length / numVertices; + + var positions = new Array(numVertices * 3); + var index = 0; + positions[index++] = p0.x; + positions[index++] = p0.y; + positions[index++] = p0.z; + for (var i = 1; i < numVertices; i++) { + var p = getPointAtDistance(p0, p1, i*distanceBetweenVertices, length); + positions[index++] = p[0]; + positions[index++] = p[1]; + positions[index++] = p[2]; + } + + return positions; + } + + function createGeometryFromPositions(ellipsoid, positions, granularity) { + var cleanedPositions = PolygonPipeline.removeDuplicates(positions); + if (cleanedPositions.length < 3) { + throw new DeveloperError('Duplicate positions result in not enough positions to form a polygon.'); + } + + var tangentPlane = EllipsoidTangentPlane.fromPoints(cleanedPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(cleanedPositions, createGeometryFromPositionsPositions); + + var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + cleanedPositions.reverse(); + } + var subdividedPositions = []; + var length = cleanedPositions.length; + var i; + for (i = 0; i < length-1; i++) { + subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); + } + subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); + + length = subdividedPositions.length/3; + var indices = []; + for (i = 0; i < length-1; i++) { + indices.push(i, i+1); + } + indices.push(length-1, 0); + + return new GeometryInstance({ + geometry : new Geometry({ + attributes: new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : subdividedPositions + }) + }), + indices: indices, + primitiveType: PrimitiveType.LINES + }) + }); + } + + var scratchPosition = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchBoundingSphere = new BoundingSphere(); + + var scaleToGeodeticHeightN1 = new Cartesian3(); + var scaleToGeodeticHeightN2 = new Cartesian3(); + var scaleToGeodeticHeightP = new Cartesian3(); + function scaleToGeodeticHeightExtruded(geometry, maxHeight, minHeight, ellipsoid) { + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + + var n1 = scaleToGeodeticHeightN1; + var n2 = scaleToGeodeticHeightN2; + var p = scaleToGeodeticHeightP; + + if (typeof geometry !== 'undefined' && typeof geometry.attributes !== 'undefined' && typeof geometry.attributes.position !== 'undefined') { + var positions = geometry.attributes.position.values; + var length = positions.length / 2; + + for ( var i = 0; i < length; i += 3) { + Cartesian3.fromArray(positions, i, p); + + ellipsoid.scaleToGeodeticSurface(p, p); + ellipsoid.geodeticSurfaceNormal(p, n1); + + Cartesian3.multiplyByScalar(n1, maxHeight, n2); + Cartesian3.add(p, n2, n2); + positions[i] = n2.x; + positions[i + 1] = n2.y; + positions[i + 2] = n2.z; + + Cartesian3.multiplyByScalar(n1, minHeight, n2); + Cartesian3.add(p, n2, n2); + positions[i + length] = n2.x; + positions[i + 1 + length] = n2.y; + positions[i + 2 + length] = n2.z; + } + } + return geometry; + } + + function createGeometryFromPositionsExtruded(ellipsoid, positions, granularity) { + var cleanedPositions = PolygonPipeline.removeDuplicates(positions); + if (cleanedPositions.length < 3) { + throw new DeveloperError('Duplicate positions result in not enough positions to form a polygon.'); + } + + var tangentPlane = EllipsoidTangentPlane.fromPoints(cleanedPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(cleanedPositions, createGeometryFromPositionsPositions); + + var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + cleanedPositions.reverse(); + } + var subdividedPositions = []; + var length = cleanedPositions.length; + var i; + var corners = new Array(subdividedPositions.length); + corners[0] = 0; + for (i = 0; i < length-1; i++) { + subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); + corners[i+1] = subdividedPositions.length/3; + } + subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); + + length = subdividedPositions.length/3; + var indices = []; + for (i = 0; i < length-1; i++) { + indices.push(i, i+1); + indices.push(i + length, i+1 + length); + } + indices.push(length-1, 0); + indices.push(length + length-1, length); + + for (i = 0; i < corners.length; i++) { + var corner = corners[i]; + indices.push(corner, corner + length); + } + + subdividedPositions = subdividedPositions.concat(subdividedPositions); + + return new GeometryInstance({ + geometry : new Geometry({ + attributes: new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : subdividedPositions + }) + }), + indices: indices, + primitiveType: PrimitiveType.LINES + }) + }); + } + + /** + * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is either defined + * by an array of Cartesian points, or a polygon hierarchy. + * + * @alias PolygonGeometry + * @constructor + * + * @param {Object} options.polygonHierarchy A polygon hierarchy that can include holes. + * @param {Number} [options.height=0.0] The height of the polygon. + * @param {Number} [options.extrudedHeight] The height of the polygon. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * + * @exception {DeveloperError} polygonHierarchy is required. + * @exception {DeveloperError} At least three positions are required. + * @exception {DeveloperError} Duplicate positions result in not enough positions to form a polygon. + * + * @example + * // create a polygon from points + * var geometry = new PolygonGeometry({ + * polygonHierarchy : { + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0), + * Cartographic.fromDegrees(-75.0, 30.0), + * Cartographic.fromDegrees(-70.0, 30.0), + * Cartographic.fromDegrees(-68.0, 40.0) + * ]) + * } + * }); + * + * // create a nested polygon with holes + * var geometryWithHole = new PolygonGeometry({ + * polygonHierarchy : { + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-109.0, 30.0), + * Cartographic.fromDegrees(-95.0, 30.0), + * Cartographic.fromDegrees(-95.0, 40.0), + * Cartographic.fromDegrees(-109.0, 40.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-107.0, 31.0), + * Cartographic.fromDegrees(-107.0, 39.0), + * Cartographic.fromDegrees(-97.0, 39.0), + * Cartographic.fromDegrees(-97.0, 31.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-105.0, 33.0), + * Cartographic.fromDegrees(-99.0, 33.0), + * Cartographic.fromDegrees(-99.0, 37.0), + * Cartographic.fromDegrees(-105.0, 37.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-103.0, 34.0), + * Cartographic.fromDegrees(-101.0, 34.0), + * Cartographic.fromDegrees(-101.0, 36.0), + * Cartographic.fromDegrees(-103.0, 36.0) + * ]) + * }] + * }] + * }] + * } + * }); + * + * //create extruded polygon + * var geometry = new Cesium.PolygonGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cesium.Cartographic.fromDegrees(-72.0, 40.0), + * Cesium.Cartographic.fromDegrees(-70.0, 35.0), + * Cesium.Cartographic.fromDegrees(-75.0, 30.0), + * Cesium.Cartographic.fromDegrees(-70.0, 30.0), + * Cesium.Cartographic.fromDegrees(-68.0, 40.0) + * ]), + * extrudedHeight: 300000 + * }); + * + */ + var PolygonGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var height = defaultValue(options.height, 0.0); + + var extrudedHeight = defaultValue(options.extrudedHeight, undefined); + var extrude = (typeof extrudedHeight !== 'undefined' && !CesiumMath.equalsEpsilon(height, extrudedHeight, CesiumMath.EPSILON6)); + if (extrude) { + var h = extrudedHeight; + extrudedHeight = Math.min(h, height); + height = Math.max(h, height); + } + + var polygonHierarchy = options.polygonHierarchy; + if (typeof polygonHierarchy === 'undefined') { + throw new DeveloperError('options.polygonHierarchy is required.'); + } + + var boundingSphere; + var outerPositions; + + // create from a polygon hierarchy + // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf + var polygons = []; + var queue = new Queue(); + queue.enqueue(polygonHierarchy); + var i; + while (queue.length !== 0) { + var outerNode = queue.dequeue(); + var outerRing = outerNode.positions; + + if (outerRing.length < 3) { + throw new DeveloperError('At least three positions are required.'); + } + + var numChildren = outerNode.holes ? outerNode.holes.length : 0; + // The outer polygon contains inner polygons + for (i = 0; i < numChildren; i++) { + var hole = outerNode.holes[i]; + polygons.push(hole.positions); + + var numGrandchildren = 0; + if (typeof hole.holes !== 'undefined') { + numGrandchildren = hole.holes.length; + } + + for (var j = 0; j < numGrandchildren; j++) { + queue.enqueue(hole.holes[j]); + } + } + + polygons.push(outerRing); + } + + outerPositions = polygons[0]; + // The bounding volume is just around the boundary points, so there could be cases for + // contrived polygons on contrived ellipsoids - very oblate ones - where the bounding + // volume doesn't cover the polygon. + boundingSphere = BoundingSphere.fromPoints(outerPositions); + + var geometry; + var geometries = []; + + if (extrude) { + for (i = 0; i < polygons.length; i++) { + geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity); + if (typeof geometry !== 'undefined') { + geometry.geometry = scaleToGeodeticHeightExtruded(geometry.geometry, height, extrudedHeight, ellipsoid); + geometries.push(geometry); + } + } + } else { + for (i = 0; i < polygons.length; i++) { + geometry = createGeometryFromPositions(ellipsoid, polygons[i], granularity); + if (typeof geometry !== 'undefined') { + geometry.geometry = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry, height, ellipsoid); + geometries.push(geometry); + } + } + } + + geometry = GeometryPipeline.combine(geometries); + + var center = boundingSphere.center; + scratchNormal = ellipsoid.geodeticSurfaceNormal(center, scratchNormal); + scratchPosition = Cartesian3.multiplyByScalar(scratchNormal, height, scratchPosition); + center = Cartesian3.add(center, scratchPosition, center); + + if (extrude) { + scratchBoundingSphere = boundingSphere.clone(scratchBoundingSphere); + center = scratchBoundingSphere.center; + scratchPosition = Cartesian3.multiplyByScalar(scratchNormal, extrudedHeight, scratchPosition); + center = Cartesian3.add(center, scratchPosition, center); + boundingSphere = BoundingSphere.union(boundingSphere, scratchBoundingSphere, boundingSphere); + } + + geometry.attributes.position.values = new Float64Array(geometry.attributes.position.values); + geometry.indices = IndexDatatype.createTypedArray(geometry.attributes.position.values.length / 3, geometry.indices); + + var attributes = geometry.attributes; + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = geometry.indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = geometry.primitiveType; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = boundingSphere; + }; + + /** + * Creates a polygon from an array of positions. + * + * @memberof PolygonGeometry + * + * @param {Array} options.positions An array of positions that defined the corner points of the polygon. + * @param {Number} [options.height=0.0] The height of the polygon. + * @param {Number} [options.extrudedHeight] The height of the polygon extrusion. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Number} [options.stRotation=0.0] The rotation of the texture coordiantes, in radians. A positive rotation is counter-clockwise. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * + * @exception {DeveloperError} options.positions is required. + * @exception {DeveloperError} At least three positions are required. + * @exception {DeveloperError} Duplicate positions result in not enough positions to form a polygon. + * + * @example + * // create a polygon from points + * var geometry = new PolygonGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0), + * Cartographic.fromDegrees(-75.0, 30.0), + * Cartographic.fromDegrees(-70.0, 30.0), + * Cartographic.fromDegrees(-68.0, 40.0) + * ]) + * }); + */ + PolygonGeometry.fromPositions = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.positions === 'undefined') { + throw new DeveloperError('options.positions is required.'); + } + + var newOptions = { + polygonHierarchy : { + positions : options.positions + }, + height : options.height, + extrudedHeight : options.extrudedHeight, + ellipsoid : options.ellipsoid, + granularity : options.granularity + }; + return new PolygonGeometry(newOptions); + }; + + return PolygonGeometry; +}); \ No newline at end of file From d75900203533d781046ba37c22ac1f366b0bff21 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 2 Aug 2013 12:25:19 -0400 Subject: [PATCH 05/25] outline extent --- Source/Core/BoxOutlineGeometry.js | 6 +- Source/Core/CylinderOutlineGeometry.js | 6 +- Source/Core/ExtentGeometry.js | 22 +- Source/Core/ExtentOutlineGeometry.js | 407 +++++++++++++++++++++++++ Source/Core/PolygonGeometry.js | 4 - Source/Core/PolygonOutlineGeometry.js | 14 - 6 files changed, 420 insertions(+), 39 deletions(-) create mode 100644 Source/Core/ExtentOutlineGeometry.js diff --git a/Source/Core/BoxOutlineGeometry.js b/Source/Core/BoxOutlineGeometry.js index efe494e0a761..723047cc80b2 100644 --- a/Source/Core/BoxOutlineGeometry.js +++ b/Source/Core/BoxOutlineGeometry.js @@ -7,8 +7,7 @@ define([ './defaultValue', './BoundingSphere', './GeometryAttribute', - './GeometryAttributes', - './VertexFormat' + './GeometryAttributes' ], function( DeveloperError, Cartesian3, @@ -17,8 +16,7 @@ define([ defaultValue, BoundingSphere, GeometryAttribute, - GeometryAttributes, - VertexFormat) { + GeometryAttributes) { "use strict"; var diffScratch = new Cartesian3(); diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index a87c3a45a7eb..0e232d9318e3 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -10,8 +10,7 @@ define([ './PrimitiveType', './BoundingSphere', './GeometryAttribute', - './GeometryAttributes', - './VertexFormat' + './GeometryAttributes' ], function( defaultValue, DeveloperError, @@ -23,8 +22,7 @@ define([ PrimitiveType, BoundingSphere, GeometryAttribute, - GeometryAttributes, - VertexFormat) { + GeometryAttributes) { "use strict"; var radiusScratch = new Cartesian2(); diff --git a/Source/Core/ExtentGeometry.js b/Source/Core/ExtentGeometry.js index 3586924c7f30..d64c7c786c2c 100644 --- a/Source/Core/ExtentGeometry.js +++ b/Source/Core/ExtentGeometry.js @@ -1,6 +1,5 @@ /*global define*/ define([ - './clone', './defaultValue', './BoundingSphere', './Cartesian2', @@ -24,7 +23,6 @@ define([ './Quaternion', './VertexFormat' ], function( - clone, defaultValue, BoundingSphere, Cartesian2, @@ -332,7 +330,7 @@ define([ return wallPositions; } - function constructExtent(options, vertexFormat, params) { + function constructExtent(vertexFormat, params) { var ellipsoid = params.ellipsoid; var size = params.size; var height = params.height; @@ -401,26 +399,24 @@ define([ } return { - boundingSphere : BoundingSphere.fromExtent3D(options.extent, ellipsoid, surfaceHeight), + boundingSphere : BoundingSphere.fromExtent3D(params.extent, ellipsoid, surfaceHeight), geometry : geo }; } - - function constructExtrudedExtent(options, vertexFormat, params) { + function constructExtrudedExtent(extrudedOptions, vertexFormat, params) { var surfaceHeight = params.surfaceHeight; var height = params.height; var width = params.width; var size = params.size; var ellipsoid = params.ellipsoid; - var extrudedOptions = options.extrudedOptions; if (typeof extrudedOptions.height !== 'number') { - return constructExtent(options, vertexFormat, params); + return constructExtent(vertexFormat, params); } var minHeight = Math.min(extrudedOptions.height, surfaceHeight); var maxHeight = Math.max(extrudedOptions.height, surfaceHeight); if (CesiumMath.equalsEpsilon(minHeight, maxHeight, 0.1)) { - return constructExtent(options, vertexFormat, params); + return constructExtent(vertexFormat, params); } var closeTop = defaultValue(extrudedOptions.closeTop, true); @@ -608,8 +604,8 @@ define([ ]); } - var topBS = BoundingSphere.fromExtent3D(options.extent, ellipsoid, maxHeight, topBoundingSphere); - var bottomBS = BoundingSphere.fromExtent3D(options.extent, ellipsoid, minHeight, bottomBoundingSphere); + var topBS = BoundingSphere.fromExtent3D(params.extent, ellipsoid, maxHeight, topBoundingSphere); + var bottomBS = BoundingSphere.fromExtent3D(params.extent, ellipsoid, minHeight, bottomBoundingSphere); var boundingSphere = BoundingSphere.union(topBS, bottomBS); return { @@ -766,9 +762,9 @@ define([ var extentGeometry; if (typeof options.extrudedOptions !== 'undefined') { - extentGeometry = constructExtrudedExtent(options, vertexFormat, params); + extentGeometry = constructExtrudedExtent(options.extrudedOptions, vertexFormat, params); } else { - extentGeometry = constructExtent(options, vertexFormat, params); + extentGeometry = constructExtent(vertexFormat, params); } var geometry = extentGeometry.geometry; diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js new file mode 100644 index 000000000000..68a0448f94db --- /dev/null +++ b/Source/Core/ExtentOutlineGeometry.js @@ -0,0 +1,407 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingSphere', + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './DeveloperError', + './Ellipsoid', + './GeographicProjection', + './GeometryAttribute', + './GeometryAttributes', + './Math', + './Matrix2', + './PrimitiveType' + ], function( + defaultValue, + BoundingSphere, + Cartesian3, + Cartographic, + ComponentDatatype, + DeveloperError, + Ellipsoid, + GeographicProjection, + GeometryAttribute, + GeometryAttributes, + CesiumMath, + Matrix2, + PrimitiveType) { + "use strict"; + + function isValidLatLon(latitude, longitude) { + if (latitude < -CesiumMath.PI_OVER_TWO || latitude > CesiumMath.PI_OVER_TWO) { + return false; + } + if (longitude > CesiumMath.PI || longitude < -CesiumMath.PI) { + return false; + } + return true; + } + + var nw = new Cartesian3(); + var nwCartographic = new Cartographic(); + var centerCartographic = new Cartographic(); + var center = new Cartesian3(); + var rotationMatrix = new Matrix2(); + var proj = new GeographicProjection(); + var position = new Cartesian3(); + var extrudedPosition = new Cartesian3(); + var bottomBoundingSphere = new BoundingSphere(); + var topBoundingSphere = new BoundingSphere(); + + var cos = Math.cos; + var sin = Math.sin; + var sqrt = Math.sqrt; + + var stLatitude, stLongitude; + + function computePosition(params, row, col, maxHeight, minHeight) { + var radiiSquared = params.radiiSquared; + + stLatitude = nwCartographic.latitude - params.granYCos * row + col * params.granXSin; + var cosLatitude = cos(stLatitude); + var nZ = sin(stLatitude); + var kZ = radiiSquared.z * nZ; + + stLongitude = nwCartographic.longitude + row * params.granYSin + col * params.granXCos; + var nX = cosLatitude * cos(stLongitude); + var nY = cosLatitude * sin(stLongitude); + + var kX = radiiSquared.x * nX; + var kY = radiiSquared.y * nY; + + var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ)); + + var rSurfaceX = kX / gamma; + var rSurfaceY = kY / gamma; + var rSurfaceZ = kZ / gamma; + + if (typeof maxHeight !== 'undefined') { + position.x = rSurfaceX + nX * maxHeight; // top + position.y = rSurfaceY + nY * maxHeight; + position.z = rSurfaceZ + nZ * maxHeight; + } + + if (typeof minHeight !== 'undefined') { + extrudedPosition.x = rSurfaceX + nX * minHeight; // bottom + extrudedPosition.y = rSurfaceY + nY * minHeight; + extrudedPosition.z = rSurfaceZ + nZ * minHeight; + } + } + + function constructExtent(params) { + var extent = params.extent; + var ellipsoid = params.ellipsoid; + var size = params.size; + var height = params.height; + var width = params.width; + var surfaceHeight = params.surfaceHeight; + + var positions = new Float64Array(size * 3); + + var posIndex = 0; + var row = 0; + var col; + for (col = 0; col < width; col++) { + computePosition(params, row, col, surfaceHeight); + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + col = width - 1; + for (row = 1; row < height; row++) { + computePosition(params, row, col, surfaceHeight); + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + row = height - 1; + for (col = width-2; col >=0; col--){ + computePosition(params, row, col, surfaceHeight); + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + col = 0; + for (row = height - 2; row > 0; row--) { + computePosition(params, row, col, surfaceHeight); + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + var indices = []; + for(var i = 0; i < (positions.length/3)-1; i++) { + indices.push(i, i+1); + } + indices.push((positions.length/3)-1, 0); + + return { + boundingSphere : BoundingSphere.fromExtent3D(extent, ellipsoid, surfaceHeight), + positions: positions, + indices: indices + }; + } + + function constructExtrudedExtent(params, extrudedHeight) { + var surfaceHeight = params.surfaceHeight; + if (typeof extrudedHeight !== 'number') { + return constructExtent(params); + } + var minHeight = Math.min(extrudedHeight, surfaceHeight); + var maxHeight = Math.max(extrudedHeight, surfaceHeight); + if (CesiumMath.equalsEpsilon(minHeight, maxHeight, 0.1)) { + return constructExtent(params); + } + var extent = params.extent; + var height = params.height; + var width = params.width; + var size = params.size; + var ellipsoid = params.ellipsoid; + + var posIndex = 0; + var row = 0; + var col; + var positions = new Float64Array(size * 2 * 3); + for (col = 0; col < width; col++) { + computePosition(params, row, col, maxHeight, minHeight); + + positions[posIndex + size*3] = extrudedPosition.x; + positions[posIndex + size*3 + 1] = extrudedPosition.y; + positions[posIndex + size*3 + 2] = extrudedPosition.z; + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + col = width - 1; + for (row = 1; row < height; row++) { + computePosition(params, row, col, maxHeight, minHeight); + + positions[posIndex + size*3] = extrudedPosition.x; + positions[posIndex + size*3 + 1] = extrudedPosition.y; + positions[posIndex + size*3 + 2] = extrudedPosition.z; + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + row = height - 1; + for (col = width-2; col >=0; col--){ + computePosition(params, row, col, maxHeight, minHeight); + + positions[posIndex + size*3] = extrudedPosition.x; + positions[posIndex + size*3 + 1] = extrudedPosition.y; + positions[posIndex + size*3 + 2] = extrudedPosition.z; + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + col = 0; + for (row = height - 2; row > 0; row--) { + computePosition(params, row, col, maxHeight, minHeight); + + positions[posIndex + size*3] = extrudedPosition.x; + positions[posIndex + size*3 + 1] = extrudedPosition.y; + positions[posIndex + size*3 + 2] = extrudedPosition.z; + + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; + } + + var indices = []; + var length = positions.length/6; + for (var i = 0; i < length - 1; i++) { + indices.push(i, i+1); + indices.push(i + length, i + length + 1); + } + indices.push(length - 1, 0); + indices.push(length + length - 1, length); + + indices.push(0, length); + indices.push(width-1, length + width-1); + indices.push(width + height - 2, width + height - 2 + length); + indices.push(2*width + height - 3, 2*width + height - 3 + length); + + + var topBS = BoundingSphere.fromExtent3D(extent, ellipsoid, maxHeight, topBoundingSphere); + var bottomBS = BoundingSphere.fromExtent3D(extent, ellipsoid, minHeight, bottomBoundingSphere); + var boundingSphere = BoundingSphere.union(topBS, bottomBS); + + return { + boundingSphere : boundingSphere, + positions: positions, + indices: indices + }; + } + + /** + * A {@link Geometry} that represents geometry for a cartographic extent on an ellipsoid centered at the origin. + * + * @alias ExtentGeometry + * @constructor + * + * @param {Extent} options.extent A cartographic extent with north, south, east and west properties in radians. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the extent lies. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * @param {Number} [options.height=0.0] The height from the surface of the ellipsoid. + * @param {Number} [options.rotation=0.0] The rotation of the extent, in radians. A positive rotation is counter-clockwise. + * @param {Object} [options.extrudedOptions] Extruded options + * @param {Number} [options.extrudedOptions.height] Height of extruded surface. + * @param {Boolean} [options.extrudedOptions.closeTop=true] true to render top of the extruded extent; false otherwise. + * @param {Boolean} [options.extrudedOptions.closeBottom=true] true to render bottom of the extruded extent; false otherwise. + * + * @exception {DeveloperError} options.extent is required and must have north, south, east and west attributes. + * @exception {DeveloperError} options.extent.north must be in the interval [-Pi/2, Pi/2]. + * @exception {DeveloperError} options.extent.south must be in the interval [-Pi/2, Pi/2]. + * @exception {DeveloperError} options.extent.east must be in the interval [-Pi, Pi]. + * @exception {DeveloperError} options.extent.west must be in the interval [-Pi, Pi]. + * @exception {DeveloperError} options.extent.north must be greater than extent.south. + * @exception {DeveloperError} options.extent.east must be greater than extent.west. + * @exception {DeveloperError} Rotated extent is invalid. + * + * @example + * var extent = new ExtentGeometry({ + * ellipsoid : Ellipsoid.WGS84, + * extent : Extent.fromDegrees(-80.0, 39.0, -74.0, 42.0), + * height : 10000.0 + * }); + */ + var ExtentGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var extent = options.extent; + if (typeof extent === 'undefined') { + throw new DeveloperError('extent is required.'); + } + + extent.validate(); + + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var width = Math.ceil((extent.east - extent.west) / granularity) + 1; + var height = Math.ceil((extent.north - extent.south) / granularity) + 1; + var granularityX = (extent.east - extent.west) / (width - 1); + var granularityY = (extent.north - extent.south) / (height - 1); + + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var radiiSquared = ellipsoid.getRadiiSquared(); + + var surfaceHeight = defaultValue(options.height, 0.0); + var rotation = options.rotation; + + extent.getNorthwest(nwCartographic); + extent.getCenter(centerCartographic); + + var granYCos = granularityY; + var granXCos = granularityX; + var granYSin = 0.0; + var granXSin = 0.0; + + if (typeof rotation !== 'undefined') { + var cosRotation = cos(rotation); + granYCos *= cosRotation; + granXCos *= cosRotation; + + var sinRotation = sin(rotation); + granYSin = granularityY * sinRotation; + granXSin = granularityX * sinRotation; + + proj.project(nwCartographic, nw); + proj.project(centerCartographic, center); + + nw.subtract(center, nw); + Matrix2.fromRotation(rotation, rotationMatrix); + rotationMatrix.multiplyByVector(nw, nw); + nw.add(center, nw); + proj.unproject(nw, nwCartographic); + + var latitude = nwCartographic.latitude; + var latitude0 = latitude + (width - 1) * granXSin; + var latitude1 = latitude - granYCos * (height - 1); + var latitude2 = latitude - granYCos * (height - 1) + (width - 1) * granXSin; + + var north = Math.max(latitude, latitude0, latitude1, latitude2); + var south = Math.min(latitude, latitude0, latitude1, latitude2); + + var longitude = nwCartographic.longitude; + var longitude0 = longitude + (width - 1) * granXCos; + var longitude1 = longitude + (height - 1) * granYSin; + var longitude2 = longitude + (height - 1) * granYSin + (width - 1) * granXCos; + + var east = Math.max(longitude, longitude0, longitude1, longitude2); + var west = Math.min(longitude, longitude0, longitude1, longitude2); + + if (!isValidLatLon(north, west) || !isValidLatLon(north, east) || + !isValidLatLon(south, west) || !isValidLatLon(south, east)) { + throw new DeveloperError('Rotated extent is invalid.'); + } + } + + var size = 2*width + 2*height - 4; + + var params = { + granYCos : granYCos, + granYSin : granYSin, + granXCos : granXCos, + granXSin : granXSin, + radiiSquared : radiiSquared, + ellipsoid : ellipsoid, + extent : extent, + width : width, + height : height, + surfaceHeight : surfaceHeight, + size : size + }; + + var extentGeometry; + if (typeof options.extrudedHeight !== 'undefined') { + extentGeometry = constructExtrudedExtent(params, options.extrudedHeight); + } else { + extentGeometry = constructExtent(params); + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : extentGeometry.positions + }) + }); + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = new Uint32Array(extentGeometry.indices); + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = extentGeometry.boundingSphere; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + }; + + return ExtentGeometry; +}); diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index fd0149b60d9a..5518e7e51fb1 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -5,7 +5,6 @@ define([ './BoundingSphere', './Cartesian2', './Cartesian3', - './Cartesian4', './ComponentDatatype', './DeveloperError', './Ellipsoid', @@ -16,7 +15,6 @@ define([ './GeometryInstance', './GeometryPipeline', './IndexDatatype', - './Intersect', './Math', './Matrix3', './PolygonPipeline', @@ -31,7 +29,6 @@ define([ BoundingSphere, Cartesian2, Cartesian3, - Cartesian4, ComponentDatatype, DeveloperError, Ellipsoid, @@ -42,7 +39,6 @@ define([ GeometryInstance, GeometryPipeline, IndexDatatype, - Intersect, CesiumMath, Matrix3, PolygonPipeline, diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index 4491afc7a1e0..eea9327bfa6a 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -1,11 +1,8 @@ /*global define*/ define([ './defaultValue', - './BoundingRectangle', './BoundingSphere', - './Cartesian2', './Cartesian3', - './Cartesian4', './ComponentDatatype', './DeveloperError', './Ellipsoid', @@ -16,22 +13,15 @@ define([ './GeometryInstance', './GeometryPipeline', './IndexDatatype', - './Intersect', './Math', - './Matrix3', './PolygonPipeline', './PrimitiveType', - './Quaternion', './Queue', - './VertexFormat', './WindingOrder' ], function( defaultValue, - BoundingRectangle, BoundingSphere, - Cartesian2, Cartesian3, - Cartesian4, ComponentDatatype, DeveloperError, Ellipsoid, @@ -42,14 +32,10 @@ define([ GeometryInstance, GeometryPipeline, IndexDatatype, - Intersect, CesiumMath, - Matrix3, PolygonPipeline, PrimitiveType, - Quaternion, Queue, - VertexFormat, WindingOrder) { "use strict"; var createGeometryFromPositionsPositions = []; From eb5d30a6b3f725a320c0ceec26a00179ef9d61a2 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 2 Aug 2013 13:57:08 -0400 Subject: [PATCH 06/25] change extent extruded params --- .../gallery/Geometry and Appearances.html | 4 +- Source/Core/ExtentGeometry.js | 46 +++++++++++++------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 84078813c3df..4951fe4344c1 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -111,9 +111,7 @@ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, height: 700000, rotation: Cesium.Math.toRadians(45.0), - extrudedOptions: { - height: 1000000 - } + extrudedHeight: 1000000 }), attributes: { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) diff --git a/Source/Core/ExtentGeometry.js b/Source/Core/ExtentGeometry.js index d64c7c786c2c..7f79b813813b 100644 --- a/Source/Core/ExtentGeometry.js +++ b/Source/Core/ExtentGeometry.js @@ -404,23 +404,26 @@ define([ }; } - function constructExtrudedExtent(extrudedOptions, vertexFormat, params) { + function constructExtrudedExtent(vertexFormat, params) { var surfaceHeight = params.surfaceHeight; - var height = params.height; - var width = params.width; - var size = params.size; - var ellipsoid = params.ellipsoid; - if (typeof extrudedOptions.height !== 'number') { + var extrudedHeight = params.extrudedHeight; + + if (typeof extrudedHeight !== 'number') { return constructExtent(vertexFormat, params); } - var minHeight = Math.min(extrudedOptions.height, surfaceHeight); - var maxHeight = Math.max(extrudedOptions.height, surfaceHeight); + var minHeight = Math.min(extrudedHeight, surfaceHeight); + var maxHeight = Math.max(extrudedHeight, surfaceHeight); if (CesiumMath.equalsEpsilon(minHeight, maxHeight, 0.1)) { return constructExtent(vertexFormat, params); } - var closeTop = defaultValue(extrudedOptions.closeTop, true); - var closeBottom = defaultValue(extrudedOptions.closeBottom, true); + var height = params.height; + var width = params.width; + var size = params.size; + var ellipsoid = params.ellipsoid; + + var closeTop = defaultValue(params.closeTop, true); + var closeBottom = defaultValue(params.closeBottom, true); var perimeterPositions = 2 * width + 2 * height - 4; var wallCount = (perimeterPositions + 4) * 2; @@ -628,9 +631,9 @@ define([ * @param {Number} [options.rotation=0.0] The rotation of the extent, in radians. A positive rotation is counter-clockwise. * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise. * @param {Object} [options.extrudedOptions] Extruded options - * @param {Number} [options.extrudedOptions.height] Height of extruded surface. - * @param {Boolean} [options.extrudedOptions.closeTop=true] true to render top of the extruded extent; false otherwise. - * @param {Boolean} [options.extrudedOptions.closeBottom=true] true to render bottom of the extruded extent; false otherwise. + * @param {Number} [options.extrudedHeight] Height of extruded surface. + * @param {Boolean} [options.closeTop=true] true to render top of an extruded extent; false otherwise. (Only applicable if options.extrudedHeight is not equal to options.height.) + * @param {Boolean} [options.closeBottom=true] true to render bottom of an extruded extent; false otherwise. (Only applicable if options.extrudedHeight is not equal to options.height.) * * @exception {DeveloperError} options.extent is required and must have north, south, east and west attributes. * @exception {DeveloperError} options.extent.north must be in the interval [-Pi/2, Pi/2]. @@ -642,11 +645,21 @@ define([ * @exception {DeveloperError} Rotated extent is invalid. * * @example + * //an extent * var extent = new ExtentGeometry({ * ellipsoid : Ellipsoid.WGS84, * extent : Extent.fromDegrees(-80.0, 39.0, -74.0, 42.0), * height : 10000.0 * }); + * + * //an extruded extent without a top + * var extent = new ExtentGeometry({ + * ellipsoid : Ellipsoid.WGS84, + * extent : Extent.fromDegrees(-80.0, 39.0, -74.0, 42.0), + * height : 10000.0, + * extrudedHieght: 300000, + * closeTop: false + * }); */ var ExtentGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -761,8 +774,11 @@ define([ }; var extentGeometry; - if (typeof options.extrudedOptions !== 'undefined') { - extentGeometry = constructExtrudedExtent(options.extrudedOptions, vertexFormat, params); + if (typeof options.extrudedHeight !== 'undefined') { + params.extrudedHeight = options.extrudedHeight; + params.closeTop = options.closeTop; + params.closeBottom = options.closeBottom; + extentGeometry = constructExtrudedExtent(vertexFormat, params); } else { extentGeometry = constructExtent(vertexFormat, params); } From 33f6cc10b183058284ca466db1c8e0005b16a061 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 2 Aug 2013 16:17:13 -0400 Subject: [PATCH 07/25] ellipse outline --- Source/Core/EllipseOutlineGeometry.js | 412 ++++++++++++++++++++++++++ Source/Core/ExtentOutlineGeometry.js | 8 +- Source/Core/PolygonOutlineGeometry.js | 20 +- 3 files changed, 426 insertions(+), 14 deletions(-) create mode 100644 Source/Core/EllipseOutlineGeometry.js diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js new file mode 100644 index 000000000000..f597601a51ee --- /dev/null +++ b/Source/Core/EllipseOutlineGeometry.js @@ -0,0 +1,412 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingSphere', + './Cartesian3', + './ComponentDatatype', + './IndexDatatype', + './DeveloperError', + './Ellipsoid', + './GeometryAttribute', + './GeometryAttributes', + './Math', + './Matrix3', + './PrimitiveType', + './Quaternion' + ], function( + defaultValue, + BoundingSphere, + Cartesian3, + ComponentDatatype, + IndexDatatype, + DeveloperError, + Ellipsoid, + GeometryAttribute, + GeometryAttributes, + CesiumMath, + Matrix3, + PrimitiveType, + Quaternion) { + "use strict"; + + var rotAxis = new Cartesian3(); + var tempVec = new Cartesian3(); + var unitQuat = new Quaternion(); + var rotMtx = new Matrix3(); + + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + + var scratchNormal = new Cartesian3(); + + var unitPosScratch = new Cartesian3(); + var eastVecScratch = new Cartesian3(); + var northVecScratch = new Cartesian3(); + + function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { + var azimuth = theta + rotation; + + Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); + Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec); + Cartesian3.add(rotAxis, tempVec, rotAxis); + + var cosThetaSquared = Math.cos(theta); + cosThetaSquared = cosThetaSquared * cosThetaSquared; + + var sinThetaSquared = Math.sin(theta); + sinThetaSquared = sinThetaSquared * sinThetaSquared; + + var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared); + var angle = radius / mag; + + // Create the quaternion to rotate the position vector to the boundary of the ellipse. + Quaternion.fromAxisAngle(rotAxis, angle, unitQuat); + Matrix3.fromQuaternion(unitQuat, rotMtx); + + Matrix3.multiplyByVector(rotMtx, unitPos, result); + Cartesian3.normalize(result, result); + Cartesian3.multiplyByScalar(result, mag, result); + return result; + } + + function raisePositionsToHeight(positions, options, extrude) { + var ellipsoid = options.ellipsoid; + var height = options.height; + var extrudedHeight = options.extrudedHeight; + var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3; + + var finalPositions = new Float64Array(size * 3); + var normal = scratchNormal; + + var length = positions.length; + var bottomOffset = (extrude) ? length : 0; + for ( var i = 0; i < length; i += 3) { + var i1 = i + 1; + var i2 = i + 2; + var position = Cartesian3.fromArray(positions, i, scratchCartesian1); + var extrudedPosition; + + position = ellipsoid.scaleToGeodeticSurface(position, position); + extrudedPosition = position.clone(scratchCartesian2); + normal = ellipsoid.geodeticSurfaceNormal(position, normal); + var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian3); + position = Cartesian3.add(position, scaledNormal, position); + + if (extrude) { + scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal); + extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition); + + finalPositions[i + bottomOffset] = extrudedPosition.x; + finalPositions[i1 + bottomOffset] = extrudedPosition.y; + finalPositions[i2 + bottomOffset] = extrudedPosition.z; + } + + finalPositions[i] = position.x; + finalPositions[i1] = position.y; + finalPositions[i2] = position.z; + } + + var attributes = new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : finalPositions + }) + }); + + return attributes; + } + + function computeEllipsePositions(options) { + var semiMinorAxis = options.semiMinorAxis; + var semiMajorAxis = options.semiMajorAxis; + var rotation = options.rotation; + var center = options.center; + var granularity = options.granularity; + + var MAX_ANOMALY_LIMIT = 2.31; + + var aSqr = semiMinorAxis * semiMinorAxis; + var bSqr = semiMajorAxis * semiMajorAxis; + var ab = semiMajorAxis * semiMinorAxis; + + var mag = center.magnitude(); + + var unitPos = Cartesian3.normalize(center, unitPosScratch); + var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch); + eastVec = Cartesian3.normalize(eastVec, eastVec); + var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch); + + // The number of points in the first quadrant + var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); + var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); + + // If the number of points were three, the ellipse + // would be tessellated like below: + // + // *---* + // / | \ | \ + // *---*---*---* + // / | \ | \ | \ | \ + // *---*---*---*---*---* + // | \ | \ | \ | \ | \ | + // *---*---*---*---*---* + // \ | \ | \ | \ | / + // *---*---*---* + // \ | \ | / + // *---* + // Notice each vertical column contains an even number of positions. + // The sum of the first n even numbers is n * (n + 1). Double it for the number of points + // for the whole ellipse. Note: this is just an estimate and may actually be less depending + // on the number of iterations before the angle reaches pi/2. + var position = scratchCartesian1; + var reflectedPosition = scratchCartesian2; + + var outerLeft = []; + var outerRight = []; + + var i; + + // Compute points in the 'northern' half of the ellipse + var theta = CesiumMath.PI_OVER_TWO; + for (i = 0; i < numPts && theta > 0; ++i) { + position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + outerRight.unshift(position.x, position.y, position.z); + if (i !== 0) { + outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); + } + + theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta; + } + + // Set numPts if theta reached zero + numPts = i; + + // Compute points in the 'southern' half of the ellipse + for (i = numPts; i > 0; --i) { + theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; + + position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + outerRight.unshift(position.x, position.y, position.z); + if (i !== 1) { + outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); + } + } + + return outerRight.concat(outerLeft); + } + + var boundingSphereCenter = new Cartesian3(); + function computeEllipse(options) { + var center = options.center; + boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter); + boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter); + var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis); + var positions = computeEllipsePositions(options); + var attributes = raisePositionsToHeight(positions, options, false); + + var indices = []; + for (var i = 0; i < positions.length/3 - 1; i++) { + indices.push(i, i+1); + } + indices.push(positions.length/3 - 1, 0); + + indices = IndexDatatype.createTypedArray(positions.length / 3, indices); + return { + boundingSphere : boundingSphere, + attributes : attributes, + indices : indices + }; + } + + var topBoundingSphere = new BoundingSphere(); + var bottomBoundingSphere = new BoundingSphere(); + function computeExtrudedEllipse(options) { + var numLengthLines = defaultValue(options.numLengthLines, 10); + if (numLengthLines < 0) { + throw new DeveloperError('options.numLengthLines cannot be less than zero'); + } + + var center = options.center; + var ellipsoid = options.ellipsoid; + var semiMajorAxis = options.semiMajorAxis; + var scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scratchCartesian1), options.height, scratchCartesian1); + topBoundingSphere.center = Cartesian3.add(center, scaledNormal, topBoundingSphere.center); + topBoundingSphere.radius = semiMajorAxis; + + scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scaledNormal), options.extrudedHeight, scaledNormal); + bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center); + bottomBoundingSphere.radius = semiMajorAxis; + + var positions = computeEllipsePositions(options); + var attributes = raisePositionsToHeight(positions, options, true); + + var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere); + + var indices = []; + + var length = attributes.position.values.length/6; + for (var i = 0; i < length - 1; i++) { + indices.push(i, i + 1); + indices.push(i + length, i + length + 1); + } + indices.push(length - 1, 0); + indices.push(length + length - 1, length); + + var numSide; + if (numLengthLines > 0) { + var numSideLines = Math.min(numLengthLines, length); + numSide = Math.round(length/numSideLines); + } + var maxI = Math.min(numSide*10, length); + if (numLengthLines > 0) { + for (i = 0; i < maxI; i+= numSide){ + indices.push(i, i + length); + } + } + + indices = IndexDatatype.createTypedArray(positions.length / 3, indices); + return { + boundingSphere : boundingSphere, + attributes : attributes, + indices : indices + }; + } + + /** + * + * A {@link Geometry} that represents geometry for an ellipse on an ellipsoid + * + * @alias EllipseOutlineGeometry + * @constructor + * + * @param {Cartesian3} options.center The ellipse's center point in the fixed frame. + * @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters. + * @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on. + * @param {Number} [options.height=0.0] The height above the ellipsoid. + * @param {Number} [options.extrudedHeight] The height of the extrusion. + * @param {Number} [options.rotation=0.0] The angle from north (clockwise) in radians. The default is zero. + * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians. + * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the ellipse + * + * @exception {DeveloperError} center is required. + * @exception {DeveloperError} semiMajorAxis is required. + * @exception {DeveloperError} semiMinorAxis is required. + * @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero. + * @exception {DeveloperError} semiMajorAxis must be larger than the semiMajorAxis. + * @exception {DeveloperError} granularity must be greater than zero. + * + * @example + * // Create an ellipse. + * var ellipsoid = Ellipsoid.WGS84; + * var ellipse = new EllipseOutlineGeometry({ + * ellipsoid : ellipsoid, + * center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883)), + * semiMajorAxis : 500000.0, + * semiMinorAxis : 300000.0, + * rotation : CesiumMath.toRadians(60.0) + * }); + */ + var EllipseOutlineGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var center = options.center; + var semiMajorAxis = options.semiMajorAxis; + var semiMinorAxis = options.semiMinorAxis; + + if (typeof center === 'undefined') { + throw new DeveloperError('center is required.'); + } + + if (typeof semiMajorAxis === 'undefined') { + throw new DeveloperError('semiMajorAxis is required.'); + } + + if (typeof semiMinorAxis === 'undefined') { + throw new DeveloperError('semiMinorAxis is required.'); + } + + if (semiMajorAxis <= 0.0 || semiMinorAxis <= 0.0) { + throw new DeveloperError('Semi-major and semi-minor axes must be greater than zero.'); + } + + if (semiMajorAxis < semiMinorAxis) { + throw new DeveloperError('semiMajorAxis must be larger than the semiMajorAxis.'); + } + + var numLengthLines = defaultValue(options.numLengthLines, 10); + if (numLengthLines < 0) { + throw new DeveloperError('options.numLengthLines cannot be less than zero'); + } + + + var newOptions = { + center : center, + semiMajorAxis : semiMajorAxis, + semiMinorAxis : semiMinorAxis, + ellipsoid : defaultValue(options.ellipsoid, Ellipsoid.WGS84), + rotation : defaultValue(options.rotation, 0.0), + height : defaultValue(options.height, 0.0), + granularity : defaultValue(options.granularity, 0.02), + extrudedHeight : options.extrudedHeight, + numLengthLines : numLengthLines + }; + + if (newOptions.granularity <= 0.0) { + throw new DeveloperError('granularity must be greater than zero.'); + } + + var extrude = (typeof newOptions.extrudedHeight !== 'undefined' && !CesiumMath.equalsEpsilon(newOptions.height, newOptions.extrudedHeight, 1)); + + var ellipseGeometry; + if (extrude) { + var h = newOptions.extrudedHeight; + var height = newOptions.height; + newOptions.extrudedHeight = Math.min(h, height); + newOptions.height = Math.max(h, height); + ellipseGeometry = computeExtrudedEllipse(newOptions); + } else { + ellipseGeometry = computeEllipse(newOptions); + } + + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = ellipseGeometry.attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = ellipseGeometry.indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = ellipseGeometry.boundingSphere; + }; + + return EllipseOutlineGeometry; +}); \ No newline at end of file diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js index 68a0448f94db..4e01188285ad 100644 --- a/Source/Core/ExtentOutlineGeometry.js +++ b/Source/Core/ExtentOutlineGeometry.js @@ -244,7 +244,7 @@ define([ /** * A {@link Geometry} that represents geometry for a cartographic extent on an ellipsoid centered at the origin. * - * @alias ExtentGeometry + * @alias ExtentOutlineGeometry * @constructor * * @param {Extent} options.extent A cartographic extent with north, south, east and west properties in radians. @@ -267,13 +267,13 @@ define([ * @exception {DeveloperError} Rotated extent is invalid. * * @example - * var extent = new ExtentGeometry({ + * var extent = new ExtentOutlineGeometry({ * ellipsoid : Ellipsoid.WGS84, * extent : Extent.fromDegrees(-80.0, 39.0, -74.0, 42.0), * height : 10000.0 * }); */ - var ExtentGeometry = function(options) { + var ExtentOutlineGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var extent = options.extent; @@ -403,5 +403,5 @@ define([ this.primitiveType = PrimitiveType.LINES; }; - return ExtentGeometry; + return ExtentOutlineGeometry; }); diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index eea9327bfa6a..602829c6cfec 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -219,7 +219,7 @@ define([ * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is either defined * by an array of Cartesian points, or a polygon hierarchy. * - * @alias PolygonGeometry + * @alias PolygonOutlineGeometry * @constructor * * @param {Object} options.polygonHierarchy A polygon hierarchy that can include holes. @@ -236,7 +236,7 @@ define([ * * @example * // create a polygon from points - * var geometry = new PolygonGeometry({ + * var geometry = new PolygonOutlineGeometry({ * polygonHierarchy : { * positions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), @@ -249,7 +249,7 @@ define([ * }); * * // create a nested polygon with holes - * var geometryWithHole = new PolygonGeometry({ + * var geometryWithHole = new PolygonOutlineGeometry({ * polygonHierarchy : { * positions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-109.0, 30.0), @@ -285,7 +285,7 @@ define([ * }); * * //create extruded polygon - * var geometry = new Cesium.PolygonGeometry({ + * var geometry = new Cesium.PolygonOutlineGeometry({ * positions : ellipsoid.cartographicArrayToCartesianArray([ * Cesium.Cartographic.fromDegrees(-72.0, 40.0), * Cesium.Cartographic.fromDegrees(-70.0, 35.0), @@ -297,7 +297,7 @@ define([ * }); * */ - var PolygonGeometry = function(options) { + var PolygonOutlineGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); @@ -435,7 +435,7 @@ define([ /** * Creates a polygon from an array of positions. * - * @memberof PolygonGeometry + * @memberof PolygonOutlineGeometry * * @param {Array} options.positions An array of positions that defined the corner points of the polygon. * @param {Number} [options.height=0.0] The height of the polygon. @@ -451,7 +451,7 @@ define([ * * @example * // create a polygon from points - * var geometry = new PolygonGeometry({ + * var geometry = new PolygonOutlineGeometry({ * positions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0), @@ -461,7 +461,7 @@ define([ * ]) * }); */ - PolygonGeometry.fromPositions = function(options) { + PolygonOutlineGeometry.fromPositions = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); if (typeof options.positions === 'undefined') { @@ -477,8 +477,8 @@ define([ ellipsoid : options.ellipsoid, granularity : options.granularity }; - return new PolygonGeometry(newOptions); + return new PolygonOutlineGeometry(newOptions); }; - return PolygonGeometry; + return PolygonOutlineGeometry; }); \ No newline at end of file From 8035cfa386b63a20e9443efde85795c9bb05f5c0 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 2 Aug 2013 17:17:36 -0400 Subject: [PATCH 08/25] circle outline --- Source/Core/CircleOutlineGeometry.js | 100 +++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Source/Core/CircleOutlineGeometry.js diff --git a/Source/Core/CircleOutlineGeometry.js b/Source/Core/CircleOutlineGeometry.js new file mode 100644 index 000000000000..bae6e568625d --- /dev/null +++ b/Source/Core/CircleOutlineGeometry.js @@ -0,0 +1,100 @@ +/*global define*/ +define([ + './clone', + './defaultValue', + './DeveloperError', + './EllipseOutlineGeometry' + ], function( + clone, + defaultValue, + DeveloperError, + EllipseOutlineGeometry) { + "use strict"; + + /** + * A {@link Geometry} that represents vertices and indices for a circle on the ellipsoid. + * + * @alias CircleOutlineGeometry + * @constructor + * + * @param {Cartesian3} options.center The circle's center point in the fixed frame. + * @param {Number} options.radius The radius in meters. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on. + * @param {Number} [options.height=0.0] The height above the ellipsoid. + * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians. + * @param {Number} [options.extrudedHeight=0.0] The height of the extrusion relative to the ellipsoid. + * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the ellipse + * + * @exception {DeveloperError} center is required. + * @exception {DeveloperError} radius is required. + * @exception {DeveloperError} radius must be greater than zero. + * @exception {DeveloperError} granularity must be greater than zero. + * + * @example + * // Create a circle. + * var ellipsoid = Ellipsoid.WGS84; + * var circle = new CircleOutlineGeometry({ + * ellipsoid : ellipsoid, + * center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883)), + * radius : 100000.0 + * }); + */ + var CircleOutlineGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var radius = options.radius; + + if (typeof radius === 'undefined') { + throw new DeveloperError('radius is required.'); + } + + if (radius <= 0.0) { + throw new DeveloperError('radius must be greater than zero.'); + } + + var ellipseGeometryOptions = { + center : options.center, + semiMajorAxis : radius, + semiMinorAxis : radius, + ellipsoid : options.ellipsoid, + height : options.height, + extrudedHeight : options.extrudedHeight, + granularity : options.granularity, + numLengthLines : options.numLengthLines + + }; + var ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = ellipseGeometry.attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = ellipseGeometry.indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = ellipseGeometry.primitiveType; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = ellipseGeometry.boundingSphere; + }; + + return CircleOutlineGeometry; +}); \ No newline at end of file From 64fb274d1a7e0008f4d6cd4069113bb20bc8ea97 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 5 Aug 2013 13:44:42 -0400 Subject: [PATCH 09/25] tests --- Source/Core/CircleGeometry.js | 2 - Source/Core/CircleOutlineGeometry.js | 6 +- Source/Core/CylinderOutlineGeometry.js | 13 +- Source/Core/EllipseGeometry.js | 2 - Source/Core/EllipseOutlineGeometry.js | 22 +- Source/Core/ExtentGeometry.js | 4 - Source/Core/ExtentOutlineGeometry.js | 3 - Source/Core/WallGeometry.js | 2 - ...tlineSpec.js => BoxOutlineGeometrySpec.js} | 18 +- Specs/Core/CircleGeometrySpec.js | 27 +- Specs/Core/CircleOutlineGeometrySpec.js | 95 +++++++ Specs/Core/CylinderGeometrySpec.js | 6 - ...Spec.js => CylinderOutlineGeometrySpec.js} | 45 +-- Specs/Core/EllipseGeometrySpec.js | 2 - Specs/Core/EllipseOutlineGeometrySpec.js | 116 ++++++++ Specs/Core/EllipsoidGeometrySpec.js | 2 - Specs/Core/ExtentGeometrySpec.js | 50 +--- Specs/Core/ExtentOutlineGeometrySpec.js | 128 +++++++++ Specs/Core/PolygonGeometrySpec.js | 16 ++ Specs/Core/PolygonOutlineGeometrySpec.js | 258 ++++++++++++++++++ Specs/Core/SphereGeometrySpec.js | 2 - Specs/Scene/GeometryRenderingSpec.js | 6 +- 22 files changed, 686 insertions(+), 139 deletions(-) rename Specs/Core/{BoxGeometryOutlineSpec.js => BoxOutlineGeometrySpec.js} (78%) create mode 100644 Specs/Core/CircleOutlineGeometrySpec.js rename Specs/Core/{CylinderGeometryOutlineSpec.js => CylinderOutlineGeometrySpec.js} (68%) create mode 100644 Specs/Core/EllipseOutlineGeometrySpec.js create mode 100644 Specs/Core/ExtentOutlineGeometrySpec.js create mode 100644 Specs/Core/PolygonOutlineGeometrySpec.js diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js index 471467b1c84b..378e49d8d98d 100644 --- a/Source/Core/CircleGeometry.js +++ b/Source/Core/CircleGeometry.js @@ -1,11 +1,9 @@ /*global define*/ define([ - './clone', './defaultValue', './DeveloperError', './EllipseGeometry' ], function( - clone, defaultValue, DeveloperError, EllipseGeometry) { diff --git a/Source/Core/CircleOutlineGeometry.js b/Source/Core/CircleOutlineGeometry.js index bae6e568625d..fa78165ea483 100644 --- a/Source/Core/CircleOutlineGeometry.js +++ b/Source/Core/CircleOutlineGeometry.js @@ -1,11 +1,9 @@ /*global define*/ define([ - './clone', './defaultValue', './DeveloperError', './EllipseOutlineGeometry' ], function( - clone, defaultValue, DeveloperError, EllipseOutlineGeometry) { @@ -23,7 +21,7 @@ define([ * @param {Number} [options.height=0.0] The height above the ellipsoid. * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians. * @param {Number} [options.extrudedHeight=0.0] The height of the extrusion relative to the ellipsoid. - * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the ellipse + * @param {Boolean} [options.sideLinesCount = 10] Number of lines to draw between the top and bottom of an extruded circle. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} radius is required. @@ -59,7 +57,7 @@ define([ height : options.height, extrudedHeight : options.extrudedHeight, granularity : options.granularity, - numLengthLines : options.numLengthLines + countSideLines : options.countSideLines }; var ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions); diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index 0e232d9318e3..b6a56960d9fa 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -37,7 +37,7 @@ define([ * @param {Number} options.topRadius The radius of the top of the cylinder * @param {Number} options.bottomRadius The radius of the bottom of the cylinder * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder - * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the cylinder + * @param {Boolean} [options.countSideLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder * * @exception {DeveloperError} options.length must be greater than 0 * @exception {DeveloperError} options.topRadius must be greater than 0 @@ -78,10 +78,7 @@ define([ throw new DeveloperError('options.slices must be greater that 3'); } - var numLengthLines = defaultValue(options.numLengthLines, 10); - if (numLengthLines < 0) { - throw new DeveloperError('options.numLengthLines cannot be less than zero'); - } + var countSideLines = Math.max(defaultValue(options.countSideLines, 10), 0); var topZ = length * 0.5; var bottomZ = -topZ; @@ -112,8 +109,8 @@ define([ } var numIndices = slices * 2; var numSide; - if (numLengthLines > 0) { - var numSideLines = Math.min(numLengthLines, slices); + if (countSideLines > 0) { + var numSideLines = Math.min(countSideLines, slices); numSide = Math.round(slices/numSideLines); numIndices += numSideLines; } @@ -132,7 +129,7 @@ define([ indices[index++] = slices + slices - 1; indices[index++] = slices; - if (numLengthLines > 0) { + if (countSideLines > 0) { for (i = 0; i < slices; i+= numSide){ indices[index++] = i; indices[index++] = i + slices; diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index 4fb00c39fdb9..9d466f720929 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -16,7 +16,6 @@ define([ './GeometryAttribute', './GeometryAttributes', './Math', - './Matrix2', './Matrix3', './PrimitiveType', './Quaternion', @@ -38,7 +37,6 @@ define([ GeometryAttribute, GeometryAttributes, CesiumMath, - Matrix2, Matrix3, PrimitiveType, Quaternion, diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index f597601a51ee..547d2baaae7c 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -227,10 +227,8 @@ define([ var topBoundingSphere = new BoundingSphere(); var bottomBoundingSphere = new BoundingSphere(); function computeExtrudedEllipse(options) { - var numLengthLines = defaultValue(options.numLengthLines, 10); - if (numLengthLines < 0) { - throw new DeveloperError('options.numLengthLines cannot be less than zero'); - } + var countSideLines = defaultValue(options.countSideLines, 10); + countSideLines = Math.max(countSideLines, 0); var center = options.center; var ellipsoid = options.ellipsoid; @@ -259,12 +257,12 @@ define([ indices.push(length + length - 1, length); var numSide; - if (numLengthLines > 0) { - var numSideLines = Math.min(numLengthLines, length); + if (countSideLines > 0) { + var numSideLines = Math.min(countSideLines, length); numSide = Math.round(length/numSideLines); } var maxI = Math.min(numSide*10, length); - if (numLengthLines > 0) { + if (countSideLines > 0) { for (i = 0; i < maxI; i+= numSide){ indices.push(i, i + length); } @@ -293,7 +291,7 @@ define([ * @param {Number} [options.extrudedHeight] The height of the extrusion. * @param {Number} [options.rotation=0.0] The angle from north (clockwise) in radians. The default is zero. * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians. - * @param {Boolean} [options.numLengthLines = 10] Number of edges to draw along the length of the ellipse + * @param {Boolean} [options.sideLinesCount = 10] Number of lines to draw between the top and bottom surface of an extruded ellipse. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} semiMajorAxis is required. @@ -340,12 +338,6 @@ define([ throw new DeveloperError('semiMajorAxis must be larger than the semiMajorAxis.'); } - var numLengthLines = defaultValue(options.numLengthLines, 10); - if (numLengthLines < 0) { - throw new DeveloperError('options.numLengthLines cannot be less than zero'); - } - - var newOptions = { center : center, semiMajorAxis : semiMajorAxis, @@ -355,7 +347,7 @@ define([ height : defaultValue(options.height, 0.0), granularity : defaultValue(options.granularity, 0.02), extrudedHeight : options.extrudedHeight, - numLengthLines : numLengthLines + countSideLines : Math.max(defaultValue(options.countSideLines, 10), 0) }; if (newOptions.granularity <= 0.0) { diff --git a/Source/Core/ExtentGeometry.js b/Source/Core/ExtentGeometry.js index 7f79b813813b..9d7b6d09d3d1 100644 --- a/Source/Core/ExtentGeometry.js +++ b/Source/Core/ExtentGeometry.js @@ -407,10 +407,6 @@ define([ function constructExtrudedExtent(vertexFormat, params) { var surfaceHeight = params.surfaceHeight; var extrudedHeight = params.extrudedHeight; - - if (typeof extrudedHeight !== 'number') { - return constructExtent(vertexFormat, params); - } var minHeight = Math.min(extrudedHeight, surfaceHeight); var maxHeight = Math.max(extrudedHeight, surfaceHeight); if (CesiumMath.equalsEpsilon(minHeight, maxHeight, 0.1)) { diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js index 4e01188285ad..bdcbc51af80c 100644 --- a/Source/Core/ExtentOutlineGeometry.js +++ b/Source/Core/ExtentOutlineGeometry.js @@ -149,9 +149,6 @@ define([ function constructExtrudedExtent(params, extrudedHeight) { var surfaceHeight = params.surfaceHeight; - if (typeof extrudedHeight !== 'number') { - return constructExtent(params); - } var minHeight = Math.min(extrudedHeight, surfaceHeight); var maxHeight = Math.max(extrudedHeight, surfaceHeight); if (CesiumMath.equalsEpsilon(minHeight, maxHeight, 0.1)) { diff --git a/Source/Core/WallGeometry.js b/Source/Core/WallGeometry.js index aa6d0b65907c..578878b647ba 100644 --- a/Source/Core/WallGeometry.js +++ b/Source/Core/WallGeometry.js @@ -12,7 +12,6 @@ define([ './GeometryAttribute', './GeometryAttributes', './Math', - './PolylinePipeline', './PolygonPipeline', './PrimitiveType', './VertexFormat', @@ -30,7 +29,6 @@ define([ GeometryAttribute, GeometryAttributes, CesiumMath, - PolylinePipeline, PolygonPipeline, PrimitiveType, VertexFormat, diff --git a/Specs/Core/BoxGeometryOutlineSpec.js b/Specs/Core/BoxOutlineGeometrySpec.js similarity index 78% rename from Specs/Core/BoxGeometryOutlineSpec.js rename to Specs/Core/BoxOutlineGeometrySpec.js index 0fc9a02ea2d3..df85ab5e8702 100644 --- a/Specs/Core/BoxGeometryOutlineSpec.js +++ b/Specs/Core/BoxOutlineGeometrySpec.js @@ -1,18 +1,16 @@ /*global defineSuite*/ defineSuite([ - 'Core/BoxGeometryOutline', - 'Core/VertexFormat', + 'Core/BoxOutlineGeometry', 'Core/Cartesian3' ], function( - BoxGeometryOutline, - VertexFormat, + BoxOutlineGeometry, Cartesian3) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ it('constructor throws without minimum corner', function() { expect(function() { - return new BoxGeometryOutline({ + return new BoxOutlineGeometry({ maximumCorner : new Cartesian3() }); }).toThrow(); @@ -20,14 +18,14 @@ defineSuite([ it('constructor throws without maximum corner', function() { expect(function() { - return new BoxGeometryOutline({ + return new BoxOutlineGeometry({ minimumCorner : new Cartesian3() }); }).toThrow(); }); it('constructor creates optimized number of positions for VertexFormat.POSITIONS_ONLY', function() { - var m = new BoxGeometryOutline({ + var m = new BoxOutlineGeometry({ minimumCorner : new Cartesian3(-1, -2, -3), maximumCorner : new Cartesian3(1, 2, 3) }); @@ -38,20 +36,20 @@ defineSuite([ it('fromDimensions throws without dimensions', function() { expect(function() { - return BoxGeometryOutline.fromDimensions(); + return BoxOutlineGeometry.fromDimensions(); }).toThrow(); }); it('fromDimensions throws with negative dimensions', function() { expect(function() { - return BoxGeometryOutline.fromDimensions({ + return BoxOutlineGeometry.fromDimensions({ dimensions : new Cartesian3(1, 2, -1) }); }).toThrow(); }); it('fromDimensions', function() { - var m = BoxGeometryOutline.fromDimensions({ + var m = BoxOutlineGeometry.fromDimensions({ dimensions : new Cartesian3(1, 2, 3) }); diff --git a/Specs/Core/CircleGeometrySpec.js b/Specs/Core/CircleGeometrySpec.js index f3bfcf209b8e..f80356022639 100644 --- a/Specs/Core/CircleGeometrySpec.js +++ b/Specs/Core/CircleGeometrySpec.js @@ -1,15 +1,15 @@ /*global defineSuite*/ defineSuite([ 'Core/CircleGeometry', - 'Core/Cartesian3', 'Core/Cartographic', 'Core/Ellipsoid', + 'Core/Math', 'Core/VertexFormat' ], function( CircleGeometry, - Cartesian3, Cartographic, Ellipsoid, + CesiumMath, VertexFormat) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ @@ -115,4 +115,27 @@ defineSuite([ expect(m.attributes.binormal.values.length).toEqual(3 * (24 + 10) * 2); expect(m.indices.length).toEqual(3 * (34 + 10) * 2); }); + + it('compute texture coordinates with rotation', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleGeometry({ + vertexFormat : VertexFormat.POSITION_AND_ST, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0, + stRotation : CesiumMath.PI_OVER_TWO + }); + + var positions = m.attributes.position.values; + var st = m.attributes.st.values; + var length = st.length; + + expect(positions.length).toEqual(3 * 24); + expect(length).toEqual(2 * 24); + expect(m.indices.length).toEqual(3 * 34); + + expect(st[length - 2]).toEqualEpsilon(0.5, CesiumMath.EPSILON2); + expect(st[length - 1]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + }); }); diff --git a/Specs/Core/CircleOutlineGeometrySpec.js b/Specs/Core/CircleOutlineGeometrySpec.js new file mode 100644 index 000000000000..240dcfe635a3 --- /dev/null +++ b/Specs/Core/CircleOutlineGeometrySpec.js @@ -0,0 +1,95 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/CircleOutlineGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/VertexFormat' + ], function( + CircleOutlineGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without a center', function() { + expect(function() { + return new CircleOutlineGeometry({ + radius : 1.0 + }); + }).toThrow(); + }); + + it('throws without a radius', function() { + expect(function() { + return new CircleOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()) + }); + }).toThrow(); + }); + + it('throws with a negative radius', function() { + expect(function() { + return new CircleOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + radius : -1.0 + }); + }).toThrow(); + }); + + it('throws with a negative granularity', function() { + expect(function() { + return new CircleOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + radius : 1.0, + granularity : -1.0 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 10); + expect(m.indices.length).toEqual(2 * 10); + expect(m.boundingSphere.radius).toEqual(1); + }); + + it('computes positions extruded', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0, + extrudedHeight : 10000 + }); + + expect(m.attributes.position.values.length).toEqual(2 * 10 * 3); + expect(m.indices.length).toEqual(2 * 10 * 2 + (10*2)); + }); + + + it('computes positions extruded, no lines between top and bottom', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0, + extrudedHeight : 10000, + countSideLines : 0 + }); + + expect(m.attributes.position.values.length).toEqual(2 * 10 * 3); + expect(m.indices.length).toEqual(2 * 10 * 2); + }); +}); diff --git a/Specs/Core/CylinderGeometrySpec.js b/Specs/Core/CylinderGeometrySpec.js index afec7c414714..7bcda96844e6 100644 --- a/Specs/Core/CylinderGeometrySpec.js +++ b/Specs/Core/CylinderGeometrySpec.js @@ -1,15 +1,9 @@ /*global defineSuite*/ defineSuite([ 'Core/CylinderGeometry', - 'Core/Cartesian3', - 'Core/Ellipsoid', - 'Core/Math', 'Core/VertexFormat' ], function( CylinderGeometry, - Cartesian3, - Ellipsoid, - CesiumMath, VertexFormat) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ diff --git a/Specs/Core/CylinderGeometryOutlineSpec.js b/Specs/Core/CylinderOutlineGeometrySpec.js similarity index 68% rename from Specs/Core/CylinderGeometryOutlineSpec.js rename to Specs/Core/CylinderOutlineGeometrySpec.js index 5732c9c46721..d246dc01b023 100644 --- a/Specs/Core/CylinderGeometryOutlineSpec.js +++ b/Specs/Core/CylinderOutlineGeometrySpec.js @@ -1,28 +1,20 @@ /*global defineSuite*/ defineSuite([ - 'Core/CylinderGeometryOutline', - 'Core/Cartesian3', - 'Core/Ellipsoid', - 'Core/Math', - 'Core/VertexFormat' + 'Core/CylinderOutlineGeometry' ], function( - CylinderGeometryOutline, - Cartesian3, - Ellipsoid, - CesiumMath, - VertexFormat) { + CylinderOutlineGeometry) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ it('constructor throws with no length', function() { expect(function() { - return new CylinderGeometryOutline({}); + return new CylinderOutlineGeometry({}); }).toThrow(); }); it('constructor throws with length less than 0', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: -1 }); }).toThrow(); @@ -30,7 +22,7 @@ defineSuite([ it('constructor throws with no topRadius', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1 }); }).toThrow(); @@ -38,7 +30,7 @@ defineSuite([ it('constructor throws with topRadius less than 0', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1, topRadius: -1 }); @@ -47,7 +39,7 @@ defineSuite([ it('constructor throws with no bottomRadius', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1, topRadius: 1 }); @@ -56,7 +48,7 @@ defineSuite([ it('constructor throws with bottomRadius less than 0', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1, topRadius: 1, bottomRadius: -1 @@ -66,7 +58,7 @@ defineSuite([ it('constructor throws if top and bottom radius are 0', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1, topRadius: 0, bottomRadius: 0 @@ -76,7 +68,7 @@ defineSuite([ it('constructor throws if slices is less than 3', function() { expect(function() { - return new CylinderGeometryOutline({ + return new CylinderOutlineGeometry({ length: 1, topRadius: 1, bottomRadius: 1, @@ -85,19 +77,8 @@ defineSuite([ }).toThrow(); }); - it('constructor throws if numLengthLines is less than 0', function() { - expect(function() { - return new CylinderGeometryOutline({ - length: 1, - topRadius: 1, - bottomRadius: 1, - numLengthLines: -4 - }); - }).toThrow(); - }); - it('computes positions', function() { - var m = new CylinderGeometryOutline({ + var m = new CylinderOutlineGeometry({ length: 1, topRadius: 1, bottomRadius: 1, @@ -109,12 +90,12 @@ defineSuite([ }); it('computes positions with no lines along the length', function() { - var m = new CylinderGeometryOutline({ + var m = new CylinderOutlineGeometry({ length: 1, topRadius: 1, bottomRadius: 1, slices: 3, - numLengthLines: 0 + countSideLines: 0 }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 2); diff --git a/Specs/Core/EllipseGeometrySpec.js b/Specs/Core/EllipseGeometrySpec.js index 272b1f326a89..18588d4f5adc 100644 --- a/Specs/Core/EllipseGeometrySpec.js +++ b/Specs/Core/EllipseGeometrySpec.js @@ -1,14 +1,12 @@ /*global defineSuite*/ defineSuite([ 'Core/EllipseGeometry', - 'Core/Cartesian3', 'Core/Cartographic', 'Core/Ellipsoid', 'Core/Math', 'Core/VertexFormat' ], function( EllipseGeometry, - Cartesian3, Cartographic, Ellipsoid, CesiumMath, diff --git a/Specs/Core/EllipseOutlineGeometrySpec.js b/Specs/Core/EllipseOutlineGeometrySpec.js new file mode 100644 index 000000000000..8ec134e922dc --- /dev/null +++ b/Specs/Core/EllipseOutlineGeometrySpec.js @@ -0,0 +1,116 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/EllipseOutlineGeometry', + 'Core/Cartographic', + 'Core/Ellipsoid' + ], function( + EllipseOutlineGeometry, + Cartographic, + Ellipsoid) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without a center', function() { + expect(function() { + return new EllipseOutlineGeometry({ + semiMajorAxis : 1.0, + semiMinorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws without a semiMajorAxis', function() { + expect(function() { + return new EllipseOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMinorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws without a semiMinorAxis', function() { + expect(function() { + return new EllipseOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws with a negative axis', function() { + expect(function() { + return new EllipseOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : -1.0 + }); + }).toThrow(); + }); + + it('throws with a negative granularity', function() { + expect(function() { + return new EllipseOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : 1.0, + granularity : -1.0 + }); + }).toThrow(); + }); + + it('throws when semiMajorAxis is less than the semiMajorAxis', function() { + expect(function() { + return new EllipseOutlineGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : 2.0 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new EllipseOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + semiMajorAxis : 1.0, + semiMinorAxis : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 10); + expect(m.indices.length).toEqual(2 * 10); + expect(m.boundingSphere.radius).toEqual(1); + }); + + it('computes positions extruded', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new EllipseOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + semiMajorAxis : 1.0, + semiMinorAxis : 1.0, + extrudedHeight : 50000 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 10 * 2); + expect(m.indices.length).toEqual(2 * 10 * 2 + (10 *2)); + }); + + it('computes positions extruded, no lines drawn between top and bottom', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new EllipseOutlineGeometry({ + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + semiMajorAxis : 1.0, + semiMinorAxis : 1.0, + extrudedHeight : 50000, + countSideLines : 0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 10 * 2); + expect(m.indices.length).toEqual(2 * 10 * 2); + }); +}); diff --git a/Specs/Core/EllipsoidGeometrySpec.js b/Specs/Core/EllipsoidGeometrySpec.js index 40434b69c0d8..810835287f7f 100644 --- a/Specs/Core/EllipsoidGeometrySpec.js +++ b/Specs/Core/EllipsoidGeometrySpec.js @@ -2,13 +2,11 @@ defineSuite([ 'Core/EllipsoidGeometry', 'Core/Cartesian3', - 'Core/Ellipsoid', 'Core/Math', 'Core/VertexFormat' ], function( EllipsoidGeometry, Cartesian3, - Ellipsoid, CesiumMath, VertexFormat) { "use strict"; diff --git a/Specs/Core/ExtentGeometrySpec.js b/Specs/Core/ExtentGeometrySpec.js index 6dbe7f3ebce2..426f0d456a4c 100644 --- a/Specs/Core/ExtentGeometrySpec.js +++ b/Specs/Core/ExtentGeometrySpec.js @@ -143,9 +143,7 @@ defineSuite([ vertexFormat : VertexFormat.POSITION_ONLY, extent : extent, granularity : 1.0, - extrudedOptions : { - height : 2 - } + extrudedHeight : 2 }); var positions = m.attributes.position.values; @@ -158,9 +156,7 @@ defineSuite([ vertexFormat : VertexFormat.ALL, extent : new Extent(-2.0, -1.0, 0.0, 1.0), granularity : 1.0, - extrudedOptions : { - height : 2 - } + extrudedHeight : 2 }); expect(m.attributes.position.values.length).toEqual((9 + 8 + 4) * 3 * 2); expect(m.attributes.st.values.length).toEqual((9 + 8 + 4) * 2 * 2); @@ -178,9 +174,7 @@ defineSuite([ extent : extent, rotation : angle, granularity : 1.0, - extrudedOptions : { - height : 2 - } + extrudedHeight : 2 }); var positions = m.attributes.position.values; var length = positions.length; @@ -204,10 +198,8 @@ defineSuite([ vertexFormat : VertexFormat.POSITION_ONLY, extent : extent, granularity : 1.0, - extrudedOptions : { - height : 2, - closeTop : false - } + extrudedHeight : 2, + closeTop : false }); var positions = m.attributes.position.values; @@ -221,10 +213,8 @@ defineSuite([ vertexFormat : VertexFormat.POSITION_ONLY, extent : extent, granularity : 1.0, - extrudedOptions : { - height : 2, - closeBottom : false - } + extrudedHeight : 2, + closeBottom : false }); var positions = m.attributes.position.values; @@ -238,11 +228,9 @@ defineSuite([ vertexFormat : VertexFormat.POSITION_ONLY, extent : extent, granularity : 1.0, - extrudedOptions : { - height : 2, - closeTop : false, - closeBottom : false - } + extrudedHeight : 2, + closeTop : false, + closeBottom : false }); var positions = m.attributes.position.values; @@ -250,29 +238,13 @@ defineSuite([ expect(m.indices.length).toEqual(4 * 3 * 4); }); - it('computes non-extruded extent if height is not specified', function() { - var extent = new Extent(-2.0, -1.0, 0.0, 1.0); - var m = new ExtentGeometry({ - vertexFormat : VertexFormat.POSITION_ONLY, - extent : extent, - granularity : 1.0, - extrudedOptions : {} - }); - var positions = m.attributes.position.values; - - expect(positions.length).toEqual(9 * 3); - expect(m.indices.length).toEqual(8 * 3); - }); - it('computes non-extruded extent if height is small', function() { var extent = new Extent(-2.0, -1.0, 0.0, 1.0); var m = new ExtentGeometry({ vertexFormat : VertexFormat.POSITION_ONLY, extent : extent, granularity : 1.0, - extrudedOptions : { - height : 0.1 - } + extrudedHeight : 0.1 }); var positions = m.attributes.position.values; diff --git a/Specs/Core/ExtentOutlineGeometrySpec.js b/Specs/Core/ExtentOutlineGeometrySpec.js new file mode 100644 index 000000000000..31ac38b8b048 --- /dev/null +++ b/Specs/Core/ExtentOutlineGeometrySpec.js @@ -0,0 +1,128 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/ExtentOutlineGeometry', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/Extent', + 'Core/GeographicProjection', + 'Core/Math', + 'Core/Matrix2' + ], function( + ExtentOutlineGeometry, + Cartesian3, + Ellipsoid, + Extent, + GeographicProjection, + CesiumMath, + Matrix2) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('computes positions', function() { + var extent = new Extent(-2.0, -1.0, 0.0, 1.0); + var m = new ExtentOutlineGeometry({ + extent : extent, + granularity : 1.0 + }); + var positions = m.attributes.position.values; + + expect(positions.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(8 * 2); + + var expectedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); + expect(new Cartesian3(positions[0], positions[1], positions[2])).toEqual(expectedNWCorner); + }); + + it('compute positions with rotation', function() { + var extent = new Extent(-1, -1, 1, 1); + var angle = CesiumMath.PI_OVER_TWO; + var m = new ExtentOutlineGeometry({ + extent : extent, + rotation : angle, + granularity : 1.0 + }); + var positions = m.attributes.position.values; + var length = positions.length; + + expect(length).toEqual(8 * 3); + expect(m.indices.length).toEqual(8 * 2); + + var unrotatedNWCorner = extent.getNorthwest(); + var projection = new GeographicProjection(); + var projectedNWCorner = projection.project(unrotatedNWCorner); + var rotation = Matrix2.fromRotation(angle); + var rotatedNWCornerCartographic = projection.unproject(rotation.multiplyByVector(projectedNWCorner)); + var rotatedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(rotatedNWCornerCartographic); + var actual = new Cartesian3(positions[0], positions[1], positions[2]); + expect(actual).toEqualEpsilon(rotatedNWCorner, CesiumMath.EPSILON6); + }); + + it('throws without extent', function() { + expect(function() { + return new ExtentOutlineGeometry({}); + }).toThrow(); + }); + + it('throws if rotated extent is invalid', function() { + expect(function() { + return new ExtentOutlineGeometry({ + extent : new Extent(-CesiumMath.PI_OVER_TWO, 1, CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO), + rotation : CesiumMath.PI_OVER_TWO + }); + }).toThrow(); + }); + + it('computes positions extruded', function() { + var extent = new Extent(-2.0, -1.0, 0.0, 1.0); + var m = new ExtentOutlineGeometry({ + extent : extent, + granularity : 1.0, + extrudedHeight : 2 + }); + var positions = m.attributes.position.values; + + expect(positions.length).toEqual(8 * 3 * 2); + expect(m.indices.length).toEqual(8 * 2 * 2 + 4 * 2); + }); + + it('compute positions with rotation extruded', function() { + var extent = new Extent(-1, -1, 1, 1); + var angle = CesiumMath.PI_OVER_TWO; + var m = new ExtentOutlineGeometry({ + extent : extent, + rotation : angle, + granularity : 1.0, + extrudedHeight : 2 + }); + var positions = m.attributes.position.values; + var length = positions.length; + + expect(length).toEqual(8 * 3 * 2); + expect(m.indices.length).toEqual(8 * 2 * 2 + 4 * 2); + + var unrotatedNWCorner = extent.getNorthwest(); + var projection = new GeographicProjection(); + var projectedNWCorner = projection.project(unrotatedNWCorner); + var rotation = Matrix2.fromRotation(angle); + var rotatedNWCornerCartographic = projection.unproject(rotation.multiplyByVector(projectedNWCorner)); + rotatedNWCornerCartographic.height = 2; + var rotatedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(rotatedNWCornerCartographic); + var actual = new Cartesian3(positions[0], positions[1], positions[2]); + expect(actual).toEqualEpsilon(rotatedNWCorner, CesiumMath.EPSILON6); + }); + + + it('computes non-extruded extent if height is small', function() { + var extent = new Extent(-2.0, -1.0, 0.0, 1.0); + var m = new ExtentOutlineGeometry({ + extent : extent, + granularity : 1.0, + extrudedHeight : 0.1 + }); + var positions = m.attributes.position.values; + + expect(positions.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(8 * 2); + }); + +}); diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js index a8fe52b00b50..ca7bc5f7f86e 100644 --- a/Specs/Core/PolygonGeometrySpec.js +++ b/Specs/Core/PolygonGeometrySpec.js @@ -65,6 +65,22 @@ defineSuite([ }).toThrow(); }); + it('throws due to duplicate positions extruded', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + + expect(function() { + return PolygonGeometry.fromPositions({ + positions : [ + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)) + ], + ellipsoid : ellipsoid, + extrudedHeight: 2 + }); + }).toThrow(); + }); + it('throws due to duplicate hierarchy positions', function() { var ellipsoid = Ellipsoid.UNIT_SPHERE; var hierarchy = { diff --git a/Specs/Core/PolygonOutlineGeometrySpec.js b/Specs/Core/PolygonOutlineGeometrySpec.js new file mode 100644 index 000000000000..efd894567e1a --- /dev/null +++ b/Specs/Core/PolygonOutlineGeometrySpec.js @@ -0,0 +1,258 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolygonOutlineGeometry', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/Shapes' + ], function( + PolygonOutlineGeometry, + BoundingSphere, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath, + Shapes) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without hierarchy', function() { + expect(function() { + return new PolygonOutlineGeometry(); + }).toThrow(); + }); + + it('throws without positions', function() { + expect(function() { + return PolygonOutlineGeometry.fromPositions(); + }).toThrow(); + }); + + it('throws with less than three positions', function() { + expect(function() { + return PolygonOutlineGeometry.fromPositions({ positions : [new Cartesian3()] }); + }).toThrow(); + }); + + it('throws with polygon hierarchy with less than three positions', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + new Cartographic() + ]) + }; + + expect(function() { + return new PolygonOutlineGeometry({ polygonHierarchy : hierarchy }); + }).toThrow(); + }); + + it('throws due to duplicate positions', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + + expect(function() { + return PolygonOutlineGeometry.fromPositions({ + positions : [ + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)) + ], + ellipsoid : ellipsoid + }); + }).toThrow(); + }); + + + it('throws due to duplicate positions extruded', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + + expect(function() { + return PolygonOutlineGeometry.fromPositions({ + positions : [ + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)) + ], + ellipsoid : ellipsoid, + extrudedeHeight: 2 + }); + }).toThrow(); + }); + + it('throws due to duplicate hierarchy positions', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var hierarchy = { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(1.0, 1.0, 0.0), + Cartographic.fromDegrees(1.0, 1.0, 0.0), + Cartographic.fromDegrees(1.0, 1.0, 0.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 0.0, 0.0), + Cartographic.fromDegrees(0.0, 0.0, 0.0), + Cartographic.fromDegrees(0.0, 0.0, 0.0) + ]) + }] + }; + + expect(function() { + return new PolygonOutlineGeometry({ + polygonHierarchy : hierarchy, + ellipsoid : ellipsoid + }); + }).toThrow(); + }); + + it('computes positions', function() { + var p = PolygonOutlineGeometry.fromPositions({ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, 50.0, 0.0), + Cartographic.fromDegrees(-50.0, 50.0, 0.0) + ]), + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 6); + expect(p.indices.length).toEqual(2 * 6); + }); + + it('creates a polygon from hierarchy', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-124.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 40.0, 0.0), + Cartographic.fromDegrees(-124.0, 40.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-122.0, 36.0, 0.0), + Cartographic.fromDegrees(-122.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 36.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-120.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 38.5, 0.0), + Cartographic.fromDegrees(-120.0, 38.5, 0.0) + ]) + }] + }] + }; + + var p = new PolygonOutlineGeometry({ + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 12); + expect(p.indices.length).toEqual(2 * 12); + }); + + it('creates a polygon from clockwise hierarchy', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-124.0, 35.0, 0.0), + Cartographic.fromDegrees(-124.0, 40.0, 0.0), + Cartographic.fromDegrees(-110.0, 40.0, 0.0), + Cartographic.fromDegrees(-110.0, 35.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-122.0, 36.0, 0.0), + Cartographic.fromDegrees(-112.0, 36.0, 0.0), + Cartographic.fromDegrees(-112.0, 39.0, 0.0), + Cartographic.fromDegrees(-122.0, 39.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-120.0, 36.5, 0.0), + Cartographic.fromDegrees(-120.0, 38.5, 0.0), + Cartographic.fromDegrees(-114.0, 38.5, 0.0), + Cartographic.fromDegrees(-114.0, 36.5, 0.0) + ]) + }] + }] + }; + + var p = new PolygonOutlineGeometry({ + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 12); + expect(p.indices.length).toEqual(2 * 12); + }); + + it('computes correct bounding sphere at height 0', function() { + var ellipsoid = Ellipsoid.WGS84; + var center = new Cartographic(0.2930215893394521, 0.818292397338644, 1880.6159971414636); + var positions = Shapes.computeCircleBoundary(ellipsoid, ellipsoid.cartographicToCartesian(center), 10000); + + var p = PolygonOutlineGeometry.fromPositions({ + positions : positions, + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.boundingSphere).toEqual(BoundingSphere.fromPoints(positions)); + }); + + it('computes positions extruded', function() { + var p = PolygonOutlineGeometry.fromPositions({ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, 50.0, 0.0), + Cartographic.fromDegrees(-50.0, 50.0, 0.0) + ]), + granularity : CesiumMath.PI_OVER_THREE, + extrudedHeight: 30000 + }); + + expect(p.attributes.position.values.length).toEqual(3 * 6 * 2); + expect(p.indices.length).toEqual(2 * 6 * 2 + 4*2); + }); + + it('creates a polygon from hierarchy extruded', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-124.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 40.0, 0.0), + Cartographic.fromDegrees(-124.0, 40.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-122.0, 36.0, 0.0), + Cartographic.fromDegrees(-122.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 36.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-120.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 38.5, 0.0), + Cartographic.fromDegrees(-120.0, 38.5, 0.0) + ]) + }] + }] + }; + + var p = new PolygonOutlineGeometry({ + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE, + extrudedHeight: 30000 + }); + + expect(p.attributes.position.values.length).toEqual(3 * 12 * 2); + expect(p.indices.length).toEqual(2 * 12 * 2 + 12*2); + }); + +}, 'WebGL'); diff --git a/Specs/Core/SphereGeometrySpec.js b/Specs/Core/SphereGeometrySpec.js index 8c0815e4f07b..a114d1e05b76 100644 --- a/Specs/Core/SphereGeometrySpec.js +++ b/Specs/Core/SphereGeometrySpec.js @@ -2,13 +2,11 @@ defineSuite([ 'Core/SphereGeometry', 'Core/Cartesian3', - 'Core/Ellipsoid', 'Core/Math', 'Core/VertexFormat' ], function( SphereGeometry, Cartesian3, - Ellipsoid, CesiumMath, VertexFormat) { "use strict"; diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js index c623569dd3dc..393c4d20c62b 100644 --- a/Specs/Scene/GeometryRenderingSpec.js +++ b/Specs/Scene/GeometryRenderingSpec.js @@ -447,7 +447,7 @@ defineSuite([ }); }, 'WebGL'); - describe('Extruded ExtentGeometry', function() { + describe('Extruded EllipseGeometry', function() { var instance; var extrudedHeight; var geometryHeight; @@ -673,9 +673,7 @@ defineSuite([ ellipsoid : ellipsoid, extent : extent, height : geometryHeight, - extrudedOptions : { - height : extrudedHeight - } + extrudedHeight : extrudedHeight }), id : 'extent', attributes : { From cdb9d00ff7321f62173da198eb9b8696e748b77e Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 5 Aug 2013 15:35:33 -0400 Subject: [PATCH 10/25] fix cylinder indices count --- Source/Core/CylinderGeometry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/CylinderGeometry.js b/Source/Core/CylinderGeometry.js index 1b87279dfaa9..48d9cd2f7613 100644 --- a/Source/Core/CylinderGeometry.js +++ b/Source/Core/CylinderGeometry.js @@ -218,7 +218,7 @@ define([ } } - var numIndices = 18 * slices - 24; + var numIndices = 12 * slices - 12; var indices = IndexDatatype.createTypedArray(numVertices, numIndices); index = 0; var j = 0; From f147ff686d5f4d30fb013aa57c32b5c9da168efd Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 5 Aug 2013 18:04:51 -0400 Subject: [PATCH 11/25] initial ellipsoid outline --- Source/Core/EllipsoidOutlineGeometry.js | 148 ++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 Source/Core/EllipsoidOutlineGeometry.js diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js new file mode 100644 index 000000000000..c2e1ecefe155 --- /dev/null +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -0,0 +1,148 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError', + './Cartesian3', + './Math', + './Ellipsoid', + './ComponentDatatype', + './IndexDatatype', + './PrimitiveType', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat' + ], function( + defaultValue, + DeveloperError, + Cartesian3, + CesiumMath, + Ellipsoid, + ComponentDatatype, + IndexDatatype, + PrimitiveType, + BoundingSphere, + GeometryAttribute, + GeometryAttributes, + VertexFormat) { + "use strict"; + + var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); + + /** + * A {@link Geometry} that represents vertices and indices for an ellipsoid centered at the origin. + * + * @alias EllipsoidGeometry + * @constructor + * + * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions. + * @param {Number} [options.numberOfPartitions=32] The number of times to partition the ellipsoid in a plane formed by two radii in a single quadrant. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} options.numberOfPartitions must be greater than zero. + * + * @example + * var ellipsoid = new EllipsoidGeometry({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * radii : new Cartesian3(1000000.0, 500000.0, 500000.0) + * }); + */ + var EllipsoidGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var radii = defaultValue(options.radii, defaultRadii); + var ellipsoid = Ellipsoid.fromCartesian3(radii); + var stackPartitions = defaultValue(options.stackPartitions, 100); + var slicePartitions = defaultValue(options.slicePartitions, 100); + if (stackPartitions < 0 || slicePartitions < 0) { + throw new DeveloperError('options.stackPartitions and options.slicePartitions must be greater than zero.'); + } + if (stackPartitions === 0 && slicePartitions === 0) { + throw new DeveloperError('options.stackPartitions and options.slicePartitions cannot both equal zero.'); + } + + var positions = []; + var indices = []; + + positions.push(0, 0, radii.z); + var i; + var j; + for (i = 1; i < stackPartitions; i++) { + var phi = Math.PI * i / stackPartitions; + var cosPhi = Math.cos(phi); + var sinPhi = Math.sin(phi); + + for (j = 0; j < slicePartitions; j++) { + var theta = CesiumMath.TWO_PI * j / slicePartitions; + var cosTheta = Math.cos(theta); + var sinTheta = Math.sin(theta); + + positions.push(radii.x * cosTheta * sinPhi, + radii.y * sinTheta * sinPhi, + radii.z * cosPhi); + } + } + positions.push(0, 0, -radii.z); + + for (j = 1; j < slicePartitions; ++j) { + indices.push(0, j); + } + indices.push(0, slicePartitions); + + for (i = 0; i < stackPartitions - 2; ++i) { + var topRowOffset = (i * slicePartitions) + 1; + var bottomRowOffset = ((i + 1) * slicePartitions) + 1; + for (j = 0; j < slicePartitions - 1; ++j) { + indices.push(topRowOffset + j, topRowOffset + j + 1); + indices.push(topRowOffset + j, bottomRowOffset + j); + } + indices.push(topRowOffset + slicePartitions - 1, topRowOffset); + indices.push(bottomRowOffset + slicePartitions - 1, topRowOffset + slicePartitions - 1); + } + + var lastPosition = positions.length/3 - 1; + for (j = lastPosition - 1; j > lastPosition - slicePartitions; --j) { + indices.push(lastPosition, j); + } + indices.push(lastPosition, lastPosition - slicePartitions); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type Object + * + * @see Geometry#attributes + */ + this.attributes = new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array(positions) + }) + }); + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = IndexDatatype.createTypedArray(length, indices); + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = BoundingSphere.fromEllipsoid(ellipsoid); + }; + + return EllipsoidGeometry; +}); \ No newline at end of file From 7a4c21e9ff3c1559e6e73bce2e5a41ad609988d6 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 6 Aug 2013 11:20:52 -0400 Subject: [PATCH 12/25] finished ellipsoid and sphere outline --- Source/Core/EllipsoidOutlineGeometry.js | 107 ++++++++++++++++-------- Source/Core/SphereGeometry.js | 2 +- Source/Core/SphereOutlineGeometry.js | 81 ++++++++++++++++++ 3 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 Source/Core/SphereOutlineGeometry.js diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index c2e1ecefe155..c961537be44c 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -30,52 +30,83 @@ define([ var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); /** - * A {@link Geometry} that represents vertices and indices for an ellipsoid centered at the origin. + * A {@link Geometry} that represents vertices and indices for the outline of an ellipsoid centered at the origin. * - * @alias EllipsoidGeometry + * @alias EllipsoidOutlineGeometry * @constructor * * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions. - * @param {Number} [options.numberOfPartitions=32] The number of times to partition the ellipsoid in a plane formed by two radii in a single quadrant. - * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines). + * @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines). + * @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature . * - * @exception {DeveloperError} options.numberOfPartitions must be greater than zero. + * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one. + * @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero. + * @exception {DeveloperError} options.subdivisions must be greater than or equal to zero. * * @example - * var ellipsoid = new EllipsoidGeometry({ - * vertexFormat : VertexFormat.POSITION_ONLY, - * radii : new Cartesian3(1000000.0, 500000.0, 500000.0) + * var ellipsoid = new EllipsoidOutlineGeometry({ + * radii : new Cartesian3(1000000.0, 500000.0, 500000.0), + * stackPartitions: 6, + * slicePartitions: 5 * }); */ - var EllipsoidGeometry = function(options) { + var EllipsoidOutlineGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var radii = defaultValue(options.radii, defaultRadii); var ellipsoid = Ellipsoid.fromCartesian3(radii); - var stackPartitions = defaultValue(options.stackPartitions, 100); - var slicePartitions = defaultValue(options.slicePartitions, 100); - if (stackPartitions < 0 || slicePartitions < 0) { - throw new DeveloperError('options.stackPartitions and options.slicePartitions must be greater than zero.'); + var stackPartitions = defaultValue(options.stackPartitions, 10); + var slicePartitions = defaultValue(options.slicePartitions, 8); + var subdivisions = defaultValue(options.subdivisions, 200); + if (stackPartitions < 1) { + throw new DeveloperError('options.stackPartitions must be greater than or equal to one.'); } - if (stackPartitions === 0 && slicePartitions === 0) { - throw new DeveloperError('options.stackPartitions and options.slicePartitions cannot both equal zero.'); + if (slicePartitions < 0) { + throw new DeveloperError('options.slicePartitions must be greater than or equal to zero.'); + } + + if (subdivisions < 0) { + throw new DeveloperError('options.subdivisions must be greater than or equal to zero.'); } var positions = []; var indices = []; - positions.push(0, 0, radii.z); var i; var j; + var phi; + var cosPhi; + var sinPhi; + var theta; + var cosTheta; + var sinTheta; for (i = 1; i < stackPartitions; i++) { - var phi = Math.PI * i / stackPartitions; - var cosPhi = Math.cos(phi); - var sinPhi = Math.sin(phi); + phi = Math.PI * i / stackPartitions; + cosPhi = Math.cos(phi); + sinPhi = Math.sin(phi); + + for (j = 0; j < subdivisions; j++) { + theta = CesiumMath.TWO_PI * j / subdivisions; + cosTheta = Math.cos(theta); + sinTheta = Math.sin(theta); + + positions.push(radii.x * cosTheta * sinPhi, + radii.y * sinTheta * sinPhi, + radii.z * cosPhi); + } + } + + positions.push(0, 0, radii.z); + for (i = 1; i < subdivisions; i++) { + phi = Math.PI * i / subdivisions; + cosPhi = Math.cos(phi); + sinPhi = Math.sin(phi); for (j = 0; j < slicePartitions; j++) { - var theta = CesiumMath.TWO_PI * j / slicePartitions; - var cosTheta = Math.cos(theta); - var sinTheta = Math.sin(theta); + theta = CesiumMath.TWO_PI * j / slicePartitions; + cosTheta = Math.cos(theta); + sinTheta = Math.sin(theta); positions.push(radii.x * cosTheta * sinPhi, radii.y * sinTheta * sinPhi, @@ -84,24 +115,32 @@ define([ } positions.push(0, 0, -radii.z); + for (i = 0; i < stackPartitions - 1; ++i) { + var topRowOffset = (i * subdivisions); + for (j = 0; j < subdivisions - 1; ++j) { + indices.push(topRowOffset + j, topRowOffset + j + 1); + } + indices.push(topRowOffset + subdivisions - 1, topRowOffset); + } + + var sliceOffset = subdivisions * (stackPartitions - 1); for (j = 1; j < slicePartitions; ++j) { - indices.push(0, j); + indices.push(sliceOffset, sliceOffset + j); } - indices.push(0, slicePartitions); + indices.push(sliceOffset, sliceOffset + slicePartitions); + + for (i = 0; i < subdivisions - 2; ++i) { + var topOffset = (i * slicePartitions) + 1 + sliceOffset; + var bottomOffset = ((i + 1) * slicePartitions) + 1 + sliceOffset; - for (i = 0; i < stackPartitions - 2; ++i) { - var topRowOffset = (i * slicePartitions) + 1; - var bottomRowOffset = ((i + 1) * slicePartitions) + 1; for (j = 0; j < slicePartitions - 1; ++j) { - indices.push(topRowOffset + j, topRowOffset + j + 1); - indices.push(topRowOffset + j, bottomRowOffset + j); + indices.push(bottomOffset + j, topOffset + j); } - indices.push(topRowOffset + slicePartitions - 1, topRowOffset); - indices.push(bottomRowOffset + slicePartitions - 1, topRowOffset + slicePartitions - 1); + indices.push(bottomOffset + slicePartitions - 1, topOffset + slicePartitions - 1); } var lastPosition = positions.length/3 - 1; - for (j = lastPosition - 1; j > lastPosition - slicePartitions; --j) { + for (j = lastPosition - 1; j > lastPosition - slicePartitions-1; --j) { indices.push(lastPosition, j); } indices.push(lastPosition, lastPosition - slicePartitions); @@ -130,7 +169,7 @@ define([ this.indices = IndexDatatype.createTypedArray(length, indices); /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ @@ -144,5 +183,5 @@ define([ this.boundingSphere = BoundingSphere.fromEllipsoid(ellipsoid); }; - return EllipsoidGeometry; + return EllipsoidOutlineGeometry; }); \ No newline at end of file diff --git a/Source/Core/SphereGeometry.js b/Source/Core/SphereGeometry.js index 6004628cd350..f587f2866996 100644 --- a/Source/Core/SphereGeometry.js +++ b/Source/Core/SphereGeometry.js @@ -10,7 +10,7 @@ define([ "use strict"; /** - * A {@link Geometry} that represents vertices and indices for an ellipse on the ellipsoid. + * A {@link Geometry} that represents vertices and indices for a sphere on the ellipsoid. * * Creates vertices and indices for an sphere centered at the origin. * diff --git a/Source/Core/SphereOutlineGeometry.js b/Source/Core/SphereOutlineGeometry.js new file mode 100644 index 000000000000..b5d8e6c070f5 --- /dev/null +++ b/Source/Core/SphereOutlineGeometry.js @@ -0,0 +1,81 @@ +/*global define*/ +define([ + './defaultValue', + './Cartesian3', + './EllipsoidOutlineGeometry' + ], function( + defaultValue, + Cartesian3, + EllipsoidOutlineGeometry) { + "use strict"; + + /** + * A {@link Geometry} that represents vertices and indices for the outline of a sphere on the ellipsoid. + * + * Creates vertices and indices for an sphere centered at the origin. + * + * @alias SphereOutlineGeometry + * @constructor + * + * @param {Number} [options.radius=1.0] The radius of the sphere. + * @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines). + * @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines). + * @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature . + * + * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one. + * @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero. + * @exception {DeveloperError} options.subdivisions must be greater than or equal to zero. + * + * @example + * var sphere = new SphereOutlineGeometry({ + * radius : 100.0, + * stackPartitions : 6, + * slicePartitions: 5 + * }); + */ + var SphereOutlineGeometry = function(options) { + var radius = defaultValue(options.radius, 1.0); + var radii = new Cartesian3(radius, radius, radius); + var ellipsoidOptions = { + radii: radii, + stackPartitions: options.stackPartitions, + slicePartitions: options.slicePartitions, + subdivision: options.subdivisions + }; + + var ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type Object + * + * @see Geometry#attributes + */ + this.attributes = ellipsoidGeometry.attributes; + + /** + * Index data that - along with {@link Geometry#primitiveType} - determines the primitives in the geometry. + * + * @type Array + */ + this.indices = ellipsoidGeometry.indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = ellipsoidGeometry.primitiveType; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = ellipsoidGeometry.boundingSphere; + }; + + return SphereOutlineGeometry; +}); \ No newline at end of file From 44e1a906d8d8349a3310fa5b2cd1bd7d0fb76360 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 6 Aug 2013 13:54:30 -0400 Subject: [PATCH 13/25] ellipsoid and sphere outline tests --- Source/Core/EllipsoidOutlineGeometry.js | 4 +- Source/Core/SphereOutlineGeometry.js | 2 +- Specs/Core/CylinderGeometrySpec.js | 8 ++-- Specs/Core/EllipsoidOutlineGeometrySpec.js | 50 ++++++++++++++++++++++ Specs/Core/SphereOutlineGeometrySpec.js | 49 +++++++++++++++++++++ 5 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 Specs/Core/EllipsoidOutlineGeometrySpec.js create mode 100644 Specs/Core/SphereOutlineGeometrySpec.js diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index c961537be44c..bd5ed2a6f0c8 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -124,10 +124,9 @@ define([ } var sliceOffset = subdivisions * (stackPartitions - 1); - for (j = 1; j < slicePartitions; ++j) { + for (j = 1; j < slicePartitions + 1; ++j) { indices.push(sliceOffset, sliceOffset + j); } - indices.push(sliceOffset, sliceOffset + slicePartitions); for (i = 0; i < subdivisions - 2; ++i) { var topOffset = (i * slicePartitions) + 1 + sliceOffset; @@ -143,7 +142,6 @@ define([ for (j = lastPosition - 1; j > lastPosition - slicePartitions-1; --j) { indices.push(lastPosition, j); } - indices.push(lastPosition, lastPosition - slicePartitions); /** * An object containing {@link GeometryAttribute} properties named after each of the diff --git a/Source/Core/SphereOutlineGeometry.js b/Source/Core/SphereOutlineGeometry.js index b5d8e6c070f5..66b616e7d78e 100644 --- a/Source/Core/SphereOutlineGeometry.js +++ b/Source/Core/SphereOutlineGeometry.js @@ -40,7 +40,7 @@ define([ radii: radii, stackPartitions: options.stackPartitions, slicePartitions: options.slicePartitions, - subdivision: options.subdivisions + subdivisions: options.subdivisions }; var ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions); diff --git a/Specs/Core/CylinderGeometrySpec.js b/Specs/Core/CylinderGeometrySpec.js index 7bcda96844e6..6a89e1371140 100644 --- a/Specs/Core/CylinderGeometrySpec.js +++ b/Specs/Core/CylinderGeometrySpec.js @@ -89,7 +89,7 @@ defineSuite([ }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 4); - expect(m.indices.length).toEqual(10 * 3); + expect(m.indices.length).toEqual(8 * 3); }); it('compute all vertex attributes', function() { @@ -106,7 +106,7 @@ defineSuite([ expect(m.attributes.normal.values.length).toEqual(3 * 3 * 4); expect(m.attributes.tangent.values.length).toEqual(3 * 3 * 4); expect(m.attributes.binormal.values.length).toEqual(3 * 3 * 4); - expect(m.indices.length).toEqual(10 * 3); + expect(m.indices.length).toEqual(8 * 3); }); it('computes positions with topRadius equals 0', function() { @@ -119,7 +119,7 @@ defineSuite([ }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 4); - expect(m.indices.length).toEqual(10 * 3); + expect(m.indices.length).toEqual(8 * 3); }); it('computes positions with bottomRadius equals 0', function() { @@ -132,6 +132,6 @@ defineSuite([ }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 4); - expect(m.indices.length).toEqual(10 * 3); + expect(m.indices.length).toEqual(8 * 3); }); }); \ No newline at end of file diff --git a/Specs/Core/EllipsoidOutlineGeometrySpec.js b/Specs/Core/EllipsoidOutlineGeometrySpec.js new file mode 100644 index 000000000000..c59101b7c0ad --- /dev/null +++ b/Specs/Core/EllipsoidOutlineGeometrySpec.js @@ -0,0 +1,50 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/EllipsoidOutlineGeometry', + 'Core/Cartesian3', + 'Core/Math', + 'Core/VertexFormat' + ], function( + EllipsoidOutlineGeometry, + Cartesian3, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws if stackPartitions less than 1', function() { + expect(function() { + return new EllipsoidOutlineGeometry({ + stackPartitions: 0 + }); + }).toThrow(); + }); + + it('constructor throws if slicePartitions less than 0', function() { + expect(function() { + return new EllipsoidOutlineGeometry({ + slicePartitions: -1 + }); + }).toThrow(); + }); + + it('constructor throws if subdivisions less than 0', function() { + expect(function() { + return new EllipsoidOutlineGeometry({ + subdivisions: -2 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var m = new EllipsoidOutlineGeometry({ + stackPartitions : 2, + slicePartitions: 2, + subdivisions: 2 + }); + + expect(m.attributes.position.values.length).toEqual(6 * 3); + expect(m.indices.length).toEqual(6 * 2); + expect(m.boundingSphere.radius).toEqual(1); + }); +}); \ No newline at end of file diff --git a/Specs/Core/SphereOutlineGeometrySpec.js b/Specs/Core/SphereOutlineGeometrySpec.js new file mode 100644 index 000000000000..d8639e0d2331 --- /dev/null +++ b/Specs/Core/SphereOutlineGeometrySpec.js @@ -0,0 +1,49 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/SphereOutlineGeometry', + 'Core/Cartesian3', + 'Core/Math', + 'Core/VertexFormat' + ], function( + SphereOutlineGeometry, + Cartesian3, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + it('constructor throws if stackPartitions less than 1', function() { + expect(function() { + return new SphereOutlineGeometry({ + stackPartitions: 0 + }); + }).toThrow(); + }); + + it('constructor throws if slicePartitions less than 0', function() { + expect(function() { + return new SphereOutlineGeometry({ + slicePartitions: -1 + }); + }).toThrow(); + }); + + it('constructor throws if subdivisions less than 0', function() { + expect(function() { + return new SphereOutlineGeometry({ + subdivisions: -2 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var m = new SphereOutlineGeometry({ + stackPartitions : 2, + slicePartitions: 2, + subdivisions: 2 + }); + + expect(m.attributes.position.values.length).toEqual(6 * 3); + expect(m.indices.length).toEqual(6 * 2); + expect(m.boundingSphere.radius).toEqual(1); + }); +}); \ No newline at end of file From d3282e699e1ada10193470c7731e28427a65895c Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 6 Aug 2013 15:48:51 -0400 Subject: [PATCH 14/25] clean up indices and doc --- Source/Core/CircleOutlineGeometry.js | 4 +- Source/Core/CylinderOutlineGeometry.js | 37 ++++++++-------- Source/Core/EllipseOutlineGeometry.js | 58 ++++++++++--------------- Source/Core/EllipsoidOutlineGeometry.js | 55 ++++++++++++++--------- Source/Core/ExtentOutlineGeometry.js | 46 +++++++++++++------- Source/Core/PolygonGeometry.js | 4 +- Source/Core/PolygonOutlineGeometry.js | 47 +++++++++++--------- Source/Core/SphereOutlineGeometry.js | 5 +-- 8 files changed, 139 insertions(+), 117 deletions(-) diff --git a/Source/Core/CircleOutlineGeometry.js b/Source/Core/CircleOutlineGeometry.js index fa78165ea483..ae7c94b0e3c2 100644 --- a/Source/Core/CircleOutlineGeometry.js +++ b/Source/Core/CircleOutlineGeometry.js @@ -10,7 +10,7 @@ define([ "use strict"; /** - * A {@link Geometry} that represents vertices and indices for a circle on the ellipsoid. + * A {@link Geometry} that represents vertices and indices for the outline of a circle on the ellipsoid. * * @alias CircleOutlineGeometry * @constructor @@ -80,7 +80,7 @@ define([ this.indices = ellipseGeometry.indices; /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index b6a56960d9fa..7571cbb86b28 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -28,22 +28,22 @@ define([ var radiusScratch = new Cartesian2(); /** - * A {@link Geometry} that represents vertices and indices for the edges of cylinder. + * A {@link Geometry} that represents vertices and indices for the outline of a cylinder. * * @alias CylinderGeometryOutline * @constructor * - * @param {Number} options.length The length of the cylinder - * @param {Number} options.topRadius The radius of the top of the cylinder - * @param {Number} options.bottomRadius The radius of the bottom of the cylinder - * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder - * @param {Boolean} [options.countSideLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder + * @param {Number} options.length The length of the cylinder. + * @param {Number} options.topRadius The radius of the top of the cylinder. + * @param {Number} options.bottomRadius The radius of the bottom of the cylinder. + * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder. + * @param {Boolean} [options.countSideLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder. * - * @exception {DeveloperError} options.length must be greater than 0 - * @exception {DeveloperError} options.topRadius must be greater than 0 - * @exception {DeveloperError} options.bottomRadius must be greater than 0 - * @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0 - * @exception {DeveloperError} options.slices must be greater that 3 + * @exception {DeveloperError} options.length must be greater than 0. + * @exception {DeveloperError} options.topRadius must be greater than 0. + * @exception {DeveloperError} options.bottomRadius must be greater than 0. + * @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0. + * @exception {DeveloperError} options.slices must be greater that 3. * * @example * // create cylinder geometry @@ -59,23 +59,23 @@ define([ var length = options.length; if (typeof length === 'undefined' || length <= 0) { - throw new DeveloperError('options.length must be greater than 0'); + throw new DeveloperError('options.length must be greater than 0.'); } var topRadius = options.topRadius; if (typeof topRadius === 'undefined' || topRadius < 0) { - throw new DeveloperError('options.topRadius must be greater than 0'); + throw new DeveloperError('options.topRadius must be greater than 0.'); } var bottomRadius = options.bottomRadius; if (typeof bottomRadius === 'undefined' || bottomRadius < 0) { - throw new DeveloperError('options.bottomRadius must be greater than 0'); + throw new DeveloperError('options.bottomRadius must be greater than 0.'); } if (bottomRadius === 0 && topRadius === 0) { - throw new DeveloperError('bottomRadius and topRadius cannot both equal 0'); + throw new DeveloperError('bottomRadius and topRadius cannot both equal 0.'); } var slices = defaultValue(options.slices, 100); if (slices < 3) { - throw new DeveloperError('options.slices must be greater that 3'); + throw new DeveloperError('options.slices must be greater that 3.'); } var countSideLines = Math.max(defaultValue(options.countSideLines, 10), 0); @@ -140,7 +140,7 @@ define([ attributes.position = new GeometryAttribute({ componentDatatype: ComponentDatatype.DOUBLE, componentsPerAttribute: 3, - values: new Float64Array(positions) + values: positions }); radiusScratch.x = length * 0.5; @@ -149,8 +149,7 @@ define([ var boundingSphere = new BoundingSphere(Cartesian3.ZERO, radiusScratch.magnitude()); /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type Object * diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index 547d2baaae7c..31f96f4b92fc 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -142,24 +142,6 @@ define([ var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); - // If the number of points were three, the ellipse - // would be tessellated like below: - // - // *---* - // / | \ | \ - // *---*---*---* - // / | \ | \ | \ | \ - // *---*---*---*---*---* - // | \ | \ | \ | \ | \ | - // *---*---*---*---*---* - // \ | \ | \ | \ | / - // *---*---*---* - // \ | \ | / - // *---* - // Notice each vertical column contains an even number of positions. - // The sum of the first n even numbers is n * (n + 1). Double it for the number of points - // for the whole ellipse. Note: this is just an estimate and may actually be less depending - // on the number of iterations before the angle reaches pi/2. var position = scratchCartesian1; var reflectedPosition = scratchCartesian2; @@ -210,13 +192,15 @@ define([ var positions = computeEllipsePositions(options); var attributes = raisePositionsToHeight(positions, options, false); - var indices = []; + var indices = IndexDatatype.createTypedArray(positions.length / 3, positions.length/3*2); + var index = 0; for (var i = 0; i < positions.length/3 - 1; i++) { - indices.push(i, i+1); + indices[index++] = i; + indices[index++] = i+1; } - indices.push(positions.length/3 - 1, 0); + indices[index++] = positions.length/3 - 1; + indices[index++] = 0; - indices = IndexDatatype.createTypedArray(positions.length / 3, indices); return { boundingSphere : boundingSphere, attributes : attributes, @@ -243,18 +227,23 @@ define([ var positions = computeEllipsePositions(options); var attributes = raisePositionsToHeight(positions, options, true); - + positions = attributes.position.values; var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere); + var length = positions.length/3; + var indices = IndexDatatype.createTypedArray(length, length * 2 + countSideLines * 2); - var indices = []; - - var length = attributes.position.values.length/6; + length /= 2; + var index = 0; for (var i = 0; i < length - 1; i++) { - indices.push(i, i + 1); - indices.push(i + length, i + length + 1); + indices[index++] = i; + indices[index++] = i + 1; + indices[index++] = i + length; + indices[index++] = i + length + 1; } - indices.push(length - 1, 0); - indices.push(length + length - 1, length); + indices[index++] = length - 1; + indices[index++] = 0; + indices[index++] = length + length - 1; + indices[index++] = length; var numSide; if (countSideLines > 0) { @@ -264,11 +253,11 @@ define([ var maxI = Math.min(numSide*10, length); if (countSideLines > 0) { for (i = 0; i < maxI; i+= numSide){ - indices.push(i, i + length); + indices[index++] = i; + indices[index++] = i + length; } } - indices = IndexDatatype.createTypedArray(positions.length / 3, indices); return { boundingSphere : boundingSphere, attributes : attributes, @@ -369,8 +358,7 @@ define([ /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type GeometryAttributes * @@ -386,7 +374,7 @@ define([ this.indices = ellipseGeometry.indices; /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index bd5ed2a6f0c8..2124c03e8e1e 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -70,8 +70,10 @@ define([ throw new DeveloperError('options.subdivisions must be greater than or equal to zero.'); } - var positions = []; - var indices = []; + var positionSize = (stackPartitions - 1)*subdivisions + (subdivisions - 1)*slicePartitions + 2; + var positions = new Float64Array(positionSize * 3); + var indicesSize = (stackPartitions - 1) * subdivisions + slicePartitions * subdivisions; + var indices = IndexDatatype.createTypedArray(length, indicesSize * 2); var i; var j; @@ -81,6 +83,7 @@ define([ var theta; var cosTheta; var sinTheta; + var index = 0; for (i = 1; i < stackPartitions; i++) { phi = Math.PI * i / stackPartitions; cosPhi = Math.cos(phi); @@ -91,13 +94,15 @@ define([ cosTheta = Math.cos(theta); sinTheta = Math.sin(theta); - positions.push(radii.x * cosTheta * sinPhi, - radii.y * sinTheta * sinPhi, - radii.z * cosPhi); + positions[index++] = radii.x * cosTheta * sinPhi; + positions[index++] = radii.y * sinTheta * sinPhi; + positions[index++] = radii.z * cosPhi; } } - positions.push(0, 0, radii.z); + positions[index++] = 0; + positions[index++] = 0; + positions[index++] = radii.z; for (i = 1; i < subdivisions; i++) { phi = Math.PI * i / subdivisions; cosPhi = Math.cos(phi); @@ -108,24 +113,30 @@ define([ cosTheta = Math.cos(theta); sinTheta = Math.sin(theta); - positions.push(radii.x * cosTheta * sinPhi, - radii.y * sinTheta * sinPhi, - radii.z * cosPhi); + positions[index++] = radii.x * cosTheta * sinPhi; + positions[index++] = radii.y * sinTheta * sinPhi; + positions[index++] = radii.z * cosPhi; } } - positions.push(0, 0, -radii.z); + positions[index++] = 0; + positions[index++] = 0; + positions[index++] = -radii.z; + index = 0; for (i = 0; i < stackPartitions - 1; ++i) { var topRowOffset = (i * subdivisions); for (j = 0; j < subdivisions - 1; ++j) { - indices.push(topRowOffset + j, topRowOffset + j + 1); + indices[index++] = topRowOffset + j; + indices[index++] = topRowOffset + j + 1; } - indices.push(topRowOffset + subdivisions - 1, topRowOffset); + indices[index++] = topRowOffset + subdivisions - 1; + indices[index++] = topRowOffset; } var sliceOffset = subdivisions * (stackPartitions - 1); for (j = 1; j < slicePartitions + 1; ++j) { - indices.push(sliceOffset, sliceOffset + j); + indices[index++] = sliceOffset; + indices[index++] = sliceOffset + j; } for (i = 0; i < subdivisions - 2; ++i) { @@ -133,19 +144,21 @@ define([ var bottomOffset = ((i + 1) * slicePartitions) + 1 + sliceOffset; for (j = 0; j < slicePartitions - 1; ++j) { - indices.push(bottomOffset + j, topOffset + j); + indices[index++] = bottomOffset + j; + indices[index++] = topOffset + j; } - indices.push(bottomOffset + slicePartitions - 1, topOffset + slicePartitions - 1); + indices[index++] = bottomOffset + slicePartitions - 1; + indices[index++] = topOffset + slicePartitions - 1; } var lastPosition = positions.length/3 - 1; - for (j = lastPosition - 1; j > lastPosition - slicePartitions-1; --j) { - indices.push(lastPosition, j); + for (j = lastPosition - 1; j > lastPosition - slicePartitions - 1; --j) { + indices[index++] = lastPosition; + indices[index++] = j; } /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type Object * @@ -155,7 +168,7 @@ define([ position: new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : new Float64Array(positions) + values : positions }) }); @@ -164,7 +177,7 @@ define([ * * @type Array */ - this.indices = IndexDatatype.createTypedArray(length, indices); + this.indices = indices; /** * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js index bdcbc51af80c..fb68f8b1e83a 100644 --- a/Source/Core/ExtentOutlineGeometry.js +++ b/Source/Core/ExtentOutlineGeometry.js @@ -5,6 +5,7 @@ define([ './Cartesian3', './Cartographic', './ComponentDatatype', + './IndexDatatype', './DeveloperError', './Ellipsoid', './GeographicProjection', @@ -19,6 +20,7 @@ define([ Cartesian3, Cartographic, ComponentDatatype, + IndexDatatype, DeveloperError, Ellipsoid, GeographicProjection, @@ -134,11 +136,16 @@ define([ positions[posIndex++] = position.y; positions[posIndex++] = position.z; } - var indices = []; + var indicesSize = positions.length/3 * 2; + var indices = IndexDatatype.createTypedArray(positions.length/3, indicesSize); + + var index = 0; for(var i = 0; i < (positions.length/3)-1; i++) { - indices.push(i, i+1); + indices[index++] = i; + indices[index++] = i+1; } - indices.push((positions.length/3)-1, 0); + indices[index++] = (positions.length/3)-1; + indices[index++] = 0; return { boundingSphere : BoundingSphere.fromExtent3D(extent, ellipsoid, surfaceHeight), @@ -212,19 +219,29 @@ define([ positions[posIndex++] = position.z; } - var indices = []; + var indicesSize = positions.length/3 * 2 + 8; + var indices = IndexDatatype.createTypedArray(positions.length/3, indicesSize); var length = positions.length/6; + var index = 0; for (var i = 0; i < length - 1; i++) { - indices.push(i, i+1); - indices.push(i + length, i + length + 1); + indices[index++] = i; + indices[index++] =i+1; + indices[index++] = i + length; + indices[index++] = i + length + 1; } - indices.push(length - 1, 0); - indices.push(length + length - 1, length); + indices[index++] = length - 1; + indices[index++] = 0; + indices[index++] = length + length - 1; + indices[index++] = length; - indices.push(0, length); - indices.push(width-1, length + width-1); - indices.push(width + height - 2, width + height - 2 + length); - indices.push(2*width + height - 3, 2*width + height - 3 + length); + indices[index++] = 0; + indices[index++] = length; + indices[index++] = width-1; + indices[index++] = length + width-1; + indices[index++] = width + height - 2; + indices[index++] = width + height - 2 + length; + indices[index++] = 2*width + height - 3; + indices[index++] = 2*width + height - 3 + length; var topBS = BoundingSphere.fromExtent3D(extent, ellipsoid, maxHeight, topBoundingSphere); @@ -364,8 +381,7 @@ define([ } /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type GeometryAttributes * @@ -384,7 +400,7 @@ define([ * * @type Array */ - this.indices = new Uint32Array(extentGeometry.indices); + this.indices = extentGeometry.indices; /** * A tight-fitting bounding sphere that encloses the vertices of the geometry. * diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 5518e7e51fb1..d08b0965816b 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -491,8 +491,8 @@ define([ } /** - * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is either defined - * by an array of Cartesian points, or a polygon hierarchy. + * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is defined + * by a polygon hierarchy. * * @alias PolygonGeometry * @constructor diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index 602829c6cfec..34a8ebb1905d 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -98,11 +98,15 @@ define([ subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); length = subdividedPositions.length/3; - var indices = []; + var indicesSize = length*2; + var indices = IndexDatatype.createTypedArray(subdividedPositions.length/3, indicesSize); + var index = 0; for (i = 0; i < length-1; i++) { - indices.push(i, i+1); + indices[index++] = i; + indices[index++] = i+1; } - indices.push(length-1, 0); + indices[index++] = length-1; + indices[index++] = 0; return new GeometryInstance({ geometry : new Geometry({ @@ -110,7 +114,7 @@ define([ position: new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : subdividedPositions + values : new Float64Array(subdividedPositions) }) }), indices: indices, @@ -185,17 +189,24 @@ define([ subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); length = subdividedPositions.length/3; - var indices = []; + var indicesSize = ((length * 2) + corners.length)*2; + var indices = IndexDatatype.createTypedArray(subdividedPositions.length/3, indicesSize); + var index = 0; for (i = 0; i < length-1; i++) { - indices.push(i, i+1); - indices.push(i + length, i+1 + length); + indices[index++] = i; + indices[index++] = i+1; + indices[index++] = i + length; + indices[index++] = i+1 + length; } - indices.push(length-1, 0); - indices.push(length + length-1, length); + indices[index++] = length-1; + indices[index++] = 0; + indices[index++] = length + length-1; + indices[index++] = length; for (i = 0; i < corners.length; i++) { var corner = corners[i]; - indices.push(corner, corner + length); + indices[index++] = corner; + indices[index++] = corner + length; } subdividedPositions = subdividedPositions.concat(subdividedPositions); @@ -206,7 +217,7 @@ define([ position: new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : subdividedPositions + values : new Float64Array(subdividedPositions) }) }), indices: indices, @@ -216,8 +227,8 @@ define([ } /** - * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is either defined - * by an array of Cartesian points, or a polygon hierarchy. + * A {@link Geometry} that represents vertices and indices for the outline of a polygon on the ellipsoid. The polygon is defined + * by a polygon hierarchy. * * @alias PolygonOutlineGeometry * @constructor @@ -395,14 +406,10 @@ define([ boundingSphere = BoundingSphere.union(boundingSphere, scratchBoundingSphere, boundingSphere); } - geometry.attributes.position.values = new Float64Array(geometry.attributes.position.values); - geometry.indices = IndexDatatype.createTypedArray(geometry.attributes.position.values.length / 3, geometry.indices); - var attributes = geometry.attributes; /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type GeometryAttributes * @@ -418,7 +425,7 @@ define([ this.indices = geometry.indices; /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ @@ -433,7 +440,7 @@ define([ }; /** - * Creates a polygon from an array of positions. + * Creates a polygon outline from an array of positions. * * @memberof PolygonOutlineGeometry * diff --git a/Source/Core/SphereOutlineGeometry.js b/Source/Core/SphereOutlineGeometry.js index 66b616e7d78e..3f5e5f0cac95 100644 --- a/Source/Core/SphereOutlineGeometry.js +++ b/Source/Core/SphereOutlineGeometry.js @@ -46,8 +46,7 @@ define([ var ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions); /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type Object * @@ -63,7 +62,7 @@ define([ this.indices = ellipsoidGeometry.indices; /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ From 473adce70935d244b403f9b8a88ccc742bff5568 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 6 Aug 2013 17:50:31 -0400 Subject: [PATCH 15/25] cleanup documentation --- Source/Core/BoxOutlineGeometry.js | 4 +-- Source/Core/CircleOutlineGeometry.js | 7 ++--- Source/Core/CylinderOutlineGeometry.js | 10 +++---- Source/Core/EllipseOutlineGeometry.js | 25 +++++++++-------- Source/Core/EllipsoidOutlineGeometry.js | 10 +++---- Source/Core/ExtentOutlineGeometry.js | 32 +++++++++++----------- Source/Core/SphereOutlineGeometry.js | 6 ++-- Specs/Core/CircleOutlineGeometrySpec.js | 10 ++----- Specs/Core/CylinderOutlineGeometrySpec.js | 2 +- Specs/Core/EllipseOutlineGeometrySpec.js | 2 +- Specs/Core/EllipsoidOutlineGeometrySpec.js | 20 +++++--------- Specs/Core/SphereOutlineGeometrySpec.js | 10 ++----- 12 files changed, 60 insertions(+), 78 deletions(-) diff --git a/Source/Core/BoxOutlineGeometry.js b/Source/Core/BoxOutlineGeometry.js index 723047cc80b2..8efe871524f4 100644 --- a/Source/Core/BoxOutlineGeometry.js +++ b/Source/Core/BoxOutlineGeometry.js @@ -72,6 +72,7 @@ define([ positions[9] = min.x; positions[10] = max.y; positions[11] = min.z; + positions[12] = min.x; positions[13] = min.y; positions[14] = max.z; @@ -129,8 +130,7 @@ define([ var radius = diff.magnitude() * 0.5; /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type GeometryAttributes * diff --git a/Source/Core/CircleOutlineGeometry.js b/Source/Core/CircleOutlineGeometry.js index ae7c94b0e3c2..a0407b3a8755 100644 --- a/Source/Core/CircleOutlineGeometry.js +++ b/Source/Core/CircleOutlineGeometry.js @@ -21,7 +21,7 @@ define([ * @param {Number} [options.height=0.0] The height above the ellipsoid. * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians. * @param {Number} [options.extrudedHeight=0.0] The height of the extrusion relative to the ellipsoid. - * @param {Boolean} [options.sideLinesCount = 10] Number of lines to draw between the top and bottom of an extruded circle. + * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom of an extruded circle. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} radius is required. @@ -57,14 +57,13 @@ define([ height : options.height, extrudedHeight : options.extrudedHeight, granularity : options.granularity, - countSideLines : options.countSideLines + lateralSurfaceLines : options.lateralSurfaceLines }; var ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions); /** - * An object containing {@link GeometryAttribute} properties named after each of the - * true values of the {@link VertexFormat} option. + * An object containing {@link GeometryAttribute} position property. * * @type GeometryAttributes * diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index 7571cbb86b28..1648880cc949 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -37,7 +37,7 @@ define([ * @param {Number} options.topRadius The radius of the top of the cylinder. * @param {Number} options.bottomRadius The radius of the bottom of the cylinder. * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder. - * @param {Boolean} [options.countSideLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder. + * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder. * * @exception {DeveloperError} options.length must be greater than 0. * @exception {DeveloperError} options.topRadius must be greater than 0. @@ -78,7 +78,7 @@ define([ throw new DeveloperError('options.slices must be greater that 3.'); } - var countSideLines = Math.max(defaultValue(options.countSideLines, 10), 0); + var lateralSurfaceLines = Math.max(defaultValue(options.lateralSurfaceLines, 10), 0); var topZ = length * 0.5; var bottomZ = -topZ; @@ -109,8 +109,8 @@ define([ } var numIndices = slices * 2; var numSide; - if (countSideLines > 0) { - var numSideLines = Math.min(countSideLines, slices); + if (lateralSurfaceLines > 0) { + var numSideLines = Math.min(lateralSurfaceLines, slices); numSide = Math.round(slices/numSideLines); numIndices += numSideLines; } @@ -129,7 +129,7 @@ define([ indices[index++] = slices + slices - 1; indices[index++] = slices; - if (countSideLines > 0) { + if (lateralSurfaceLines > 0) { for (i = 0; i < slices; i+= numSide){ indices[index++] = i; indices[index++] = i + slices; diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index 31f96f4b92fc..a855de4b08d7 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -192,13 +192,14 @@ define([ var positions = computeEllipsePositions(options); var attributes = raisePositionsToHeight(positions, options, false); - var indices = IndexDatatype.createTypedArray(positions.length / 3, positions.length/3*2); + var length = positions.length / 3; + var indices = IndexDatatype.createTypedArray(length, length*2); var index = 0; - for (var i = 0; i < positions.length/3 - 1; i++) { + for (var i = 0; i < length - 1; i++) { indices[index++] = i; indices[index++] = i+1; } - indices[index++] = positions.length/3 - 1; + indices[index++] = length - 1; indices[index++] = 0; return { @@ -211,8 +212,8 @@ define([ var topBoundingSphere = new BoundingSphere(); var bottomBoundingSphere = new BoundingSphere(); function computeExtrudedEllipse(options) { - var countSideLines = defaultValue(options.countSideLines, 10); - countSideLines = Math.max(countSideLines, 0); + var lateralSurfaceLines = defaultValue(options.lateralSurfaceLines, 10); + lateralSurfaceLines = Math.max(lateralSurfaceLines, 0); var center = options.center; var ellipsoid = options.ellipsoid; @@ -230,7 +231,7 @@ define([ positions = attributes.position.values; var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere); var length = positions.length/3; - var indices = IndexDatatype.createTypedArray(length, length * 2 + countSideLines * 2); + var indices = IndexDatatype.createTypedArray(length, length * 2 + lateralSurfaceLines * 2); length /= 2; var index = 0; @@ -246,12 +247,12 @@ define([ indices[index++] = length; var numSide; - if (countSideLines > 0) { - var numSideLines = Math.min(countSideLines, length); + if (lateralSurfaceLines > 0) { + var numSideLines = Math.min(lateralSurfaceLines, length); numSide = Math.round(length/numSideLines); } var maxI = Math.min(numSide*10, length); - if (countSideLines > 0) { + if (lateralSurfaceLines > 0) { for (i = 0; i < maxI; i+= numSide){ indices[index++] = i; indices[index++] = i + length; @@ -267,7 +268,7 @@ define([ /** * - * A {@link Geometry} that represents geometry for an ellipse on an ellipsoid + * A {@link Geometry} that represents geometry for the outline of an ellipse on an ellipsoid * * @alias EllipseOutlineGeometry * @constructor @@ -280,7 +281,7 @@ define([ * @param {Number} [options.extrudedHeight] The height of the extrusion. * @param {Number} [options.rotation=0.0] The angle from north (clockwise) in radians. The default is zero. * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians. - * @param {Boolean} [options.sideLinesCount = 10] Number of lines to draw between the top and bottom surface of an extruded ellipse. + * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom surface of an extruded ellipse. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} semiMajorAxis is required. @@ -336,7 +337,7 @@ define([ height : defaultValue(options.height, 0.0), granularity : defaultValue(options.granularity, 0.02), extrudedHeight : options.extrudedHeight, - countSideLines : Math.max(defaultValue(options.countSideLines, 10), 0) + lateralSurfaceLines : Math.max(defaultValue(options.lateralSurfaceLines, 10), 0) }; if (newOptions.granularity <= 0.0) { diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index 2124c03e8e1e..4e5a70dd6c44 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -10,8 +10,7 @@ define([ './PrimitiveType', './BoundingSphere', './GeometryAttribute', - './GeometryAttributes', - './VertexFormat' + './GeometryAttributes' ], function( defaultValue, DeveloperError, @@ -23,8 +22,7 @@ define([ PrimitiveType, BoundingSphere, GeometryAttribute, - GeometryAttributes, - VertexFormat) { + GeometryAttributes) { "use strict"; var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); @@ -70,9 +68,9 @@ define([ throw new DeveloperError('options.subdivisions must be greater than or equal to zero.'); } - var positionSize = (stackPartitions - 1)*subdivisions + (subdivisions - 1)*slicePartitions + 2; + var indicesSize = subdivisions * (stackPartitions + slicePartitions - 1); + var positionSize = indicesSize - slicePartitions + 2; var positions = new Float64Array(positionSize * 3); - var indicesSize = (stackPartitions - 1) * subdivisions + slicePartitions * subdivisions; var indices = IndexDatatype.createTypedArray(length, indicesSize * 2); var i; diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js index fb68f8b1e83a..944705764670 100644 --- a/Source/Core/ExtentOutlineGeometry.js +++ b/Source/Core/ExtentOutlineGeometry.js @@ -164,19 +164,19 @@ define([ var extent = params.extent; var height = params.height; var width = params.width; - var size = params.size; + var size = params.size * 3; var ellipsoid = params.ellipsoid; var posIndex = 0; var row = 0; var col; - var positions = new Float64Array(size * 2 * 3); + var positions = new Float64Array(size * 2); for (col = 0; col < width; col++) { computePosition(params, row, col, maxHeight, minHeight); - positions[posIndex + size*3] = extrudedPosition.x; - positions[posIndex + size*3 + 1] = extrudedPosition.y; - positions[posIndex + size*3 + 2] = extrudedPosition.z; + positions[posIndex + size] = extrudedPosition.x; + positions[posIndex + size + 1] = extrudedPosition.y; + positions[posIndex + size + 2] = extrudedPosition.z; positions[posIndex++] = position.x; positions[posIndex++] = position.y; @@ -186,9 +186,9 @@ define([ for (row = 1; row < height; row++) { computePosition(params, row, col, maxHeight, minHeight); - positions[posIndex + size*3] = extrudedPosition.x; - positions[posIndex + size*3 + 1] = extrudedPosition.y; - positions[posIndex + size*3 + 2] = extrudedPosition.z; + positions[posIndex + size] = extrudedPosition.x; + positions[posIndex + size + 1] = extrudedPosition.y; + positions[posIndex + size + 2] = extrudedPosition.z; positions[posIndex++] = position.x; positions[posIndex++] = position.y; @@ -198,9 +198,9 @@ define([ for (col = width-2; col >=0; col--){ computePosition(params, row, col, maxHeight, minHeight); - positions[posIndex + size*3] = extrudedPosition.x; - positions[posIndex + size*3 + 1] = extrudedPosition.y; - positions[posIndex + size*3 + 2] = extrudedPosition.z; + positions[posIndex + size] = extrudedPosition.x; + positions[posIndex + size + 1] = extrudedPosition.y; + positions[posIndex + size + 2] = extrudedPosition.z; positions[posIndex++] = position.x; positions[posIndex++] = position.y; @@ -210,9 +210,9 @@ define([ for (row = height - 2; row > 0; row--) { computePosition(params, row, col, maxHeight, minHeight); - positions[posIndex + size*3] = extrudedPosition.x; - positions[posIndex + size*3 + 1] = extrudedPosition.y; - positions[posIndex + size*3 + 2] = extrudedPosition.z; + positions[posIndex + size] = extrudedPosition.x; + positions[posIndex + size + 1] = extrudedPosition.y; + positions[posIndex + size + 2] = extrudedPosition.z; positions[posIndex++] = position.x; positions[posIndex++] = position.y; @@ -256,7 +256,7 @@ define([ } /** - * A {@link Geometry} that represents geometry for a cartographic extent on an ellipsoid centered at the origin. + * A {@link Geometry} that represents geometry for the outline of a a cartographic extent on an ellipsoid centered at the origin. * * @alias ExtentOutlineGeometry * @constructor @@ -409,7 +409,7 @@ define([ this.boundingSphere = extentGeometry.boundingSphere; /** - * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. * * @type PrimitiveType */ diff --git a/Source/Core/SphereOutlineGeometry.js b/Source/Core/SphereOutlineGeometry.js index 3f5e5f0cac95..d23b002526d4 100644 --- a/Source/Core/SphereOutlineGeometry.js +++ b/Source/Core/SphereOutlineGeometry.js @@ -10,7 +10,7 @@ define([ "use strict"; /** - * A {@link Geometry} that represents vertices and indices for the outline of a sphere on the ellipsoid. + * A {@link Geometry} that represents vertices and indices for the outline of a sphere. * * Creates vertices and indices for an sphere centered at the origin. * @@ -18,8 +18,8 @@ define([ * @constructor * * @param {Number} [options.radius=1.0] The radius of the sphere. - * @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines). - * @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines). + * @param {Number} [options.stackPartitions=10] The count of stacks for the sphere (1 greater than the number of parallel lines). + * @param {Number} [options.slicePartitions=8] The count of slices for the sphere (Equal to the number of radial lines). * @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature . * * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one. diff --git a/Specs/Core/CircleOutlineGeometrySpec.js b/Specs/Core/CircleOutlineGeometrySpec.js index 240dcfe635a3..b7744d7fbcdb 100644 --- a/Specs/Core/CircleOutlineGeometrySpec.js +++ b/Specs/Core/CircleOutlineGeometrySpec.js @@ -1,16 +1,12 @@ /*global defineSuite*/ defineSuite([ 'Core/CircleOutlineGeometry', - 'Core/Cartesian3', 'Core/Cartographic', - 'Core/Ellipsoid', - 'Core/VertexFormat' + 'Core/Ellipsoid' ], function( CircleOutlineGeometry, - Cartesian3, Cartographic, - Ellipsoid, - VertexFormat) { + Ellipsoid) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ @@ -86,7 +82,7 @@ defineSuite([ granularity : 0.75, radius : 1.0, extrudedHeight : 10000, - countSideLines : 0 + lateralSurfaceLines : 0 }); expect(m.attributes.position.values.length).toEqual(2 * 10 * 3); diff --git a/Specs/Core/CylinderOutlineGeometrySpec.js b/Specs/Core/CylinderOutlineGeometrySpec.js index d246dc01b023..052bd6e0e918 100644 --- a/Specs/Core/CylinderOutlineGeometrySpec.js +++ b/Specs/Core/CylinderOutlineGeometrySpec.js @@ -95,7 +95,7 @@ defineSuite([ topRadius: 1, bottomRadius: 1, slices: 3, - countSideLines: 0 + lateralSurfaceLines: 0 }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 2); diff --git a/Specs/Core/EllipseOutlineGeometrySpec.js b/Specs/Core/EllipseOutlineGeometrySpec.js index 8ec134e922dc..a85da4646c6f 100644 --- a/Specs/Core/EllipseOutlineGeometrySpec.js +++ b/Specs/Core/EllipseOutlineGeometrySpec.js @@ -107,7 +107,7 @@ defineSuite([ semiMajorAxis : 1.0, semiMinorAxis : 1.0, extrudedHeight : 50000, - countSideLines : 0 + lateralSurfaceLines : 0 }); expect(m.attributes.position.values.length).toEqual(3 * 10 * 2); diff --git a/Specs/Core/EllipsoidOutlineGeometrySpec.js b/Specs/Core/EllipsoidOutlineGeometrySpec.js index c59101b7c0ad..02ca36273045 100644 --- a/Specs/Core/EllipsoidOutlineGeometrySpec.js +++ b/Specs/Core/EllipsoidOutlineGeometrySpec.js @@ -1,14 +1,8 @@ /*global defineSuite*/ defineSuite([ - 'Core/EllipsoidOutlineGeometry', - 'Core/Cartesian3', - 'Core/Math', - 'Core/VertexFormat' + 'Core/EllipsoidOutlineGeometry' ], function( - EllipsoidOutlineGeometry, - Cartesian3, - CesiumMath, - VertexFormat) { + EllipsoidOutlineGeometry) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ @@ -38,13 +32,13 @@ defineSuite([ it('computes positions', function() { var m = new EllipsoidOutlineGeometry({ - stackPartitions : 2, - slicePartitions: 2, - subdivisions: 2 + stackPartitions : 3, + slicePartitions: 3, + subdivisions: 3 }); - expect(m.attributes.position.values.length).toEqual(6 * 3); - expect(m.indices.length).toEqual(6 * 2); + expect(m.attributes.position.values.length).toEqual(14 * 3); + expect(m.indices.length).toEqual(15 * 2); expect(m.boundingSphere.radius).toEqual(1); }); }); \ No newline at end of file diff --git a/Specs/Core/SphereOutlineGeometrySpec.js b/Specs/Core/SphereOutlineGeometrySpec.js index d8639e0d2331..87957939428f 100644 --- a/Specs/Core/SphereOutlineGeometrySpec.js +++ b/Specs/Core/SphereOutlineGeometrySpec.js @@ -1,14 +1,8 @@ /*global defineSuite*/ defineSuite([ - 'Core/SphereOutlineGeometry', - 'Core/Cartesian3', - 'Core/Math', - 'Core/VertexFormat' + 'Core/SphereOutlineGeometry' ], function( - SphereOutlineGeometry, - Cartesian3, - CesiumMath, - VertexFormat) { + SphereOutlineGeometry) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ it('constructor throws if stackPartitions less than 1', function() { From d4e173c4aab850689198addfe9f98857848596b2 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 7 Aug 2013 15:44:20 -0400 Subject: [PATCH 16/25] wall outline, sandcastle example --- .../gallery/Geometry and Appearances.html | 714 ++++++++++++++---- .../gallery/Geometry and Appearances.jpg | Bin 39901 -> 42687 bytes Source/Core/WallGeometry.js | 3 +- Source/Core/WallOutlineGeometry.js | 399 ++++++++++ 4 files changed, 989 insertions(+), 127 deletions(-) create mode 100644 Source/Core/WallOutlineGeometry.js diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 4951fe4344c1..ced30ebf182c 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -11,7 +11,8 @@ @@ -32,166 +33,603 @@ var primitives = scene.getPrimitives(); var ellipsoid = viewer.centralBody.getEllipsoid(); + var solidWhite = new Cesium.ColorGeometryInstanceAttribute(1.0, 1.0, 1.0, 1.0); + // Combine instances for an extent, polygon, ellipse, and circle into a single primitive. - + + var extent = Cesium.Extent.fromDegrees(-92.0, 20.0, -86.0, 27.0); var extentInstance = new Cesium.GeometryInstance({ geometry : new Cesium.ExtentGeometry({ - extent : Cesium.Extent.fromDegrees(-92.0, 30.0, -85.0, 40.0), - vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + extent : extent, + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT, + stRotation: Cesium.Math.toRadians(45) }) }); + var extentOutlineInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.ExtentOutlineGeometry({ + extent : extent + }), + attributes : { + color : solidWhite + } + }); + var positions = ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-107.0, 27.0), + Cesium.Cartographic.fromDegrees(-107.0, 25.0), + Cesium.Cartographic.fromDegrees(-102.0, 23.0), + Cesium.Cartographic.fromDegrees(-97.0, 21.0), + Cesium.Cartographic.fromDegrees(-97.0, 25.0) + ]); + var polygonInstance = new Cesium.GeometryInstance({ - geometry : new Cesium.PolygonGeometry({ - polygonHierarchy : { - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-109.0, 30.0), - Cesium.Cartographic.fromDegrees(-95.0, 30.0), - Cesium.Cartographic.fromDegrees(-95.0, 40.0), - Cesium.Cartographic.fromDegrees(-109.0, 40.0) - ]), - holes : [{ - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-107.0, 31.0), - Cesium.Cartographic.fromDegrees(-107.0, 39.0), - Cesium.Cartographic.fromDegrees(-97.0, 39.0), - Cesium.Cartographic.fromDegrees(-97.0, 31.0) - ]), - holes : [{ - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-105.0, 33.0), - Cesium.Cartographic.fromDegrees(-99.0, 33.0), - Cesium.Cartographic.fromDegrees(-99.0, 37.0), - Cesium.Cartographic.fromDegrees(-105.0, 37.0) - ]), - holes : [{ - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-103.0, 34.0), - Cesium.Cartographic.fromDegrees(-101.0, 34.0), - Cesium.Cartographic.fromDegrees(-101.0, 36.0), - Cesium.Cartographic.fromDegrees(-103.0, 36.0) - ]) - }] - }] - }] - }, + geometry : Cesium.PolygonGeometry.fromPositions({ + positions : positions, vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); - + var polygonOutlineInstance = new Cesium.GeometryInstance({ + geometry : Cesium.PolygonOutlineGeometry.fromPositions({ + positions : positions + }), + attributes : { + color : solidWhite + } + }); + + var center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-80.0, 25.0)); + var semiMinorAxis = 300000.0; + var semiMajorAxis = 500000.0; + var rotation = Cesium.Math.toRadians(-40.0); var ellipseInstance = new Cesium.GeometryInstance({ geometry : new Cesium.EllipseGeometry({ - center : ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-80.0, 35.0)), - semiMinorAxis : 200000.0, - semiMajorAxis : 500000.0, - rotation : Cesium.Math.toRadians(30.0), + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, + rotation : rotation, + stRotation: Cesium.Math.toRadians(22), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); + var ellipseOutlineInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.EllipseOutlineGeometry({ + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, + rotation : rotation + }), + attributes : { + color : solidWhite + } + }); + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-72.0, 25.0)); + var radius = 250000.0; var circleInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CircleGeometry({ - center : ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-72.0, 35.0)), - radius : 200000.0, + center : center, + radius : radius, + stRotation: Cesium.Math.toRadians(90), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); - + var circleOutlineInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.CircleOutlineGeometry({ + center : center, + radius : radius + }), + attributes : { + color : solidWhite + } + }); + primitives.add(new Cesium.Primitive({ geometryInstances : [extentInstance, polygonInstance, ellipseInstance, circleInstance], appearance : new Cesium.EllipsoidSurfaceAppearance({ material : Cesium.Material.fromType(scene.getContext(), 'Stripe') }) })); - + primitives.add(new Cesium.Primitive({ + geometryInstances : [extentOutlineInstance, polygonOutlineInstance, ellipseOutlineInstance, circleOutlineInstance], + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : Math.min(4.0, scene.getContext().getMaximumAliasedLineWidth()) + } + }) + })); + // Create extruded extent + extent = Cesium.Extent.fromDegrees(-118.0, 38.0, -116.0, 40.0); + var extrudedHeight = 500000.0; var extrudedExtent = new Cesium.GeometryInstance({ - geometry: new Cesium.ExtentGeometry({ - extent: Cesium.Extent.fromDegrees(-110.0, 38.0, -107.0, 40.0), + geometry : new Cesium.ExtentGeometry({ + extent : extent, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 700000, - rotation: Cesium.Math.toRadians(45.0), - extrudedHeight: 1000000 + extrudedHeight : extrudedHeight }), - attributes: { + attributes : { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } }); - + + var extrudedOutlineExtent = new Cesium.GeometryInstance({ + geometry : new Cesium.ExtentOutlineGeometry({ + extent : extent, + + extrudedHeight : extrudedHeight + }), + attributes : { + color : solidWhite + } + }); + // Create extruded extent + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-117.0, 35.0)); + semiMinorAxis = 100000.0; + semiMajorAxis = 200000.0; + rotation = Cesium.Math.toRadians(90); + var height = 100000.0; + extrudedHeight = 200000.0; var extrudedEllipse = new Cesium.GeometryInstance({ geometry : new Cesium.EllipseGeometry({ - center : ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-110.0, 35.0)), - semiMinorAxis : 100000.0, - semiMajorAxis : 200000.0, - rotation : Cesium.Math.toRadians(-40.0), + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 300000, - extrudedHeight: 700000 + height : height, + rotation: rotation, + extrudedHeight : extrudedHeight }), - attributes: { + attributes : { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } }); + var extrudedOutlineEllipse = new Cesium.GeometryInstance({ + geometry : new Cesium.EllipseOutlineGeometry({ + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, + height : height, + rotation: rotation, + extrudedHeight : extrudedHeight + }), + attributes : { + color : solidWhite + } + }); // Create extruded polygon + var polygonHierarchy = { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-118.0, 30.0), + Cesium.Cartographic.fromDegrees(-115.0, 30.0), + Cesium.Cartographic.fromDegrees(-117.1, 31.1), + Cesium.Cartographic.fromDegrees(-118.0, 33.0) + ]) + }; + height = 300000.0; + extrudedHeight = 700000.0; var extrudedPolygon = new Cesium.GeometryInstance({ geometry : new Cesium.PolygonGeometry({ - polygonHierarchy : { - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-113.0, 30.0), - Cesium.Cartographic.fromDegrees(-110.0, 30.0), - Cesium.Cartographic.fromDegrees(-110.0, 33.0), - Cesium.Cartographic.fromDegrees(-111.5, 31.0), - Cesium.Cartographic.fromDegrees(-113.0, 33.0) - ]) - }, + polygonHierarchy : polygonHierarchy, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - extrudedHeight: 300000 + extrudedHeight : extrudedHeight, + height: height }), - attributes: { + attributes : { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } }); + var extrudedOutlinePolygon = new Cesium.GeometryInstance({ + geometry : new Cesium.PolygonOutlineGeometry({ + polygonHierarchy : polygonHierarchy, + extrudedHeight : extrudedHeight, + height: height + }), + attributes : { + color : solidWhite + } + }); // cylinder + var length = 200000.0; + var topRadius = 150000.0; + var bottomRadius = 150000.0; + var modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)); var cylinderInstance = new Cesium.GeometryInstance({ - geometry : new Cesium.CylinderGeometry({ - length: 200000, - topRadius: 150000, - bottomRadius: 300000, - vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT - }), - id: 'cylinder', - modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-80.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)), + geometry : new Cesium.CylinderGeometry({ + length : length, + topRadius : topRadius, + bottomRadius : bottomRadius, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT + }), + modelMatrix : modelMatrix, + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + }); + var cylinderOutlineInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.CylinderOutlineGeometry({ + length : length, + topRadius : topRadius, + bottomRadius : bottomRadius + }), + modelMatrix : modelMatrix, + attributes : { + color : solidWhite + } + }); + + primitives.add(new Cesium.Primitive({ + geometryInstances : [extrudedPolygon, extrudedExtent, extrudedEllipse, cylinderInstance], + appearance : new Cesium.PerInstanceColorAppearance({ + translucent : false, + closed : true + }) + })); + primitives.add(new Cesium.Primitive({ + geometryInstances : [extrudedOutlineExtent, extrudedOutlineEllipse, extrudedOutlinePolygon, cylinderOutlineInstance], + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : Math.min(4.0, scene.getContext().getMaximumAliasedLineWidth()) + } + }) + })); + + // Create box and ellipsoid boxes, and use the instance's + // modelMatrix to scale and position them. + var dimensions = new Cesium.Cartesian3(1.0, 1.0, 1.0); + var boxGeometry = Cesium.BoxGeometry.fromDimensions({ + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + dimensions : dimensions + }); + var boxOutlineGeometry = Cesium.BoxOutlineGeometry.fromDimensions({ + dimensions : dimensions + }); + + var radii = new Cesium.Cartesian3(0.5, 0.5, 1.0); + var ellipsoidGeometry = new Cesium.EllipsoidGeometry({ + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + radii : radii + }); + + var ellipsoidOutlineGeometry = new Cesium.EllipsoidOutlineGeometry({ + radii : radii, + stackPartitions : 6, + slicePartitions : 5 + }); + + radius = 0.75; + var sphereGeometry = new Cesium.SphereGeometry({ + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + radius : radius + }); + + var sphereOutlineGeometry = new Cesium.SphereOutlineGeometry({ + radius : radius, + stackPartitions : 6, + slicePartitions : 5 + }); + + var instances = []; + var outlineInstances = []; + var i; + var boxModelMatrix, ellipsoidModelMatrix, sphereModelMatrix; + for (i = 0; i < 5; ++i) { + height = 100000.0 + (200000.0 * i); + boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-106.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-98.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + + instances.push(new Cesium.GeometryInstance({ + geometry : boxGeometry, + modelMatrix : boxModelMatrix, attributes : { - color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 0.5) + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } - }); - + })); + outlineInstances.push(new Cesium.GeometryInstance({ + geometry : boxOutlineGeometry, + modelMatrix : boxModelMatrix, + attributes : { + color : solidWhite + } + })); + + instances.push(new Cesium.GeometryInstance({ + geometry : ellipsoidGeometry, + modelMatrix : ellipsoidModelMatrix, + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + })); + outlineInstances.push(new Cesium.GeometryInstance({ + geometry : ellipsoidOutlineGeometry, + modelMatrix : ellipsoidModelMatrix, + attributes : { + color : solidWhite + } + })); + + instances.push(new Cesium.GeometryInstance({ + geometry : sphereGeometry, + modelMatrix : sphereModelMatrix, + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + })); + outlineInstances.push(new Cesium.GeometryInstance({ + geometry : sphereOutlineGeometry, + modelMatrix : sphereModelMatrix, + attributes : { + color : solidWhite + } + })); + } + primitives.add(new Cesium.Primitive({ - geometryInstances: [extrudedPolygon, extrudedEllipse, extrudedExtent, cylinderInstance], - appearance: new Cesium.PerInstanceColorAppearance({ - translucent: false, - closed: true + geometryInstances : instances, + appearance : new Cesium.PerInstanceColorAppearance({ + translucent : false, + closed : true + }) + })); + primitives.add(new Cesium.Primitive({ + geometryInstances : outlineInstances, + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : Math.min(4.0, scene.getContext().getMaximumAliasedLineWidth()) + } + }) + })); + + // Create a single wall + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-95.0, 50.0), + Cesium.Cartographic.fromDegrees(-85.0, 50.0), + Cesium.Cartographic.fromDegrees(-75.0, 50.0) + ]); + var maximumHeights = [500000, 1000000, 500000]; + var minimumHeights = [0, 500000, 0]; + + var wallInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.WallGeometry({ + positions : positions, + maximumHeights: maximumHeights, + minimumHeights: minimumHeights + + }), + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 0.7) + } + }); + + var wallOutlineInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.WallOutlineGeometry({ + positions : positions, + maximumHeights: maximumHeights, + minimumHeights: minimumHeights + }), + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(0.7, 0.7, 0.7, 1.0) + } + }); + + scene.getPrimitives().add(new Cesium.Primitive({ + geometryInstances : wallInstance, + appearance : new Cesium.PerInstanceColorAppearance() + })); + primitives.add(new Cesium.Primitive({ + geometryInstances : wallOutlineInstance, + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : Math.min(4.0, scene.getContext().getMaximumAliasedLineWidth()) + } + }) + })); + + extent = Cesium.Extent.fromDegrees(-92.0, 30.0, -85.0, 40.0); + extentInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.ExtentGeometry({ + extent : extent, + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + }) + }); + + polygonHierarchy = { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-109.0, 30.0), + Cesium.Cartographic.fromDegrees(-95.0, 30.0), + Cesium.Cartographic.fromDegrees(-95.0, 40.0), + Cesium.Cartographic.fromDegrees(-109.0, 40.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-107.0, 31.0), + Cesium.Cartographic.fromDegrees(-107.0, 39.0), + Cesium.Cartographic.fromDegrees(-97.0, 39.0), + Cesium.Cartographic.fromDegrees(-97.0, 31.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-105.0, 33.0), + Cesium.Cartographic.fromDegrees(-99.0, 33.0), + Cesium.Cartographic.fromDegrees(-99.0, 37.0), + Cesium.Cartographic.fromDegrees(-105.0, 37.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-103.0, 34.0), + Cesium.Cartographic.fromDegrees(-101.0, 34.0), + Cesium.Cartographic.fromDegrees(-101.0, 36.0), + Cesium.Cartographic.fromDegrees(-103.0, 36.0) + ]) + }] + }] + }] + }; + polygonInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.PolygonGeometry({ + polygonHierarchy : polygonHierarchy, + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + }) + }); + + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-80.0, 35.0)); + semiMinorAxis = 200000.0; + semiMajorAxis = 500000.0; + rotation = Cesium.Math.toRadians(30.0); + ellipseInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.EllipseGeometry({ + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, + rotation : rotation, + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + }) + }); + + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-72.0, 35.0)); + radius = 200000.0; + circleInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.CircleGeometry({ + center : center, + radius : radius, + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + }) + }); + + primitives.add(new Cesium.Primitive({ + geometryInstances : [extentInstance, polygonInstance, ellipseInstance, circleInstance], + appearance : new Cesium.EllipsoidSurfaceAppearance({ + material : Cesium.Material.fromType(scene.getContext(), 'Stripe') + }) + })); + + // Create extruded extent + extent = Cesium.Extent.fromDegrees(-110.0, 38.0, -107.0, 40.0); + height = 700000.0; + extrudedHeight = 1000000.0; + rotation = Cesium.Math.toRadians(45.0); + extrudedExtent = new Cesium.GeometryInstance({ + geometry : new Cesium.ExtentGeometry({ + extent : extent, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + height : height, + rotation : rotation, + extrudedHeight : extrudedHeight + }), + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + }); + + // Create extruded extent + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-110.0, 35.0)); + semiMinorAxis = 100000.0; + semiMajorAxis = 200000.0; + rotation = Cesium.Math.toRadians(-40.0); + height = 300000.0; + extrudedHeight = 700000.0; + extrudedEllipse = new Cesium.GeometryInstance({ + geometry : new Cesium.EllipseGeometry({ + center : center, + semiMinorAxis : semiMinorAxis, + semiMajorAxis : semiMajorAxis, + rotation : rotation, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + height : height, + extrudedHeight : extrudedHeight + }), + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + }); + + // Create extruded polygon + polygonHierarchy = { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-113.0, 30.0), + Cesium.Cartographic.fromDegrees(-110.0, 30.0), + Cesium.Cartographic.fromDegrees(-110.0, 33.0), + Cesium.Cartographic.fromDegrees(-111.5, 31.0), + Cesium.Cartographic.fromDegrees(-113.0, 33.0) + ]) + }; + extrudedHeight = 300000.0; + extrudedPolygon = new Cesium.GeometryInstance({ + geometry : new Cesium.PolygonGeometry({ + polygonHierarchy : polygonHierarchy, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + extrudedHeight : extrudedHeight + }), + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + }); + + // cylinder + length = 400000.0; + topRadius = 0.0; + bottomRadius = 200000.0; + modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)); + cylinderInstance = new Cesium.GeometryInstance({ + geometry : new Cesium.CylinderGeometry({ + length : length, + topRadius : topRadius, + bottomRadius : bottomRadius, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT + }), + modelMatrix : modelMatrix, + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + }); + + primitives.add(new Cesium.Primitive({ + geometryInstances : [extrudedPolygon, extrudedExtent, extrudedEllipse, cylinderInstance], + appearance : new Cesium.PerInstanceColorAppearance({ + translucent : false, + closed : true }) })); // Combine instances each with a unique color. - // We can combine heterogeneous geometries as we - // do here as long as vertex formats match. - var instances = []; - var i; - var height; - + // We can combine heterogeneous geometries as we + // do here as long as vertex formats match. + instances = []; + + center = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-65.0, 35.0)); + radius = 200000.0; + extent = Cesium.Extent.fromDegrees(-67.0, 27.0, -63.0, 32.0); for (i = 0; i < 5; ++i) { height = 200000.0 * i; instances.push(new Cesium.GeometryInstance({ geometry : new Cesium.CircleGeometry({ - center : ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-65.0, 35.0)), - radius : 200000.0, + center : center, + radius : radius, height : height, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), @@ -199,10 +637,10 @@ color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 0.5) } })); - + instances.push(new Cesium.GeometryInstance({ geometry : new Cesium.ExtentGeometry({ - extent : Cesium.Extent.fromDegrees(-62.0, 33.0, -57.0, 38.0), + extent : extent, height : height, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), @@ -216,41 +654,63 @@ geometryInstances : instances, appearance : new Cesium.PerInstanceColorAppearance() })); - + // Create box and ellipsoid boxes, and use the instance's // modelMatrix to scale and position them. - var boxGeometry = Cesium.BoxGeometry.fromDimensions({ + dimensions = new Cesium.Cartesian3(1.0, 1.0, 1.0); + boxGeometry = Cesium.BoxGeometry.fromDimensions({ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - dimensions : new Cesium.Cartesian3(1.0, 1.0, 1.0) + dimensions : dimensions }); - var ellipsoidGeometry = new Cesium.EllipsoidGeometry({ + + radii = new Cesium.Cartesian3(0.5, 0.5, 1.0); + ellipsoidGeometry = new Cesium.EllipsoidGeometry({ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - radii : new Cesium.Cartesian3(0.5, 0.5, 1.0) + radii : radii + }); + + radius = 0.75; + sphereGeometry = new Cesium.SphereGeometry({ + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + radius : radius }); instances = []; + outlineInstances = []; for (i = 0; i < 5; ++i) { height = 100000.0 + (200000.0 * i); - + boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-108.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-104.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-100.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + instances.push(new Cesium.GeometryInstance({ geometry : boxGeometry, - modelMatrix : Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-105.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0), + modelMatrix : boxModelMatrix, attributes : { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } })); - + instances.push(new Cesium.GeometryInstance({ geometry : ellipsoidGeometry, - modelMatrix : Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0), + modelMatrix : ellipsoidModelMatrix, + attributes : { + color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) + } + })); + + instances.push(new Cesium.GeometryInstance({ + geometry : sphereGeometry, + modelMatrix : sphereModelMatrix, attributes : { color : new Cesium.ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 1.0) } })); } - + primitives.add(new Cesium.Primitive({ geometryInstances : instances, appearance : new Cesium.PerInstanceColorAppearance({ @@ -258,13 +718,13 @@ closed : true }) })); - + // Combine several polylines, each with a unique color. // Override the appearance render state to change the // line on system's that support it (Linx/Mac). - + instances = []; - + for (i = 0; i < 40; i += 2) { instances.push(new Cesium.GeometryInstance({ geometry : new Cesium.SimplePolylineGeometry({ @@ -278,7 +738,7 @@ } })); } - + primitives.add(new Cesium.Primitive({ geometryInstances : instances, appearance : new Cesium.PerInstanceColorAppearance({ @@ -291,29 +751,31 @@ } }) })); - + // Create a single wall - var wallInstance = new Cesium.GeometryInstance({ + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-90.0, 43.0, 100000.0), + Cesium.Cartographic.fromDegrees(-87.5, 45.0, 100000.0), + Cesium.Cartographic.fromDegrees(-85.0, 43.0, 100000.0), + Cesium.Cartographic.fromDegrees(-87.5, 41.0, 100000.0), + Cesium.Cartographic.fromDegrees(-90.0, 43.0, 100000.0) + ]); + + wallInstance = new Cesium.GeometryInstance({ geometry : new Cesium.WallGeometry({ - positions : ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-100.0, 42.0, 100000.0), - Cesium.Cartographic.fromDegrees(-95.0, 42.0, 100000.0), - Cesium.Cartographic.fromDegrees(-95.0, 45.0, 100000.0), - Cesium.Cartographic.fromDegrees(-100.0, 45.0, 100000.0), - Cesium.Cartographic.fromDegrees(-100.0, 42.0, 100000.0) - ]) + positions : positions }) }); var wallMaterial = Cesium.Material.fromType(scene.getContext(), 'Checkerboard'); - var wallPrimitive = new Cesium.Primitive({ + wallMaterial.uniforms.repeat = new Cesium.Cartesian2(20.0, 6.0); + + scene.getPrimitives().add(new Cesium.Primitive({ geometryInstances : wallInstance, appearance : new Cesium.MaterialAppearance({ material : wallMaterial, faceForward : true }) - }); - wallMaterial.uniforms.repeat = new Cesium.Cartesian2(20.0, 6.0); - scene.getPrimitives().add(wallPrimitive); + })); Sandcastle.finishedLoading(); }); diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.jpg b/Apps/Sandcastle/gallery/Geometry and Appearances.jpg index 247975d32a7bf239c36b91a82deee9a78e770d04..2f969eb2e4892cecef07cbf6d0e2a00da79042e3 100644 GIT binary patch literal 42687 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

*v1X0W5Q6SM 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≥;};+~Mq>u4+mg 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 INcuhgf1TVfL;wH) delta 39488 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 that give the maximum height of the + * wall at positions. If undefined, the height of each position in used. + * @param {Array} [minimumHeights] An array parallel to positions that give the minimum height of the + * wall at positions. If undefined, the height at each position is 0.0. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} positions is required. + * @exception {DeveloperError} positions and maximumHeights must have the same length. + * @exception {DeveloperError} positions and minimumHeights must have the same length. + * @exception {DeveloperError} unique positions must be greater than or equal to 2. + * + * @example + * var positions = [ + * Cartographic.fromDegrees(19.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 47.0, 10000.0) + * ]; + * + * // create a wall that spans from ground level to 10000 meters + * var wall = new WallGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray(positions) + * }); + */ + var WallGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var wallPositions = options.positions; + var maximumHeights = options.maximumHeights; + var minimumHeights = options.minimumHeights; + + if (typeof wallPositions === 'undefined') { + throw new DeveloperError('positions is required.'); + } + + if (typeof maximumHeights !== 'undefined' && maximumHeights.length !== wallPositions.length) { + throw new DeveloperError('positions and maximumHeights must have the same length.'); + } + + if (typeof minimumHeights !== 'undefined' && minimumHeights.length !== wallPositions.length) { + throw new DeveloperError('positions and minimumHeights must have the same length.'); + } + + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + + var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); + + wallPositions = o.positions; + maximumHeights = o.topHeights; + minimumHeights = o.bottomHeights; + + if (wallPositions < 2) { + throw new DeveloperError('unique positions must be greater than or equal to 2'); + } + + if (wallPositions.length >= 3) { + // Order positions counter-clockwise + var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); + + if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { + wallPositions.reverse(); + maximumHeights.reverse(); + + if (typeof minimumHeights !== 'undefined') { + minimumHeights.reverse(); + } + } + } + + var i; + var length = wallPositions.length; + var newMaxHeights = []; + var newMinHeights = (typeof minimumHeights !== 'undefined') ? [] : undefined; + var newWallPositions = []; + for (i = 0; i < length-1; i++) { + var p1 = wallPositions[i]; + var p2 = wallPositions[i + 1]; + var h1 = maximumHeights[i]; + var h2 = maximumHeights[i + 1]; + newMaxHeights = newMaxHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); + + if (typeof minimumHeights !== 'undefined') { + p1 = wallPositions[i]; + p2 = wallPositions[i + 1]; + h1 = minimumHeights[i]; + h2 = minimumHeights[i + 1]; + newMinHeights = newMinHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); + } + + newWallPositions = newWallPositions.concat(PolylinePipeline.scaleToSurface([p1, p2], granularity, ellipsoid)); + } + + length = newWallPositions.length; + var size = length * 2; + + var positions = new Float64Array(size); + + var positionIndex = 0; + + var bottomPositions; + var topPositions; + + var minH = defaultValue(newMinHeights, 0); + bottomPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, minH, ellipsoid); + topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, newMaxHeights, ellipsoid); + + // add lower and upper points one after the other, lower + // points being even and upper points being odd + length /= 3; + for (i = 0; i < length; ++i) { + var i3 = i * 3; + var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1); + var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2); + + // insert the lower point + positions[positionIndex++] = bottomPosition.x; + positions[positionIndex++] = bottomPosition.y; + positions[positionIndex++] = bottomPosition.z; + + // insert the upper point + positions[positionIndex++] = topPosition.x; + positions[positionIndex++] = topPosition.y; + positions[positionIndex++] = topPosition.z; + } + + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); + + var numVertices = size / 3; + size = 6*numVertices-10; + var indices = IndexDatatype.createTypedArray(numVertices, size); + + var edgeIndex = 0; + for (i = 0; i < numVertices - 2; i += 2) { + var LL = i; + var LR = i + 2; + var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1); + var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2); + if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON6)) { + continue; + } + var UL = i + 1; + var UR = i + 3; + + indices[edgeIndex++] = UL; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = UL; + indices[edgeIndex++] = UR; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = LR; + } + + indices[edgeIndex++] = numVertices - 2; + indices[edgeIndex++] = numVertices - 1; + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = new BoundingSphere.fromVertices(positions); + }; + + /** + * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, + * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. + * + * @memberof WallGeometry + * + * @param {Array} positions An array of Cartesian objects, which are the points of the wall. + * @param {Number} [maximumHeight] A constant that defines the maximum height of the + * wall at positions. If undefined, the height of each position in used. + * @param {Number} [minimumHeight] A constant that defines the minimum height of the + * wall at positions. If undefined, the height at each position is 0.0. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation + * + * @exception {DeveloperError} positions is required. + * + * @example + * var positions = [ + * Cartographic.fromDegrees(19.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 47.0, 10000.0) + * ]; + * + * // create a wall that spans from 10000 meters to 20000 meters + * var wall = new WallGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray(positions), + * topHeight : 20000.0, + * bottomHeight : 10000.0 + * }); + */ + WallGeometry.fromConstantHeights = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var positions = options.positions; + if (typeof positions === 'undefined') { + throw new DeveloperError('options.positions is required.'); + } + + var minHeights; + var maxHeights; + + var min = options.minimumHeight; + var max = options.maximumHeight; + + var doMin = (typeof min !== 'undefined'); + var doMax = (typeof max !== 'undefined'); + if (doMin || doMax) { + var length = positions.length; + minHeights = (doMin) ? new Array(length) : undefined; + maxHeights = (doMax) ? new Array(length) : undefined; + + for (var i = 0; i < length; ++i) { + if (doMin) { + minHeights[i] = min; + } + + if (doMax) { + maxHeights[i] = max; + } + } + } + + var newOptions = { + positions : positions, + maximumHeights : maxHeights, + minimumHeights : minHeights, + ellipsoid : options.ellipsoid + }; + return new WallGeometry(newOptions); + }; + + return WallGeometry; +}); From 8b6dd25912a82470fa95f9a437390776370b360c Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 7 Aug 2013 16:10:09 -0400 Subject: [PATCH 17/25] outline tests --- Source/Core/WallOutlineGeometry.js | 20 +-- Specs/Core/WallOutlineGeometrySpec.js | 178 ++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 Specs/Core/WallOutlineGeometrySpec.js diff --git a/Source/Core/WallOutlineGeometry.js b/Source/Core/WallOutlineGeometry.js index d6feb28f69f7..c3e72b391e45 100644 --- a/Source/Core/WallOutlineGeometry.js +++ b/Source/Core/WallOutlineGeometry.js @@ -42,7 +42,7 @@ define([ function subdivideHeights(p0, p1, h0, h1, granularity) { var angleBetween = Cartesian3.angleBetween(p0, p1); - var numPoints = Math.ceil(angleBetween/granularity) + 1; + var numPoints = Math.ceil(angleBetween/granularity); var heights = new Array(numPoints); var i; if (h0 === h1) { @@ -53,16 +53,14 @@ define([ } var dHeight = h1 - h0; - var heightPerVertex = dHeight / (numPoints - 1); + var heightPerVertex = dHeight / (numPoints); - for (i = 1; i < numPoints - 1; i++) { + for (i = 1; i < numPoints; i++) { var h = h0 + i*heightPerVertex; heights[i] = h; } heights[0] = h0; - heights[numPoints - 1] = h1; - return heights; } @@ -181,7 +179,7 @@ define([ maximumHeights = o.topHeights; minimumHeights = o.bottomHeights; - if (wallPositions < 2) { + if (wallPositions.length < 2) { throw new DeveloperError('unique positions must be greater than or equal to 2'); } @@ -219,9 +217,13 @@ define([ h2 = minimumHeights[i + 1]; newMinHeights = newMinHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); } - - newWallPositions = newWallPositions.concat(PolylinePipeline.scaleToSurface([p1, p2], granularity, ellipsoid)); } + newMaxHeights.push(maximumHeights[length-1]); + if (typeof minimumHeights !== 'undefined') { + newMinHeights.push(minimumHeights[length-1]); + } + + newWallPositions = PolylinePipeline.scaleToSurface(wallPositions, granularity, ellipsoid); length = newWallPositions.length; var size = length * 2; @@ -265,7 +267,7 @@ define([ }); var numVertices = size / 3; - size = 6*numVertices-10; + size = 2*numVertices - 4 + numVertices; var indices = IndexDatatype.createTypedArray(numVertices, size); var edgeIndex = 0; diff --git a/Specs/Core/WallOutlineGeometrySpec.js b/Specs/Core/WallOutlineGeometrySpec.js new file mode 100644 index 000000000000..f496722455de --- /dev/null +++ b/Specs/Core/WallOutlineGeometrySpec.js @@ -0,0 +1,178 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/WallOutlineGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math' + ], function( + WallOutlineGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var ellipsoid = Ellipsoid.WGS84; + + it('throws with no positions', function() { + expect(function() { + return new WallOutlineGeometry(); + }).toThrow(); + }); + + it('throws when positions and minimumHeights length do not match', function() { + expect(function() { + return new WallOutlineGeometry({ + positions : new Array(2), + minimumHeights : new Array(3) + }); + }).toThrow(); + }); + + it('throws when positions and maximumHeights length do not match', function() { + expect(function() { + return new WallOutlineGeometry({ + positions : new Array(2), + maximumHeights : new Array(3) + }); + }).toThrow(); + }); + + it('throws with less than 2 positions', function() { + expect(function() { + return new WallOutlineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray([Cartographic.fromDegrees(49.0, 18.0, 1000.0)]) + }); + }).toThrow(); + }); + + it('throws with less than 2 unique positions', function() { + expect(function() { + return new WallOutlineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(49.0, 18.0, 5000.0), + Cartographic.fromDegrees(49.0, 18.0, 1000.0) + ]) + }); + }).toThrow(); + }); + + it('creates positions relative to ellipsoid', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var w = new WallOutlineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray(coords), + granularity : Math.PI + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(4 * 2); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(0.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(1000.0, CesiumMath.EPSILON8); + }); + + it('creates positions with minimum and maximum heights', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var w = new WallOutlineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray(coords), + minimumHeights : [1000.0, 2000.0], + maximumHeights : [3000.0, 4000.0], + granularity : Math.PI + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(4 * 2); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(1000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(3000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 6)); + expect(cartographic.height).toEqualEpsilon(2000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 9)); + expect(cartographic.height).toEqualEpsilon(4000.0, CesiumMath.EPSILON8); + }); + + it('cleans positions with duplicates', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(49.0, 18.0, 2000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0), + Cartographic.fromDegrees(51.0, 18.0, 1000.0), + Cartographic.fromDegrees(51.0, 18.0, 1000.0) + ]; + var w = new WallOutlineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray(coords) + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(3 * 2 * 3); + expect(w.indices.length).toEqual(7 * 2); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(0.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(2000.0, CesiumMath.EPSILON8); + }); + + it('fromConstantHeights throws without positions', function() { + expect(function() { + return WallOutlineGeometry.fromConstantHeights(); + }).toThrow(); + }); + + it('creates positions with constant minimum and maximum heights', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var min = 1000.0; + var max = 2000.0; + + var w = WallOutlineGeometry.fromConstantHeights({ + positions : ellipsoid.cartographicArrayToCartesianArray(coords), + minimumHeight : min, + maximumHeight : max + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(2 * 4); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(min, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(max, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 6)); + expect(cartographic.height).toEqualEpsilon(min, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 9)); + expect(cartographic.height).toEqualEpsilon(max, CesiumMath.EPSILON8); + }); +}); + From 87b56fd581544bd379ea50b6af0dc9b712f82641 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 7 Aug 2013 17:04:14 -0400 Subject: [PATCH 18/25] cleanup --- Apps/Sandcastle/gallery/Geometry and Appearances.html | 3 +-- CHANGES.md | 6 +++++- Source/Core/WallGeometry.js | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index ced30ebf182c..3eabfa3f90bf 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -11,8 +11,7 @@ diff --git a/CHANGES.md b/CHANGES.md index 70827048f6ab..721ad8847c21 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,10 @@ Beta Releases ### b20 - 2013-09-01 * Breaking changes: - * ... + * Replaced `ExtentGeometry` parameters for extruded extent to make it consistent with other Geometries. + * options.extrudedOptions.height -> options.extrudedHeight + * options.extrudedOptions.closeTop -> options.closeBottom + * options.extrudedOptions.closeBottom -> options.closeTop * Fixed broken surface rendering in Columbus View when using the `EllipsoidTerrainProvider`. * Optimized polyline bounding spheres. * Upgraded Knockout from version 2.2.1 to 2.3.0. @@ -15,6 +18,7 @@ Beta Releases * Improved `WallGeometry` to follow the curvature of the earth. * Added `PolylinePipeline.scaleToSurface`. * Added `PolylinePipeline.scaleToGeodeticHeight`. +* Added outline geometry [#1021](https://github.com/AnalyticalGraphicsInc/cesium/pull/1021) ### b19 - 2013-08-01 diff --git a/Source/Core/WallGeometry.js b/Source/Core/WallGeometry.js index 3858b8e55556..e6b582de0f84 100644 --- a/Source/Core/WallGeometry.js +++ b/Source/Core/WallGeometry.js @@ -535,3 +535,4 @@ define([ return WallGeometry; }); + From 164ca28f77782d82877957299746ee179fb6c0ad Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 12 Aug 2013 13:48:53 -0400 Subject: [PATCH 19/25] clean up --- .../gallery/Geometry and Appearances.html | 1 - Source/Core/BoxOutlineGeometry.js | 9 ++------- Source/Core/CircleOutlineGeometry.js | 5 ++--- Source/Core/CylinderGeometry.js | 4 ++-- Source/Core/CylinderOutlineGeometry.js | 14 +++++++------- Source/Core/EllipseOutlineGeometry.js | 17 ++++++++--------- Source/Core/EllipsoidOutlineGeometry.js | 4 ++-- Specs/Core/CircleOutlineGeometrySpec.js | 4 ++-- Specs/Core/CylinderOutlineGeometrySpec.js | 2 +- Specs/Core/EllipseOutlineGeometrySpec.js | 4 ++-- 10 files changed, 28 insertions(+), 36 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 3eabfa3f90bf..438cf865ccbb 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -158,7 +158,6 @@ var extrudedOutlineExtent = new Cesium.GeometryInstance({ geometry : new Cesium.ExtentOutlineGeometry({ extent : extent, - extrudedHeight : extrudedHeight }), attributes : { diff --git a/Source/Core/BoxOutlineGeometry.js b/Source/Core/BoxOutlineGeometry.js index 8efe871524f4..fbe9efa89cbb 100644 --- a/Source/Core/BoxOutlineGeometry.js +++ b/Source/Core/BoxOutlineGeometry.js @@ -54,11 +54,8 @@ define([ } var attributes = new GeometryAttributes(); - var indices; - var positions; - - // Positions only - no need to duplicate corner points - positions = new Float64Array(8 * 3); + var indices = new Uint16Array(12 * 2); + var positions = new Float64Array(8 * 3); positions[0] = min.x; positions[1] = min.y; @@ -92,8 +89,6 @@ define([ values : positions }); - indices = new Uint16Array(12 * 2); - // top indices[0] = 4; indices[1] = 5; diff --git a/Source/Core/CircleOutlineGeometry.js b/Source/Core/CircleOutlineGeometry.js index a0407b3a8755..94b4af6d48f7 100644 --- a/Source/Core/CircleOutlineGeometry.js +++ b/Source/Core/CircleOutlineGeometry.js @@ -21,7 +21,7 @@ define([ * @param {Number} [options.height=0.0] The height above the ellipsoid. * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians. * @param {Number} [options.extrudedHeight=0.0] The height of the extrusion relative to the ellipsoid. - * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom of an extruded circle. + * @param {Number} [options.numberOfVerticalLines = 16] Number of lines to draw between the top and bottom of an extruded circle. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} radius is required. @@ -57,8 +57,7 @@ define([ height : options.height, extrudedHeight : options.extrudedHeight, granularity : options.granularity, - lateralSurfaceLines : options.lateralSurfaceLines - + numberOfVerticalLines : options.numberOfVerticalLines }; var ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions); diff --git a/Source/Core/CylinderGeometry.js b/Source/Core/CylinderGeometry.js index 48d9cd2f7613..10666ac8e866 100644 --- a/Source/Core/CylinderGeometry.js +++ b/Source/Core/CylinderGeometry.js @@ -43,7 +43,7 @@ define([ * @param {Number} options.length The length of the cylinder * @param {Number} options.topRadius The radius of the top of the cylinder * @param {Number} options.bottomRadius The radius of the bottom of the cylinder - * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder + * @param {Number} [options.slices = 128] The number of edges around perimeter of the cylinder * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * * @exception {DeveloperError} options.length must be greater than 0 @@ -82,7 +82,7 @@ define([ var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); - var slices = defaultValue(options.slices, 100); + var slices = defaultValue(options.slices, 128); if (slices < 3) { throw new DeveloperError('options.slices must be greater that 3'); } diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index 1648880cc949..04216fc9f406 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -36,8 +36,8 @@ define([ * @param {Number} options.length The length of the cylinder. * @param {Number} options.topRadius The radius of the top of the cylinder. * @param {Number} options.bottomRadius The radius of the bottom of the cylinder. - * @param {Number} [options.slices = 100] The number of edges around perimeter of the cylinder. - * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom surfaces of the cylinder. + * @param {Number} [options.slices = 128] The number of edges around perimeter of the cylinder. + * @param {Number} [options.numberOfVerticalLines = 16] Number of lines to draw between the top and bottom surfaces of the cylinder. * * @exception {DeveloperError} options.length must be greater than 0. * @exception {DeveloperError} options.topRadius must be greater than 0. @@ -73,12 +73,12 @@ define([ throw new DeveloperError('bottomRadius and topRadius cannot both equal 0.'); } - var slices = defaultValue(options.slices, 100); + var slices = defaultValue(options.slices, 128); if (slices < 3) { throw new DeveloperError('options.slices must be greater that 3.'); } - var lateralSurfaceLines = Math.max(defaultValue(options.lateralSurfaceLines, 10), 0); + var numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0); var topZ = length * 0.5; var bottomZ = -topZ; @@ -109,8 +109,8 @@ define([ } var numIndices = slices * 2; var numSide; - if (lateralSurfaceLines > 0) { - var numSideLines = Math.min(lateralSurfaceLines, slices); + if (numberOfVerticalLines > 0) { + var numSideLines = Math.min(numberOfVerticalLines, slices); numSide = Math.round(slices/numSideLines); numIndices += numSideLines; } @@ -129,7 +129,7 @@ define([ indices[index++] = slices + slices - 1; indices[index++] = slices; - if (lateralSurfaceLines > 0) { + if (numberOfVerticalLines > 0) { for (i = 0; i < slices; i+= numSide){ indices[index++] = i; indices[index++] = i + slices; diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index a855de4b08d7..7565df682065 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -212,8 +212,8 @@ define([ var topBoundingSphere = new BoundingSphere(); var bottomBoundingSphere = new BoundingSphere(); function computeExtrudedEllipse(options) { - var lateralSurfaceLines = defaultValue(options.lateralSurfaceLines, 10); - lateralSurfaceLines = Math.max(lateralSurfaceLines, 0); + var numberOfVerticalLines = defaultValue(options.numberOfVerticalLines, 16); + numberOfVerticalLines = Math.max(numberOfVerticalLines, 0); var center = options.center; var ellipsoid = options.ellipsoid; @@ -231,7 +231,7 @@ define([ positions = attributes.position.values; var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere); var length = positions.length/3; - var indices = IndexDatatype.createTypedArray(length, length * 2 + lateralSurfaceLines * 2); + var indices = IndexDatatype.createTypedArray(length, length * 2 + numberOfVerticalLines * 2); length /= 2; var index = 0; @@ -247,12 +247,12 @@ define([ indices[index++] = length; var numSide; - if (lateralSurfaceLines > 0) { - var numSideLines = Math.min(lateralSurfaceLines, length); + if (numberOfVerticalLines > 0) { + var numSideLines = Math.min(numberOfVerticalLines, length); numSide = Math.round(length/numSideLines); } var maxI = Math.min(numSide*10, length); - if (lateralSurfaceLines > 0) { + if (numberOfVerticalLines > 0) { for (i = 0; i < maxI; i+= numSide){ indices[index++] = i; indices[index++] = i + length; @@ -281,7 +281,7 @@ define([ * @param {Number} [options.extrudedHeight] The height of the extrusion. * @param {Number} [options.rotation=0.0] The angle from north (clockwise) in radians. The default is zero. * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians. - * @param {Number} [options.lateralSurfaceLines = 10] Number of lines to draw between the top and bottom surface of an extruded ellipse. + * @param {Number} [options.numberOfVerticalLines = 16] Number of lines to draw between the top and bottom surface of an extruded ellipse. * * @exception {DeveloperError} center is required. * @exception {DeveloperError} semiMajorAxis is required. @@ -337,7 +337,7 @@ define([ height : defaultValue(options.height, 0.0), granularity : defaultValue(options.granularity, 0.02), extrudedHeight : options.extrudedHeight, - lateralSurfaceLines : Math.max(defaultValue(options.lateralSurfaceLines, 10), 0) + numberOfVerticalLines : Math.max(defaultValue(options.numberOfVerticalLines, 16), 0) }; if (newOptions.granularity <= 0.0) { @@ -357,7 +357,6 @@ define([ ellipseGeometry = computeEllipse(newOptions); } - /** * An object containing {@link GeometryAttribute} position property. * diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index 4e5a70dd6c44..40f91785f3b0 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -36,7 +36,7 @@ define([ * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions. * @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines). * @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines). - * @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature . + * @param {Number} [options.subdivisions=128] The number of points per line, determining the granularity of the curvature . * * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one. * @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero. @@ -56,7 +56,7 @@ define([ var ellipsoid = Ellipsoid.fromCartesian3(radii); var stackPartitions = defaultValue(options.stackPartitions, 10); var slicePartitions = defaultValue(options.slicePartitions, 8); - var subdivisions = defaultValue(options.subdivisions, 200); + var subdivisions = defaultValue(options.subdivisions, 128); if (stackPartitions < 1) { throw new DeveloperError('options.stackPartitions must be greater than or equal to one.'); } diff --git a/Specs/Core/CircleOutlineGeometrySpec.js b/Specs/Core/CircleOutlineGeometrySpec.js index b7744d7fbcdb..b0149e39995a 100644 --- a/Specs/Core/CircleOutlineGeometrySpec.js +++ b/Specs/Core/CircleOutlineGeometrySpec.js @@ -70,7 +70,7 @@ defineSuite([ }); expect(m.attributes.position.values.length).toEqual(2 * 10 * 3); - expect(m.indices.length).toEqual(2 * 10 * 2 + (10*2)); + expect(m.indices.length).toEqual(2 * 10 * 2 + (16*2)); }); @@ -82,7 +82,7 @@ defineSuite([ granularity : 0.75, radius : 1.0, extrudedHeight : 10000, - lateralSurfaceLines : 0 + numberOfVerticalLines : 0 }); expect(m.attributes.position.values.length).toEqual(2 * 10 * 3); diff --git a/Specs/Core/CylinderOutlineGeometrySpec.js b/Specs/Core/CylinderOutlineGeometrySpec.js index 052bd6e0e918..76b4894b2261 100644 --- a/Specs/Core/CylinderOutlineGeometrySpec.js +++ b/Specs/Core/CylinderOutlineGeometrySpec.js @@ -95,7 +95,7 @@ defineSuite([ topRadius: 1, bottomRadius: 1, slices: 3, - lateralSurfaceLines: 0 + numberOfVerticalLines: 0 }); expect(m.attributes.position.values.length).toEqual(3 * 3 * 2); diff --git a/Specs/Core/EllipseOutlineGeometrySpec.js b/Specs/Core/EllipseOutlineGeometrySpec.js index a85da4646c6f..40792d41b393 100644 --- a/Specs/Core/EllipseOutlineGeometrySpec.js +++ b/Specs/Core/EllipseOutlineGeometrySpec.js @@ -95,7 +95,7 @@ defineSuite([ }); expect(m.attributes.position.values.length).toEqual(3 * 10 * 2); - expect(m.indices.length).toEqual(2 * 10 * 2 + (10 *2)); + expect(m.indices.length).toEqual(2 * 10 * 2 + (16 *2)); }); it('computes positions extruded, no lines drawn between top and bottom', function() { @@ -107,7 +107,7 @@ defineSuite([ semiMajorAxis : 1.0, semiMinorAxis : 1.0, extrudedHeight : 50000, - lateralSurfaceLines : 0 + numberOfVerticalLines : 0 }); expect(m.attributes.position.values.length).toEqual(3 * 10 * 2); From f78ffcf3b10d9ecda22389930f2cdd0afda0dd89 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 12 Aug 2013 16:24:01 -0400 Subject: [PATCH 20/25] deduplicate --- Source/Core/EllipseGeometry.js | 219 +---------------------- Source/Core/EllipseGeometryLibrary.js | 244 ++++++++++++++++++++++++++ Source/Core/EllipseOutlineGeometry.js | 174 ++---------------- Source/Core/PolygonGeometry.js | 95 ++-------- Source/Core/PolygonGeometryLibrary.js | 87 +++++++++ Source/Core/PolygonOutlineGeometry.js | 83 +-------- Source/Core/PolylinePipeline.js | 21 ++- Source/Core/WallGeometry.js | 145 +-------------- Source/Core/WallGeometryLibrary.js | 178 +++++++++++++++++++ Source/Core/WallOutlineGeometry.js | 146 +-------------- 10 files changed, 588 insertions(+), 804 deletions(-) create mode 100644 Source/Core/EllipseGeometryLibrary.js create mode 100644 Source/Core/PolygonGeometryLibrary.js create mode 100644 Source/Core/WallGeometryLibrary.js diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index 9d466f720929..4f52a7b0473d 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -9,6 +9,7 @@ define([ './IndexDatatype', './DeveloperError', './Ellipsoid', + './EllipseGeometryLibrary', './GeographicProjection', './Geometry', './GeometryPipeline', @@ -30,6 +31,7 @@ define([ IndexDatatype, DeveloperError, Ellipsoid, + EllipseGeometryLibrary, GeographicProjection, Geometry, GeometryPipeline, @@ -43,11 +45,6 @@ define([ VertexFormat) { "use strict"; - var rotAxis = new Cartesian3(); - var tempVec = new Cartesian3(); - var unitQuat = new Quaternion(); - var rotMtx = new Matrix3(); - var scratchCartesian1 = new Cartesian3(); var scratchCartesian2 = new Cartesian3(); var scratchCartesian3 = new Cartesian3(); @@ -60,49 +57,17 @@ define([ var scratchTangent = new Cartesian3(); var scratchBinormal = new Cartesian3(); - var unitPosScratch = new Cartesian3(); - var eastVecScratch = new Cartesian3(); - var northVecScratch = new Cartesian3(); var scratchCartographic = new Cartographic(); var projectedCenterScratch = new Cartesian3(); - function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { - var azimuth = theta + rotation; - - Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); - Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec); - Cartesian3.add(rotAxis, tempVec, rotAxis); - - var cosThetaSquared = Math.cos(theta); - cosThetaSquared = cosThetaSquared * cosThetaSquared; - - var sinThetaSquared = Math.sin(theta); - sinThetaSquared = sinThetaSquared * sinThetaSquared; - - var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared); - var angle = radius / mag; - - // Create the quaternion to rotate the position vector to the boundary of the ellipse. - Quaternion.fromAxisAngle(rotAxis, angle, unitQuat); - Matrix3.fromQuaternion(unitQuat, rotMtx); - - Matrix3.multiplyByVector(rotMtx, unitPos, result); - Cartesian3.normalize(result, result); - Cartesian3.multiplyByScalar(result, mag, result); - return result; - } - function computeTopBottomAttributes(positions, options, extrude) { var vertexFormat = options.vertexFormat; var center = options.center; var semiMajorAxis = options.semiMajorAxis; var ellipsoid = options.ellipsoid; - var height = options.height; - var extrudedHeight = options.extrudedHeight; var stRotation = options.stRotation; var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3; - var finalPositions = new Float64Array(size * 3); var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined; var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined; var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined; @@ -131,7 +96,6 @@ define([ var i1 = i + 1; var i2 = i + 2; var position = Cartesian3.fromArray(positions, i, scratchCartesian1); - var extrudedPosition; if (vertexFormat.st) { var rotatedPoint = Matrix3.multiplyByVector(textureMatrix, position, scratchCartesian2); @@ -150,27 +114,7 @@ define([ textureCoordinates[textureCoordIndex++] = texCoordScratch.y; } - position = ellipsoid.scaleToGeodeticSurface(position, position); - extrudedPosition = position.clone(scratchCartesian2); normal = ellipsoid.geodeticSurfaceNormal(position, normal); - var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian4); - position = Cartesian3.add(position, scaledNormal, position); - - if (extrude) { - scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal); - extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition); - } - - if (vertexFormat.position) { - if (extrude) { - finalPositions[i + bottomOffset] = extrudedPosition.x; - finalPositions[i1 + bottomOffset] = extrudedPosition.y; - finalPositions[i2 + bottomOffset] = extrudedPosition.z; - } - finalPositions[i] = position.x; - finalPositions[i1] = position.y; - finalPositions[i2] = position.z; - } if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { if (vertexFormat.tangent || vertexFormat.binormal) { @@ -179,8 +123,8 @@ define([ } if (vertexFormat.normal) { normals[i] = normal.x; - normals[i + 1] = normal.y; - normals[i + 2] = normal.z; + normals[i1] = normal.y; + normals[i2] = normal.z; if (extrude) { normals[i + bottomOffset] = -normal.x; normals[i1 + bottomOffset] = -normal.y; @@ -190,8 +134,8 @@ define([ if (vertexFormat.tangent) { tangents[i] = tangent.x; - tangents[i + 1] = tangent.y; - tangents[i + 2] = tangent.z; + tangents[i1] = tangent.y; + tangents[i2] = tangent.z; if (extrude) { tangents[i + bottomOffset] = -tangent.x; tangents[i1 + bottomOffset] = -tangent.y; @@ -216,6 +160,7 @@ define([ var attributes = new GeometryAttributes(); if (vertexFormat.position) { + var finalPositions = EllipseGeometryLibrary.raisePositionsToHeight(positions, options, extrude); attributes.position = new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, @@ -257,152 +202,6 @@ define([ return attributes; } - function computeEllipsePositions(options, doPerimeter) { - var semiMinorAxis = options.semiMinorAxis; - var semiMajorAxis = options.semiMajorAxis; - var rotation = options.rotation; - var center = options.center; - var granularity = options.granularity; - - var MAX_ANOMALY_LIMIT = 2.31; - - var aSqr = semiMinorAxis * semiMinorAxis; - var bSqr = semiMajorAxis * semiMajorAxis; - var ab = semiMajorAxis * semiMinorAxis; - - var mag = center.magnitude(); - - var unitPos = Cartesian3.normalize(center, unitPosScratch); - var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch); - eastVec = Cartesian3.normalize(eastVec, eastVec); - var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch); - - // The number of points in the first quadrant - var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); - var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); - - // If the number of points were three, the ellipse - // would be tessellated like below: - // - // *---* - // / | \ | \ - // *---*---*---* - // / | \ | \ | \ | \ - // *---*---*---*---*---* - // | \ | \ | \ | \ | \ | - // *---*---*---*---*---* - // \ | \ | \ | \ | / - // *---*---*---* - // \ | \ | / - // *---* - // Notice each vertical column contains an even number of positions. - // The sum of the first n even numbers is n * (n + 1). Double it for the number of points - // for the whole ellipse. Note: this is just an estimate and may actually be less depending - // on the number of iterations before the angle reaches pi/2. - var size = 2 * numPts * (numPts + 1); - var positions = new Array(size * 3); - var positionIndex = 0; - var position = scratchCartesian1; - var reflectedPosition = scratchCartesian2; - - var outerLeft; - var outerRight; - if (doPerimeter) { - outerLeft = []; - outerRight = []; - } - - var i; - var j; - var numInterior; - var t; - var interiorPosition; - - // Compute points in the 'northern' half of the ellipse - var theta = CesiumMath.PI_OVER_TWO; - for (i = 0; i < numPts && theta > 0; ++i) { - position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); - - positions[positionIndex++] = position.x; - positions[positionIndex++] = position.y; - positions[positionIndex++] = position.z; - - numInterior = 2 * i + 2; - for (j = 1; j < numInterior - 1; ++j) { - t = j / (numInterior - 1); - interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); - positions[positionIndex++] = interiorPosition.x; - positions[positionIndex++] = interiorPosition.y; - positions[positionIndex++] = interiorPosition.z; - } - - positions[positionIndex++] = reflectedPosition.x; - positions[positionIndex++] = reflectedPosition.y; - positions[positionIndex++] = reflectedPosition.z; - - if (doPerimeter) { - outerRight.unshift(position.x, position.y, position.z); - if (i !== 0) { - outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); - } - } - - theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta; - } - - // Set numPts if theta reached zero - numPts = i; - - // Compute points in the 'southern' half of the ellipse - for (i = numPts; i > 0; --i) { - theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; - - position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); - - positions[positionIndex++] = position.x; - positions[positionIndex++] = position.y; - positions[positionIndex++] = position.z; - - numInterior = 2 * (i - 1) + 2; - for (j = 1; j < numInterior - 1; ++j) { - t = j / (numInterior - 1); - interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); - positions[positionIndex++] = interiorPosition.x; - positions[positionIndex++] = interiorPosition.y; - positions[positionIndex++] = interiorPosition.z; - } - - positions[positionIndex++] = reflectedPosition.x; - positions[positionIndex++] = reflectedPosition.y; - positions[positionIndex++] = reflectedPosition.z; - - if (doPerimeter) { - outerRight.unshift(position.x, position.y, position.z); - if (i !== 1) { - outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); - } - } - } - - // The original length may have been an over-estimate - if (positions.length !== positionIndex) { - size = positionIndex / 3; - positions.length = positionIndex; - } - - var r = { - positions : positions, - numPts : numPts - }; - - if (doPerimeter) { - r.outerPositions = outerRight.concat(outerLeft); - } - - return r; - } function topIndices(numPts) { // The number of triangles in the ellipse on the positive x half-space and for @@ -493,7 +292,7 @@ define([ boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter); boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter); var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis); - var cep = computeEllipsePositions(options); + var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, false); var positions = cep.positions; var numPts = cep.numPts; var attributes = computeTopBottomAttributes(positions, options, false); @@ -714,7 +513,7 @@ define([ bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center); bottomBoundingSphere.radius = semiMajorAxis; - var cep = computeEllipsePositions(options, true); + var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, true); var positions = cep.positions; var numPts = cep.numPts; var outerPositions = cep.outerPositions; diff --git a/Source/Core/EllipseGeometryLibrary.js b/Source/Core/EllipseGeometryLibrary.js new file mode 100644 index 000000000000..59ea7d93b641 --- /dev/null +++ b/Source/Core/EllipseGeometryLibrary.js @@ -0,0 +1,244 @@ +/*global define*/ +define([ + './Cartesian3', + './Ellipsoid', + './Math', + './Matrix3', + './Quaternion' + ], function( + Cartesian3, + Ellipsoid, + CesiumMath, + Matrix3, + Quaternion) { + "use strict"; + + var EllipseGeometryLibrary = {}; + + var rotAxis = new Cartesian3(); + var tempVec = new Cartesian3(); + var unitQuat = new Quaternion(); + var rotMtx = new Matrix3(); + + EllipseGeometryLibrary.pointOnEllipsoid = function(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { + var azimuth = theta + rotation; + + Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); + Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec); + Cartesian3.add(rotAxis, tempVec, rotAxis); + + var cosThetaSquared = Math.cos(theta); + cosThetaSquared = cosThetaSquared * cosThetaSquared; + + var sinThetaSquared = Math.sin(theta); + sinThetaSquared = sinThetaSquared * sinThetaSquared; + + var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared); + var angle = radius / mag; + + // Create the quaternion to rotate the position vector to the boundary of the ellipse. + Quaternion.fromAxisAngle(rotAxis, angle, unitQuat); + Matrix3.fromQuaternion(unitQuat, rotMtx); + + Matrix3.multiplyByVector(rotMtx, unitPos, result); + Cartesian3.normalize(result, result); + Cartesian3.multiplyByScalar(result, mag, result); + return result; + }; + + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + var scratchNormal = new Cartesian3(); + EllipseGeometryLibrary.raisePositionsToHeight = function(positions, options, extrude) { + var ellipsoid = options.ellipsoid; + var height = options.height; + var extrudedHeight = options.extrudedHeight; + var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3; + + var finalPositions = new Float64Array(size * 3); + var normal = scratchNormal; + + var length = positions.length; + var bottomOffset = (extrude) ? length : 0; + for ( var i = 0; i < length; i += 3) { + var i1 = i + 1; + var i2 = i + 2; + var position = Cartesian3.fromArray(positions, i, scratchCartesian1); + var extrudedPosition; + + position = ellipsoid.scaleToGeodeticSurface(position, position); + extrudedPosition = position.clone(scratchCartesian2); + normal = ellipsoid.geodeticSurfaceNormal(position, normal); + var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian3); + position = Cartesian3.add(position, scaledNormal, position); + + if (extrude) { + scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal); + extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition); + + finalPositions[i + bottomOffset] = extrudedPosition.x; + finalPositions[i1 + bottomOffset] = extrudedPosition.y; + finalPositions[i2 + bottomOffset] = extrudedPosition.z; + } + + finalPositions[i] = position.x; + finalPositions[i1] = position.y; + finalPositions[i2] = position.z; + } + + return finalPositions; + }; + + var unitPosScratch = new Cartesian3(); + var eastVecScratch = new Cartesian3(); + var northVecScratch = new Cartesian3(); + EllipseGeometryLibrary.computeEllipsePositions = function(options, addFillPositions, addEdgePositions) { + var semiMinorAxis = options.semiMinorAxis; + var semiMajorAxis = options.semiMajorAxis; + var rotation = options.rotation; + var center = options.center; + var granularity = options.granularity; + + var MAX_ANOMALY_LIMIT = 2.31; + + var aSqr = semiMinorAxis * semiMinorAxis; + var bSqr = semiMajorAxis * semiMajorAxis; + var ab = semiMajorAxis * semiMinorAxis; + + var mag = center.magnitude(); + + var unitPos = Cartesian3.normalize(center, unitPosScratch); + var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch); + eastVec = Cartesian3.normalize(eastVec, eastVec); + var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch); + + // The number of points in the first quadrant + var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); + var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); + + // If the number of points were three, the ellipse + // would be tessellated like below: + // + // *---* + // / | \ | \ + // *---*---*---* + // / | \ | \ | \ | \ + // *---*---*---*---*---* + // | \ | \ | \ | \ | \ | + // *---*---*---*---*---* + // \ | \ | \ | \ | / + // *---*---*---* + // \ | \ | / + // *---* + // Notice each vertical column contains an even number of positions. + // The sum of the first n even numbers is n * (n + 1). Double it for the number of points + // for the whole ellipse. Note: this is just an estimate and may actually be less depending + // on the number of iterations before the angle reaches pi/2. + var size = 2 * numPts * (numPts + 1); + var positions = (addFillPositions) ? new Array(size * 3) : undefined; + var positionIndex = 0; + var position = scratchCartesian1; + var reflectedPosition = scratchCartesian2; + + var outerLeft = (addEdgePositions) ? [] : undefined; + var outerRight = (addEdgePositions) ? [] : undefined; + + var i; + var j; + var numInterior; + var t; + var interiorPosition; + + // Compute points in the 'northern' half of the ellipse + var theta = CesiumMath.PI_OVER_TWO; + for (i = 0; i < numPts && theta > 0; ++i) { + position = EllipseGeometryLibrary.pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = EllipseGeometryLibrary.pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + if (addFillPositions) { + positions[positionIndex++] = position.x; + positions[positionIndex++] = position.y; + positions[positionIndex++] = position.z; + + numInterior = 2 * i + 2; + for (j = 1; j < numInterior - 1; ++j) { + t = j / (numInterior - 1); + interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); + positions[positionIndex++] = interiorPosition.x; + positions[positionIndex++] = interiorPosition.y; + positions[positionIndex++] = interiorPosition.z; + } + + positions[positionIndex++] = reflectedPosition.x; + positions[positionIndex++] = reflectedPosition.y; + positions[positionIndex++] = reflectedPosition.z; + } + + if (addEdgePositions) { + outerRight.unshift(position.x, position.y, position.z); + if (i !== 0) { + outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); + } + } + + theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta; + } + + // Set numPts if theta reached zero + numPts = i; + + // Compute points in the 'southern' half of the ellipse + for (i = numPts; i > 0; --i) { + theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; + + position = EllipseGeometryLibrary.pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = EllipseGeometryLibrary.pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + if (addFillPositions) { + positions[positionIndex++] = position.x; + positions[positionIndex++] = position.y; + positions[positionIndex++] = position.z; + + numInterior = 2 * (i - 1) + 2; + for (j = 1; j < numInterior - 1; ++j) { + t = j / (numInterior - 1); + interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); + positions[positionIndex++] = interiorPosition.x; + positions[positionIndex++] = interiorPosition.y; + positions[positionIndex++] = interiorPosition.z; + } + + positions[positionIndex++] = reflectedPosition.x; + positions[positionIndex++] = reflectedPosition.y; + positions[positionIndex++] = reflectedPosition.z; + } + + if (addEdgePositions) { + outerRight.unshift(position.x, position.y, position.z); + if (i !== 1) { + outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); + } + } + } + + var r = {}; + if (addFillPositions) { + // The original length may have been an over-estimate + if (positions.length !== positionIndex) { + size = positionIndex / 3; + positions.length = positionIndex; + } + r.positions = positions; + r.numPts = numPts; + } + + if (addEdgePositions) { + r.outerPositions = outerRight.concat(outerLeft); + } + + return r; + }; + + return EllipseGeometryLibrary; +}); \ No newline at end of file diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index 7565df682065..19491f86dd1e 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -7,6 +7,7 @@ define([ './IndexDatatype', './DeveloperError', './Ellipsoid', + './EllipseGeometryLibrary', './GeometryAttribute', './GeometryAttributes', './Math', @@ -21,6 +22,7 @@ define([ IndexDatatype, DeveloperError, Ellipsoid, + EllipseGeometryLibrary, GeometryAttribute, GeometryAttributes, CesiumMath, @@ -29,169 +31,24 @@ define([ Quaternion) { "use strict"; - var rotAxis = new Cartesian3(); - var tempVec = new Cartesian3(); - var unitQuat = new Quaternion(); - var rotMtx = new Matrix3(); - var scratchCartesian1 = new Cartesian3(); - var scratchCartesian2 = new Cartesian3(); - var scratchCartesian3 = new Cartesian3(); - - var scratchNormal = new Cartesian3(); - - var unitPosScratch = new Cartesian3(); - var eastVecScratch = new Cartesian3(); - var northVecScratch = new Cartesian3(); - - function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { - var azimuth = theta + rotation; - - Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); - Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec); - Cartesian3.add(rotAxis, tempVec, rotAxis); - - var cosThetaSquared = Math.cos(theta); - cosThetaSquared = cosThetaSquared * cosThetaSquared; - - var sinThetaSquared = Math.sin(theta); - sinThetaSquared = sinThetaSquared * sinThetaSquared; - - var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared); - var angle = radius / mag; - - // Create the quaternion to rotate the position vector to the boundary of the ellipse. - Quaternion.fromAxisAngle(rotAxis, angle, unitQuat); - Matrix3.fromQuaternion(unitQuat, rotMtx); - - Matrix3.multiplyByVector(rotMtx, unitPos, result); - Cartesian3.normalize(result, result); - Cartesian3.multiplyByScalar(result, mag, result); - return result; - } - - function raisePositionsToHeight(positions, options, extrude) { - var ellipsoid = options.ellipsoid; - var height = options.height; - var extrudedHeight = options.extrudedHeight; - var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3; - - var finalPositions = new Float64Array(size * 3); - var normal = scratchNormal; - - var length = positions.length; - var bottomOffset = (extrude) ? length : 0; - for ( var i = 0; i < length; i += 3) { - var i1 = i + 1; - var i2 = i + 2; - var position = Cartesian3.fromArray(positions, i, scratchCartesian1); - var extrudedPosition; - - position = ellipsoid.scaleToGeodeticSurface(position, position); - extrudedPosition = position.clone(scratchCartesian2); - normal = ellipsoid.geodeticSurfaceNormal(position, normal); - var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian3); - position = Cartesian3.add(position, scaledNormal, position); - - if (extrude) { - scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal); - extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition); - - finalPositions[i + bottomOffset] = extrudedPosition.x; - finalPositions[i1 + bottomOffset] = extrudedPosition.y; - finalPositions[i2 + bottomOffset] = extrudedPosition.z; - } + var boundingSphereCenter = new Cartesian3(); - finalPositions[i] = position.x; - finalPositions[i1] = position.y; - finalPositions[i2] = position.z; - } + function computeEllipse(options) { + var center = options.center; + boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter); + boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter); + var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis); + var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions; var attributes = new GeometryAttributes({ position: new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : finalPositions + values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, false) }) }); - return attributes; - } - - function computeEllipsePositions(options) { - var semiMinorAxis = options.semiMinorAxis; - var semiMajorAxis = options.semiMajorAxis; - var rotation = options.rotation; - var center = options.center; - var granularity = options.granularity; - - var MAX_ANOMALY_LIMIT = 2.31; - - var aSqr = semiMinorAxis * semiMinorAxis; - var bSqr = semiMajorAxis * semiMajorAxis; - var ab = semiMajorAxis * semiMinorAxis; - - var mag = center.magnitude(); - - var unitPos = Cartesian3.normalize(center, unitPosScratch); - var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch); - eastVec = Cartesian3.normalize(eastVec, eastVec); - var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch); - - // The number of points in the first quadrant - var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); - var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); - - var position = scratchCartesian1; - var reflectedPosition = scratchCartesian2; - - var outerLeft = []; - var outerRight = []; - - var i; - - // Compute points in the 'northern' half of the ellipse - var theta = CesiumMath.PI_OVER_TWO; - for (i = 0; i < numPts && theta > 0; ++i) { - position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); - - outerRight.unshift(position.x, position.y, position.z); - if (i !== 0) { - outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); - } - - theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta; - } - - // Set numPts if theta reached zero - numPts = i; - - // Compute points in the 'southern' half of the ellipse - for (i = numPts; i > 0; --i) { - theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; - - position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); - - outerRight.unshift(position.x, position.y, position.z); - if (i !== 1) { - outerLeft.push(reflectedPosition.x, reflectedPosition.y, reflectedPosition.z); - } - } - - return outerRight.concat(outerLeft); - } - - var boundingSphereCenter = new Cartesian3(); - function computeEllipse(options) { - var center = options.center; - boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter); - boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter); - var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis); - var positions = computeEllipsePositions(options); - var attributes = raisePositionsToHeight(positions, options, false); - var length = positions.length / 3; var indices = IndexDatatype.createTypedArray(length, length*2); var index = 0; @@ -226,8 +83,15 @@ define([ bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center); bottomBoundingSphere.radius = semiMajorAxis; - var positions = computeEllipsePositions(options); - var attributes = raisePositionsToHeight(positions, options, true); + var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions; + var attributes = new GeometryAttributes({ + position: new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, true) + }) + }); + positions = attributes.position.values; var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere); var length = positions.length/3; diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index d08b0965816b..df1fc585eb43 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -17,6 +17,7 @@ define([ './IndexDatatype', './Math', './Matrix3', + './PolygonGeometryLibrary', './PolygonPipeline', './PrimitiveType', './Quaternion', @@ -41,6 +42,7 @@ define([ IndexDatatype, CesiumMath, Matrix3, + PolygonGeometryLibrary, PolygonPipeline, PrimitiveType, Quaternion, @@ -114,8 +116,8 @@ define([ var scratchTangent = new Cartesian3(); var scratchBinormal = new Cartesian3(); var scratchBoundingSphere = new BoundingSphere(); - var p1 = new Cartesian3(); - var p2 = new Cartesian3(); + var p1Scratch = new Cartesian3(); + var p2Scratch = new Cartesian3(); var appendTextureCoordinatesOrigin = new Cartesian2(); var appendTextureCoordinatesCartesian2 = new Cartesian2(); @@ -186,10 +188,10 @@ define([ if (wall) { if (i+3 < length) { - p1 = Cartesian3.fromArray(flatPositions, i + 3, p1); + var p1 = Cartesian3.fromArray(flatPositions, i + 3, p1Scratch); if (recomputeNormal) { - p2 = Cartesian3.fromArray(flatPositions, i + length, p2); + var p2 = Cartesian3.fromArray(flatPositions, i + length, p2Scratch); p1.subtract(position, p1); p2.subtract(position, p2); normal = Cartesian3.cross(p2, p1, normal).normalize(normal); @@ -301,80 +303,6 @@ define([ return geometry; } - var scaleToGeodeticHeightN1 = new Cartesian3(); - var scaleToGeodeticHeightN2 = new Cartesian3(); - var scaleToGeodeticHeightP = new Cartesian3(); - function scaleToGeodeticHeightExtruded(geometry, maxHeight, minHeight, ellipsoid) { - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - - var n1 = scaleToGeodeticHeightN1; - var n2 = scaleToGeodeticHeightN2; - var p = scaleToGeodeticHeightP; - - if (typeof geometry !== 'undefined' && typeof geometry.attributes !== 'undefined' && typeof geometry.attributes.position !== 'undefined') { - var positions = geometry.attributes.position.values; - var length = positions.length / 2; - - for ( var i = 0; i < length; i += 3) { - Cartesian3.fromArray(positions, i, p); - - ellipsoid.scaleToGeodeticSurface(p, p); - ellipsoid.geodeticSurfaceNormal(p, n1); - - Cartesian3.multiplyByScalar(n1, maxHeight, n2); - Cartesian3.add(p, n2, n2); - positions[i] = n2.x; - positions[i + 1] = n2.y; - positions[i + 2] = n2.z; - - Cartesian3.multiplyByScalar(n1, minHeight, n2); - Cartesian3.add(p, n2, n2); - positions[i + length] = n2.x; - positions[i + 1 + length] = n2.y; - positions[i + 2 + length] = n2.z; - } - } - return geometry; - } - - var distanceScratch = new Cartesian3(); - function getPointAtDistance(p0, p1, distance, length) { - distanceScratch = p1.subtract(p0, distanceScratch); - distanceScratch = distanceScratch.multiplyByScalar(distance/length, distanceScratch); - distanceScratch = p0.add(distanceScratch, distanceScratch); - return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; - } - - function subdivideLine(p0, p1, granularity) { - var length = Cartesian3.distance(p0, p1); - var angleBetween = Cartesian3.angleBetween(p0, p1); - var n = angleBetween/granularity; - var countDivide = Math.ceil(Math.log(n)/Math.log(2)); - if (countDivide < 1) { - countDivide = 0; - } - var numVertices = Math.pow(2, countDivide); - - var distanceBetweenVertices = length / numVertices; - - var positions = new Array(numVertices * 3); - var index = 0; - positions[index++] = p0.x; - positions[index++] = p0.y; - positions[index++] = p0.z; - for (var i = 1; i < numVertices; i++) { - var p = getPointAtDistance(p0, p1, i*distanceBetweenVertices, length); - positions[index++] = p[0]; - positions[index++] = p[1]; - positions[index++] = p[2]; - } - positions[index++] = p1.x; - positions[index++] = p1.y; - positions[index++] = p1.z; - - return positions; - } - function computeWallIndices(positions, granularity){ var edgePositions = []; var subdividedEdge; @@ -383,8 +311,13 @@ define([ var i; var length = positions.length; + var p1; + var p2; for (i = 0; i < length; i++) { - subdividedEdge = subdivideLine(positions[i], positions[(i+1)%length], granularity); + p1 = positions[i]; + p2 = positions[(i+1)%length]; + subdividedEdge = PolygonGeometryLibrary.subdivideLine(p1, p2, granularity); + subdividedEdge.push(p2.x, p2.y, p2.z); edgePositions = edgePositions.concat(subdividedEdge); } @@ -661,14 +594,14 @@ define([ geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, polygonHierarchy[i]); if (typeof geometry !== 'undefined') { topAndBottom = geometry.topAndBottom; - topAndBottom.geometry = scaleToGeodeticHeightExtruded(topAndBottom.geometry, height, extrudedHeight, ellipsoid); + topAndBottom.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(topAndBottom.geometry, height, extrudedHeight, ellipsoid); topAndBottom.geometry = computeAttributes(vertexFormat, topAndBottom.geometry, outerPositions, ellipsoid, stRotation, true, false); geometries.push(topAndBottom); walls = geometry.walls; for (var k = 0; k < walls.length; k++) { var wall = walls[k]; - wall.geometry = scaleToGeodeticHeightExtruded(wall.geometry, height, extrudedHeight, ellipsoid); + wall.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(wall.geometry, height, extrudedHeight, ellipsoid); wall.geometry = computeAttributes(vertexFormat, wall.geometry, outerPositions, ellipsoid, stRotation, true, true); geometries.push(wall); } diff --git a/Source/Core/PolygonGeometryLibrary.js b/Source/Core/PolygonGeometryLibrary.js new file mode 100644 index 000000000000..39d4d86129bb --- /dev/null +++ b/Source/Core/PolygonGeometryLibrary.js @@ -0,0 +1,87 @@ +/*global define*/ +define([ + './defaultValue', + './Cartesian3', + './Ellipsoid' + ], function( + defaultValue, + Cartesian3, + Ellipsoid) { + "use strict"; + + var PolygonGeometryLibrary = {}; + + var distanceScratch = new Cartesian3(); + function getPointAtDistance(p0, p1, distance, length) { + distanceScratch = p1.subtract(p0, distanceScratch); + distanceScratch = distanceScratch.multiplyByScalar(distance/length, distanceScratch); + distanceScratch = p0.add(distanceScratch, distanceScratch); + return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; + } + + PolygonGeometryLibrary.subdivideLine = function(p0, p1, granularity) { + var length = Cartesian3.distance(p0, p1); + var angleBetween = Cartesian3.angleBetween(p0, p1); + var n = angleBetween/granularity; + var countDivide = Math.ceil(Math.log(n)/Math.log(2)); + if (countDivide < 1) { + countDivide = 0; + } + var numVertices = Math.pow(2, countDivide); + + var distanceBetweenVertices = length / numVertices; + + var positions = new Array(numVertices * 3); + var index = 0; + positions[index++] = p0.x; + positions[index++] = p0.y; + positions[index++] = p0.z; + for (var i = 1; i < numVertices; i++) { + var p = getPointAtDistance(p0, p1, i*distanceBetweenVertices, length); + positions[index++] = p[0]; + positions[index++] = p[1]; + positions[index++] = p[2]; + } + + return positions; + }; + + + var scaleToGeodeticHeightN1 = new Cartesian3(); + var scaleToGeodeticHeightN2 = new Cartesian3(); + var scaleToGeodeticHeightP = new Cartesian3(); + PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid) { + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + + var n1 = scaleToGeodeticHeightN1; + var n2 = scaleToGeodeticHeightN2; + var p = scaleToGeodeticHeightP; + + if (typeof geometry !== 'undefined' && typeof geometry.attributes !== 'undefined' && typeof geometry.attributes.position !== 'undefined') { + var positions = geometry.attributes.position.values; + var length = positions.length / 2; + + for ( var i = 0; i < length; i += 3) { + Cartesian3.fromArray(positions, i, p); + + ellipsoid.scaleToGeodeticSurface(p, p); + ellipsoid.geodeticSurfaceNormal(p, n1); + + Cartesian3.multiplyByScalar(n1, maxHeight, n2); + Cartesian3.add(p, n2, n2); + positions[i] = n2.x; + positions[i + 1] = n2.y; + positions[i + 2] = n2.z; + + Cartesian3.multiplyByScalar(n1, minHeight, n2); + Cartesian3.add(p, n2, n2); + positions[i + length] = n2.x; + positions[i + 1 + length] = n2.y; + positions[i + 2 + length] = n2.z; + } + } + return geometry; + }; + + return PolygonGeometryLibrary; +}); \ No newline at end of file diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index 34a8ebb1905d..909599bf97eb 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -14,6 +14,7 @@ define([ './GeometryPipeline', './IndexDatatype', './Math', + './PolygonGeometryLibrary', './PolygonPipeline', './PrimitiveType', './Queue', @@ -33,6 +34,7 @@ define([ GeometryPipeline, IndexDatatype, CesiumMath, + PolygonGeometryLibrary, PolygonPipeline, PrimitiveType, Queue, @@ -40,41 +42,6 @@ define([ "use strict"; var createGeometryFromPositionsPositions = []; - var distanceScratch = new Cartesian3(); - function getPointAtDistance(p0, p1, distance, length) { - distanceScratch = p1.subtract(p0, distanceScratch); - distanceScratch = distanceScratch.multiplyByScalar(distance/length, distanceScratch); - distanceScratch = p0.add(distanceScratch, distanceScratch); - return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; - } - - function subdivideLine(p0, p1, granularity) { - var length = Cartesian3.distance(p0, p1); - var angleBetween = Cartesian3.angleBetween(p0, p1); - var n = angleBetween/granularity; - var countDivide = Math.ceil(Math.log(n)/Math.log(2)); - if (countDivide < 1) { - countDivide = 0; - } - var numVertices = Math.pow(2, countDivide); - - var distanceBetweenVertices = length / numVertices; - - var positions = new Array(numVertices * 3); - var index = 0; - positions[index++] = p0.x; - positions[index++] = p0.y; - positions[index++] = p0.z; - for (var i = 1; i < numVertices; i++) { - var p = getPointAtDistance(p0, p1, i*distanceBetweenVertices, length); - positions[index++] = p[0]; - positions[index++] = p[1]; - positions[index++] = p[2]; - } - - return positions; - } - function createGeometryFromPositions(ellipsoid, positions, granularity) { var cleanedPositions = PolygonPipeline.removeDuplicates(positions); if (cleanedPositions.length < 3) { @@ -93,9 +60,9 @@ define([ var length = cleanedPositions.length; var i; for (i = 0; i < length-1; i++) { - subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); + subdividedPositions = subdividedPositions.concat(PolygonGeometryLibrary.subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); } - subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); + subdividedPositions = subdividedPositions.concat(PolygonGeometryLibrary.subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); length = subdividedPositions.length/3; var indicesSize = length*2; @@ -127,42 +94,6 @@ define([ var scratchNormal = new Cartesian3(); var scratchBoundingSphere = new BoundingSphere(); - var scaleToGeodeticHeightN1 = new Cartesian3(); - var scaleToGeodeticHeightN2 = new Cartesian3(); - var scaleToGeodeticHeightP = new Cartesian3(); - function scaleToGeodeticHeightExtruded(geometry, maxHeight, minHeight, ellipsoid) { - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - - var n1 = scaleToGeodeticHeightN1; - var n2 = scaleToGeodeticHeightN2; - var p = scaleToGeodeticHeightP; - - if (typeof geometry !== 'undefined' && typeof geometry.attributes !== 'undefined' && typeof geometry.attributes.position !== 'undefined') { - var positions = geometry.attributes.position.values; - var length = positions.length / 2; - - for ( var i = 0; i < length; i += 3) { - Cartesian3.fromArray(positions, i, p); - - ellipsoid.scaleToGeodeticSurface(p, p); - ellipsoid.geodeticSurfaceNormal(p, n1); - - Cartesian3.multiplyByScalar(n1, maxHeight, n2); - Cartesian3.add(p, n2, n2); - positions[i] = n2.x; - positions[i + 1] = n2.y; - positions[i + 2] = n2.z; - - Cartesian3.multiplyByScalar(n1, minHeight, n2); - Cartesian3.add(p, n2, n2); - positions[i + length] = n2.x; - positions[i + 1 + length] = n2.y; - positions[i + 2 + length] = n2.z; - } - } - return geometry; - } - function createGeometryFromPositionsExtruded(ellipsoid, positions, granularity) { var cleanedPositions = PolygonPipeline.removeDuplicates(positions); if (cleanedPositions.length < 3) { @@ -183,10 +114,10 @@ define([ var corners = new Array(subdividedPositions.length); corners[0] = 0; for (i = 0; i < length-1; i++) { - subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); + subdividedPositions = subdividedPositions.concat(PolygonGeometryLibrary.subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); corners[i+1] = subdividedPositions.length/3; } - subdividedPositions = subdividedPositions.concat(subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); + subdividedPositions = subdividedPositions.concat(PolygonGeometryLibrary.subdivideLine(cleanedPositions[length-1], cleanedPositions[0], granularity)); length = subdividedPositions.length/3; var indicesSize = ((length * 2) + corners.length)*2; @@ -377,7 +308,7 @@ define([ for (i = 0; i < polygons.length; i++) { geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity); if (typeof geometry !== 'undefined') { - geometry.geometry = scaleToGeodeticHeightExtruded(geometry.geometry, height, extrudedHeight, ellipsoid); + geometry.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(geometry.geometry, height, extrudedHeight, ellipsoid); geometries.push(geometry); } } diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 5aec8d00c0ba..0cf2a4314fa1 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -260,7 +260,7 @@ define([ }; /** - * Raises the positions to the given height. Assumes all points are at height 0. + * Raises the positions to the given height. * * @memberof PolylinePipeline * @@ -271,7 +271,6 @@ define([ * @returns {Array} The array of positions scaled to height. * @exception {DeveloperError} positions must be defined. - * @exception {DeveloperError} height must be defined. * @exception {DeveloperError} height.length must be equal to positions.length * * @example @@ -282,20 +281,28 @@ define([ * * var raisedPositions = PolylinePipeline.scaleToGeodeticHeight(positions, heights); */ - PolylinePipeline.scaleToGeodeticHeight = function(positions, height, ellipsoid) { + PolylinePipeline.scaleToGeodeticHeight = function(positions, ellipsoid, height) { if (typeof positions === 'undefined') { throw new DeveloperError('positions must be defined.'); } - if (typeof height === 'undefined') { - throw new DeveloperError('height must be defined.'); - } + height = defaultValue(height, 0); ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var h; var length = positions.length; var i; var p = scaleP; var newPositions = new Array(positions.length); + if (height === 0) { + for(i = 0; i < length; i+=3) { + p = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(positions, i, p), p); + newPositions[i] = p.x; + newPositions[i + 1] = p.y; + newPositions[i + 2] = p.z; + } + return newPositions; + } + + var h; if (Array.isArray(height)) { if (height.length !== length/3) { throw new DeveloperError('height.length must be equal to positions.length'); diff --git a/Source/Core/WallGeometry.js b/Source/Core/WallGeometry.js index e6b582de0f84..464f8ef7f883 100644 --- a/Source/Core/WallGeometry.js +++ b/Source/Core/WallGeometry.js @@ -16,6 +16,7 @@ define([ './PolygonPipeline', './PrimitiveType', './VertexFormat', + './WallGeometryLibrary', './WindingOrder' ], function( defaultValue, @@ -34,11 +35,9 @@ define([ PolygonPipeline, PrimitiveType, VertexFormat, + WallGeometryLibrary, WindingOrder) { "use strict"; - - var scratchCartographic1 = new Cartographic(); - var scratchCartographic2 = new Cartographic(); var scratchCartesian3Position1 = new Cartesian3(); var scratchCartesian3Position2 = new Cartesian3(); var scratchCartesian3Position3 = new Cartesian3(); @@ -48,84 +47,6 @@ define([ var scratchTangent = new Cartesian3(); var scratchNormal = new Cartesian3(); - function subdivideHeights(p0, p1, h0, h1, granularity) { - var angleBetween = Cartesian3.angleBetween(p0, p1); - var numPoints = Math.ceil(angleBetween/granularity) + 1; - var heights = new Array(numPoints); - var i; - if (h0 === h1) { - for (i = 0; i < numPoints; i++) { - heights[i] = h0; - } - return heights; - } - - var dHeight = h1 - h0; - var heightPerVertex = dHeight / (numPoints - 1); - - for (i = 1; i < numPoints - 1; i++) { - var h = h0 + i*heightPerVertex; - heights[i] = h; - } - - heights[0] = h0; - heights[numPoints - 1] = h1; - - return heights; - } - - function latLonEquals(c0, c1) { - return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); - } - - function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) { - var hasBottomHeights = (typeof bottomHeights !== 'undefined'); - var hasTopHeights = (typeof topHeights !== 'undefined'); - var cleanedPositions = []; - var cleanedTopHeights = []; - var cleanedBottomHeights = hasBottomHeights ? [] : undefined; - - var length = positions.length; - if (length < 2) { - return positions.slice(0); - } - - var v0 = positions[0]; - cleanedPositions.push(v0); - var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1); - if (hasTopHeights) { - c0.height = topHeights[0]; - } - cleanedTopHeights.push(c0.height); - if (hasBottomHeights) { - cleanedBottomHeights.push(bottomHeights[0]); - } - for (var i = 1; i < length; ++i) { - var v1 = positions[i]; - var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2); - if (hasTopHeights) { - c1.height = topHeights[i]; - } - if (!latLonEquals(c0, c1)) { - cleanedPositions.push(v1); // Shallow copy! - cleanedTopHeights.push(c1.height); - if (hasBottomHeights) { - cleanedBottomHeights.push(bottomHeights[i]); - } - } else if (c0.height < c1.height) { - cleanedTopHeights[i-1] = c1.height; - } - - c0 = c1.clone(c0); - } - - return { - positions: cleanedPositions, - topHeights: cleanedTopHeights, - bottomHeights: cleanedBottomHeights - }; - } - /** * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. @@ -184,55 +105,12 @@ define([ var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); - - wallPositions = o.positions; - maximumHeights = o.topHeights; - minimumHeights = o.bottomHeights; - - if (wallPositions.length < 2) { - throw new DeveloperError('unique positions must be greater than or equal to 2'); - } + var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true); + var newWallPositions = pos.newWallPositions; + var bottomPositions = pos.bottomPositions; + var topPositions = pos.topPositions; - if (wallPositions.length >= 3) { - // Order positions counter-clockwise - var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); - - if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { - wallPositions.reverse(); - maximumHeights.reverse(); - - if (typeof minimumHeights !== 'undefined') { - minimumHeights.reverse(); - } - } - } - - var i; - var length = wallPositions.length; - var newMaxHeights = []; - var newMinHeights = (typeof minimumHeights !== 'undefined') ? [] : undefined; - var newWallPositions = []; - for (i = 0; i < length-1; i++) { - var p1 = wallPositions[i]; - var p2 = wallPositions[i + 1]; - var h1 = maximumHeights[i]; - var h2 = maximumHeights[i + 1]; - newMaxHeights = newMaxHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); - - if (typeof minimumHeights !== 'undefined') { - p1 = wallPositions[i]; - p2 = wallPositions[i + 1]; - h1 = minimumHeights[i]; - h2 = minimumHeights[i + 1]; - newMinHeights = newMinHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); - } - - newWallPositions = newWallPositions.concat(PolylinePipeline.scaleToSurface([p1, p2], granularity, ellipsoid)); - } - - length = newWallPositions.length; + var length = newWallPositions.length; var size = length * 2; var positions = vertexFormat.position ? new Float64Array(size) : undefined; @@ -247,14 +125,6 @@ define([ var tangentIndex = 0; var stIndex = 0; - var bottomPositions; - var topPositions; - - var minH = defaultValue(newMinHeights, 0); - bottomPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, minH, ellipsoid); - topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, newMaxHeights, ellipsoid); - - // add lower and upper points one after the other, lower // points being even and upper points being odd var normal = scratchNormal; @@ -262,6 +132,7 @@ define([ var binormal = scratchBinormal; var recomputeNormal = true; length /= 3; + var i; for (i = 0; i < length; ++i) { var i3 = i * 3; var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1); diff --git a/Source/Core/WallGeometryLibrary.js b/Source/Core/WallGeometryLibrary.js new file mode 100644 index 000000000000..c540608b5dcb --- /dev/null +++ b/Source/Core/WallGeometryLibrary.js @@ -0,0 +1,178 @@ +/*global define*/ +define([ + './Cartographic', + './Cartesian3', + './DeveloperError', + './EllipsoidTangentPlane', + './PolygonPipeline', + './PolylinePipeline', + './Math', + './WindingOrder' + ], function( + Cartographic, + Cartesian3, + DeveloperError, + EllipsoidTangentPlane, + PolygonPipeline, + PolylinePipeline, + CesiumMath, + WindingOrder) { + "use strict"; + + var WallGeometryLibrary = {}; + + WallGeometryLibrary.subdivideHeights = function(p0, p1, h0, h1, granularity) { + var angleBetween = Cartesian3.angleBetween(p0, p1); + var numPoints = Math.ceil(angleBetween/granularity); + var heights = new Array(numPoints); + var i; + if (h0 === h1) { + for (i = 0; i < numPoints; i++) { + heights[i] = h0; + } + return heights; + } + + var dHeight = h1 - h0; + var heightPerVertex = dHeight / (numPoints); + + for (i = 1; i < numPoints; i++) { + var h = h0 + i*heightPerVertex; + heights[i] = h; + } + + heights[0] = h0; + return heights; + }; + + function latLonEquals(c0, c1) { + return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); + } + + var scratchCartographic1 = new Cartographic(); + var scratchCartographic2 = new Cartographic(); + function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) { + var hasBottomHeights = (typeof bottomHeights !== 'undefined'); + var hasTopHeights = (typeof topHeights !== 'undefined'); + var cleanedPositions = []; + var cleanedTopHeights = []; + var cleanedBottomHeights = hasBottomHeights ? [] : undefined; + + var length = positions.length; + if (length < 2) { + return positions.slice(0); + } + + var v0 = positions[0]; + cleanedPositions.push(v0); + var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1); + if (hasTopHeights) { + c0.height = topHeights[0]; + } + cleanedTopHeights.push(c0.height); + if (hasBottomHeights) { + cleanedBottomHeights.push(bottomHeights[0]); + } + for (var i = 1; i < length; ++i) { + var v1 = positions[i]; + var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2); + if (hasTopHeights) { + c1.height = topHeights[i]; + } + if (!latLonEquals(c0, c1)) { + cleanedPositions.push(v1); // Shallow copy! + cleanedTopHeights.push(c1.height); + if (hasBottomHeights) { + cleanedBottomHeights.push(bottomHeights[i]); + } + } else if (c0.height < c1.height) { + cleanedTopHeights[i-1] = c1.height; + } + + c0 = c1.clone(c0); + } + + return { + positions: cleanedPositions, + topHeights: cleanedTopHeights, + bottomHeights: cleanedBottomHeights + }; + } + + WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) { + var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); + + wallPositions = o.positions; + maximumHeights = o.topHeights; + minimumHeights = o.bottomHeights; + + if (wallPositions.length < 2) { + throw new DeveloperError('unique positions must be greater than or equal to 2'); + } + var hasMinHeights = (typeof minimumHeights !== 'undefined'); + + if (wallPositions.length >= 3) { + // Order positions counter-clockwise + var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); + + if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { + wallPositions.reverse(); + maximumHeights.reverse(); + + if (hasMinHeights) { + minimumHeights.reverse(); + } + } + } + + var i; + var length = wallPositions.length; + var newMaxHeights = []; + var newMinHeights = (hasMinHeights) ? [] : undefined; + var newWallPositions = []; + for (i = 0; i < length-1; i++) { + var p1 = wallPositions[i]; + var p2 = wallPositions[i + 1]; + var h1 = maximumHeights[i]; + var h2 = maximumHeights[i + 1]; + newMaxHeights = newMaxHeights.concat(WallGeometryLibrary.subdivideHeights(p1, p2, h1, h2, granularity)); + if (duplicateCorners) { + newMaxHeights.push(h2); + } + + if (hasMinHeights) { + p1 = wallPositions[i]; + p2 = wallPositions[i + 1]; + h1 = minimumHeights[i]; + h2 = minimumHeights[i + 1]; + newMinHeights = newMinHeights.concat(WallGeometryLibrary.subdivideHeights(p1, p2, h1, h2, granularity)); + if (duplicateCorners) { + newMinHeights.push(h2); + } + } + + if (duplicateCorners) { + newWallPositions = newWallPositions.concat(PolylinePipeline.scaleToSurface([p1, p2], granularity, ellipsoid)); + } + } + + if (!duplicateCorners) { + newWallPositions = PolylinePipeline.scaleToSurface(wallPositions, granularity, ellipsoid); + newMaxHeights.push(maximumHeights[length-1]); + if (hasMinHeights) { + newMinHeights.push(minimumHeights[length-1]); + } + } + var bottomPositions = (hasMinHeights) ? PolylinePipeline.scaleToGeodeticHeight(newWallPositions, ellipsoid, newMinHeights) : newWallPositions.slice(0); + var topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, ellipsoid, newMaxHeights); + + return { + newWallPositions: newWallPositions, + bottomPositions: bottomPositions, + topPositions: topPositions + }; + }; + + return WallGeometryLibrary; +}); \ No newline at end of file diff --git a/Source/Core/WallOutlineGeometry.js b/Source/Core/WallOutlineGeometry.js index c3e72b391e45..dfad0cdf0584 100644 --- a/Source/Core/WallOutlineGeometry.js +++ b/Source/Core/WallOutlineGeometry.js @@ -15,6 +15,7 @@ define([ './PolylinePipeline', './PolygonPipeline', './PrimitiveType', + './WallGeometryLibrary', './WindingOrder' ], function( defaultValue, @@ -32,90 +33,13 @@ define([ PolylinePipeline, PolygonPipeline, PrimitiveType, + WallGeometryLibrary, WindingOrder) { "use strict"; - var scratchCartographic1 = new Cartographic(); - var scratchCartographic2 = new Cartographic(); var scratchCartesian3Position1 = new Cartesian3(); var scratchCartesian3Position2 = new Cartesian3(); - function subdivideHeights(p0, p1, h0, h1, granularity) { - var angleBetween = Cartesian3.angleBetween(p0, p1); - var numPoints = Math.ceil(angleBetween/granularity); - var heights = new Array(numPoints); - var i; - if (h0 === h1) { - for (i = 0; i < numPoints; i++) { - heights[i] = h0; - } - return heights; - } - - var dHeight = h1 - h0; - var heightPerVertex = dHeight / (numPoints); - - for (i = 1; i < numPoints; i++) { - var h = h0 + i*heightPerVertex; - heights[i] = h; - } - - heights[0] = h0; - return heights; - } - - function latLonEquals(c0, c1) { - return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); - } - - function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) { - var hasBottomHeights = (typeof bottomHeights !== 'undefined'); - var hasTopHeights = (typeof topHeights !== 'undefined'); - var cleanedPositions = []; - var cleanedTopHeights = []; - var cleanedBottomHeights = hasBottomHeights ? [] : undefined; - - var length = positions.length; - if (length < 2) { - return positions.slice(0); - } - - var v0 = positions[0]; - cleanedPositions.push(v0); - var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1); - if (hasTopHeights) { - c0.height = topHeights[0]; - } - cleanedTopHeights.push(c0.height); - if (hasBottomHeights) { - cleanedBottomHeights.push(bottomHeights[0]); - } - for (var i = 1; i < length; ++i) { - var v1 = positions[i]; - var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2); - if (hasTopHeights) { - c1.height = topHeights[i]; - } - if (!latLonEquals(c0, c1)) { - cleanedPositions.push(v1); // Shallow copy! - cleanedTopHeights.push(c1.height); - if (hasBottomHeights) { - cleanedBottomHeights.push(bottomHeights[i]); - } - } else if (c0.height < c1.height) { - cleanedTopHeights[i-1] = c1.height; - } - - c0 = c1.clone(c0); - } - - return { - positions: cleanedPositions, - topHeights: cleanedTopHeights, - bottomHeights: cleanedBottomHeights - }; - } - /** * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. @@ -173,75 +97,21 @@ define([ var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); - - wallPositions = o.positions; - maximumHeights = o.topHeights; - minimumHeights = o.bottomHeights; - - if (wallPositions.length < 2) { - throw new DeveloperError('unique positions must be greater than or equal to 2'); - } - - if (wallPositions.length >= 3) { - // Order positions counter-clockwise - var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); - - if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { - wallPositions.reverse(); - maximumHeights.reverse(); + var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, false); + var newWallPositions = pos.newWallPositions; + var bottomPositions = pos.bottomPositions; + var topPositions = pos.topPositions; - if (typeof minimumHeights !== 'undefined') { - minimumHeights.reverse(); - } - } - } - - var i; - var length = wallPositions.length; - var newMaxHeights = []; - var newMinHeights = (typeof minimumHeights !== 'undefined') ? [] : undefined; - var newWallPositions = []; - for (i = 0; i < length-1; i++) { - var p1 = wallPositions[i]; - var p2 = wallPositions[i + 1]; - var h1 = maximumHeights[i]; - var h2 = maximumHeights[i + 1]; - newMaxHeights = newMaxHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); - - if (typeof minimumHeights !== 'undefined') { - p1 = wallPositions[i]; - p2 = wallPositions[i + 1]; - h1 = minimumHeights[i]; - h2 = minimumHeights[i + 1]; - newMinHeights = newMinHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); - } - } - newMaxHeights.push(maximumHeights[length-1]); - if (typeof minimumHeights !== 'undefined') { - newMinHeights.push(minimumHeights[length-1]); - } - - newWallPositions = PolylinePipeline.scaleToSurface(wallPositions, granularity, ellipsoid); - - length = newWallPositions.length; + var length = newWallPositions.length; var size = length * 2; var positions = new Float64Array(size); - var positionIndex = 0; - var bottomPositions; - var topPositions; - - var minH = defaultValue(newMinHeights, 0); - bottomPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, minH, ellipsoid); - topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, newMaxHeights, ellipsoid); - // add lower and upper points one after the other, lower // points being even and upper points being odd length /= 3; + var i; for (i = 0; i < length; ++i) { var i3 = i * 3; var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1); From af5384c284515bf755d3e08f25626ac4011dc50b Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 12 Aug 2013 17:09:28 -0400 Subject: [PATCH 21/25] fix errors --- Apps/Sandcastle/gallery/Geometry and Appearances.html | 2 +- Source/Core/EllipseOutlineGeometry.js | 2 +- Source/Core/PolygonGeometry.js | 4 ++-- Source/Core/PolygonOutlineGeometry.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 438cf865ccbb..343007cecf69 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -55,7 +55,7 @@ var positions = ellipsoid.cartographicArrayToCartesianArray([ Cesium.Cartographic.fromDegrees(-107.0, 27.0), - Cesium.Cartographic.fromDegrees(-107.0, 25.0), + Cesium.Cartographic.fromDegrees(-107.0, 22.0), Cesium.Cartographic.fromDegrees(-102.0, 23.0), Cesium.Cartographic.fromDegrees(-97.0, 21.0), Cesium.Cartographic.fromDegrees(-97.0, 25.0) diff --git a/Source/Core/EllipseOutlineGeometry.js b/Source/Core/EllipseOutlineGeometry.js index 19491f86dd1e..edf9c2d1088f 100644 --- a/Source/Core/EllipseOutlineGeometry.js +++ b/Source/Core/EllipseOutlineGeometry.js @@ -115,7 +115,7 @@ define([ var numSideLines = Math.min(numberOfVerticalLines, length); numSide = Math.round(length/numSideLines); } - var maxI = Math.min(numSide*10, length); + var maxI = Math.min(numSide*numberOfVerticalLines, length); if (numberOfVerticalLines > 0) { for (i = 0; i < maxI; i+= numSide){ indices[index++] = i; diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index df1fc585eb43..b06168dcfb43 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -330,8 +330,8 @@ define([ for (i = 0 ; i < length; i++) { UL = i; UR = UL + 1; - p1 = Cartesian3.fromArray(edgePositions, UL*3, p1); - p2 = Cartesian3.fromArray(edgePositions, UR*3, p2); + p1 = Cartesian3.fromArray(edgePositions, UL*3, p1Scratch); + p2 = Cartesian3.fromArray(edgePositions, UR*3, p2Scratch); if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON6)) { continue; } diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index 909599bf97eb..4cb0454870a8 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -111,7 +111,7 @@ define([ var subdividedPositions = []; var length = cleanedPositions.length; var i; - var corners = new Array(subdividedPositions.length); + var corners = new Array(length); corners[0] = 0; for (i = 0; i < length-1; i++) { subdividedPositions = subdividedPositions.concat(PolygonGeometryLibrary.subdivideLine(cleanedPositions[i], cleanedPositions[i+1], granularity)); From 47c1cdfb0c9e657d9bd543c209572507e7202484 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 13 Aug 2013 09:49:52 -0400 Subject: [PATCH 22/25] documentation --- Source/Core/EllipseGeometryLibrary.js | 12 ++++++++++++ Source/Core/PolygonGeometryLibrary.js | 8 ++++++++ Source/Core/WallGeometryLibrary.js | 12 ++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Source/Core/EllipseGeometryLibrary.js b/Source/Core/EllipseGeometryLibrary.js index 59ea7d93b641..cf9b4623cc03 100644 --- a/Source/Core/EllipseGeometryLibrary.js +++ b/Source/Core/EllipseGeometryLibrary.js @@ -20,6 +20,10 @@ define([ var unitQuat = new Quaternion(); var rotMtx = new Matrix3(); + /** + * Returns a point on an ellipsoid + * @private + */ EllipseGeometryLibrary.pointOnEllipsoid = function(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { var azimuth = theta + rotation; @@ -50,6 +54,10 @@ define([ var scratchCartesian2 = new Cartesian3(); var scratchCartesian3 = new Cartesian3(); var scratchNormal = new Cartesian3(); + /** + * Returns the positions raised to the given heights + * @private + */ EllipseGeometryLibrary.raisePositionsToHeight = function(positions, options, extrude) { var ellipsoid = options.ellipsoid; var height = options.height; @@ -93,6 +101,10 @@ define([ var unitPosScratch = new Cartesian3(); var eastVecScratch = new Cartesian3(); var northVecScratch = new Cartesian3(); + /** + * Returns an array of positions that make up the ellipse. + * @private + */ EllipseGeometryLibrary.computeEllipsePositions = function(options, addFillPositions, addEdgePositions) { var semiMinorAxis = options.semiMinorAxis; var semiMajorAxis = options.semiMajorAxis; diff --git a/Source/Core/PolygonGeometryLibrary.js b/Source/Core/PolygonGeometryLibrary.js index 39d4d86129bb..2feaf65b5c58 100644 --- a/Source/Core/PolygonGeometryLibrary.js +++ b/Source/Core/PolygonGeometryLibrary.js @@ -19,6 +19,10 @@ define([ return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; } + /** + * Returns an array of points between p0 and p1 + * @returns private + */ PolygonGeometryLibrary.subdivideLine = function(p0, p1, granularity) { var length = Cartesian3.distance(p0, p1); var angleBetween = Cartesian3.angleBetween(p0, p1); @@ -50,6 +54,10 @@ define([ var scaleToGeodeticHeightN1 = new Cartesian3(); var scaleToGeodeticHeightN2 = new Cartesian3(); var scaleToGeodeticHeightP = new Cartesian3(); + /** + * Raises the positions to the correct heights + * @private + */ PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); diff --git a/Source/Core/WallGeometryLibrary.js b/Source/Core/WallGeometryLibrary.js index c540608b5dcb..f27863950374 100644 --- a/Source/Core/WallGeometryLibrary.js +++ b/Source/Core/WallGeometryLibrary.js @@ -21,7 +21,7 @@ define([ var WallGeometryLibrary = {}; - WallGeometryLibrary.subdivideHeights = function(p0, p1, h0, h1, granularity) { + function subdivideHeights(p0, p1, h0, h1, granularity) { var angleBetween = Cartesian3.angleBetween(p0, p1); var numPoints = Math.ceil(angleBetween/granularity); var heights = new Array(numPoints); @@ -43,7 +43,7 @@ define([ heights[0] = h0; return heights; - }; + } function latLonEquals(c0, c1) { return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); @@ -99,6 +99,10 @@ define([ }; } + /** + * Returns three sets of positions: the positions at the ellipsoid surface, at the maximum height and at the minimum height. + * @private + */ WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) { var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); @@ -136,7 +140,7 @@ define([ var p2 = wallPositions[i + 1]; var h1 = maximumHeights[i]; var h2 = maximumHeights[i + 1]; - newMaxHeights = newMaxHeights.concat(WallGeometryLibrary.subdivideHeights(p1, p2, h1, h2, granularity)); + newMaxHeights = newMaxHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); if (duplicateCorners) { newMaxHeights.push(h2); } @@ -146,7 +150,7 @@ define([ p2 = wallPositions[i + 1]; h1 = minimumHeights[i]; h2 = minimumHeights[i + 1]; - newMinHeights = newMinHeights.concat(WallGeometryLibrary.subdivideHeights(p1, p2, h1, h2, granularity)); + newMinHeights = newMinHeights.concat(subdivideHeights(p1, p2, h1, h2, granularity)); if (duplicateCorners) { newMinHeights.push(h2); } From 20635273aafa3b2592c18981b1c641139ba935cf Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 13 Aug 2013 10:30:01 -0400 Subject: [PATCH 23/25] added library for cylinder. --- .../gallery/Geometry and Appearances.html | 2 +- Source/Core/CylinderGeometry.js | 45 +++--------- Source/Core/CylinderGeometryLibrary.js | 71 +++++++++++++++++++ Source/Core/CylinderOutlineGeometry.js | 33 ++------- Source/Core/EllipseGeometryLibrary.js | 16 ++--- Source/Core/PolylinePipeline.js | 7 +- Source/Core/WallGeometryLibrary.js | 4 +- 7 files changed, 98 insertions(+), 80 deletions(-) create mode 100644 Source/Core/CylinderGeometryLibrary.js diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 343007cecf69..879e12c6a0ce 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -591,7 +591,7 @@ topRadius = 0.0; bottomRadius = 200000.0; modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 200000.0)); cylinderInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CylinderGeometry({ length : length, diff --git a/Source/Core/CylinderGeometry.js b/Source/Core/CylinderGeometry.js index 10666ac8e866..c391b1541169 100644 --- a/Source/Core/CylinderGeometry.js +++ b/Source/Core/CylinderGeometry.js @@ -4,6 +4,7 @@ define([ './DeveloperError', './Cartesian2', './Cartesian3', + './CylinderGeometryLibrary', './Math', './ComponentDatatype', './IndexDatatype', @@ -17,6 +18,7 @@ define([ DeveloperError, Cartesian2, Cartesian3, + CylinderGeometryLibrary, CesiumMath, ComponentDatatype, IndexDatatype, @@ -79,24 +81,17 @@ define([ if (bottomRadius === 0 && topRadius === 0) { throw new DeveloperError('bottomRadius and topRadius cannot both equal 0'); } - - var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); - var slices = defaultValue(options.slices, 128); if (slices < 3) { throw new DeveloperError('options.slices must be greater that 3'); } - var twoSlices = slices + slices; - var threeSlices = slices + twoSlices; - var topZ = length * 0.5; - var bottomZ = -topZ; + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + var twoSlices = slices + slices; + var threeSlices = slices + twoSlices; var numVertices = twoSlices + twoSlices; - var positions = new Array(twoSlices); - var bottomCircle = new Array(slices); - var topCircle = new Array(slices); var st = (vertexFormat.st) ? new Float32Array(numVertices * 2) : undefined; var normals = (vertexFormat.normal) ? new Float32Array(numVertices * 3) : undefined; var tangents = (vertexFormat.tangent) ? new Float32Array(numVertices * 3) : undefined; @@ -105,9 +100,6 @@ define([ var computeNormal = (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal); var computeTangent = (vertexFormat.tangent || vertexFormat.binormal); var i; - var index = 0; - var bottomIndex = 0; - var topIndex = 0; var normalIndex = 0; var tangentIndex = 0; var binormalIndex = 0; @@ -116,30 +108,11 @@ define([ normal.z = 0; var tangent = tangentScratch; var binormal = binormalScratch; + var positions = CylinderGeometryLibrary.computePositions(options, slices, true); for (i = 0; i < slices; i++) { var angle = i / slices * CesiumMath.TWO_PI; var x = Math.cos(angle); var y = Math.sin(angle); - var bottomX = x * bottomRadius; - var bottomY = y * bottomRadius; - var topX = x * topRadius; - var topY = y * topRadius; - - bottomCircle[bottomIndex++] = bottomX; - bottomCircle[bottomIndex++] = bottomY; - bottomCircle[bottomIndex++] = bottomZ; - - topCircle[topIndex++] = topX; - topCircle[topIndex++] = topY; - topCircle[topIndex++] = topZ; - - positions[index++] = bottomX; - positions[index++] = bottomY; - positions[index++] = bottomZ; - positions[index++] = topX; - positions[index++] = topY; - positions[index++] = topZ; - if (computeNormal) { normal.x = x; normal.y = y; @@ -178,8 +151,6 @@ define([ } } - positions = positions.concat(bottomCircle).concat(topCircle); - if (computeNormal) { for (i = 0; i < slices; i++) { if (vertexFormat.normal) { @@ -220,7 +191,7 @@ define([ var numIndices = 12 * slices - 12; var indices = IndexDatatype.createTypedArray(numVertices, numIndices); - index = 0; + var index = 0; var j = 0; for (i = 0; i < slices - 1; i++) { indices[index++] = j; @@ -268,7 +239,7 @@ define([ attributes.position = new GeometryAttribute({ componentDatatype: ComponentDatatype.DOUBLE, componentsPerAttribute: 3, - values: new Float64Array(positions) + values: positions }); } diff --git a/Source/Core/CylinderGeometryLibrary.js b/Source/Core/CylinderGeometryLibrary.js new file mode 100644 index 000000000000..ec04d9bf36f9 --- /dev/null +++ b/Source/Core/CylinderGeometryLibrary.js @@ -0,0 +1,71 @@ +/*global define*/ +define([ + './Cartesian3', + './DeveloperError', + './Ellipsoid', + './Math', + './Matrix3', + './Quaternion' + ], function( + Cartesian3, + DeveloperError, + Ellipsoid, + CesiumMath, + Matrix3, + Quaternion) { + "use strict"; + + var CylinderGeometryLibrary = {}; + + /** + * Finds the positions of a cylinder + * @private + */ + CylinderGeometryLibrary.computePositions = function(options, slices, fill){ + var length = options.length; + var topRadius = options.topRadius; + var bottomRadius = options.bottomRadius; + var topZ = length * 0.5; + var bottomZ = -topZ; + + var twoSlice = slices + slices; + var size = (fill) ? 2 * twoSlice : twoSlice; + var positions = new Float64Array(size*3); + var i; + var index = 0; + var tbIndex = 0; + var bottomOffset = (fill) ? twoSlice*3 : 0; + var topOffset = (fill) ? (twoSlice + slices)*3 : slices*3; + + for (i = 0; i < slices; i++) { + var angle = i / slices * CesiumMath.TWO_PI; + var x = Math.cos(angle); + var y = Math.sin(angle); + var bottomX = x * bottomRadius; + var bottomY = y * bottomRadius; + var topX = x * topRadius; + var topY = y * topRadius; + + positions[tbIndex + bottomOffset] = bottomX; + positions[tbIndex + bottomOffset + 1] = bottomY; + positions[tbIndex + bottomOffset + 2] = bottomZ; + + positions[tbIndex + topOffset] = topX; + positions[tbIndex + topOffset + 1] = topY; + positions[tbIndex + topOffset + 2] = topZ; + tbIndex += 3; + if (fill) { + positions[index++] = bottomX; + positions[index++] = bottomY; + positions[index++] = bottomZ; + positions[index++] = topX; + positions[index++] = topY; + positions[index++] = topZ; + } + } + + return positions; + }; + + return CylinderGeometryLibrary; +}); \ No newline at end of file diff --git a/Source/Core/CylinderOutlineGeometry.js b/Source/Core/CylinderOutlineGeometry.js index 04216fc9f406..bd5e8a9b0081 100644 --- a/Source/Core/CylinderOutlineGeometry.js +++ b/Source/Core/CylinderOutlineGeometry.js @@ -4,6 +4,7 @@ define([ './DeveloperError', './Cartesian2', './Cartesian3', + './CylinderGeometryLibrary', './Math', './ComponentDatatype', './IndexDatatype', @@ -16,6 +17,7 @@ define([ DeveloperError, Cartesian2, Cartesian3, + CylinderGeometryLibrary, CesiumMath, ComponentDatatype, IndexDatatype, @@ -80,33 +82,9 @@ define([ var numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0); - var topZ = length * 0.5; - var bottomZ = -topZ; - var numVertices = slices * 2; - var positions = new Float64Array(numVertices * 3); - - var i; - var index = 0; - - for (i = 0; i < slices; i++) { - var angle = i / slices * CesiumMath.TWO_PI; - var x = Math.cos(angle); - var y = Math.sin(angle); - var bottomX = x * bottomRadius; - var bottomY = y * bottomRadius; - var topX = x * topRadius; - var topY = y * topRadius; - - positions[index + slices*3] = topX; - positions[index + slices*3 + 1] = topY; - positions[index + slices*3 + 2] = topZ; - - positions[index++] = bottomX; - positions[index++] = bottomY; - positions[index++] = bottomZ; - } + var positions = CylinderGeometryLibrary.computePositions(options, slices); var numIndices = slices * 2; var numSide; if (numberOfVerticalLines > 0) { @@ -116,9 +94,8 @@ define([ } var indices = IndexDatatype.createTypedArray(numVertices, numIndices*2); - index = 0; - - for (i = 0; i < slices-1; i++) { + var index = 0; + for (var i = 0; i < slices-1; i++) { indices[index++] = i; indices[index++] = i+1; indices[index++] = i + slices; diff --git a/Source/Core/EllipseGeometryLibrary.js b/Source/Core/EllipseGeometryLibrary.js index cf9b4623cc03..53a2c369d9a7 100644 --- a/Source/Core/EllipseGeometryLibrary.js +++ b/Source/Core/EllipseGeometryLibrary.js @@ -20,11 +20,7 @@ define([ var unitQuat = new Quaternion(); var rotMtx = new Matrix3(); - /** - * Returns a point on an ellipsoid - * @private - */ - EllipseGeometryLibrary.pointOnEllipsoid = function(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { + function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { var azimuth = theta + rotation; Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); @@ -48,7 +44,7 @@ define([ Cartesian3.normalize(result, result); Cartesian3.multiplyByScalar(result, mag, result); return result; - }; + } var scratchCartesian1 = new Cartesian3(); var scratchCartesian2 = new Cartesian3(); @@ -165,8 +161,8 @@ define([ // Compute points in the 'northern' half of the ellipse var theta = CesiumMath.PI_OVER_TWO; for (i = 0; i < numPts && theta > 0; ++i) { - position = EllipseGeometryLibrary.pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = EllipseGeometryLibrary.pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); if (addFillPositions) { positions[positionIndex++] = position.x; @@ -204,8 +200,8 @@ define([ for (i = numPts; i > 0; --i) { theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; - position = EllipseGeometryLibrary.pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); - reflectedPosition = EllipseGeometryLibrary.pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); if (addFillPositions) { positions[positionIndex++] = position.x; diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 0cf2a4314fa1..db7d66dfab46 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -271,6 +271,7 @@ define([ * @returns {Array} The array of positions scaled to height. * @exception {DeveloperError} positions must be defined. + * @exception {DeveloperError} height must be defined. * @exception {DeveloperError} height.length must be equal to positions.length * * @example @@ -281,11 +282,13 @@ define([ * * var raisedPositions = PolylinePipeline.scaleToGeodeticHeight(positions, heights); */ - PolylinePipeline.scaleToGeodeticHeight = function(positions, ellipsoid, height) { + PolylinePipeline.scaleToGeodeticHeight = function(positions, height, ellipsoid) { if (typeof positions === 'undefined') { throw new DeveloperError('positions must be defined.'); } - height = defaultValue(height, 0); + if (typeof height === 'undefined') { + throw new DeveloperError('height must be defined'); + } ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); var length = positions.length; diff --git a/Source/Core/WallGeometryLibrary.js b/Source/Core/WallGeometryLibrary.js index f27863950374..d67c0e7d5cfe 100644 --- a/Source/Core/WallGeometryLibrary.js +++ b/Source/Core/WallGeometryLibrary.js @@ -168,8 +168,8 @@ define([ newMinHeights.push(minimumHeights[length-1]); } } - var bottomPositions = (hasMinHeights) ? PolylinePipeline.scaleToGeodeticHeight(newWallPositions, ellipsoid, newMinHeights) : newWallPositions.slice(0); - var topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, ellipsoid, newMaxHeights); + var bottomPositions = (hasMinHeights) ? PolylinePipeline.scaleToGeodeticHeight(newWallPositions, newMinHeights, ellipsoid) : newWallPositions.slice(0); + var topPositions = PolylinePipeline.scaleToGeodeticHeight(newWallPositions, newMaxHeights, ellipsoid); return { newWallPositions: newWallPositions, From 171320995130134697cb8b1e15c413575d1ecb18 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 13 Aug 2013 14:45:42 -0400 Subject: [PATCH 24/25] lat-lon ellipsoid --- Source/Core/EllipsoidGeometry.js | 352 ++++++++++-------------- Source/Core/EllipsoidOutlineGeometry.js | 52 ++-- Source/Core/SphereGeometry.js | 9 +- Source/Scene/SkyAtmosphere.js | 3 +- Specs/Core/EllipsoidGeometrySpec.js | 33 ++- Specs/Core/GeometryPipelineSpec.js | 17 +- Specs/Core/SphereGeometrySpec.js | 33 ++- 7 files changed, 230 insertions(+), 269 deletions(-) diff --git a/Source/Core/EllipsoidGeometry.js b/Source/Core/EllipsoidGeometry.js index 861cd3ad1c70..226059d32881 100644 --- a/Source/Core/EllipsoidGeometry.js +++ b/Source/Core/EllipsoidGeometry.js @@ -27,93 +27,10 @@ define([ VertexFormat) { "use strict"; - var scratchDirection = new Cartesian3(); - - function addEdgePositions(i0, i1, numberOfPartitions, positions) { - var indices = new Array(2 + numberOfPartitions - 1); - indices[0] = i0; - - var origin = positions[i0]; - var direction = Cartesian3.subtract(positions[i1], positions[i0], scratchDirection); - - for ( var i = 1; i < numberOfPartitions; ++i) { - var delta = i / numberOfPartitions; - var position = Cartesian3.multiplyByScalar(direction, delta); - Cartesian3.add(origin, position, position); - - indices[i] = positions.length; - positions.push(position); - } - - indices[2 + (numberOfPartitions - 1) - 1] = i1; - - return indices; - } - - var scratchX = new Cartesian3(); - var scratchY = new Cartesian3(); - var scratchOffsetX = new Cartesian3(); - var scratchOffsetY = new Cartesian3(); - - function addFaceTriangles(leftBottomToTop, bottomLeftToRight, rightBottomToTop, topLeftToRight, numberOfPartitions, positions, indices) { - var origin = positions[bottomLeftToRight[0]]; - var x = Cartesian3.subtract(positions[bottomLeftToRight[bottomLeftToRight.length - 1]], origin, scratchX); - var y = Cartesian3.subtract(positions[topLeftToRight[0]], origin, scratchY); - - var bottomIndicesBuffer = []; - var topIndicesBuffer = []; - - var bottomIndices = bottomLeftToRight; - var topIndices = topIndicesBuffer; - - for ( var j = 1; j <= numberOfPartitions; ++j) { - if (j !== numberOfPartitions) { - if (j !== 1) { - // - // This copy could be avoided by ping ponging buffers. - // - bottomIndicesBuffer = topIndicesBuffer.slice(0); - bottomIndices = bottomIndicesBuffer; - } - - topIndicesBuffer[0] = leftBottomToTop[j]; - topIndicesBuffer[numberOfPartitions] = rightBottomToTop[j]; - - var deltaY = j / numberOfPartitions; - var offsetY = Cartesian3.multiplyByScalar(y, deltaY, scratchOffsetY); - - for ( var i = 1; i < numberOfPartitions; ++i) { - var deltaX = i / numberOfPartitions; - var offsetX = Cartesian3.multiplyByScalar(x, deltaX, scratchOffsetX); - var position = Cartesian3.add(origin, offsetX); - Cartesian3.add(position, offsetY, position); - - topIndicesBuffer[i] = positions.length; - positions.push(position); - } - } else { - if (j !== 1) { - bottomIndices = topIndicesBuffer; - } - topIndices = topLeftToRight; - } - - for ( var k = 0; k < numberOfPartitions; ++k) { - indices.push(bottomIndices[k]); - indices.push(bottomIndices[k + 1]); - indices.push(topIndices[k + 1]); - - indices.push(bottomIndices[k]); - indices.push(topIndices[k + 1]); - indices.push(topIndices[k]); - } - } - } - - var sphericalNormal = new Cartesian3(); - var normal = new Cartesian3(); - var tangent = new Cartesian3(); - var binormal = new Cartesian3(); + var scratchPosition = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchTangent = new Cartesian3(); + var scratchBinormal = new Cartesian3(); var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); /** @@ -123,10 +40,12 @@ define([ * @constructor * * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions. - * @param {Number} [options.numberOfPartitions=32] The number of times to partition the ellipsoid in a plane formed by two radii in a single quadrant. + * @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks. + * @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * - * @exception {DeveloperError} options.numberOfPartitions must be greater than zero. + * @exception {DeveloperError} options.slicePartitions cannot be less than three. + * @exception {DeveloperError} options.stackPartitions cannot be less than three. * * @example * var ellipsoid = new EllipsoidGeometry({ @@ -134,155 +53,120 @@ define([ * radii : new Cartesian3(1000000.0, 500000.0, 500000.0) * }); */ + + var cos = Math.cos; + var sin = Math.sin; var EllipsoidGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var radii = defaultValue(options.radii, defaultRadii); - var ellipsoid = Ellipsoid.fromCartesian3(radii); - var numberOfPartitions = defaultValue(options.numberOfPartitions, 32); + var ellipsoid = new Ellipsoid(radii.x, radii.y, radii.z); + var stackPartitions = defaultValue(options.stackPartitions, 64); + var slicePartitions = defaultValue(options.slicePartitions, 64); var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); - if (numberOfPartitions <= 0) { - throw new DeveloperError('options.numberOfPartitions must be greater than zero.'); + if (slicePartitions < 3) { + throw new DeveloperError ('options.slicePartitions cannot be less than three.'); } + if (stackPartitions < 3) { + throw new DeveloperError('options.stackPartitions cannot be less than three.'); + } + var vertexCount = 2 + (stackPartitions - 1) * slicePartitions; + var positions = new Float64Array(vertexCount * 3); + var numIndices = 6*slicePartitions*(stackPartitions - 1); + var indices = IndexDatatype.createTypedArray(vertexCount, numIndices); - var positions = []; - var indices = []; - - // - // Initial cube. In the plane, z = -1: - // - // +y - // | - // Q2 * p3 Q1 - // / | \ - // p0 *--+--* p2 +x - // \ | / - // Q3 * p1 Q4 - // | - // - // Similarly, p4 to p7 are in the plane z = 1. - // - positions.push(new Cartesian3(-1, 0, -1)); - positions.push(new Cartesian3(0, -1, -1)); - positions.push(new Cartesian3(1, 0, -1)); - positions.push(new Cartesian3(0, 1, -1)); - positions.push(new Cartesian3(-1, 0, 1)); - positions.push(new Cartesian3(0, -1, 1)); - positions.push(new Cartesian3(1, 0, 1)); - positions.push(new Cartesian3(0, 1, 1)); - - // - // Edges - // - // 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 0. Plane z = -1 - // 4 -> 5, 5 -> 6, 6 -> 7, 7 -> 4. Plane z = 1 - // 0 -> 4, 1 -> 5, 2 -> 6, 3 -> 7. From plane z = -1 to plane z - 1 - // - var edge0to1 = addEdgePositions(0, 1, numberOfPartitions, positions); - var edge1to2 = addEdgePositions(1, 2, numberOfPartitions, positions); - var edge2to3 = addEdgePositions(2, 3, numberOfPartitions, positions); - var edge3to0 = addEdgePositions(3, 0, numberOfPartitions, positions); - - var edge4to5 = addEdgePositions(4, 5, numberOfPartitions, positions); - var edge5to6 = addEdgePositions(5, 6, numberOfPartitions, positions); - var edge6to7 = addEdgePositions(6, 7, numberOfPartitions, positions); - var edge7to4 = addEdgePositions(7, 4, numberOfPartitions, positions); - - var edge0to4 = addEdgePositions(0, 4, numberOfPartitions, positions); - var edge1to5 = addEdgePositions(1, 5, numberOfPartitions, positions); - var edge2to6 = addEdgePositions(2, 6, numberOfPartitions, positions); - var edge3to7 = addEdgePositions(3, 7, numberOfPartitions, positions); - - // Q3 Face - addFaceTriangles(edge0to4, edge0to1, edge1to5, edge4to5, numberOfPartitions, positions, indices); - // Q4 Face - addFaceTriangles(edge1to5, edge1to2, edge2to6, edge5to6, numberOfPartitions, positions, indices); - // Q1 Face - addFaceTriangles(edge2to6, edge2to3, edge3to7, edge6to7, numberOfPartitions, positions, indices); - // Q2 Face - addFaceTriangles(edge3to7, edge3to0, edge0to4, edge7to4, numberOfPartitions, positions, indices); - // Plane z = 1 - addFaceTriangles(edge7to4.slice(0).reverse(), edge4to5, edge5to6, edge6to7.slice(0).reverse(), numberOfPartitions, positions, indices); - // Plane z = -1 - addFaceTriangles(edge1to2, edge0to1.slice(0).reverse(), edge3to0.slice(0).reverse(), edge2to3, numberOfPartitions, positions, indices); + var normals = (vertexFormat.normal) ? new Float32Array(vertexCount * 3) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(vertexCount * 3) : undefined; + var binormals = (vertexFormat.binormal) ? new Float32Array(vertexCount * 3) : undefined; + var st = (vertexFormat.st) ? new Float32Array(vertexCount * 2) : undefined; - var attributes = new GeometryAttributes(); + var cosTheta = new Array(slicePartitions); + var sinTheta = new Array(slicePartitions); - var length = positions.length; var i; var j; + for (i = 0; i < slicePartitions; i++) { + var theta = CesiumMath.TWO_PI * i / slicePartitions; + cosTheta[i] = cos(theta); + sinTheta[i] = sin(theta); + } - if (vertexFormat.position) { - // Expand cube into ellipsoid and flatten values - var flattenedPositions = new Float64Array(length * 3); + var index = 0; + positions[index++] = 0; // first point + positions[index++] = 0; + positions[index++] = radii.z; + + for (i = 1; i < stackPartitions; i++) { + var phi = Math.PI * i / stackPartitions; + var sinPhi = sin(phi); - for (i = j = 0; i < length; ++i) { - var item = positions[i]; - Cartesian3.normalize(item, item); - Cartesian3.multiplyComponents(item, radii, item); + var xSinPhi = radii.x * sinPhi; + var ySinPhi = radii.y * sinPhi; + var zCosPhi = radii.z * cos(phi); - flattenedPositions[j++] = item.x; - flattenedPositions[j++] = item.y; - flattenedPositions[j++] = item.z; + for (j = 0; j < slicePartitions; j++) { + positions[index++] = cosTheta[j] * xSinPhi; + positions[index++] = sinTheta[j] * ySinPhi; + positions[index++] = zCosPhi; } + } + positions[index++] = 0; // last point + positions[index++] = 0; + positions[index++] = -radii.z; + var attributes = new GeometryAttributes(); + + if (vertexFormat.position) { attributes.position = new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : flattenedPositions + values : positions }); } - - if (vertexFormat.st) { - var texCoords = new Float32Array(length * 2); - var oneOverRadii = ellipsoid.getOneOverRadii(); - - for (i = j = 0; i < length; ++i) { - Cartesian3.multiplyComponents(positions[i], oneOverRadii, sphericalNormal); - Cartesian3.normalize(sphericalNormal, sphericalNormal); - - texCoords[j++] = Math.atan2(sphericalNormal.y, sphericalNormal.x) * CesiumMath.ONE_OVER_TWO_PI + 0.5; - texCoords[j++] = Math.asin(sphericalNormal.z) * CesiumMath.ONE_OVER_PI + 0.5; - } - - attributes.st = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : texCoords - }); - } - - if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { - var normals = (vertexFormat.normal) ? new Float32Array(length * 3) : undefined; - var tangents = (vertexFormat.tangent) ? new Float32Array(length * 3) : undefined; - var binormals = (vertexFormat.binormal) ? new Float32Array(length * 3) : undefined; - - for (i = j = 0; i < length; ++i, j += 3) { - ellipsoid.geodeticSurfaceNormal(positions[i], normal); - Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent).normalize(tangent); - Cartesian3.cross(normal, tangent, binormal).normalize(binormal); - - if (vertexFormat.normal) { - normals[j] = normal.x; - normals[j + 1] = normal.y; - normals[j + 2] = normal.z; + var stIndex = 0; + var normalIndex = 0; + var tangentIndex = 0; + var binormalIndex = 0; + if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { + for( i = 0; i < vertexCount; i++) { + var position = Cartesian3.fromArray(positions, i*3, scratchPosition); + var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal); + if (vertexFormat.st) { + st[stIndex++] = (Math.atan2(normal.y, normal.x) / CesiumMath.TWO_PI) + 0.5; + st[stIndex++] = (Math.asin(normal.z) / Math.PI) + 0.5; } - - if (vertexFormat.tangent) { - tangents[j] = tangent.x; - tangents[j + 1] = tangent.y; - tangents[j + 2] = tangent.z; + if (vertexFormat.normal) { + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; } - - if (vertexFormat.binormal) { - binormals[j] = binormal.x; - binormals[j + 1] = binormal.y; - binormals[j + 2] = binormal.z; + if (vertexFormat.tangent || vertexFormat.binormal) { + var tangent; + if (i === 0 || i === vertexCount - 1) { + tangent = Cartesian3.cross(Cartesian3.UNIT_X, normal, scratchTangent).normalize(tangent); + } else { + tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, scratchTangent).normalize(tangent); + } + tangents[tangentIndex++] = tangent.x; + tangents[tangentIndex++] = tangent.y; + tangents[tangentIndex++] = tangent.z; + if (vertexFormat.binormal) { + var binormal = Cartesian3.cross(normal, tangent, scratchBinormal).normalize(scratchBinormal); + binormals[binormalIndex++] = binormal.x; + binormals[binormalIndex++] = binormal.y; + binormals[binormalIndex++] = binormal.z; + } } } + if (vertexFormat.st) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : st + }); + } if (vertexFormat.normal) { attributes.normal = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -290,7 +174,6 @@ define([ values : normals }); } - if (vertexFormat.tangent) { attributes.tangent = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -298,7 +181,6 @@ define([ values : tangents }); } - if (vertexFormat.binormal) { attributes.binormal = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -308,6 +190,48 @@ define([ } } + index = 0; + for (i = 1; i < slicePartitions; i++) { //top row + indices[index++] = 0; + indices[index++] = i; + indices[index++] = i + 1; + } + indices[index++] = 0; + indices[index++] = slicePartitions; + indices[index++] = 1; + + for (i = 0; i < stackPartitions - 2; i++) { + var topOffset = (i * slicePartitions) + 1; + var bottomOffset = ((i+1) * slicePartitions) + 1; + + for (j = 0; j < slicePartitions - 1; j++) { + indices[index++] = bottomOffset + j; + indices[index++] = bottomOffset + j + 1; + indices[index++] = topOffset + j + 1; + + indices[index++] = bottomOffset + j; + indices[index++] = topOffset + j + 1; + indices[index++] = topOffset + j; + } + + indices[index++] = bottomOffset + slicePartitions - 1; + indices[index++] = bottomOffset; + indices[index++] = topOffset; + indices[index++] = bottomOffset + slicePartitions - 1; + indices[index++] = topOffset; + indices[index++] = topOffset + slicePartitions - 1; + } + + var lastPos = vertexCount - 1; + for (i = lastPos - 1; i > lastPos - slicePartitions; i--) { + indices[index++] = lastPos; + indices[index++] = i; + indices[index++] = i - 1; + } + indices[index++] = lastPos; + indices[index++] = lastPos - slicePartitions; + indices[index++] = lastPos - 1; + /** * An object containing {@link GeometryAttribute} properties named after each of the * true values of the {@link VertexFormat} option. @@ -323,7 +247,7 @@ define([ * * @type Array */ - this.indices = IndexDatatype.createTypedArray(length, indices); + this.indices = indices; /** * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. diff --git a/Source/Core/EllipsoidOutlineGeometry.js b/Source/Core/EllipsoidOutlineGeometry.js index 40f91785f3b0..e841394dc689 100644 --- a/Source/Core/EllipsoidOutlineGeometry.js +++ b/Source/Core/EllipsoidOutlineGeometry.js @@ -26,7 +26,8 @@ define([ "use strict"; var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); - + var cos = Math.cos; + var sin = Math.sin; /** * A {@link Geometry} that represents vertices and indices for the outline of an ellipsoid centered at the origin. * @@ -58,10 +59,10 @@ define([ var slicePartitions = defaultValue(options.slicePartitions, 8); var subdivisions = defaultValue(options.subdivisions, 128); if (stackPartitions < 1) { - throw new DeveloperError('options.stackPartitions must be greater than or equal to one.'); + throw new DeveloperError('options.stackPartitions cannot be less than 1'); } if (slicePartitions < 0) { - throw new DeveloperError('options.slicePartitions must be greater than or equal to zero.'); + throw new DeveloperError('options.slicePartitions cannot be less than 0'); } if (subdivisions < 0) { @@ -75,44 +76,51 @@ define([ var i; var j; + var theta; var phi; var cosPhi; var sinPhi; - var theta; - var cosTheta; - var sinTheta; var index = 0; + + var cosTheta = new Array(subdivisions); + var sinTheta = new Array(subdivisions); + for (i = 0; i < subdivisions; i++) { + theta = CesiumMath.TWO_PI * i / subdivisions; + cosTheta[i] = cos(theta); + sinTheta[i] = sin(theta); + } + for (i = 1; i < stackPartitions; i++) { phi = Math.PI * i / stackPartitions; - cosPhi = Math.cos(phi); - sinPhi = Math.sin(phi); + cosPhi = cos(phi); + sinPhi = sin(phi); for (j = 0; j < subdivisions; j++) { - theta = CesiumMath.TWO_PI * j / subdivisions; - cosTheta = Math.cos(theta); - sinTheta = Math.sin(theta); - - positions[index++] = radii.x * cosTheta * sinPhi; - positions[index++] = radii.y * sinTheta * sinPhi; + positions[index++] = radii.x * cosTheta[j] * sinPhi; + positions[index++] = radii.y * sinTheta[j] * sinPhi; positions[index++] = radii.z * cosPhi; } } + cosTheta.length = slicePartitions; + sinTheta.length = slicePartitions; + for (i = 0; i < slicePartitions; i++) { + theta = CesiumMath.TWO_PI * i / slicePartitions; + cosTheta[i] = cos(theta); + sinTheta[i] = sin(theta); + } + positions[index++] = 0; positions[index++] = 0; positions[index++] = radii.z; for (i = 1; i < subdivisions; i++) { phi = Math.PI * i / subdivisions; - cosPhi = Math.cos(phi); - sinPhi = Math.sin(phi); + cosPhi = cos(phi); + sinPhi = sin(phi); for (j = 0; j < slicePartitions; j++) { - theta = CesiumMath.TWO_PI * j / slicePartitions; - cosTheta = Math.cos(theta); - sinTheta = Math.sin(theta); - - positions[index++] = radii.x * cosTheta * sinPhi; - positions[index++] = radii.y * sinTheta * sinPhi; + positions[index++] = radii.x * cosTheta[j] * sinPhi; + positions[index++] = radii.y * sinTheta[j] * sinPhi; positions[index++] = radii.z * cosPhi; } } diff --git a/Source/Core/SphereGeometry.js b/Source/Core/SphereGeometry.js index f587f2866996..1ab6834330c2 100644 --- a/Source/Core/SphereGeometry.js +++ b/Source/Core/SphereGeometry.js @@ -18,10 +18,12 @@ define([ * @constructor * * @param {Number} [options.radius=1.0] The radius of the sphere. - * @param {Number} [options.numberOfPartitions=32] The number of times to partition the sphere in a plane formed by two radii in a single quadrant. + * @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks. + * @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * - * @exception {DeveloperError} options.numberOfPartitions must be greater than zero. + * @exception {DeveloperError} options.slicePartitions cannot be less than three. + * @exception {DeveloperError} options.stackPartitions cannot be less than three. * * @example * var sphere = new SphereGeometry({ @@ -34,7 +36,8 @@ define([ var radii = new Cartesian3(radius, radius, radius); var ellipsoidOptions = { radii: radii, - numberOfPartitions: options.numberOfPartitions, + stackPartitions: options.stackPartitions, + slicePartitions: options.slicePartitions, vertexFormat: options.vertexFormat }; diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js index 51ba88831e17..4e756c5cd2db 100644 --- a/Source/Scene/SkyAtmosphere.js +++ b/Source/Scene/SkyAtmosphere.js @@ -136,7 +136,8 @@ define([ if (!defined(command.vertexArray)) { var geometry = new EllipsoidGeometry({ radii : this._ellipsoid.getRadii().multiplyByScalar(1.025), - numberOfPartitions : 60 + slicePartitions : 256, + stackPartitions : 256 }); command.vertexArray = context.createVertexArrayFromGeometry({ geometry : geometry, diff --git a/Specs/Core/EllipsoidGeometrySpec.js b/Specs/Core/EllipsoidGeometrySpec.js index 810835287f7f..ccd6db0b76fd 100644 --- a/Specs/Core/EllipsoidGeometrySpec.js +++ b/Specs/Core/EllipsoidGeometrySpec.js @@ -12,10 +12,18 @@ defineSuite([ "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - it('constructor throws with invalid numberOfPartitions', function() { + it('constructor throws with invalid slicePartitions', function() { expect(function() { return new EllipsoidGeometry({ - numberOfPartitions : -1 + slicePartitions : -1 + }); + }).toThrow(); + }); + + it('constructor throws with invalid stackPartitions', function() { + expect(function() { + return new EllipsoidGeometry({ + stackPartitions : -1 }); }).toThrow(); }); @@ -23,7 +31,8 @@ defineSuite([ it('computes positions', function() { var m = new EllipsoidGeometry({ vertexFormat : VertexFormat.POSITION_ONLY, - numberOfPartitions : 1 + slicePartitions: 3, + stackPartitions: 3 }); expect(m.attributes.position.values.length).toEqual(3 * 8); @@ -34,21 +43,23 @@ defineSuite([ it('compute all vertex attributes', function() { var m = new EllipsoidGeometry({ vertexFormat : VertexFormat.ALL, - numberOfPartitions : 2 + slicePartitions: 3, + stackPartitions: 3 }); - expect(m.attributes.position.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.st.values.length).toEqual(2 * (8 + 6 + 12)); - expect(m.attributes.normal.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.tangent.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.binormal.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.indices.length).toEqual(2 * 3 * 4 * 6); + expect(m.attributes.position.values.length).toEqual(3 * 8); + expect(m.attributes.st.values.length).toEqual(2 * 8); + expect(m.attributes.normal.values.length).toEqual(3 * 8); + expect(m.attributes.tangent.values.length).toEqual(3 * 8); + expect(m.attributes.binormal.values.length).toEqual(3 * 8); + expect(m.indices.length).toEqual(3 * 12); }); it('computes attributes for a unit sphere', function() { var m = new EllipsoidGeometry({ vertexFormat : VertexFormat.ALL, - numberOfPartitions : 3 + slicePartitions: 3, + stackPartitions: 3 }); var positions = m.attributes.position.values; diff --git a/Specs/Core/GeometryPipelineSpec.js b/Specs/Core/GeometryPipelineSpec.js index 83ae56dfaaf4..413e38381696 100644 --- a/Specs/Core/GeometryPipelineSpec.js +++ b/Specs/Core/GeometryPipelineSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'Core/GeometryPipeline', 'Core/PrimitiveType', 'Core/ComponentDatatype', + 'Core/BoxGeometry', 'Core/EllipsoidGeometry', 'Core/Ellipsoid', 'Core/Cartesian3', @@ -20,6 +21,7 @@ defineSuite([ GeometryPipeline, PrimitiveType, ComponentDatatype, + BoxGeometry, EllipsoidGeometry, Ellipsoid, Cartesian3, @@ -1437,23 +1439,24 @@ defineSuite([ -0.4082482904638631, -0.8164965809277261, 0.4082482904638631], CesiumMath.EPSILON7); }); - it ('computeBinormalAndTangent computes tangent and binormal for an EllipsoidGeometry', function() { - var numberOfPartitions = 10; - var geometry = new EllipsoidGeometry({ + it ('computeBinormalAndTangent computes tangent and binormal for an BoxGeometry', function() { + var geometry = new BoxGeometry({ vertexFormat : new VertexFormat({ position : true, normal : true, st : true }), - numberOfPartitions : numberOfPartitions + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) }); geometry = GeometryPipeline.computeBinormalAndTangent(geometry); var actualTangents = geometry.attributes.tangent.values; var actualBinormals = geometry.attributes.binormal.values; - var expectedGeometry = new EllipsoidGeometry({ + var expectedGeometry = new BoxGeometry({ vertexFormat: VertexFormat.ALL, - numberOfPartitions : numberOfPartitions + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) }); var expectedTangents = expectedGeometry.attributes.tangent.values; var expectedBinormals = expectedGeometry.attributes.binormal.values; @@ -1461,7 +1464,7 @@ defineSuite([ expect(actualTangents.length).toEqual(expectedTangents.length); expect(actualBinormals.length).toEqual(expectedBinormals.length); - for (var i = 300; i < 500; i += 3) { + for (var i = 0; i < actualTangents.length; i += 3) { var actual = Cartesian3.fromArray(actualTangents, i); var expected = Cartesian3.fromArray(expectedTangents, i); expect(actual).toEqualEpsilon(expected, CesiumMath.EPSILON1); diff --git a/Specs/Core/SphereGeometrySpec.js b/Specs/Core/SphereGeometrySpec.js index a114d1e05b76..007b9fb53785 100644 --- a/Specs/Core/SphereGeometrySpec.js +++ b/Specs/Core/SphereGeometrySpec.js @@ -12,10 +12,18 @@ defineSuite([ "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - it('constructor throws with invalid numberOfPartitions', function() { + it('constructor throws with invalid stackPartitions', function() { expect(function() { return new SphereGeometry({ - numberOfPartitions : -1 + stackPartitions : -1 + }); + }).toThrow(); + }); + + it('constructor throws with invalid slicePartitions', function() { + expect(function() { + return new SphereGeometry({ + slicePartitions : -1 }); }).toThrow(); }); @@ -24,7 +32,8 @@ defineSuite([ var m = new SphereGeometry({ vertexFormat : VertexFormat.POSITION_ONLY, radius : 1, - numberOfPartitions : 1 + stackPartitions : 3, + slicePartitions: 3 }); expect(m.attributes.position.values.length).toEqual(3 * 8); @@ -36,22 +45,24 @@ defineSuite([ var m = new SphereGeometry({ vertexFormat : VertexFormat.ALL, radius : 1, - numberOfPartitions : 2 + stackPartitions : 3, + slicePartitions: 3 }); - expect(m.attributes.position.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.st.values.length).toEqual(2 * (8 + 6 + 12)); - expect(m.attributes.normal.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.tangent.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.attributes.binormal.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.indices.length).toEqual(2 * 3 * 4 * 6); + expect(m.attributes.position.values.length).toEqual(3 * 8); + expect(m.attributes.st.values.length).toEqual(2 * 8); + expect(m.attributes.normal.values.length).toEqual(3 * 8); + expect(m.attributes.tangent.values.length).toEqual(3 * 8); + expect(m.attributes.binormal.values.length).toEqual(3 * 8); + expect(m.indices.length).toEqual(3 * 12); }); it('computes attributes for a unit sphere', function() { var m = new SphereGeometry({ vertexFormat : VertexFormat.ALL, radius : 1, - numberOfPartitions : 3 + stackPartitions : 3, + slicePartitions: 3 }); var positions = m.attributes.position.values; From 1781d039a76f008f1bc829b4f427519a4d36abb9 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 15 Aug 2013 16:48:44 -0400 Subject: [PATCH 25/25] minor cleanup --- Source/Core/ExtentOutlineGeometry.js | 2 +- Source/Core/WallOutlineGeometry.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Core/ExtentOutlineGeometry.js b/Source/Core/ExtentOutlineGeometry.js index 88ce6ab4032a..c292db6f86ab 100644 --- a/Source/Core/ExtentOutlineGeometry.js +++ b/Source/Core/ExtentOutlineGeometry.js @@ -87,7 +87,7 @@ define([ position.z = rSurfaceZ + nZ * maxHeight; } - if ( minHeight !== 'undefined') { + if (defined(minHeight)) { extrudedPosition.x = rSurfaceX + nX * minHeight; // bottom extrudedPosition.y = rSurfaceY + nY * minHeight; extrudedPosition.z = rSurfaceZ + nZ * minHeight; diff --git a/Source/Core/WallOutlineGeometry.js b/Source/Core/WallOutlineGeometry.js index 15e3710c720d..8ed3afdc04a6 100644 --- a/Source/Core/WallOutlineGeometry.js +++ b/Source/Core/WallOutlineGeometry.js @@ -77,7 +77,7 @@ define([ * positions : ellipsoid.cartographicArrayToCartesianArray(positions) * }); */ - var WallGeometry = function(options) { + var WallOutlineGeometry = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var wallPositions = options.positions; @@ -201,7 +201,7 @@ define([ * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. * - * @memberof WallGeometry + * @memberof WallOutlineGeometry * * @param {Array} positions An array of Cartesian objects, which are the points of the wall. * @param {Number} [maximumHeight] A constant that defines the maximum height of the @@ -222,13 +222,13 @@ define([ * ]; * * // create a wall that spans from 10000 meters to 20000 meters - * var wall = new WallGeometry({ + * var wall = new WallOutlineGeometry({ * positions : ellipsoid.cartographicArrayToCartesianArray(positions), * topHeight : 20000.0, * bottomHeight : 10000.0 * }); */ - WallGeometry.fromConstantHeights = function(options) { + WallOutlineGeometry.fromConstantHeights = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var positions = options.positions; @@ -266,8 +266,8 @@ define([ minimumHeights : minHeights, ellipsoid : options.ellipsoid }; - return new WallGeometry(newOptions); + return new WallOutlineGeometry(newOptions); }; - return WallGeometry; + return WallOutlineGeometry; });