diff --git a/Apps/Sandcastle/gallery/Polygon.html b/Apps/Sandcastle/gallery/Polygon.html index e5052bbc06f5..d9fd0a94d3f1 100644 --- a/Apps/Sandcastle/gallery/Polygon.html +++ b/Apps/Sandcastle/gallery/Polygon.html @@ -125,6 +125,21 @@ } }); +var purplePolygonUsingRhumbLines = viewer.entities.add({ + name : 'Purple polygon using rhumb lines with outline', + polygon : { + hierarchy : Cesium.Cartesian3.fromDegreesArray([-120.0, 45.0, + -80.0, 45.0, + -80.0, 55.0, + -120.0, 55.0]), + extrudedHeight: 50000, + material : Cesium.Color.PURPLE, + outline : true, + outlineColor : Cesium.Color.MAGENTA, + arcType : Cesium.ArcType.RHUMB + } +}); + viewer.zoomTo(viewer.entities);//Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Apps/Sandcastle/gallery/Polyline.html b/Apps/Sandcastle/gallery/Polyline.html index 6ae30db914c5..40e35964751c 100644 --- a/Apps/Sandcastle/gallery/Polyline.html +++ b/Apps/Sandcastle/gallery/Polyline.html @@ -40,6 +40,17 @@ } }); +var greenRhumbLine = viewer.entities.add({ + name : 'Green rhumb line', + polyline : { + positions : Cesium.Cartesian3.fromDegreesArray([-75, 35, + -125, 35]), + width : 5, + arcType : Cesium.ArcType.RHUMB, + material : Cesium.Color.GREEN + } +}); + var glowingLine = viewer.entities.add({ name : 'Glowing blue line on the surface', polyline : { @@ -73,7 +84,7 @@ positions : Cesium.Cartesian3.fromDegreesArrayHeights([-75, 43, 500000, -125, 43, 500000]), width : 10, - followSurface : false, + arcType : Cesium.ArcType.NONE, material : new Cesium.PolylineArrowMaterialProperty(Cesium.Color.PURPLE) } }); diff --git a/Apps/Sandcastle/gallery/development/Ground Primitive.html b/Apps/Sandcastle/gallery/development/Ground Primitive.html index 7406c23b0225..936323c65b6f 100644 --- a/Apps/Sandcastle/gallery/development/Ground Primitive.html +++ b/Apps/Sandcastle/gallery/development/Ground Primitive.html @@ -31,6 +31,7 @@ terrainProvider: Cesium.createWorldTerrain() }); var scene = viewer.scene; +viewer.extend(Cesium.viewerCesiumInspectorMixin); function offsetPositions(positions, degreeOffset) { positions = scene.globe.ellipsoid.cartesianArrayToCartographicArray(positions); @@ -325,6 +326,26 @@ }), classificationType : Cesium.ClassificationType.TERRAIN })); + + // Rhumb line polygon geometry + scene.groundPrimitives.add(new Cesium.GroundPrimitive({ + geometryInstances : new Cesium.GeometryInstance({ + geometry : new Cesium.PolygonGeometry({ + polygonHierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray([ + -130, 55, + -100, 55, + -100, 45, + -130, 45 + ])), + arcType : Cesium.ArcType.RHUMB + }), + attributes: { + color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 0.0, 0.5)) + }, + id : 'rhumbPolygon' + }), + classificationType : Cesium.ClassificationType.TERRAIN + })); }); Sandcastle.reset = function() { diff --git a/Apps/Sandcastle/gallery/development/Polylines.html b/Apps/Sandcastle/gallery/development/Polylines.html index 2f40bfaebf59..db16318e2ddc 100644 --- a/Apps/Sandcastle/gallery/development/Polylines.html +++ b/Apps/Sandcastle/gallery/development/Polylines.html @@ -119,6 +119,20 @@ }) }); Sandcastle.declare(fadingPolyline); // For highlighting on mouseover in Sandcastle. + + // A rhumb line with two points. + var rhumbLine = polylines.add({ + positions : Cesium.PolylinePipeline.generateCartesianRhumbArc({ + positions : Cesium.Cartesian3.fromDegreesArray([-130.0, 30.0, + -75.0, 30.0]) + }), + width: 5, + material : Cesium.Material.fromType('Color', { + color : new Cesium.Color(0.0, 1.0, 0.0, 1.0) + }) + }); + Sandcastle.declare(rhumbLine); // For highlighting on mouseover in Sandcastle. + } var viewer = new Cesium.Viewer('cesiumContainer'); diff --git a/CHANGES.md b/CHANGES.md index b49986abbbf2..b19c37bda547 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ Change Log ##### Deprecated :hourglass_flowing_sand: * `Scene.clampToHeight` now takes an optional `width` argument before the `result` argument. The previous function definition will no longer work in 1.56. [#7287](https://github.com/AnalyticalGraphicsInc/cesium/pull/7287) +* `PolylineGeometry.followSurface` has been superceded by `PolylineGeometry.arcType`. The previous definition will no longer work in 1.57. Replace `followSurface: false` with `arcType: Cesium.ArcType.NONE` and `followSurface: true` with `arcType: Cesium.ArcType.GEODESIC`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492) +* `SimplePolylineGeometry.followSurface` has been superceded by `SimplePolylineGeometry.arcType`. The previous definition will no longer work in 1.57. Replace `followSurface: false` with `arcType: Cesium.ArcType.NONE` and `followSurface: true` with `arcType: Cesium.ArcType.GEODESIC`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492) ##### Additions :tada: * Added support for textured ground entities (entities with unspecified `height`) and `GroundPrimitives` on 3D Tiles. [#7434](https://github.com/AnalyticalGraphicsInc/cesium/pull/7434) @@ -17,6 +19,7 @@ Change Log * Added the ability to specify the width of the intersection volume for `Scene.sampleHeight`, `Scene.clampToHeight`, `Scene.sampleHeightMostDetailed`, and `Scene.clampToHeightMostDetailed`. [#7287](https://github.com/AnalyticalGraphicsInc/cesium/pull/7287) * Added a [new Sandcastle example](https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/?src=Time%20Dynamic%20Wheels.html) on using `nodeTransformations` to rotate a model's wheels based on its velocity. [#7361](https://github.com/AnalyticalGraphicsInc/cesium/pull/7361) * Added `EllipsoidRhumbLine` class as a rhumb line counterpart to `EllipsoidGeodesic`. [#7484](https://github.com/AnalyticalGraphicsInc/cesium/pull/7484) +* Added rhumb line support to `PolygonGeometry`, `PolygonOutlineGeometry`, `PolylineGeometry`, `GroundPolylineGeometry`, and `SimplePolylineGeometry`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492) ##### Fixes :wrench: * Fixed 3D Tiles performance regression. [#7482](https://github.com/AnalyticalGraphicsInc/cesium/pull/7482) @@ -28,6 +31,7 @@ Change Log * Fixed Sandcastle's "Open in New Window" button not displaying imagery due to blob URI limitations. [#7250](https://github.com/AnalyticalGraphicsInc/cesium/pull/7250) * Fixed an issue where setting `scene.globe.cartographicLimitRectangle` to `undefined` would cause a crash. [#7477](https://github.com/AnalyticalGraphicsInc/cesium/issues/7477) * Fixed `PrimitiveCollection.removeAll` to no longer `contain` removed primitives. [#7491](https://github.com/AnalyticalGraphicsInc/cesium/pull/7491) +* Fixed `GeoJsonDataSource` to use polygons and polylines that use rhumb lines. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492) ### 1.53 - 2019-01-02 diff --git a/Source/Core/ArcType.js b/Source/Core/ArcType.js new file mode 100644 index 000000000000..f4c54cc9305c --- /dev/null +++ b/Source/Core/ArcType.js @@ -0,0 +1,39 @@ +define([ + './freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * ArcType defines the path that should be taken connecting vertices. + * + * @exports ArcType + */ + var ArcType = { + /** + * Straight line that does not conform to the surface of the ellipsoid. + * + * @type {Number} + * @constant + */ + NONE : 0, + + /** + * Follow geodesic path. + * + * @type {Number} + * @constant + */ + GEODESIC : 1, + + /** + * Follow rhumb or loxodrome path. + * + * @type {Number} + * @constant + */ + RHUMB : 2 + }; + + return freezeObject(ArcType); +}); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 938a2c08e001..d950f2296b27 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -1,5 +1,6 @@ define([ './ApproximateTerrainHeights', + './ArcType', './arrayRemoveDuplicates', './BoundingSphere', './Cartesian3', @@ -13,6 +14,7 @@ define([ './defineProperties', './Ellipsoid', './EllipsoidGeodesic', + './EllipsoidRhumbLine', './EncodedCartesian3', './GeographicProjection', './Geometry', @@ -25,6 +27,7 @@ define([ './WebMercatorProjection' ], function( ApproximateTerrainHeights, + ArcType, arrayRemoveDuplicates, BoundingSphere, Cartesian3, @@ -38,6 +41,7 @@ define([ defineProperties, Ellipsoid, EllipsoidGeodesic, + EllipsoidRhumbLine, EncodedCartesian3, GeographicProjection, Geometry, @@ -80,6 +84,7 @@ define([ * @param {Number} [options.width=1.0] The screen space width in pixels. * @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}. * * @exception {DeveloperError} At least two positions are required. * @@ -104,6 +109,9 @@ define([ if ((!defined(positions)) || (positions.length < 2)) { throw new DeveloperError('At least two positions are required.'); } + if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) { + throw new DeveloperError('Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB.'); + } //>>includeEnd('debug'); /** @@ -130,6 +138,13 @@ define([ */ this.loop = defaultValue(options.loop, false); + /** + * The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}. + * @type {ArcType} + * @default ArcType.GEODESIC + */ + this.arcType = defaultValue(options.arcType, ArcType.GEODESIC); + this._ellipsoid = Ellipsoid.WGS84; // MapProjections can't be packed, so store the index to a known MapProjection. @@ -150,7 +165,7 @@ define([ */ packedLength: { get: function() { - return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0; + return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0; } } }); @@ -195,12 +210,19 @@ define([ var interpolatedBottomScratch = new Cartesian3(); var interpolatedTopScratch = new Cartesian3(); var interpolatedNormalScratch = new Cartesian3(); - function interpolateSegment(start, end, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) { + function interpolateSegment(start, end, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) { if (granularity === 0.0) { return; } - var ellipsoidGeodesic = new EllipsoidGeodesic(start, end, ellipsoid); - var surfaceDistance = ellipsoidGeodesic.surfaceDistance; + + var ellipsoidLine; + if (arcType === ArcType.GEODESIC) { + ellipsoidLine = new EllipsoidGeodesic(start, end, ellipsoid); + } else if (arcType === ArcType.RHUMB) { + ellipsoidLine = new EllipsoidRhumbLine(start, end, ellipsoid); + } + + var surfaceDistance = ellipsoidLine.surfaceDistance; if (surfaceDistance < granularity) { return; } @@ -214,7 +236,7 @@ define([ var pointsToAdd = segments - 1; var packIndex = normalsArray.length; for (var i = 0; i < pointsToAdd; i++) { - var interpolatedCartographic = ellipsoidGeodesic.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch); + var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch); var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, minHeight, interpolatedBottomScratch); var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, maxHeight, interpolatedTopScratch); @@ -266,6 +288,7 @@ define([ array[index++] = value.granularity; array[index++] = value.loop ? 1.0 : 0.0; + array[index++] = value.arcType; Ellipsoid.pack(value._ellipsoid, array, index); index += Ellipsoid.packedLength; @@ -299,6 +322,7 @@ define([ var granularity = array[index++]; var loop = array[index++] === 1.0; + var arcType = array[index++]; var ellipsoid = Ellipsoid.unpack(array, index); index += Ellipsoid.packedLength; @@ -311,6 +335,7 @@ define([ positions : positions, granularity : granularity, loop : loop, + arcType : arcType, ellipsoid : ellipsoid }); geometry._projectionIndex = projectionIndex; @@ -321,6 +346,7 @@ define([ result._positions = positions; result.granularity = granularity; result.loop = loop; + result.arcType = arcType; result._ellipsoid = ellipsoid; result._projectionIndex = projectionIndex; result._scene3DOnly = scene3DOnly; @@ -384,6 +410,9 @@ define([ var nextBottomScratch = new Cartesian3(); var vertexNormalScratch = new Cartesian3(); var intersectionScratch = new Cartesian3(); + var cartographicScratch0 = new Cartographic(); + var cartographicScratch1 = new Cartographic(); + var cartographicIntersectionScratch = new Cartographic(); /** * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles. @@ -397,6 +426,7 @@ define([ var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry._ellipsoid; var granularity = groundPolylineGeometry.granularity; + var arcType = groundPolylineGeometry.arcType; var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid); var minHeight = WALL_INITIAL_MIN_HEIGHT; @@ -417,7 +447,12 @@ define([ // may get split by the plane of IDL + Prime Meridian. var p0; var p1; + var c0; + var c1; + var rhumbLine = new EllipsoidRhumbLine(undefined, undefined, ellipsoid); var intersection; + var intersectionCartographic; + var intersectionLongitude; var splitPositions = [positions[0]]; for (i = 0; i < positionsLength - 1; i++) { p0 = positions[i]; @@ -426,7 +461,21 @@ define([ if (defined(intersection) && !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { - splitPositions.push(Cartesian3.clone(intersection)); + if (groundPolylineGeometry.arcType === ArcType.GEODESIC) { + splitPositions.push(Cartesian3.clone(intersection)); + } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) { + intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude; + c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0); + c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1); + rhumbLine.setEndPoints(c0, c1); + intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch); + intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch); + if (defined(intersection) && + !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && + !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { + splitPositions.push(Cartesian3.clone(intersection)); + } + } } splitPositions.push(p1); } @@ -438,7 +487,21 @@ define([ if (defined(intersection) && !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { - splitPositions.push(Cartesian3.clone(intersection)); + if (groundPolylineGeometry.arcType === ArcType.GEODESIC) { + splitPositions.push(Cartesian3.clone(intersection)); + } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) { + intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude; + c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0); + c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1); + rhumbLine.setEndPoints(c0, c1); + intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch); + intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch); + if (defined(intersection) && + !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && + !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { + splitPositions.push(Cartesian3.clone(intersection)); + } + } } } var cartographicsLength = splitPositions.length; @@ -495,7 +558,7 @@ define([ cartographicsArray.push(startCartographic.latitude); cartographicsArray.push(startCartographic.longitude); - interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); + interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); // All inbetween points for (i = 1; i < cartographicsLength - 1; ++i) { @@ -514,7 +577,7 @@ define([ cartographicsArray.push(vertexCartographic.latitude); cartographicsArray.push(vertexCartographic.longitude); - interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); + interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); } // Last point - either loop or attach a normal "perpendicular" to the wall. @@ -542,7 +605,7 @@ define([ cartographicsArray.push(endCartographic.longitude); if (loop) { - interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); + interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); index = normalsArray.length; for (i = 0; i < 3; ++i) { normalsArray[index + i] = normalsArray[i]; diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index c91026befa5c..b6cb3d35a7ee 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -1,4 +1,5 @@ define([ + './ArcType', './arrayFill', './BoundingRectangle', './BoundingSphere', @@ -29,6 +30,7 @@ define([ './VertexFormat', './WindingOrder' ], function( + ArcType, arrayFill, BoundingRectangle, BoundingSphere, @@ -386,14 +388,14 @@ define([ var createGeometryFromPositionsExtrudedPositions = []; - function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat) { + function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat, arcType) { var geos = { walls : [] }; var i; if (closeTop || closeBottom) { - var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat); + var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat, arcType); var edgePoints = topGeo.attributes.position.values; var indices = topGeo.indices; @@ -455,7 +457,7 @@ define([ outerRing = outerRing.slice().reverse(); } - var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight); + var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight, arcType); geos.walls.push(new GeometryInstance({ geometry : wallGeo })); @@ -472,7 +474,7 @@ define([ hole = hole.slice().reverse(); } - wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity); + wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity, perPositionHeight, arcType); geos.walls.push(new GeometryInstance({ geometry : wallGeo })); @@ -498,6 +500,7 @@ define([ * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height. * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open. * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}. * * @see PolygonGeometry#createGeometry * @see PolygonGeometry#fromPositions @@ -578,6 +581,9 @@ define([ if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) { throw new DeveloperError('Cannot use both options.perPositionHeight and options.height'); } + if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) { + throw new DeveloperError('Invalid arcType. Valid options are ArcType.GEODESIC and ArcType.RHUMB.'); + } //>>includeEnd('debug'); var polygonHierarchy = options.polygonHierarchy; @@ -610,6 +616,7 @@ define([ this._shadowVolume = defaultValue(options.shadowVolume, false); this._workerName = 'createPolygonGeometry'; this._offsetAttribute = options.offsetAttribute; + this._arcType = defaultValue(options.arcType, ArcType.GEODESIC); this._rectangle = undefined; this._textureCoordinateRotationPoints = undefined; @@ -618,7 +625,7 @@ define([ * The number of elements used to pack the object into an array. * @type {Number} */ - this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + 11; + this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + 12; } /** @@ -635,6 +642,7 @@ define([ * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height. * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open. * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}. * @returns {PolygonGeometry} * * @@ -673,7 +681,8 @@ define([ perPositionHeight : options.perPositionHeight, closeTop : options.closeTop, closeBottom : options.closeBottom, - offsetAttribute : options.offsetAttribute + offsetAttribute : options.offsetAttribute, + arcType : options.arcType }; return new PolygonGeometry(newOptions); }; @@ -713,6 +722,7 @@ define([ array[startingIndex++] = value._closeBottom ? 1.0 : 0.0; array[startingIndex++] = value._shadowVolume ? 1.0 : 0.0; array[startingIndex++] = defaultValue(value._offsetAttribute, -1); + array[startingIndex++] = value._arcType; array[startingIndex] = value.packedLength; return array; @@ -760,6 +770,7 @@ define([ var closeBottom = array[startingIndex++] === 1.0; var shadowVolume = array[startingIndex++] === 1.0; var offsetAttribute = array[startingIndex++]; + var arcType = array[startingIndex++]; var packedLength = array[startingIndex]; if (!defined(result)) { @@ -779,6 +790,7 @@ define([ result._closeBottom = closeBottom; result._shadowVolume = shadowVolume; result._offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute; + result._arcType = arcType; result.packedLength = packedLength; return result; }; @@ -820,6 +832,7 @@ define([ var perPositionHeight = polygonGeometry._perPositionHeight; var closeTop = polygonGeometry._closeTop; var closeBottom = polygonGeometry._closeBottom; + var arcType = polygonGeometry._arcType; var outerPositions = polygonHierarchy.positions; if (outerPositions.length < 3) { @@ -856,7 +869,8 @@ define([ bottom: false, top: true, wall: false, - extrude: false + extrude: false, + arcType: arcType }; var i; @@ -868,7 +882,7 @@ define([ options.shadowVolume = polygonGeometry._shadowVolume; options.offsetAttribute = polygonGeometry._offsetAttribute; for (i = 0; i < polygons.length; i++) { - var splitGeometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat); + var splitGeometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat, arcType); var topAndBottom; if (closeTop && closeBottom) { @@ -901,7 +915,7 @@ define([ } else { for (i = 0; i < polygons.length; i++) { var geometryInstance = new GeometryInstance({ - geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat) + geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat, arcType) }); geometryInstance.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometryInstance.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight); options.geometry = geometryInstance.geometry; @@ -962,7 +976,8 @@ define([ extrudedHeight : minHeight, height : maxHeight, vertexFormat : VertexFormat.POSITION_ONLY, - shadowVolume: true + shadowVolume: true, + arcType : polygonGeometry._arcType }); }; diff --git a/Source/Core/PolygonGeometryLibrary.js b/Source/Core/PolygonGeometryLibrary.js index 7bdd83da721a..5d8142a450c1 100644 --- a/Source/Core/PolygonGeometryLibrary.js +++ b/Source/Core/PolygonGeometryLibrary.js @@ -1,11 +1,15 @@ define([ + './ArcType', './arrayRemoveDuplicates', './Cartesian2', './Cartesian3', + './Cartographic', './ComponentDatatype', './defaultValue', './defined', + './DeveloperError', './Ellipsoid', + './EllipsoidRhumbLine', './Geometry', './GeometryAttribute', './GeometryAttributes', @@ -19,13 +23,17 @@ define([ './Queue', './WindingOrder' ], function( + ArcType, arrayRemoveDuplicates, Cartesian2, Cartesian3, + Cartographic, ComponentDatatype, defaultValue, defined, + DeveloperError, Ellipsoid, + EllipsoidRhumbLine, Geometry, GeometryAttribute, GeometryAttributes, @@ -141,7 +149,20 @@ define([ PolygonGeometryLibrary.subdivideLineCount = function(p0, p1, minDistance) { var distance = Cartesian3.distance(p0, p1); var n = distance / minDistance; - var countDivide = Math.max(0, Math.ceil(Math.log(n) / Math.log(2))); + var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n))); + return Math.pow(2, countDivide); + }; + + var scratchCartographic0 = new Cartographic(); + var scratchCartographic1 = new Cartographic(); + var scratchCartographic2 = new Cartographic(); + var scratchCartesian0 = new Cartesian3(); + PolygonGeometryLibrary.subdivideRhumbLineCount = function(ellipsoid, p0, p1, minDistance) { + var c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0); + var c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1); + var rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid); + var n = rhumb.surfaceDistance / minDistance; + var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n))); return Math.pow(2, countDivide); }; @@ -168,6 +189,35 @@ define([ return positions; }; + PolygonGeometryLibrary.subdivideRhumbLine = function(ellipsoid, p0, p1, minDistance, result) { + var c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0); + var c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1); + var rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid); + + var n = rhumb.surfaceDistance / minDistance; + var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n))); + var numVertices = Math.pow(2, countDivide); + var distanceBetweenVertices = rhumb.surfaceDistance / numVertices; + + if (!defined(result)) { + result = []; + } + + var positions = result; + positions.length = numVertices * 3; + + var index = 0; + for ( var i = 0; i < numVertices; i++) { + var c = rhumb.interpolateUsingSurfaceDistance(i * distanceBetweenVertices, scratchCartographic2); + var p = ellipsoid.cartographicToCartesian(c, scratchCartesian0); + positions[index++] = p.x; + positions[index++] = p.y; + positions[index++] = p.z; + } + + return positions; + }; + var scaleToGeodeticHeightN1 = new Cartesian3(); var scaleToGeodeticHeightN2 = new Cartesian3(); var scaleToGeodeticHeightP1 = new Cartesian3(); @@ -403,7 +453,7 @@ define([ return result; }; - PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) { + PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat, arcType) { var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes); /* If polygon is completely unrenderable, just use the first three vertices */ @@ -442,14 +492,18 @@ define([ return geometry; } - return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); + if (arcType === ArcType.GEODESIC) { + return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); + } else if (arcType === ArcType.RHUMB) { + return PolygonPipeline.computeRhumbLineSubdivision(ellipsoid, positions, indices, granularity); + } }; var computeWallIndicesSubdivided = []; var p1Scratch = new Cartesian3(); var p2Scratch = new Cartesian3(); - PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) { + PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight, arcType) { var edgePositions; var topEdgeLength; var i; @@ -463,8 +517,14 @@ define([ var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + if (arcType === ArcType.GEODESIC) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + } + } else if (arcType === ArcType.RHUMB) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance); + } } topEdgeLength = (numVertices + length) * 3; @@ -473,7 +533,12 @@ define([ p1 = positions[i]; p2 = positions[(i + 1) % length]; - var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided); + var tempPositions; + if (arcType === ArcType.GEODESIC) { + tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided); + } else if (arcType === ArcType.RHUMB) { + tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, p1, p2, minDistance, computeWallIndicesSubdivided); + } var tempPositionsLength = tempPositions.length; for (var j = 0; j < tempPositionsLength; ++j, ++index) { edgePositions[index] = tempPositions[j]; diff --git a/Source/Core/PolygonOutlineGeometry.js b/Source/Core/PolygonOutlineGeometry.js index 8d7b404a5874..180c19e0dbec 100644 --- a/Source/Core/PolygonOutlineGeometry.js +++ b/Source/Core/PolygonOutlineGeometry.js @@ -1,4 +1,5 @@ define([ + './ArcType', './arrayFill', './arrayRemoveDuplicates', './BoundingSphere', @@ -24,6 +25,7 @@ define([ './Queue', './WindingOrder' ], function( + ArcType, arrayFill, arrayRemoveDuplicates, BoundingSphere, @@ -52,7 +54,7 @@ define([ var createGeometryFromPositionsPositions = []; var createGeometryFromPositionsSubdivided = []; - function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight) { + function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight, arcType) { var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions); @@ -70,12 +72,23 @@ define([ if (!perPositionHeight) { var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + if (arcType === ArcType.GEODESIC) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + } + } else if (arcType === ArcType.RHUMB) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance); + } } subdividedPositions = new Float64Array(numVertices * 3); for (i = 0; i < length; i++) { - var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + var tempPositions; + if (arcType === ArcType.GEODESIC) { + tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + } else if (arcType === ArcType.RHUMB) { + tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + } var tempPositionsLength = tempPositions.length; for (var j = 0; j < tempPositionsLength; ++j) { subdividedPositions[index++] = tempPositions[j]; @@ -121,7 +134,7 @@ define([ }); } - function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight) { + function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight, arcType) { var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions); @@ -140,14 +153,25 @@ define([ if (!perPositionHeight) { var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + if (arcType === ArcType.GEODESIC) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + } + } else if (arcType === ArcType.RHUMB) { + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance); + } } subdividedPositions = new Float64Array(numVertices * 3 * 2); for (i = 0; i < length; ++i) { corners[i] = index / 3; - var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + var tempPositions; + if (arcType === ArcType.GEODESIC) { + tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + } else if (arcType === ArcType.RHUMB) { + tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided); + } var tempPositionsLength = tempPositions.length; for (var j = 0; j < tempPositionsLength; ++j) { subdividedPositions[index++] = tempPositions[j]; @@ -218,6 +242,7 @@ define([ * @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. * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of path the outline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}. * * @see PolygonOutlineGeometry#createGeometry * @see PolygonOutlineGeometry#fromPositions @@ -297,6 +322,9 @@ define([ if (options.perPositionHeight && defined(options.height)) { throw new DeveloperError('Cannot use both options.perPositionHeight and options.height'); } + if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) { + throw new DeveloperError('Invalid arcType. Valid options are ArcType.GEODESIC and ArcType.RHUMB.'); + } //>>includeEnd('debug'); var polygonHierarchy = options.polygonHierarchy; @@ -304,6 +332,7 @@ define([ var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); var perPositionHeight = defaultValue(options.perPositionHeight, false); var perPositionHeightExtrude = perPositionHeight && defined(options.extrudedHeight); + var arcType = defaultValue(options.arcType, ArcType.GEODESIC); var height = defaultValue(options.height, 0.0); var extrudedHeight = defaultValue(options.extrudedHeight, height); @@ -318,6 +347,7 @@ define([ this._granularity = granularity; this._height = height; this._extrudedHeight = extrudedHeight; + this._arcType = arcType; this._polygonHierarchy = polygonHierarchy; this._perPositionHeight = perPositionHeight; this._perPositionHeightExtrude = perPositionHeightExtrude; @@ -328,7 +358,7 @@ define([ * The number of elements used to pack the object into an array. * @type {Number} */ - this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 7; + this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 8; } /** @@ -358,6 +388,7 @@ define([ array[startingIndex++] = value._granularity; array[startingIndex++] = value._perPositionHeightExtrude ? 1.0 : 0.0; array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0; + array[startingIndex++] = value._arcType; array[startingIndex++] = defaultValue(value._offsetAttribute, -1); array[startingIndex] = value.packedLength; @@ -396,6 +427,7 @@ define([ var granularity = array[startingIndex++]; var perPositionHeightExtrude = array[startingIndex++] === 1.0; var perPositionHeight = array[startingIndex++] === 1.0; + var arcType = array[startingIndex++]; var offsetAttribute = array[startingIndex++]; var packedLength = array[startingIndex]; @@ -410,6 +442,7 @@ define([ result._granularity = granularity; result._perPositionHeight = perPositionHeight; result._perPositionHeightExtrude = perPositionHeightExtrude; + result._arcType = arcType; result._offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute; result.packedLength = packedLength; @@ -426,6 +459,7 @@ define([ * @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. * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of path the outline must follow. Valid options are {@link LinkType.GEODESIC} and {@link ArcType.RHUMB}. * @returns {PolygonOutlineGeometry} * * @@ -460,6 +494,7 @@ define([ ellipsoid : options.ellipsoid, granularity : options.granularity, perPositionHeight : options.perPositionHeight, + arcType: options.arcType, offsetAttribute : options.offsetAttribute }; return new PolygonOutlineGeometry(newOptions); @@ -476,6 +511,7 @@ define([ var granularity = polygonGeometry._granularity; var polygonHierarchy = polygonGeometry._polygonHierarchy; var perPositionHeight = polygonGeometry._perPositionHeight; + var arcType = polygonGeometry._arcType; var polygons = PolygonGeometryLibrary.polygonOutlinesFromHierarchy(polygonHierarchy, !perPositionHeight, ellipsoid); @@ -494,7 +530,7 @@ define([ var i; if (extrude) { for (i = 0; i < polygons.length; i++) { - geometryInstance = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight); + geometryInstance = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight, arcType); geometryInstance.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(geometryInstance.geometry, height, extrudedHeight, ellipsoid, perPositionHeight); if (defined(polygonGeometry._offsetAttribute)) { var size = geometryInstance.geometry.attributes.position.values.length / 3; @@ -516,7 +552,7 @@ define([ } } else { for (i = 0; i < polygons.length; i++) { - geometryInstance = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight); + geometryInstance = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight, arcType); geometryInstance.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometryInstance.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight); if (defined(polygonGeometry._offsetAttribute)) { diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 00b3849b00e4..cd59af0c3530 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -2,6 +2,7 @@ define([ '../ThirdParty/earcut-2.1.1', './Cartesian2', './Cartesian3', + './Cartographic', './Check', './ComponentDatatype', './defaultValue', @@ -16,6 +17,7 @@ define([ earcut, Cartesian2, Cartesian3, + Cartographic, Check, ComponentDatatype, defaultValue, @@ -227,6 +229,158 @@ define([ }); }; + var subdivisionC0Scratch = new Cartographic(); + var subdivisionC1Scratch = new Cartographic(); + var subdivisionC2Scratch = new Cartographic(); + var subdivisionCart2Scratch0 = new Cartesian2(); + var subdivisionCart2Scratch1 = new Cartesian2(); + var subdivisionCart2Scratch2 = new Cartesian2(); + var subdivisionMidCart2Scratch = new Cartesian2(); + + /** + * Subdivides positions on rhumb lines and raises points to the surface of the ellipsoid. + * + * @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on. + * @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon. + * @param {Number[]} indices An array of indices that determines the triangles in the polygon. + * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * + * @exception {DeveloperError} At least three indices are required. + * @exception {DeveloperError} The number of indices must be divisable by three. + * @exception {DeveloperError} Granularity must be greater than zero. + */ + PolygonPipeline.computeRhumbLineSubdivision = function(ellipsoid, positions, indices, granularity) { + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE); + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('ellipsoid', ellipsoid); + Check.defined('positions', positions); + Check.defined('indices', indices); + Check.typeOf.number.greaterThanOrEquals('indices.length', indices.length, 3); + Check.typeOf.number.equals('indices.length % 3', '0', indices.length % 3, 0); + Check.typeOf.number.greaterThan('granularity', granularity, 0.0); + //>>includeEnd('debug'); + + // triangles that need (or might need) to be subdivided. + var triangles = indices.slice(0); + + // New positions due to edge splits are appended to the positions list. + var i; + var length = positions.length; + var subdividedPositions = new Array(length * 3); + var q = 0; + for (i = 0; i < length; i++) { + var item = positions[i]; + subdividedPositions[q++] = item.x; + subdividedPositions[q++] = item.y; + subdividedPositions[q++] = item.z; + } + + var subdividedIndices = []; + + // Used to make sure shared edges are not split more than once. + var edges = {}; + + var granularitySqrd = granularity * granularity; + + while (triangles.length > 0) { + var i2 = triangles.pop(); + var i1 = triangles.pop(); + var i0 = triangles.pop(); + + var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch); + var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch); + var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch); + + var c0 = ellipsoid.cartesianToCartographic(v0, subdivisionC0Scratch); + var c1 = ellipsoid.cartesianToCartographic(v1, subdivisionC1Scratch); + var c2 = ellipsoid.cartesianToCartographic(v2, subdivisionC2Scratch); + + var c0Cart2 = Cartesian2.fromElements(c0.longitude, c0.latitude, subdivisionCart2Scratch0); + var c1Cart2 = Cartesian2.fromElements(c1.longitude, c1.latitude, subdivisionCart2Scratch1); + var c2Cart2 = Cartesian2.fromElements(c2.longitude, c2.latitude, subdivisionCart2Scratch2); + + var g0 = Cartesian2.distanceSquared(c0Cart2, c1Cart2); + var g1 = Cartesian2.distanceSquared(c1Cart2, c2Cart2); + var g2 = Cartesian2.distanceSquared(c2Cart2, c0Cart2); + + var max = Math.max(g0, g1, g2); + var edge; + var mid; + var midHeight; + var midCartesian3; + + // if the max length squared of a triangle edge is greater than squared granularity, subdivide the triangle + if (max > granularitySqrd) { + if (g0 === max) { + edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1); + + i = edges[edge]; + if (!defined(i)) { + mid = Cartesian2.add(c0Cart2, c1Cart2, subdivisionMidCart2Scratch); + Cartesian2.multiplyByScalar(mid, 0.5, mid); + midHeight = (c0.height + c1.height) * 0.5; + midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch); + subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z); + i = subdividedPositions.length / 3 - 1; + edges[edge] = i; + } + + triangles.push(i0, i, i2); + triangles.push(i, i1, i2); + } else if (g1 === max) { + edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2); + + i = edges[edge]; + if (!defined(i)) { + mid = Cartesian2.add(c1Cart2, c2Cart2, subdivisionMidCart2Scratch); + Cartesian2.multiplyByScalar(mid, 0.5, mid); + midHeight = (c1.height + c2.height) * 0.5; + midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch); + subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z); + i = subdividedPositions.length / 3 - 1; + edges[edge] = i; + } + + triangles.push(i1, i, i0); + triangles.push(i, i2, i0); + } else if (g2 === max) { + edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0); + + i = edges[edge]; + if (!defined(i)) { + mid = Cartesian2.add(c2Cart2, c0Cart2, subdivisionMidCart2Scratch); + Cartesian2.multiplyByScalar(mid, 0.5, mid); + midHeight = (c2.height + c0.height) * 0.5; + midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch); + subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z); + i = subdividedPositions.length / 3 - 1; + edges[edge] = i; + } + + triangles.push(i2, i, i1); + triangles.push(i, i0, i1); + } + } else { + subdividedIndices.push(i0); + subdividedIndices.push(i1); + subdividedIndices.push(i2); + } + } + + return new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : subdividedPositions + }) + }, + indices : subdividedIndices, + primitiveType : PrimitiveType.TRIANGLES + }); + }; + /** * Scales each position of a geometry's position attribute to a height, in place. * diff --git a/Source/Core/PolylineGeometry.js b/Source/Core/PolylineGeometry.js index c2c76de00730..a4232b8833e4 100644 --- a/Source/Core/PolylineGeometry.js +++ b/Source/Core/PolylineGeometry.js @@ -1,4 +1,5 @@ define([ + './ArcType', './arrayRemoveDuplicates', './BoundingSphere', './Cartesian3', @@ -6,6 +7,7 @@ define([ './ComponentDatatype', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', './Ellipsoid', './Geometry', @@ -18,6 +20,7 @@ define([ './PrimitiveType', './VertexFormat' ], function( + ArcType, arrayRemoveDuplicates, BoundingSphere, Cartesian3, @@ -25,6 +28,7 @@ define([ ComponentDatatype, defaultValue, defined, + deprecationWarning, DeveloperError, Ellipsoid, Geometry, @@ -87,8 +91,8 @@ define([ * @param {Number} [options.width=1.0] The width in pixels. * @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors. * @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices. - * @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc. - * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.arcType is not ArcType.NONE. Determines the number of positions in the buffer. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. * @@ -136,7 +140,15 @@ define([ this._width = width; this._colorsPerVertex = colorsPerVertex; this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT)); + this._followSurface = defaultValue(options.followSurface, true); + if (defined(options.followSurface)) { + deprecationWarning('PolylineGeometry.followSurface', 'PolylineGeometry.followSurface is deprecated and will be removed in Cesium 1.55. Use PolylineGeometry.arcType instead.'); + options.arcType = options.followSurface ? ArcType.GEODESIC : ArcType.NONE; + } + this._arcType = defaultValue(options.arcType, ArcType.GEODESIC); + this._followSurface = (this._arcType !== ArcType.NONE); + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84)); this._workerName = 'createPolylineGeometry'; @@ -198,7 +210,7 @@ define([ array[startingIndex++] = value._width; array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; - array[startingIndex++] = value._followSurface ? 1.0 : 0.0; + array[startingIndex++] = value._arcType; array[startingIndex] = value._granularity; return array; @@ -213,7 +225,7 @@ define([ vertexFormat : scratchVertexFormat, width : undefined, colorsPerVertex : undefined, - followSurface : undefined, + arcType : undefined, granularity : undefined }; @@ -258,7 +270,7 @@ define([ var width = array[startingIndex++]; var colorsPerVertex = array[startingIndex++] === 1.0; - var followSurface = array[startingIndex++] === 1.0; + var arcType = array[startingIndex++]; var granularity = array[startingIndex]; if (!defined(result)) { @@ -266,7 +278,7 @@ define([ scratchOptions.colors = colors; scratchOptions.width = width; scratchOptions.colorsPerVertex = colorsPerVertex; - scratchOptions.followSurface = followSurface; + scratchOptions.arcType = arcType; scratchOptions.granularity = granularity; return new PolylineGeometry(scratchOptions); } @@ -277,7 +289,7 @@ define([ result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat); result._width = width; result._colorsPerVertex = colorsPerVertex; - result._followSurface = followSurface; + result._arcType = arcType; result._granularity = granularity; return result; @@ -299,7 +311,7 @@ define([ var vertexFormat = polylineGeometry._vertexFormat; var colors = polylineGeometry._colors; var colorsPerVertex = polylineGeometry._colorsPerVertex; - var followSurface = polylineGeometry._followSurface; + var arcType = polylineGeometry._arcType; var granularity = polylineGeometry._granularity; var ellipsoid = polylineGeometry._ellipsoid; @@ -316,14 +328,23 @@ define([ return undefined; } - if (followSurface) { + if (arcType === ArcType.GEODESIC || arcType === ArcType.RHUMB) { + var subdivisionSize; + var numberOfPointsFunction; + if (arcType === ArcType.GEODESIC) { + subdivisionSize = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + numberOfPointsFunction = PolylinePipeline.numberOfPoints; + } else { + subdivisionSize = granularity; + numberOfPointsFunction = PolylinePipeline.numberOfPointsRhumbLine; + } + var heights = PolylinePipeline.extractHeights(positions, ellipsoid); - var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); if (defined(colors)) { var colorLength = 1; for (i = 0; i < positionsLength - 1; ++i) { - colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance); + colorLength += numberOfPointsFunction(positions[i], positions[i + 1], subdivisionSize); } var newColors = new Array(colorLength); @@ -331,12 +352,12 @@ define([ for (i = 0; i < positionsLength - 1; ++i) { var p0 = positions[i]; - var p1 = positions[i+1]; + var p1 = positions[i + 1]; var c0 = colors[i]; - var numColors = PolylinePipeline.numberOfPoints(p0, p1, minDistance); + var numColors = numberOfPointsFunction(p0, p1, subdivisionSize); if (colorsPerVertex && i < colorLength) { - var c1 = colors[i+1]; + var c1 = colors[i + 1]; var interpolatedColors = interpolateColors(p0, p1, c0, c1, numColors); var interpolatedColorsLength = interpolatedColors.length; for (j = 0; j < interpolatedColorsLength; ++j) { @@ -349,18 +370,27 @@ define([ } } - newColors[newColorIndex] = Color.clone(colors[colors.length-1]); + newColors[newColorIndex] = Color.clone(colors[colors.length - 1]); colors = newColors; scratchInterpolateColorsArray.length = 0; } - positions = PolylinePipeline.generateCartesianArc({ - positions: positions, - minDistance: minDistance, - ellipsoid: ellipsoid, - height: heights - }); + if (arcType === ArcType.GEODESIC) { + positions = PolylinePipeline.generateCartesianArc({ + positions: positions, + minDistance: subdivisionSize, + ellipsoid: ellipsoid, + height: heights + }); + } else { + positions = PolylinePipeline.generateCartesianRhumbArc({ + positions: positions, + granularity: subdivisionSize, + ellipsoid: ellipsoid, + height: heights + }); + } } positionsLength = positions.length; diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index d1da46883445..4c54a8d2c6cc 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -6,6 +6,7 @@ define([ './DeveloperError', './Ellipsoid', './EllipsoidGeodesic', + './EllipsoidRhumbLine', './IntersectionTests', './isArray', './Math', @@ -19,6 +20,7 @@ define([ DeveloperError, Ellipsoid, EllipsoidGeodesic, + EllipsoidRhumbLine, IntersectionTests, isArray, CesiumMath, @@ -36,6 +38,11 @@ define([ return Math.ceil(distance / minDistance); }; + PolylinePipeline.numberOfPointsRhumbLine = function(p0, p1, granularity) { + var radiansDistanceSquared = Math.pow((p0.longitude - p1.longitude), 2) + Math.pow((p0.latitude - p1.latitude), 2); + return Math.ceil(Math.sqrt(radiansDistanceSquared / (granularity * granularity))); + }; + var cartoScratch = new Cartographic(); PolylinePipeline.extractHeights = function(positions, ellipsoid) { var length = positions.length; @@ -87,6 +94,7 @@ define([ var scaleFirst = new Cartesian3(); var scaleLast = new Cartesian3(); var ellipsoidGeodesic = new EllipsoidGeodesic(); + var ellipsoidRhumb = new EllipsoidRhumbLine(); //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2. //Result includes p1, but not include p2. This function is called for a sequence of line segments, @@ -119,6 +127,41 @@ define([ return index; } + //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2. + //Result includes p1, but not include p2. This function is called for a sequence of line segments, + //and this prevents duplication of end point. + function generateCartesianRhumbArc(p0, p1, granularity, ellipsoid, h0, h1, array, offset) { + var first = ellipsoid.scaleToGeodeticSurface(p0, scaleFirst); + var last = ellipsoid.scaleToGeodeticSurface(p1, scaleLast); + var start = ellipsoid.cartesianToCartographic(first, carto1); + var end = ellipsoid.cartesianToCartographic(last, carto2); + + var numPoints = PolylinePipeline.numberOfPointsRhumbLine(start, end, granularity); + var heights = subdivideHeights(numPoints, h0, h1); + + if (!ellipsoidRhumb.ellipsoid.equals(ellipsoid)) { + ellipsoidRhumb = new EllipsoidRhumbLine(undefined, undefined, ellipsoid); + } + ellipsoidRhumb.setEndPoints(start, end); + var surfaceDistanceBetweenPoints = ellipsoidRhumb.surfaceDistance / numPoints; + + var index = offset; + start.height = h0; + var cart = ellipsoid.cartographicToCartesian(start, cartesian); + Cartesian3.pack(cart, array, index); + index += 3; + + for (var i = 1; i < numPoints; i++) { + var carto = ellipsoidRhumb.interpolateUsingSurfaceDistance(i * surfaceDistanceBetweenPoints, carto2); + carto.height = heights[i]; + cart = ellipsoid.cartographicToCartesian(carto, cartesian); + Cartesian3.pack(cart, array, index); + index += 3; + } + + return index; + } + /** * Breaks a {@link Polyline} into segments such that it does not cross the ±180 degree meridian of an ellipsoid. * @@ -287,6 +330,97 @@ define([ return newPositions; }; + var scratchCartographic0 = new Cartographic(); + var scratchCartographic1 = new Cartographic(); + + /** + * Subdivides polyline and raises all points to the specified height using Rhumb lines. Returns an array of numbers to represent the positions. + * @param {Object} options Object with the following properties: + * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions. + * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position. + * @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 {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie. + * @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid. + * + * @example + * var positions = Cesium.Cartesian3.fromDegreesArray([ + * -105.0, 40.0, + * -100.0, 38.0, + * -105.0, 35.0, + * -100.0, 32.0 + * ]); + * var surfacePositions = Cesium.PolylinePipeline.generateRhumbArc({ + * positons: positions + * }); + */ + PolylinePipeline.generateRhumbArc = function(options) { + if (!defined(options)) { + options = {}; + } + var positions = options.positions; + //>>includeStart('debug', pragmas.debug); + if (!defined(positions)) { + throw new DeveloperError('options.positions is required.'); + } + //>>includeEnd('debug'); + + var length = positions.length; + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var height = defaultValue(options.height, 0); + var hasHeightArray = isArray(height); + + if (length < 1) { + return []; + } else if (length === 1) { + var p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst); + height = hasHeightArray ? height[0] : height; + if (height !== 0) { + var n = ellipsoid.geodeticSurfaceNormal(p, cartesian); + Cartesian3.multiplyByScalar(n, height, n); + Cartesian3.add(p, n, p); + } + + return [p.x, p.y, p.z]; + } + + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + + var numPoints = 0; + var i; + + var c0 = ellipsoid.cartesianToCartographic(positions[0], scratchCartographic0); + var c1; + for (i = 0; i < length - 1; i++) { + c1 = ellipsoid.cartesianToCartographic(positions[i + 1], scratchCartographic1); + numPoints += PolylinePipeline.numberOfPointsRhumbLine(c0, c1, granularity); + c0 = Cartographic.clone(c1, scratchCartographic0); + } + + var arrayLength = (numPoints + 1) * 3; + var newPositions = new Array(arrayLength); + var offset = 0; + + for (i = 0; i < length - 1; i++) { + var p0 = positions[i]; + var p1 = positions[i + 1]; + + var h0 = hasHeightArray ? height[i] : height; + var h1 = hasHeightArray ? height[i + 1] : height; + + offset = generateCartesianRhumbArc(p0, p1, granularity, ellipsoid, h0, h1, newPositions, offset); + } + + subdivideHeightsScratchArray.length = 0; + + var lastPoint = positions[length - 1]; + var carto = ellipsoid.cartesianToCartographic(lastPoint, carto1); + carto.height = hasHeightArray ? height[length - 1] : height; + var cart = ellipsoid.cartographicToCartesian(carto, cartesian); + Cartesian3.pack(cart, newPositions, arrayLength - 3); + + return newPositions; + }; + /** * Subdivides polyline and raises all points to the specified height. Returns an array of new {Cartesian3} positions. * @param {Object} options Object with the following properties: @@ -317,5 +451,35 @@ define([ return newPositions; }; + /** + * Subdivides polyline and raises all points to the specified height using Rhumb Lines. Returns an array of new {Cartesian3} positions. + * @param {Object} options Object with the following properties: + * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions. + * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position. + * @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 {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie. + * @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid. + * + * @example + * var positions = Cesium.Cartesian3.fromDegreesArray([ + * -105.0, 40.0, + * -100.0, 38.0, + * -105.0, 35.0, + * -100.0, 32.0 + * ]); + * var surfacePositions = Cesium.PolylinePipeline.generateCartesianRhumbArc({ + * positons: positions + * }); + */ + PolylinePipeline.generateCartesianRhumbArc = function(options) { + var numberArray = PolylinePipeline.generateRhumbArc(options); + var size = numberArray.length/3; + var newPositions = new Array(size); + for (var i = 0; i < size; i++) { + newPositions[i] = Cartesian3.unpack(numberArray, i*3); + } + return newPositions; + }; + return PolylinePipeline; }); diff --git a/Source/Core/SimplePolylineGeometry.js b/Source/Core/SimplePolylineGeometry.js index c384ede5125d..65187d9d6c6c 100644 --- a/Source/Core/SimplePolylineGeometry.js +++ b/Source/Core/SimplePolylineGeometry.js @@ -1,10 +1,12 @@ define([ + './ArcType', './BoundingSphere', './Cartesian3', './Color', './ComponentDatatype', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', './Ellipsoid', './Geometry', @@ -15,12 +17,14 @@ define([ './PolylinePipeline', './PrimitiveType' ], function( + ArcType, BoundingSphere, Cartesian3, Color, ComponentDatatype, defaultValue, defined, + deprecationWarning, DeveloperError, Ellipsoid, Geometry, @@ -83,8 +87,8 @@ define([ * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip. * @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors. * @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices. - * @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc. - * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.arcType is not ArcType.NONE. Determines the number of positions in the buffer. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. * * @exception {DeveloperError} At least two positions are required. @@ -121,7 +125,15 @@ define([ this._positions = positions; this._colors = colors; this._colorsPerVertex = colorsPerVertex; + this._followSurface = defaultValue(options.followSurface, true); + if (defined(options.followSurface)) { + deprecationWarning('PolylineGeometry.followSurface', 'PolylineGeometry.followSurface is deprecated and will be removed in Cesium 1.55. Use PolylineGeometry.arcType instead.'); + options.arcType = options.followSurface ? ArcType.GEODESIC : ArcType.NONE; + } + this._arcType = defaultValue(options.arcType, ArcType.GEODESIC); + this._followSurface = this._arcType === ArcType.NONE; + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); this._workerName = 'createSimplePolylineGeometry'; @@ -179,7 +191,7 @@ define([ startingIndex += Ellipsoid.packedLength; array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; - array[startingIndex++] = value._followSurface ? 1.0 : 0.0; + array[startingIndex++] = value._arcType; array[startingIndex] = value._granularity; return array; @@ -222,7 +234,7 @@ define([ startingIndex += Ellipsoid.packedLength; var colorsPerVertex = array[startingIndex++] === 1.0; - var followSurface = array[startingIndex++] === 1.0; + var arcType = array[startingIndex++]; var granularity = array[startingIndex]; if (!defined(result)) { @@ -231,7 +243,7 @@ define([ colors : colors, ellipsoid : ellipsoid, colorsPerVertex : colorsPerVertex, - followSurface : followSurface, + arcType : arcType, granularity : granularity }); } @@ -240,7 +252,7 @@ define([ result._colors = colors; result._ellipsoid = ellipsoid; result._colorsPerVertex = colorsPerVertex; - result._followSurface = followSurface; + result._arcType = arcType; result._granularity = granularity; return result; @@ -252,7 +264,8 @@ define([ positions : scratchArray1, height: scratchArray2, ellipsoid: undefined, - minDistance : undefined + minDistance : undefined, + granularity : undefined }; /** @@ -265,7 +278,7 @@ define([ var positions = simplePolylineGeometry._positions; var colors = simplePolylineGeometry._colors; var colorsPerVertex = simplePolylineGeometry._colorsPerVertex; - var followSurface = simplePolylineGeometry._followSurface; + var arcType = simplePolylineGeometry._arcType; var granularity = simplePolylineGeometry._granularity; var ellipsoid = simplePolylineGeometry._ellipsoid; @@ -281,16 +294,34 @@ define([ var color; var offset = 0; - if (followSurface) { + if (arcType === ArcType.GEODESIC || arcType === ArcType.RHUMB) { + var subdivisionSize; + var numberOfPointsFunction; + var generateArcFunction; + if (arcType === ArcType.GEODESIC) { + subdivisionSize = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + numberOfPointsFunction = PolylinePipeline.numberOfPoints; + generateArcFunction = PolylinePipeline.generateArc; + } else { + subdivisionSize = granularity; + numberOfPointsFunction = PolylinePipeline.numberOfPointsRhumbLine; + generateArcFunction = PolylinePipeline.generateRhumbArc; + } + var heights = PolylinePipeline.extractHeights(positions, ellipsoid); + var generateArcOptions = generateArcOptionsScratch; - generateArcOptions.minDistance = minDistance; + if (arcType === ArcType.GEODESIC) { + generateArcOptions.minDistance = minDistance; + } else { + generateArcOptions.granularity = granularity; + } generateArcOptions.ellipsoid = ellipsoid; if (perSegmentColors) { var positionCount = 0; for (i = 0; i < length - 1; i++) { - positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1; + positionCount += numberOfPointsFunction(positions[i], positions[i+1], subdivisionSize) + 1; } positionValues = new Float64Array(positionCount * 3); @@ -307,7 +338,7 @@ define([ scratchArray2[0] = heights[i]; scratchArray2[1] = heights[i + 1]; - var pos = PolylinePipeline.generateArc(generateArcOptions); + var pos = generateArcFunction(generateArcOptions); if (defined(colors)) { var segLen = pos.length / 3; @@ -326,7 +357,7 @@ define([ } else { generateArcOptions.positions = positions; generateArcOptions.height= heights; - positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); + positionValues = new Float64Array(generateArcFunction(generateArcOptions)); if (defined(colors)) { colorValues = new Uint8Array(positionValues.length / 3 * 4); diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index dae9820c5d4d..04be1bef39b7 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -678,7 +678,7 @@ define([ /** * Checks if the given Scene supports polylines clamped to terrain or 3D Tiles. * If this feature is not supported, Entities with PolylineGraphics will be rendered with vertices at - * the provided heights and using the `followSurface` parameter instead of clamped to the ground. + * the provided heights and using the `arcType` parameter instead of clamped to the ground. * * @param {Scene} scene The current scene. * @returns {Boolean} Whether or not the current scene supports polylines on terrain or 3D TIles. diff --git a/Source/DataSources/GeoJsonDataSource.js b/Source/DataSources/GeoJsonDataSource.js index cb96f4419cb9..c0f090ac9794 100644 --- a/Source/DataSources/GeoJsonDataSource.js +++ b/Source/DataSources/GeoJsonDataSource.js @@ -1,4 +1,5 @@ define([ + '../Core/ArcType', '../Core/Cartesian3', '../Core/Color', '../Core/createGuid', @@ -27,6 +28,7 @@ define([ './PolygonGraphics', './PolylineGraphics' ], function( + ArcType, Cartesian3, Color, createGuid, @@ -365,6 +367,7 @@ define([ polylineGraphics.material = material; polylineGraphics.width = widthProperty; polylineGraphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction)); + polylineGraphics.arcType = ArcType.RHUMB; } function processLineString(dataSource, geoJson, geometry, crsFunction, options) { @@ -434,6 +437,7 @@ define([ polygon.outlineColor = outlineColorProperty; polygon.outlineWidth = widthProperty; polygon.material = material; + polygon.arcType = ArcType.RHUMB; var holes = []; for (var i = 1, len = coordinates.length; i < len; i++) { diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index e0e2cc9c918c..dc3d160eb289 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -1,5 +1,6 @@ define([ '../Core/ApproximateTerrainHeights', + '../Core/ArcType', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Check', @@ -33,6 +34,7 @@ define([ './Property' ], function( ApproximateTerrainHeights, + ArcType, Cartesian2, Cartesian3, Check, @@ -88,6 +90,7 @@ define([ this.granularity = undefined; this.stRotation = undefined; this.offsetAttribute = undefined; + this.arcType = undefined; } /** @@ -275,6 +278,7 @@ define([ !Property.isConstant(polygon.closeTop) || // !Property.isConstant(polygon.closeBottom) || // !Property.isConstant(polygon.zIndex) || // + !Property.isConstant(polygon.arcType) || // (this._onTerrain && !Property.isConstant(this._materialProperty)); }; @@ -322,6 +326,7 @@ define([ options.closeBottom = Property.getValueOrDefault(polygon.closeBottom, Iso8601.MINIMUM_VALUE, true); options.offsetAttribute = offsetAttribute; options.height = heightValue; + options.arcType = Property.getValueOrDefault(polygon.arcType, Iso8601.MINIMUM_VALUE, ArcType.GEODESIC); extrudedHeightValue = GroundGeometryUpdater.getGeometryExtrudedHeight(extrudedHeightValue, extrudedHeightReferenceValue); if (extrudedHeightValue === GroundGeometryUpdater.CLAMP_TO_GROUND) { @@ -399,6 +404,7 @@ define([ options.closeBottom = Property.getValueOrDefault(polygon.closeBottom, time, true); options.offsetAttribute = offsetAttribute; options.height = heightValue; + options.arcType = Property.getValueOrDefault(polygon.arcType, time, ArcType.GEODESIC); extrudedHeightValue = GroundGeometryUpdater.getGeometryExtrudedHeight(extrudedHeightValue, extrudedHeightReferenceValue); if (extrudedHeightValue === GroundGeometryUpdater.CLAMP_TO_GROUND) { diff --git a/Source/DataSources/PolygonGraphics.js b/Source/DataSources/PolygonGraphics.js index fb65382cb61e..ede07748796f 100644 --- a/Source/DataSources/PolygonGraphics.js +++ b/Source/DataSources/PolygonGraphics.js @@ -44,6 +44,7 @@ define([ * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polygon casts or receives shadows from each light source. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polygon will be displayed. * @param {Property} [options.classificationType=ClassificationType.BOTH] An enum Property specifying whether this polygon will classify terrain, 3D Tiles, or both when on the ground. + * @param {Property} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow. * @param {ConstantProperty} [options.zIndex=0] A property specifying the zIndex used for ordering ground geometry. Only has an effect if the polygon is constant and neither height or extrudedHeight are specified. * * @see Entity @@ -88,6 +89,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._classificationType = undefined; this._classificationTypeSubscription = undefined; + this._arcType = undefined; + this._arcTypeSubscription = undefined; this._zIndex = undefined; this._zIndexSubscription = undefined; this._definitionChanged = new Event(); @@ -260,6 +263,14 @@ define([ */ classificationType : createPropertyDescriptor('classificationType'), + /** + * Gets or sets the {@link ArcType} Property specifying the type of lines the polygon edges use. + * @memberof PolygonGraphics.prototype + * @type {Property} + * @default ArcType.GEODESIC + */ + arcType : createPropertyDescriptor('arcType'), + /** * Gets or sets the zIndex Prperty specifying the ordering of ground geometry. Only has an effect if the polygon is constant and neither height or extrudedHeight are specified. * @memberof PolygonGraphics.prototype @@ -298,6 +309,7 @@ define([ result.shadows = this.shadows; result.distanceDisplayCondition = this.distanceDisplayCondition; result.classificationType = this.classificationType; + result.arcType = this.arcType; result.zIndex = this.zIndex; return result; @@ -335,6 +347,7 @@ define([ this.shadows = defaultValue(this.shadows, source.shadows); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.classificationType = defaultValue(this.classificationType, source.classificationType); + this.arcType = defaultValue(this.arcType, source.arcType); this.zIndex = defaultValue(this.zIndex, source.zIndex); }; diff --git a/Source/DataSources/PolylineGeometryUpdater.js b/Source/DataSources/PolylineGeometryUpdater.js index a7aa2b5fd20a..4dfbea77392d 100644 --- a/Source/DataSources/PolylineGeometryUpdater.js +++ b/Source/DataSources/PolylineGeometryUpdater.js @@ -1,4 +1,5 @@ define([ + '../Core/ArcType', '../Core/BoundingSphere', '../Core/Check', '../Core/Color', @@ -31,6 +32,7 @@ define([ './MaterialProperty', './Property' ], function( + ArcType, BoundingSphere, Check, Color, @@ -81,12 +83,15 @@ define([ this.positions = undefined; this.width = undefined; this.followSurface = undefined; + this.arcType = undefined; this.granularity = undefined; } function GroundGeometryOptions() { this.positions = undefined; this.width = undefined; + this.arcType = undefined; + this.granularity = undefined; } /** @@ -309,6 +314,19 @@ define([ } }, + /** + * Gets a value indicating if the path of the line. + * @memberof PolylineGeometryUpdater.prototype + * + * @type {ArcType} + * @readonly + */ + arcType : { + get : function() { + return this._arcType; + } + }, + /** * Gets a value indicating if the geometry is clamped to the ground. * Returns false if polylines on terrain is not supported. @@ -498,11 +516,12 @@ define([ var width = polyline.width; var followSurface = polyline.followSurface; + var arcType = polyline.arcType; var clampToGround = polyline.clampToGround; var granularity = polyline.granularity; if (!positionsProperty.isConstant || !Property.isConstant(width) || - !Property.isConstant(followSurface) || !Property.isConstant(granularity) || + !Property.isConstant(followSurface) || !Property.isConstant(arcType) || !Property.isConstant(granularity) || !Property.isConstant(clampToGround) || !Property.isConstant(zIndex)) { if (!this._dynamic) { this._dynamic = true; @@ -533,11 +552,14 @@ define([ geometryOptions.positions = positions; geometryOptions.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined; geometryOptions.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined; + geometryOptions.arcType = defined(arcType) ? arcType.getValue(Iso8601.MINIMUM_VALUE) : undefined; geometryOptions.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined; var groundGeometryOptions = this._groundGeometryOptions; groundGeometryOptions.positions = positions; groundGeometryOptions.width = geometryOptions.width; + groundGeometryOptions.arcType = geometryOptions.arcType; + groundGeometryOptions.granularity = geometryOptions.granularity; this._clampToGround = defined(clampToGround) ? clampToGround.getValue(Iso8601.MINIMUM_VALUE) : false; @@ -626,6 +648,8 @@ define([ geometryUpdater._clampToGround = Property.getValueOrDefault(polyline._clampToGround, time, false); geometryUpdater._groundGeometryOptions.positions = positions; geometryUpdater._groundGeometryOptions.width = Property.getValueOrDefault(polyline._width, time, 1); + geometryUpdater._groundGeometryOptions.arcType = Property.getValueOrDefault(polyline._arcType, time, ArcType.GEODESIC); + geometryUpdater._groundGeometryOptions.granularity = Property.getValueOrDefault(polyline._granularity, time, 9999); var groundPrimitives = this._groundPrimitives; @@ -682,9 +706,15 @@ define([ return; } - var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true); + var followSurface = Property.getValueOrUndefined(polyline._followSurface, time); + var arcType = ArcType.GEODESIC; + if (defined(followSurface)) { + arcType = followSurface ? ArcType.GEODESIC : ArcType.NONE; + } + arcType = Property.getValueOrDefault(polyline._arcType, time, arcType); + var globe = geometryUpdater._scene.globe; - if (followSurface && defined(globe)) { + if (arcType !== ArcType.NONE && defined(globe)) { generateCartesianArcOptions.ellipsoid = globe.ellipsoid; generateCartesianArcOptions.positions = positions; generateCartesianArcOptions.granularity = Property.getValueOrUndefined(polyline._granularity, time); diff --git a/Source/DataSources/PolylineGraphics.js b/Source/DataSources/PolylineGraphics.js index 8f1e912d3cfc..775ee15b2437 100644 --- a/Source/DataSources/PolylineGraphics.js +++ b/Source/DataSources/PolylineGraphics.js @@ -1,4 +1,5 @@ define([ + '../Core/ArcType', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -7,6 +8,7 @@ define([ './createMaterialPropertyDescriptor', './createPropertyDescriptor' ], function( + ArcType, defaultValue, defined, defineProperties, @@ -26,13 +28,13 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the line strip. - * @param {Property} [options.followSurface=true] A boolean Property specifying whether the line segments should be great arcs or linearly connected. + * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. * @param {Property} [options.clampToGround=false] A boolean Property specifying whether the Polyline should be clamped to the ground. * @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels. * @param {Property} [options.show=true] A boolean Property specifying the visibility of the polyline. * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline. * @param {MaterialProperty} [options.depthFailMaterial] A property specifying the material used to draw the polyline when it is below the terrain. - * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true. + * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if arcType is not ArcType.NONE. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed. * @param {Property} [options.classificationType=ClassificationType.BOTH] An enum Property specifying whether this polyline will classify terrain, 3D Tiles, or both when on the ground. @@ -52,6 +54,8 @@ define([ this._positionsSubscription = undefined; this._followSurface = undefined; this._followSurfaceSubscription = undefined; + this._arcType = undefined; + this._arcTypeSubscription = undefined; this._clampToGround = undefined; this._clampToGroundSubscription = undefined; this._granularity = undefined; @@ -136,10 +140,20 @@ define([ * should be great arcs or linearly connected. * @memberof PolylineGraphics.prototype * @type {Property} + * @deprecated This property has been deprecated. Use {@link PolylineGraphics#arcType} instead. * @default true */ followSurface : createPropertyDescriptor('followSurface'), + /** + * Gets or sets the {@link ArcType} Property specifying whether the line segments should be great arcs, rhumb lines or linearly connected. + * @memberof PolylineGraphics.prototype + * @type {Property} + * @deprecated This property has been deprecated. Use {@link PolylineGraphics#arcType} instead. + * @default ArcType.GEODESIC + */ + arcType : createPropertyDescriptor('arcType'), + /** * Gets or sets the boolean Property specifying whether the polyline * should be clamped to the ground. @@ -150,7 +164,7 @@ define([ clampToGround : createPropertyDescriptor('clampToGround'), /** - * Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true and clampToGround is false. + * Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if arcType is not ArcType.NONE and clampToGround is false. * @memberof PolylineGraphics.prototype * @type {Property} * @default Cesium.Math.RADIANS_PER_DEGREE @@ -206,6 +220,7 @@ define([ result.positions = this.positions; result.width = this.width; result.followSurface = this.followSurface; + result.arcType = this.arcType; result.clampToGround = this.clampToGround; result.granularity = this.granularity; result.shadows = this.shadows; @@ -235,6 +250,7 @@ define([ this.positions = defaultValue(this.positions, source.positions); this.width = defaultValue(this.width, source.width); this.followSurface = defaultValue(this.followSurface, source.followSurface); + this.arcType = defaultValue(this.arcType, source.arcType); this.clampToGround = defaultValue(this.clampToGround, source.clampToGround); this.granularity = defaultValue(this.granularity, source.granularity); this.shadows = defaultValue(this.shadows, source.shadows); diff --git a/Source/Scene/DebugModelMatrixPrimitive.js b/Source/Scene/DebugModelMatrixPrimitive.js index 633324e66a05..671be07456ec 100644 --- a/Source/Scene/DebugModelMatrixPrimitive.js +++ b/Source/Scene/DebugModelMatrixPrimitive.js @@ -1,4 +1,5 @@ define([ + '../Core/ArcType', '../Core/Cartesian3', '../Core/Color', '../Core/defaultValue', @@ -10,6 +11,7 @@ define([ './PolylineColorAppearance', './Primitive' ], function( + ArcType, Cartesian3, Color, defaultValue, @@ -142,7 +144,7 @@ define([ Color.RED, Color.RED ], - followSurface: false + arcType: ArcType.NONE }), modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()), id : this.id, @@ -160,7 +162,7 @@ define([ Color.GREEN, Color.GREEN ], - followSurface: false + arcType: ArcType.NONE }), modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()), id : this.id, @@ -178,7 +180,7 @@ define([ Color.BLUE, Color.BLUE ], - followSurface: false + arcType: ArcType.NONE }), modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()), id : this.id, diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index 9fdcc6f2e00a..b41332a997f3 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -1,23 +1,25 @@ defineSuite([ 'Core/GroundPolylineGeometry', 'Core/ApproximateTerrainHeights', + 'Core/ArcType', 'Core/arraySlice', 'Core/Cartesian3', 'Core/Cartographic', - 'Core/Math', 'Core/Ellipsoid', 'Core/GeographicProjection', + 'Core/Math', 'Core/WebMercatorProjection', 'Specs/createPackableSpecs' ], function( GroundPolylineGeometry, ApproximateTerrainHeights, + ArcType, arraySlice, Cartesian3, Cartographic, - CesiumMath, Ellipsoid, GeographicProjection, + CesiumMath, WebMercatorProjection, createPackableSpecs) { 'use strict'; @@ -364,6 +366,58 @@ defineSuite([ expect(geometry.attributes.position.values.length).toEqual(24 * 3); }); + it('interpolates long polyline segments for rhumb lines', function() { + // rhumb distance = 289020, geodesic distance = 288677 + var positions = Cartesian3.fromDegreesArray([ + 10, 75, + 20, 75 + ]); + + var rhumbGroundPolylineGeometry = new GroundPolylineGeometry({ + positions : positions, + granularity : 2890.0, + arcType: ArcType.RHUMB + }); + var geodesicGroundPolylineGeometry = new GroundPolylineGeometry({ + positions : positions, + granularity : 2890.0, + arcType: ArcType.GEODESIC + }); + + var rhumbGeometry = GroundPolylineGeometry.createGeometry(rhumbGroundPolylineGeometry); + var geodesicGeometry = GroundPolylineGeometry.createGeometry(geodesicGroundPolylineGeometry); + + expect(rhumbGeometry.indices.length).toEqual(3636); + expect(geodesicGeometry.indices.length).toEqual(3600); + expect(geodesicGeometry.attributes.position.values.length).toEqual(2400); + expect(rhumbGeometry.attributes.position.values.length).toEqual(2424); + + // Interpolate one segment but not the other + positions = Cartesian3.fromDegreesArray([ + 10, 75, + 20, 75, + 20.01, 75 + ]); + rhumbGroundPolylineGeometry = new GroundPolylineGeometry({ + positions : positions, + granularity : 2890.0, + arcType: ArcType.RHUMB + }); + geodesicGroundPolylineGeometry = new GroundPolylineGeometry({ + positions : positions, + granularity : 2890.0, + arcType: ArcType.GEODESIC + }); + + rhumbGeometry = GroundPolylineGeometry.createGeometry(rhumbGroundPolylineGeometry); + geodesicGeometry = GroundPolylineGeometry.createGeometry(geodesicGroundPolylineGeometry); + + expect(rhumbGeometry.indices.length).toEqual(3636 + 36); + expect(geodesicGeometry.indices.length).toEqual(3600 + 36); + expect(geodesicGeometry.attributes.position.values.length).toEqual(2400 + 24); + expect(rhumbGeometry.attributes.position.values.length).toEqual(2424 + 24); + }); + it('loops when there are enough positions and loop is specified', function() { var groundPolylineGeometry = new GroundPolylineGeometry({ positions : Cartesian3.fromDegreesArray([ @@ -564,6 +618,7 @@ defineSuite([ Cartesian3.pack(positions[2], packedInstance, packedInstance.length); packedInstance.push(polyline.granularity); packedInstance.push(polyline.loop ? 1.0 : 0.0); + packedInstance.push(polyline.arcType); Ellipsoid.pack(Ellipsoid.WGS84, packedInstance, packedInstance.length); diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js index 15601a327dc1..935473324227 100644 --- a/Specs/Core/PolygonGeometrySpec.js +++ b/Specs/Core/PolygonGeometrySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Core/PolygonGeometry', + 'Core/ArcType', 'Core/arrayFill', 'Core/BoundingSphere', 'Core/Cartesian3', @@ -12,6 +13,7 @@ defineSuite([ 'Specs/createPackableSpecs' ], function( PolygonGeometry, + ArcType, arrayFill, BoundingSphere, Cartesian3, @@ -59,6 +61,17 @@ defineSuite([ }))).toBeUndefined(); }); + it('throws if arcType is not valid', function() { + expect(function() { + return new PolygonGeometry({ + positions : [Cartesian3.fromDegrees(0, 0), + Cartesian3.fromDegrees(1, 0), + Cartesian3.fromDegrees(1, 1)], + arcType: ArcType.NONE + }); + }).toThrowDeveloperError(); + }); + it('createGeometry returns undefined due to duplicate positions', function() { var geometry = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ positions : Cartesian3.fromDegreesArray([ @@ -176,6 +189,82 @@ defineSuite([ expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6); }); + it('create geometry creates with rhumb lines', function() { + var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : Cartesian3.fromDegreesArray([ + -1.0, -1.0, + 1.0, -1.0, + 1.0, 1.0, + -1.0, 1.0 + ]), + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + })); + + expect(p.attributes.position.values.length).toEqual(13 * 3); // 8 around edge + 5 in the middle + expect(p.indices.length).toEqual(16 * 3); //4 squares * 4 triangles per square + }); + + it('create geometry throws if arcType is STRAIGHT', function() { + expect(function() { + PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ + vertexFormat: VertexFormat.POSITION_ONLY, + positions: Cartesian3.fromDegreesArray([ + -1.0, -1.0, + 1.0, -1.0, + 1.0, 1.0, + -1.0, 1.0 + ]), + granularity: CesiumMath.RADIANS_PER_DEGREE, + arcType: ArcType.NONE + })); + }).toThrowDeveloperError(); + }); + + it('create geometry creates with lines with different number of subdivisions for geodesic and rhumb', function() { + var positions = Cartesian3.fromDegreesArray([ + -30.0, -30.0, + 30.0, -30.0, + 30.0, 30.0, + -30.0, 30.0 + ]); + var geodesic = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : positions, + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.GEODESIC + })); + var rhumb = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : positions, + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + })); + + expect(geodesic.attributes.position.values.length).not.toEqual(rhumb.attributes.position.values.length); + expect(geodesic.indices.length).not.toEqual(rhumb.indices.length); + }); + + it('computes positions with per position heights for rhumb lines', function() { + var ellipsoid = Ellipsoid.WGS84; + var height = 100.0; + var positions = Cartesian3.fromDegreesArrayHeights([ + -1.0, -1.0, height, + 1.0, -1.0, 0.0, + 1.0, 1.0, 0.0, + -1.0, 1.0, 0.0 + ]); + var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ + positions : positions, + perPositionHeight : true, + arcType : ArcType.RHUMB + })); + + expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 0)).height).toEqualEpsilon(height, CesiumMath.EPSILON6); + expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6); + }); + it('computes all attributes', function() { var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({ vertexFormat : VertexFormat.ALL, @@ -233,6 +322,43 @@ defineSuite([ expect(p.indices.length).toEqual(10 * 3); }); + it('creates a polygon from hierarchy with rhumb lines', function() { + var hierarchy = { + positions : Cartesian3.fromDegreesArray([ + -124.0, 35.0, + -110.0, 35.0, + -110.0, 40.0, + -124.0, 40.0 + ]), + holes : [{ + positions : Cartesian3.fromDegreesArray([ + -122.0, 36.0, + -122.0, 39.0, + -112.0, 39.0, + -112.0, 36.0 + ]), + holes : [{ + positions : Cartesian3.fromDegreesArray([ + -120.0, 36.5, + -114.0, 36.5, + -114.0, 38.5, + -120.0, 38.5 + ]) + }] + }] + }; + + var p = PolygonGeometry.createGeometry(new PolygonGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE, + arcType : ArcType.RHUMB + })); + + expect(p.attributes.position.values.length).toEqual(12 * 3); // 4 points * 3 rectangles + expect(p.indices.length).toEqual(10 * 3); + }); + it('removes duplicates in polygon hierarchy', function() { var hierarchy = { positions : Cartesian3.fromDegreesArray([ @@ -1067,6 +1193,6 @@ defineSuite([ addPositions(packedInstance, holePositions1); packedInstance.push(Ellipsoid.WGS84.radii.x, Ellipsoid.WGS84.radii.y, Ellipsoid.WGS84.radii.z); packedInstance.push(1.0, 0.0, 0.0, 0.0, 0.0, 0.0); - packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 0.0, 1.0, 0, 1, 0, -1, 53); + packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 0.0, 1.0, 0, 1, 0, -1, ArcType.GEODESIC, 54); createPackableSpecs(PolygonGeometry, polygon, packedInstance); }); diff --git a/Specs/Core/PolygonOutlineGeometrySpec.js b/Specs/Core/PolygonOutlineGeometrySpec.js index 013329d16c0b..1c9965c5ae52 100644 --- a/Specs/Core/PolygonOutlineGeometrySpec.js +++ b/Specs/Core/PolygonOutlineGeometrySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Core/PolygonOutlineGeometry', + 'Core/ArcType', 'Core/arrayFill', 'Core/BoundingSphere', 'Core/Cartesian3', @@ -9,6 +10,7 @@ defineSuite([ 'Specs/createPackableSpecs' ], function( PolygonOutlineGeometry, + ArcType, arrayFill, BoundingSphere, Cartesian3, @@ -53,6 +55,17 @@ defineSuite([ }))).toBeUndefined(); }); + it('throws if arcType is not valid', function() { + expect(function() { + return new PolygonOutlineGeometry({ + positions : [Cartesian3.fromDegrees(0, 0), + Cartesian3.fromDegrees(1, 0), + Cartesian3.fromDegrees(1, 1)], + arcType: ArcType.NONE + }); + }).toThrowDeveloperError(); + }); + it('createGeometry returns undefined due to duplicate positions', function() { var geometry = PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ positions : Cartesian3.fromDegreesArray([ @@ -168,6 +181,78 @@ defineSuite([ expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6); }); + it('create geometry creates with rhumb lines', function() { + var p = PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ + positions : Cartesian3.fromDegreesArray([ + -1.0, -1.0, + 1.0, -1.0, + 1.0, 1.0, + -1.0, 1.0 + ]), + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + })); + + expect(p.attributes.position.values.length).toEqual(8 * 3); // 8 around edge + expect(p.indices.length).toEqual(16); // 4 squares + }); + + it('create geometry throws if arcType is STRAIGHT', function() { + expect(function() { + PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ + positions: Cartesian3.fromDegreesArray([ + -1.0, -1.0, + 1.0, -1.0, + 1.0, 1.0, + -1.0, 1.0 + ]), + granularity: CesiumMath.RADIANS_PER_DEGREE, + arcType: ArcType.NONE + })); + }).toThrowDeveloperError(); + }); + + it('create geometry creates with lines with different number of subdivisions for geodesic and rhumb', function() { + var positions = Cartesian3.fromDegreesArray([ + -80.0, 75.0, + 80.0, 75.0, + 80.0, 45.0, + -80.0, 45.0 + ]); + var geodesic = PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ + positions : positions, + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.GEODESIC + })); + var rhumb = PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ + positions : positions, + granularity : CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + })); + + expect(geodesic.attributes.position.values.length).not.toEqual(rhumb.attributes.position.values.length); + expect(geodesic.indices.length).not.toEqual(rhumb.indices.length); + }); + + it('computes positions with per position heights for rhumb lines', function() { + var ellipsoid = Ellipsoid.WGS84; + var height = 100.0; + var positions = Cartesian3.fromDegreesArrayHeights([ + -1.0, -1.0, height, + 1.0, -1.0, 0.0, + 1.0, 1.0, 0.0, + -1.0, 1.0, 0.0 + ]); + var p = PolygonOutlineGeometry.createGeometry(PolygonOutlineGeometry.fromPositions({ + positions : positions, + perPositionHeight : true, + arcType : ArcType.RHUMB + })); + + expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 0)).height).toEqualEpsilon(height, CesiumMath.EPSILON6); + expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6); + }); + it('uses correct value with extrudedHeight and perPositionHeight', function() { var ellipsoid = Ellipsoid.WGS84; var maxHeight = 100.0; @@ -524,6 +609,6 @@ defineSuite([ packedInstance.push(3.0, 0.0); addPositions(packedInstance, holePositions1); packedInstance.push(Ellipsoid.WGS84.radii.x, Ellipsoid.WGS84.radii.y, Ellipsoid.WGS84.radii.z); - packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 1.0, -1, 43); + packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 1.0, ArcType.GEODESIC, -1, 44); createPackableSpecs(PolygonOutlineGeometry, polygon, packedInstance); }); diff --git a/Specs/Core/PolygonPipelineSpec.js b/Specs/Core/PolygonPipelineSpec.js index 47e5bc747db4..cb343750e8ff 100644 --- a/Specs/Core/PolygonPipelineSpec.js +++ b/Specs/Core/PolygonPipelineSpec.js @@ -193,4 +193,79 @@ defineSuite([ expect(subdivision.indices[1]).toEqual(1); expect(subdivision.indices[2]).toEqual(2); }); + + /////////////////////////////////////////////////////////////////////// + + it('computeRhumbLineSubdivision throws without ellipsoid', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision throws without positions', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision throws without indices', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, []); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision throws with less than 3 indices', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, [], [1, 2]); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision throws without a multiple of 3 indices', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, [], [1, 2, 3, 4]); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision throws with negative granularity', function() { + expect(function() { + PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, [], [1, 2, 3], -1.0); + }).toThrowDeveloperError(); + }); + + it('computeRhumbLineSubdivision', function() { + var positions = Cartesian3.fromDegreesArray([ + 0, 0, + 1, 0, + 1, 1 + ]); + var indices = [0, 1, 2]; + var subdivision = PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, positions, indices, 2 * CesiumMath.RADIANS_PER_DEGREE); + + expect(subdivision.attributes.position.values[0]).toEqual(positions[0].x); + expect(subdivision.attributes.position.values[1]).toEqual(positions[0].y); + expect(subdivision.attributes.position.values[2]).toEqual(positions[0].y); + expect(subdivision.attributes.position.values[3]).toEqual(positions[1].x); + expect(subdivision.attributes.position.values[4]).toEqual(positions[1].y); + expect(subdivision.attributes.position.values[5]).toEqual(positions[1].z); + expect(subdivision.attributes.position.values[6]).toEqual(positions[2].x); + expect(subdivision.attributes.position.values[7]).toEqual(positions[2].y); + expect(subdivision.attributes.position.values[8]).toEqual(positions[2].z); + + expect(subdivision.indices[0]).toEqual(0); + expect(subdivision.indices[1]).toEqual(1); + expect(subdivision.indices[2]).toEqual(2); + }); + + it('computeRhumbLineSubdivision with subdivisions', function() { + var positions = Cartesian3.fromDegreesArray([ + 0, 0, + 1, 0, + 1, 1 + ]); + var indices = [0, 1, 2]; + var subdivision = PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, positions, indices, 0.5 * CesiumMath.RADIANS_PER_DEGREE); + + expect(subdivision.attributes.position.values.length).toEqual(30); // 10 vertices + expect(subdivision.indices.length).toEqual(27); // 9 triangles + }); }); diff --git a/Specs/Core/PolylineGeometrySpec.js b/Specs/Core/PolylineGeometrySpec.js index 8a2f9d43651d..4f85c0561f22 100644 --- a/Specs/Core/PolylineGeometrySpec.js +++ b/Specs/Core/PolylineGeometrySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Core/PolylineGeometry', + 'Core/ArcType', 'Core/Cartesian3', 'Core/Color', 'Core/Ellipsoid', @@ -7,6 +8,7 @@ defineSuite([ 'Specs/createPackableSpecs' ], function( PolylineGeometry, + ArcType, Cartesian3, Color, Ellipsoid, @@ -37,6 +39,24 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('constructor converts followSurface to arcType', function() { + var line = new PolylineGeometry({ + positions: [Cartesian3.ZERO, Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + followSurface: false + }); + + expect(line._followSurface).toBe(false); + expect(line._arcType).toBe(ArcType.NONE); + + line = new PolylineGeometry({ + positions: [Cartesian3.ZERO, Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + followSurface: true + }); + + expect(line._followSurface).toBe(true); + expect(line._arcType).toBe(ArcType.GEODESIC); + }); + it('constructor returns undefined when line width is negative', function() { var positions = [new Cartesian3(1.0, 0.0, 0.0), new Cartesian3(0.0, 1.0, 0.0), new Cartesian3(0.0, 0.0, 1.0)]; var line = PolylineGeometry.createGeometry(new PolylineGeometry({ @@ -75,6 +95,36 @@ defineSuite([ expect(line.indices.length).toEqual(positions.length * 6 - 6); }); + it('constructor computes all vertex attributes for rhumb lines', function() { + var positions = Cartesian3.fromDegreesArray([ + 30, 30, + 30, 60, + 60, 60 + ]); + var line = PolylineGeometry.createGeometry(new PolylineGeometry({ + positions : positions, + width : 10.0, + vertexFormat : VertexFormat.ALL, + granularity : Math.PI, + ellipsoid : Ellipsoid.UNIT_SPHERE, + arcType : ArcType.RHUMB + })); + + expect(line.attributes.position).toBeDefined(); + expect(line.attributes.prevPosition).toBeDefined(); + expect(line.attributes.nextPosition).toBeDefined(); + expect(line.attributes.expandAndWidth).toBeDefined(); + expect(line.attributes.st).toBeDefined(); + + var numVertices = (positions.length * 4 - 4); + expect(line.attributes.position.values.length).toEqual(numVertices * 3); + expect(line.attributes.prevPosition.values.length).toEqual(numVertices * 3); + expect(line.attributes.nextPosition.values.length).toEqual(numVertices * 3); + expect(line.attributes.expandAndWidth.values.length).toEqual(numVertices * 2); + expect(line.attributes.st.values.length).toEqual(numVertices * 2); + expect(line.indices.length).toEqual(positions.length * 6 - 6); + }); + it('constructor computes per segment colors', function() { var positions = [new Cartesian3(1.0, 0.0, 0.0), new Cartesian3(0.0, 1.0, 0.0), new Cartesian3(0.0, 0.0, 1.0)]; var colors = [new Color(1.0, 0.0, 0.0, 1.0), new Color(0.0, 1.0, 0.0, 1.0), new Color(0.0, 0.0, 1.0, 1.0)]; @@ -120,7 +170,7 @@ defineSuite([ positions : positions, width : 10.0, vertexFormat : VertexFormat.POSITION_ONLY, - followSurface : false + arcType : ArcType.NONE })); expect(geometry).not.toBeDefined(); }); @@ -131,7 +181,7 @@ defineSuite([ width : 10.0, colors : [Color.RED, Color.LIME, Color.BLUE], colorsPerVertex : true, - followSurface : false, + arcType : ArcType.NONE, granularity : 11, vertexFormat : VertexFormat.POSITION_ONLY, ellipsoid : new Ellipsoid(12, 13, 14) @@ -143,11 +193,35 @@ defineSuite([ positions : positions, width : 10.0, colorsPerVertex : false, - followSurface : false, + arcType : ArcType.NONE, granularity : 11, vertexFormat : VertexFormat.POSITION_ONLY, ellipsoid : new Ellipsoid(12, 13, 14) }); packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 1, 0, 0, 0, 0, 0, 10, 0, 0, 11]; - createPackableSpecs(PolylineGeometry, line, packedInstance); + createPackableSpecs(PolylineGeometry, line, packedInstance, 'straight line'); + + line = new PolylineGeometry({ + positions : positions, + width : 10.0, + colorsPerVertex : false, + arcType : ArcType.GEODESIC, + granularity : 11, + vertexFormat : VertexFormat.POSITION_ONLY, + ellipsoid : new Ellipsoid(12, 13, 14) + }); + packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 1, 0, 0, 0, 0, 0, 10, 0, 1, 11]; + createPackableSpecs(PolylineGeometry, line, packedInstance, 'geodesic line'); + + line = new PolylineGeometry({ + positions : positions, + width : 10.0, + colorsPerVertex : false, + arcType : ArcType.RHUMB, + granularity : 11, + vertexFormat : VertexFormat.POSITION_ONLY, + ellipsoid : new Ellipsoid(12, 13, 14) + }); + packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 1, 0, 0, 0, 0, 0, 10, 0, 2, 11]; + createPackableSpecs(PolylineGeometry, line, packedInstance, 'rhumb line'); }); diff --git a/Specs/Core/PolylinePipelineSpec.js b/Specs/Core/PolylinePipelineSpec.js index e0024afbfba0..6f62ec382a52 100644 --- a/Specs/Core/PolylinePipelineSpec.js +++ b/Specs/Core/PolylinePipelineSpec.js @@ -108,4 +108,61 @@ defineSuite([ expect(newPositions).toEqual([0,0,1]); }); + it('generateRhumbArc throws without positions', function() { + expect(function() { + PolylinePipeline.generateRhumbArc(); + }).toThrowDeveloperError(); + }); + + it('generateRhumbArc accepts a height array for single value', function() { + var positions = [Cartesian3.fromDegrees(0, 0)]; + var height = [30]; + + var newPositions = PolylinePipeline.generateRhumbArc({ + positions: positions, + height: height + }); + + expect(newPositions.length).toEqual(3); + expect(Cartesian3.fromArray(newPositions, 0)).toEqualEpsilon(Cartesian3.fromDegrees(0, 0, 30), CesiumMath.EPSILON6); + }); + + it('generateRhumbArc subdivides in half', function() { + var p1 = Cartesian3.fromDegrees(0, 30); + var p2 = Cartesian3.fromDegrees(90, 30); + var p3 = Cartesian3.fromDegrees(45, 30); + var positions = [p1, p2]; + + var newPositions = PolylinePipeline.generateRhumbArc({ + positions: positions, + granularity: CesiumMath.PI_OVER_FOUR, + ellipsoid: Ellipsoid.WGS84 + }); + + expect(newPositions.length).toEqual(3*3); + var p1n = Cartesian3.fromArray(newPositions, 0); + var p3n = Cartesian3.fromArray(newPositions, 3); + var p2n = Cartesian3.fromArray(newPositions, 6); + expect(Cartesian3.equalsEpsilon(p1, p1n, CesiumMath.EPSILON4)).toEqual(true); + expect(Cartesian3.equalsEpsilon(p2, p2n, CesiumMath.EPSILON4)).toEqual(true); + expect(Cartesian3.equalsEpsilon(p3, p3n, CesiumMath.EPSILON4)).toEqual(true); + }); + + it('generateRhumbArc works with empty array', function() { + var newPositions = PolylinePipeline.generateRhumbArc({ + positions: [] + }); + + expect(newPositions.length).toEqual(0); + }); + + it('generateRhumbArc works one position', function() { + var newPositions = PolylinePipeline.generateRhumbArc({ + positions: [Cartesian3.UNIT_Z], + ellipsoid: Ellipsoid.UNIT_SPHERE + }); + + expect(newPositions.length).toEqual(3); + expect(newPositions).toEqual([0,0,1]); + }); }); diff --git a/Specs/Core/SimplePolylineGeometrySpec.js b/Specs/Core/SimplePolylineGeometrySpec.js index dee7e598eff7..193136e948e6 100644 --- a/Specs/Core/SimplePolylineGeometrySpec.js +++ b/Specs/Core/SimplePolylineGeometrySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Core/SimplePolylineGeometry', + 'Core/ArcType', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', @@ -9,6 +10,7 @@ defineSuite([ 'Specs/createPackableSpecs' ], function( SimplePolylineGeometry, + ArcType, BoundingSphere, Cartesian3, Color, @@ -55,6 +57,28 @@ defineSuite([ expect(line.boundingSphere).toEqual(BoundingSphere.fromPoints(positions)); }); + it('constructor computes all vertex attributes for rhumb lines', function() { + var positions = Cartesian3.fromDegreesArray([ + 30, 30, + 30, 60, + 60, 60 + ]); + var line = SimplePolylineGeometry.createGeometry(new SimplePolylineGeometry({ + positions: positions, + granularity: Math.PI, + ellipsoid: Ellipsoid.UNIT_SPHERE, + arcType: ArcType.RHUMB + })); + + var cartesian3Array = []; + Cartesian3.packArray(positions, cartesian3Array); + + expect(line.attributes.position.values).toEqualEpsilon(cartesian3Array, CesiumMath.EPSILON8); + expect(line.indices).toEqual([0, 1, 1, 2]); + expect(line.primitiveType).toEqual(PrimitiveType.LINES); + expect(line.boundingSphere).toEqual(BoundingSphere.fromPoints(positions)); + }); + it('constructor computes per segment colors', function() { var positions = [new Cartesian3(1.0, 0.0, 0.0), new Cartesian3(0.0, 1.0, 0.0), new Cartesian3(0.0, 0.0, 1.0)]; var colors = [new Color(1.0, 0.0, 0.0, 1.0), new Color(0.0, 1.0, 0.0, 1.0), new Color(0.0, 0.0, 1.0, 1.0)]; @@ -153,4 +177,37 @@ defineSuite([ }); packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 0, 0, 11]; createPackableSpecs(SimplePolylineGeometry, line, packedInstance); + + line = new SimplePolylineGeometry({ + positions : positions, + width : 10.0, + colorsPerVertex : false, + arcType : ArcType.GEODESIC, + granularity : 11, + ellipsoid : new Ellipsoid(12, 13, 14) + }); + packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 0, 1, 11]; + createPackableSpecs(SimplePolylineGeometry, line, packedInstance, 'geodesic line'); + + line = new SimplePolylineGeometry({ + positions : positions, + width : 10.0, + colorsPerVertex : false, + arcType : ArcType.RHUMB, + granularity : 11, + ellipsoid : new Ellipsoid(12, 13, 14) + }); + packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 0, 2, 11]; + createPackableSpecs(SimplePolylineGeometry, line, packedInstance, 'rhumb line'); + + line = new SimplePolylineGeometry({ + positions : positions, + width : 10.0, + colorsPerVertex : false, + arcType : ArcType.NONE, + granularity : 11, + ellipsoid : new Ellipsoid(12, 13, 14) + }); + packedInstance = [3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 12, 13, 14, 0, 0, 11]; + createPackableSpecs(SimplePolylineGeometry, line, packedInstance, 'straight line'); }); diff --git a/Specs/DataSources/GeoJsonDataSourceSpec.js b/Specs/DataSources/GeoJsonDataSourceSpec.js index 135e6a4d0279..4ae059cbdc05 100644 --- a/Specs/DataSources/GeoJsonDataSourceSpec.js +++ b/Specs/DataSources/GeoJsonDataSourceSpec.js @@ -761,7 +761,6 @@ defineSuite([ expect(entity.polygon.outlineWidth.getValue(time)).toEqual(GeoJsonDataSource.strokeWidth); expect(entity.polygon.outlineColor.getValue(time)).toEqual(GeoJsonDataSource.stroke); expect(entity.polygon.height).toBeUndefined(); - }); }); diff --git a/Specs/DataSources/PolygonGeometryUpdaterSpec.js b/Specs/DataSources/PolygonGeometryUpdaterSpec.js index 52814aaf77a2..6b14ca626485 100644 --- a/Specs/DataSources/PolygonGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolygonGeometryUpdaterSpec.js @@ -1,6 +1,7 @@ defineSuite([ 'DataSources/PolygonGeometryUpdater', 'Core/ApproximateTerrainHeights', + 'Core/ArcType', 'Core/Cartesian3', 'Core/Color', 'Core/Ellipsoid', @@ -30,6 +31,7 @@ defineSuite([ ], function( PolygonGeometryUpdater, ApproximateTerrainHeights, + ArcType, Cartesian3, Color, Ellipsoid, @@ -227,6 +229,16 @@ defineSuite([ expect(updater.isDynamic).toBe(true); }); + it('A time-varying arcType causes geometry to be dynamic', function() { + var entity = createBasicPolygon(); + var updater = new PolygonGeometryUpdater(entity, scene); + entity.polygon.arcType = new SampledProperty(Number); + entity.polygon.arcType.addSample(time, 1); + updater._onEntityPropertyChanged(entity, 'polygon'); + + expect(updater.isDynamic).toBe(true); + }); + it('Creates geometry with expected properties', function() { var options = { height : 431, @@ -234,8 +246,9 @@ defineSuite([ granularity : 0.97, stRotation : 12, perPositionHeight : false, - closeTop: true, - closeBottom: false + closeTop : true, + closeBottom : false, + arcType : ArcType.GEODESIC }; var entity = createBasicPolygon(); @@ -249,6 +262,7 @@ defineSuite([ polygon.height = new ConstantProperty(options.height); polygon.extrudedHeight = new ConstantProperty(options.extrudedHeight); polygon.granularity = new ConstantProperty(options.granularity); + polygon.arcType = new ConstantProperty(options.arcType); var updater = new PolygonGeometryUpdater(entity, scene); @@ -263,6 +277,7 @@ defineSuite([ expect(geometry._extrudedHeight).toEqual(options.extrudedHeight); expect(geometry._closeTop).toEqual(options.closeTop); expect(geometry._closeBottom).toEqual(options.closeBottom); + expect(geometry._arcType).toEqual(options.arcType); expect(geometry._offsetAttribute).toBeUndefined(); instance = updater.createOutlineGeometryInstance(time); @@ -358,6 +373,7 @@ defineSuite([ polygon.stRotation = createDynamicProperty(1); polygon.closeTop = createDynamicProperty(false); polygon.closeBottom = createDynamicProperty(false); + polygon.arcType = createDynamicProperty(ArcType.RHUMB); var entity = new Entity(); entity.polygon = polygon; @@ -376,6 +392,7 @@ defineSuite([ expect(options.stRotation).toEqual(polygon.stRotation.getValue()); expect(options.closeTop).toEqual(polygon.closeTop.getValue()); expect(options.closeBottom).toEqual(polygon.closeBottom.getValue()); + expect(options.arcType).toEqual(polygon.arcType.getValue()); expect(options.offsetAttribute).toBeUndefined(); }); diff --git a/Specs/DataSources/PolygonGraphicsSpec.js b/Specs/DataSources/PolygonGraphicsSpec.js index a313822e45b6..615556dbdfb7 100644 --- a/Specs/DataSources/PolygonGraphicsSpec.js +++ b/Specs/DataSources/PolygonGraphicsSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/PolygonGraphics', + 'Core/ArcType', 'Core/Color', 'Core/DistanceDisplayCondition', 'Core/PolygonHierarchy', @@ -11,6 +12,7 @@ defineSuite([ 'Specs/testMaterialDefinitionChanged' ], function( PolygonGraphics, + ArcType, Color, DistanceDisplayCondition, PolygonHierarchy, @@ -41,6 +43,7 @@ defineSuite([ shadows : ShadowMode.DISABLED, distanceDisplayCondition : new DistanceDisplayCondition(), classificationType : ClassificationType.TERRAIN, + arcType: ArcType.GEODESIC, zIndex: 22 }; @@ -62,6 +65,7 @@ defineSuite([ expect(polygon.shadows).toBeInstanceOf(ConstantProperty); expect(polygon.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(polygon.classificationType).toBeInstanceOf(ConstantProperty); + expect(polygon.arcType).toBeInstanceOf(ConstantProperty); expect(polygon.zIndex).toBeInstanceOf(ConstantProperty); expect(polygon.material.color.getValue()).toEqual(options.material); @@ -81,6 +85,7 @@ defineSuite([ expect(polygon.shadows.getValue()).toEqual(options.shadows); expect(polygon.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(polygon.classificationType.getValue()).toEqual(options.classificationType); + expect(polygon.arcType.getValue()).toEqual(options.arcType); expect(polygon.zIndex.getValue()).toEqual(22); }); @@ -103,6 +108,7 @@ defineSuite([ source.shadows = new ConstantProperty(ShadowMode.ENABLED); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); source.classificationType = new ConstantProperty(ClassificationType.TERRAIN); + source.arcType = new ConstantProperty(ArcType.RHUMB); source.zIndex = new ConstantProperty(30); var target = new PolygonGraphics(); @@ -125,6 +131,7 @@ defineSuite([ expect(target.shadows).toBe(source.shadows); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.classificationType).toBe(source.classificationType); + expect(target.arcType).toBe(source.arcType); expect(target.zIndex).toBe(source.zIndex); }); @@ -148,6 +155,7 @@ defineSuite([ var shadows = new ConstantProperty(); var distanceDisplayCondition = new ConstantProperty(); var classificationType = new ConstantProperty(); + var arcType = new ConstantProperty(); var zIndex = new ConstantProperty(); var target = new PolygonGraphics(); @@ -168,6 +176,7 @@ defineSuite([ target.shadows = shadows; target.distanceDisplayCondition = distanceDisplayCondition; target.classificationType = classificationType; + target.arcType = arcType; target.zIndex = zIndex; target.merge(source); @@ -189,6 +198,7 @@ defineSuite([ expect(target.shadows).toBe(shadows); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.classificationType).toBe(classificationType); + expect(target.arcType).toBe(arcType); expect(target.zIndex).toBe(zIndex); }); @@ -211,6 +221,7 @@ defineSuite([ source.shadows = new ConstantProperty(); source.distanceDisplayCondition = new ConstantProperty(); source.classificationType = new ConstantProperty(); + source.arcType = new ConstantProperty(); source.zIndex = new ConstantProperty(); var result = source.clone(); @@ -231,6 +242,7 @@ defineSuite([ expect(result.shadows).toBe(source.shadows); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.classificationType).toBe(source.classificationType); + expect(result.arcType).toBe(source.arcType); expect(result.zIndex).toBe(source.zIndex); }); @@ -260,6 +272,7 @@ defineSuite([ testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); testDefinitionChanged(property, 'classificationType', ClassificationType.TERRAIN, ClassificationType.BOTH); + testDefinitionChanged(property, 'arcType', ArcType.GEODESIC, ArcType.RHUMB); testDefinitionChanged(property, 'zIndex', 54, 3); }); }); diff --git a/Specs/DataSources/PolylineGeometryUpdaterSpec.js b/Specs/DataSources/PolylineGeometryUpdaterSpec.js index 67f2e43daff3..9d3ec2ca8179 100644 --- a/Specs/DataSources/PolylineGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolylineGeometryUpdaterSpec.js @@ -1,10 +1,12 @@ defineSuite([ 'DataSources/PolylineGeometryUpdater', 'Core/ApproximateTerrainHeights', + 'Core/ArcType', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', + 'Core/defined', 'Core/DistanceDisplayCondition', 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/GroundPolylineGeometry', @@ -33,10 +35,12 @@ defineSuite([ ], function( PolylineGeometryUpdater, ApproximateTerrainHeights, + ArcType, BoundingSphere, Cartesian3, Color, ColorGeometryInstanceAttribute, + defined, DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, GroundPolylineGeometry, @@ -115,6 +119,7 @@ defineSuite([ expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.clampToGround).toBe(false); + expect(updater.arcType).toBe(undefined); expect(updater.zIndex).toBe(0); expect(updater.isOutlineVisible(time)).toBe(false); @@ -159,6 +164,7 @@ defineSuite([ expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); expect(updater.clampToGround).toBe(false); + expect(updater.arcType).toBe(undefined); expect(updater.zIndex).toEqual(new ConstantProperty(0)); }); @@ -229,6 +235,14 @@ defineSuite([ expect(updater.isDynamic).toBe(true); }); + it('A time-varying arcType causes geometry to be dynamic', function() { + var entity = createBasicPolyline(); + var updater = new PolylineGeometryUpdater(entity, scene); + entity.polyline.arcType = new SampledProperty(Number); + entity.polyline.arcType.addSample(time, 1); + expect(updater.isDynamic).toBe(true); + }); + it('A time-varying zIndex causes geometry to be dynamic', function() { var entity = createBasicPolyline(); var updater = new PolylineGeometryUpdater(entity, scene); @@ -251,6 +265,7 @@ defineSuite([ polyline.granularity = new ConstantProperty(options.granularity); polyline.distanceDisplayCondition = options.distanceDisplayCondition; polyline.clampToGround = new ConstantProperty(clampToGround); + polyline.arcType = new ConstantProperty(options.arcType); var updater = new PolylineGeometryUpdater(entity, scene); @@ -265,7 +280,12 @@ defineSuite([ expect(geometry.width).toEqual(options.width); } else { expect(geometry._width).toEqual(options.width); - expect(geometry._followSurface).toEqual(options.followSurface); + if (defined(options.followSurface)) { + expect(geometry._followSurface).toEqual(options.followSurface); + } + if (defined(options.arcType)) { + expect(geometry._arcType).toEqual(options.arcType); + } expect(geometry._granularity).toEqual(options.granularity); if (options.depthFailMaterial && options.depthFailMaterial instanceof ColorMaterialProperty) { @@ -293,7 +313,8 @@ defineSuite([ width : 3, followSurface : false, clampToGround : false, - granularity : 1.0 + granularity : 1.0, + arcType : ArcType.NONE }); if (!Entity.supportsPolylinesOnTerrain(scene)) { @@ -307,7 +328,8 @@ defineSuite([ width : 3, followSurface : false, clampToGround : true, - granularity : 1.0 + granularity : 1.0, + arcType : ArcType.GEODESIC }); }); @@ -317,9 +339,10 @@ defineSuite([ material : new ColorMaterialProperty(Color.RED), depthFailMaterial : new ColorMaterialProperty(Color.BLUE), width : 3, - followSurface : false, + followSurface : true, clampToGround : false, - granularity : 1.0 + granularity : 1.0, + arcType : ArcType.GEODESIC }); }); @@ -329,9 +352,9 @@ defineSuite([ material : new ColorMaterialProperty(Color.RED), depthFailMaterial : new GridMaterialProperty(), width : 3, - followSurface : false, clampToGround : false, - granularity : 1.0 + granularity : 1.0, + arcType : ArcType.RHUMB }); }); @@ -342,7 +365,8 @@ defineSuite([ width : 4, followSurface : true, clampToGround : false, - granularity : 0.5 + granularity : 0.5, + arcType: ArcType.GEODESIC }); if (!Entity.supportsPolylinesOnTerrain(scene)) { @@ -356,7 +380,8 @@ defineSuite([ width : 4, followSurface : true, clampToGround : true, - granularity : 0.5 + granularity : 0.5, + arcType: ArcType.GEODESIC }); }); @@ -477,6 +502,7 @@ defineSuite([ polyline.material = new ColorMaterialProperty(Color.RED); polyline.followSurface = new ConstantProperty(false); polyline.granularity = new ConstantProperty(0.001); + polyline.ArcType = new ConstantProperty(ArcType.NONE); var updater = new PolylineGeometryUpdater(entity, scene); @@ -499,6 +525,7 @@ defineSuite([ expect(primitive.positions.length).toEqual(2); polyline.followSurface = new ConstantProperty(true); + polyline.arcType = new ConstantProperty(ArcType.GEODESIC); dynamicUpdater.update(time3); expect(primitive.width).toEqual(3); @@ -561,6 +588,59 @@ defineSuite([ updater.destroy(); }); + it('arcType can be dynamic', function() { + var entity = new Entity(); + var polyline = new PolylineGraphics(); + entity.polyline = polyline; + + var time = new JulianDate(0, 0); + + var arcTypeVar = ArcType.GEODESIC; + var arcType = new CallbackProperty(function() { + return arcTypeVar; + }, false); + + polyline.show = new ConstantProperty(true); + polyline.width = new ConstantProperty(1.0); + polyline.positions = new ConstantProperty([Cartesian3.fromDegrees(0, 0, 0), Cartesian3.fromDegrees(0, 1, 0)]); + polyline.material = new ColorMaterialProperty(Color.RED); + polyline.followSurface = new ConstantProperty(true); + polyline.granularity = new ConstantProperty(0.001); + polyline.clampToGround = new ConstantProperty(false); + polyline.arcType = arcType; + + var updater = new PolylineGeometryUpdater(entity, scene); + + var primitives = scene.primitives; + expect(primitives.length).toBe(0); + + var dynamicUpdater = updater.createDynamicUpdater(primitives, scene.groundPrimitives); + expect(dynamicUpdater.isDestroyed()).toBe(false); + expect(primitives.length).toBe(0); + + dynamicUpdater.update(time); + + expect(primitives.length).toBe(1); + var polylineCollection = primitives.get(0); + var polylineObject = polylineCollection.get(0); + + expect(polylineObject.show).toEqual(true); + + var geodesicPolylinePositionsLength = polylineObject.positions.length; + + arcTypeVar = ArcType.NONE; + dynamicUpdater.update(time); + + expect(polylineObject.positions.length).not.toEqual(geodesicPolylinePositionsLength); + + dynamicUpdater.destroy(); + + expect(scene.primitives.length).toBe(0); + expect(primitives.length).toBe(0); + + updater.destroy(); + }); + it('geometryChanged event is raised when expected', function() { var entity = createBasicPolyline(); var updater = new PolylineGeometryUpdater(entity, scene); @@ -767,6 +847,32 @@ defineSuite([ }); it('followSurface true with undefined globe does not call generateCartesianArc', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + var entity = createBasicPolyline(); + entity.polyline.width = createDynamicProperty(1); + scene.globe = undefined; + var updater = new PolylineGeometryUpdater(entity, scene); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); + spyOn(PolylinePipeline, 'generateCartesianArc').and.callThrough(); + dynamicUpdater.update(time); + expect(PolylinePipeline.generateCartesianArc).not.toHaveBeenCalled(); + dynamicUpdater.destroy(); + updater.destroy(); + + expect(scene.primitives.length).toBe(0); + expect(scene.groundPrimitives.length).toBe(0); + + scene.globe = new Globe(); + }); + + it('arcType GEODESIC with undefined globe does not call generateCartesianArc', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + var entity = createBasicPolyline(); entity.polyline.width = createDynamicProperty(1); scene.globe = undefined; diff --git a/Specs/DataSources/PolylineGraphicsSpec.js b/Specs/DataSources/PolylineGraphicsSpec.js index b44bb4244dfe..993af95a53ef 100644 --- a/Specs/DataSources/PolylineGraphicsSpec.js +++ b/Specs/DataSources/PolylineGraphicsSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/PolylineGraphics', + 'Core/ArcType', 'Core/Color', 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', @@ -10,6 +11,7 @@ defineSuite([ 'Specs/testMaterialDefinitionChanged' ], function( PolylineGraphics, + ArcType, Color, DistanceDisplayCondition, ColorMaterialProperty, @@ -33,6 +35,7 @@ defineSuite([ shadows : ShadowMode.DISABLED, distanceDisplayCondition : new DistanceDisplayCondition(), classificationType : ClassificationType.TERRAIN, + arcType: ArcType.GEODESIC, zIndex : 0 }; @@ -48,6 +51,7 @@ defineSuite([ expect(polyline.shadows).toBeInstanceOf(ConstantProperty); expect(polyline.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(polyline.classificationType).toBeInstanceOf(ConstantProperty); + expect(polyline.arcType).toBeInstanceOf(ConstantProperty); expect(polyline.zIndex).toBeInstanceOf(ConstantProperty); expect(polyline.material.color.getValue()).toEqual(options.material); @@ -61,6 +65,7 @@ defineSuite([ expect(polyline.shadows.getValue()).toEqual(options.shadows); expect(polyline.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(polyline.classificationType.getValue()).toEqual(options.classificationType); + expect(polyline.arcType.getValue()).toEqual(options.arcType); expect(polyline.zIndex.getValue()).toEqual(options.zIndex); }); @@ -77,6 +82,7 @@ defineSuite([ source.shadows = new ConstantProperty(ShadowMode.ENABLED); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); source.classificationType = new ConstantProperty(ClassificationType.TERRAIN); + source.arcType = new ConstantProperty(ArcType.GEODESIC); source.zIndex = new ConstantProperty(); var target = new PolylineGraphics(); @@ -92,6 +98,7 @@ defineSuite([ expect(target.shadows).toBe(source.shadows); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.classificationType).toBe(source.classificationType); + expect(target.arcType).toBe(source.arcType); expect(target.zIndex).toBe(source.zIndex); }); @@ -108,6 +115,7 @@ defineSuite([ source.shadows = new ConstantProperty(); source.distanceDisplayCondition = new ConstantProperty(); source.classificationType = new ConstantProperty(); + source.arcType = new ConstantProperty(); source.zIndex = new ConstantProperty(); var color = new ColorMaterialProperty(); @@ -121,6 +129,7 @@ defineSuite([ var shadows = new ConstantProperty(); var distanceDisplayCondition = new ConstantProperty(); var classificationType = new ConstantProperty(); + var arcType = new ConstantProperty(); var zIndex = new ConstantProperty(); var target = new PolylineGraphics(); @@ -135,6 +144,7 @@ defineSuite([ target.shadows = shadows; target.distanceDisplayCondition = distanceDisplayCondition; target.classificationType = classificationType; + target.arcType = arcType; target.zIndex = zIndex; target.merge(source); @@ -149,6 +159,7 @@ defineSuite([ expect(target.shadows).toBe(shadows); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.classificationType).toBe(classificationType); + expect(target.arcType).toBe(arcType); expect(target.zIndex).toBe(zIndex); }); @@ -165,6 +176,7 @@ defineSuite([ source.shadows = new ConstantProperty(); source.distanceDisplayCondition = new ConstantProperty(); source.classificationType = new ConstantProperty(); + source.arcType = new ConstantProperty(); source.zIndex = new ConstantProperty(); var result = source.clone(); @@ -179,6 +191,7 @@ defineSuite([ expect(result.shadows).toBe(source.shadows); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.classificationType).toBe(source.classificationType); + expect(result.arcType).toBe(source.arcType); expect(result.zIndex).toBe(source.zIndex); }); @@ -202,6 +215,7 @@ defineSuite([ testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 20.0)); testDefinitionChanged(property, 'classificationType', ClassificationType.TERRAIN); + testDefinitionChanged(property, 'arcType', ArcType.GEODESIC, ArcType.RHUMB); testDefinitionChanged(property, 'zIndex', 20, 5); }); });