diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 473e2aca2e7f..b92c90c373a6 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -141,4 +141,4 @@ define([ } } } -}); +}); \ No newline at end of file diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 3636191074c2..ce5a68761208 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -28,13 +28,10 @@ "use strict"; Cesium.Math.setRandomNumberSeed(1234); - var viewer = new Cesium.Viewer('cesiumContainer'); - var scene = viewer.scene; var primitives = scene.getPrimitives(); var ellipsoid = viewer.centralBody.getEllipsoid(); - var solidWhite = Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.WHITE); // Combine instances for an extent, polygon, ellipse, and circle into a single primitive. @@ -44,7 +41,7 @@ geometry : new Cesium.ExtentGeometry({ extent : extent, vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT, - stRotation: Cesium.Math.toRadians(45) + stRotation : Cesium.Math.toRadians(45) }) }); var extentOutlineInstance = new Cesium.GeometryInstance({ @@ -63,7 +60,7 @@ Cesium.Cartographic.fromDegrees(-97.0, 21.0), Cesium.Cartographic.fromDegrees(-97.0, 25.0) ]); - + var polygonInstance = new Cesium.GeometryInstance({ geometry : Cesium.PolygonGeometry.fromPositions({ positions : positions, @@ -89,7 +86,7 @@ semiMinorAxis : semiMinorAxis, semiMajorAxis : semiMajorAxis, rotation : rotation, - stRotation: Cesium.Math.toRadians(22), + stRotation : Cesium.Math.toRadians(22), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); @@ -111,7 +108,7 @@ geometry : new Cesium.CircleGeometry({ center : center, radius : radius, - stRotation: Cesium.Math.toRadians(90), + stRotation : Cesium.Math.toRadians(90), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); @@ -182,7 +179,7 @@ semiMajorAxis : semiMajorAxis, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, height : height, - rotation: rotation, + rotation : rotation, extrudedHeight : extrudedHeight }), attributes : { @@ -195,7 +192,7 @@ semiMinorAxis : semiMinorAxis, semiMajorAxis : semiMajorAxis, height : height, - rotation: rotation, + rotation : rotation, extrudedHeight : extrudedHeight }), attributes : { @@ -219,7 +216,7 @@ polygonHierarchy : polygonHierarchy, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, extrudedHeight : extrudedHeight, - height: height + height : height }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) @@ -229,7 +226,7 @@ geometry : new Cesium.PolygonOutlineGeometry({ polygonHierarchy : polygonHierarchy, extrudedHeight : extrudedHeight, - height: height + height : height }), attributes : { color : solidWhite @@ -241,7 +238,8 @@ var topRadius = 150000.0; var bottomRadius = 150000.0; var modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 45.0))), + new Cesium.Cartesian3(0.0, 0.0, 100000.0)); var cylinderInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CylinderGeometry({ length : length, @@ -328,11 +326,11 @@ for (i = 0; i < 5; ++i) { height = 100000.0 + (200000.0 * i); boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-106.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-106.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-98.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-98.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); instances.push(new Cesium.GeometryInstance({ geometry : boxGeometry, @@ -407,25 +405,24 @@ Cesium.Cartographic.fromDegrees(-75.0, 50.0) ]); var maximumHeights = [500000, 1000000, 500000]; - var minimumHeights = [0, 500000, 0]; + var minimumHeights = [0, 500000, 0]; var wallInstance = new Cesium.GeometryInstance({ geometry : new Cesium.WallGeometry({ positions : positions, - maximumHeights: maximumHeights, - minimumHeights: minimumHeights - + maximumHeights : maximumHeights, + minimumHeights : minimumHeights }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.7})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); var wallOutlineInstance = new Cesium.GeometryInstance({ geometry : new Cesium.WallOutlineGeometry({ positions : positions, - maximumHeights: maximumHeights, - minimumHeights: minimumHeights + maximumHeights : maximumHeights, + minimumHeights : minimumHeights }), attributes : { color : new Cesium.ColorGeometryInstanceAttribute(0.7, 0.7, 0.7, 1.0) @@ -594,7 +591,7 @@ topRadius = 0.0; bottomRadius = 200000.0; modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 200000.0)); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 200000.0)); cylinderInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CylinderGeometry({ length : length, @@ -635,7 +632,7 @@ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.5})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.5})) } })); @@ -646,7 +643,7 @@ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.5})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.5})) } })); } @@ -681,12 +678,11 @@ for (i = 0; i < 5; ++i) { height = 100000.0 + (200000.0 * i); boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-108.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-108.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-104.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-104.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-100.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); - + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-100.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); instances.push(new Cesium.GeometryInstance({ geometry : boxGeometry, modelMatrix : boxModelMatrix, @@ -719,7 +715,7 @@ closed : true }) })); - + positions = []; var colors = []; for (i = 0; i < 40; ++i) { @@ -746,7 +742,7 @@ } }) })); - + // create a polyline with a material positions = []; for (i = 0; i < 40; ++i) { @@ -765,7 +761,7 @@ material : Cesium.Material.fromType(Cesium.Material.PolylineGlowType) }) })); - + // create a polyline with per segment colors positions = []; colors = []; @@ -785,7 +781,7 @@ }), appearance : new Cesium.PolylineColorAppearance() })); - + // create a polyline with per vertex colors positions = []; colors = []; @@ -831,7 +827,7 @@ faceForward : true }) })); - + positions = ellipsoid.cartographicArrayToCartesianArray([ Cesium.Cartographic.fromDegrees(-120.0, 45.0), Cesium.Cartographic.fromDegrees(-125.0, 50.0), @@ -840,7 +836,7 @@ var width = 100000; var corridor = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT @@ -849,43 +845,43 @@ color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) } }); - + var extrudedCorridor = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 300000, - extrudedHeight: 400000 + height : 300000, + extrudedHeight : 400000 }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); - + var corridorOutline = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorOutlineGeometry( { - positions: positions, - width: width, - height: 700000 + geometry : new Cesium.CorridorOutlineGeometry({ + positions : positions, + width : width, + height : 700000 }), - attributes: { + attributes : { color : solidWhite } }); - + var corridorFill = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 700000 + height : 700000 }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); - + primitives.add(new Cesium.Primitive({ geometryInstances : [corridor, extrudedCorridor, corridorFill], appearance : new Cesium.PerInstanceColorAppearance({ @@ -894,7 +890,7 @@ faceForward : true }) })); - + primitives.add(new Cesium.Primitive({ geometryInstances : corridorOutline, appearance : new Cesium.PerInstanceColorAppearance({ @@ -908,6 +904,94 @@ }) })); + function starPositions(arms, rOuter, rInner) { + var angle = Math.PI / arms; + var pos = []; + for ( var i = 0; i < 2 * arms; i++) { + var r = (i % 2) === 0 ? rOuter : rInner; + var p = new Cesium.Cartesian2(Math.cos(i * angle) * r, Math.sin(i * angle) * r); + pos.push(p); + } + return pos; + } + + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-102.0, 15.0, 100000.0), + Cesium.Cartographic.fromDegrees(-105.0, 20.0, 200000.0), + Cesium.Cartographic.fromDegrees(-110.0, 20.0, 100000.0) + ]); + var polylineVolumeFill = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : positions, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : starPositions(7, 30000.0, 20000.0) + }), + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); + + var polylineVolumeOutline = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeOutlineGeometry({ + polylinePositions : positions, + shapePositions : starPositions(7, 30000.0, 20000.0) + }), + attributes : { + color : solidWhite + } + }); + + var polylineVolume = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-102.0, 15.0), + Cesium.Cartographic.fromDegrees(-105.0, 20.0), + Cesium.Cartographic.fromDegrees(-110.0, 20.0) + ]), + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : starPositions(7, 30000, 20000) + }), + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); + + var tubeGeometry = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-104.0, 13.0), + Cesium.Cartographic.fromDegrees(-107.0, 18.0), + Cesium.Cartographic.fromDegrees(-112.0, 18.0) + ]), + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : Cesium.Shapes.compute2DCircle(40000.0) + }), + attributes : { + color : solidWhite + } + }); + + primitives.add(new Cesium.Primitive({ + geometryInstances : [tubeGeometry, polylineVolume, polylineVolumeFill], + appearance : new Cesium.PerInstanceColorAppearance({ + translucent : false, + closed : true + }) + })); + + primitives.add(new Cesium.Primitive({ + geometryInstances : polylineVolumeOutline, + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : 1.0 + } + }) + })); + Sandcastle.finishedLoading(); }); diff --git a/Apps/Sandcastle/gallery/Polyline Volume Outline.html b/Apps/Sandcastle/gallery/Polyline Volume Outline.html new file mode 100644 index 000000000000..404a1456d1dc --- /dev/null +++ b/Apps/Sandcastle/gallery/Polyline Volume Outline.html @@ -0,0 +1,142 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Polyline Volume Outline.jpg b/Apps/Sandcastle/gallery/Polyline Volume Outline.jpg new file mode 100644 index 000000000000..887c643052ea Binary files /dev/null and b/Apps/Sandcastle/gallery/Polyline Volume Outline.jpg differ diff --git a/Apps/Sandcastle/gallery/Polyline Volume.html b/Apps/Sandcastle/gallery/Polyline Volume.html new file mode 100644 index 000000000000..3ebbd663cd24 --- /dev/null +++ b/Apps/Sandcastle/gallery/Polyline Volume.html @@ -0,0 +1,119 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Polyline Volume.jpg b/Apps/Sandcastle/gallery/Polyline Volume.jpg new file mode 100644 index 000000000000..e1bf23942f4a Binary files /dev/null and b/Apps/Sandcastle/gallery/Polyline Volume.jpg differ diff --git a/CHANGES.md b/CHANGES.md index 2c975d022876..dbdced5a0057 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,8 @@ Beta Releases // Use properties and functions in p.id } * Added `Scene.drillPick` to return list of objects each containing 1 primitive at a screen space position. +* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. +* Added `Shapes.compute2DCircle`. ### b21 - 2013-10-01 diff --git a/Source/Core/CorridorGeometryLibrary.js b/Source/Core/CorridorGeometryLibrary.js index fa5660e2fa8d..a41dd9bcaaf4 100644 --- a/Source/Core/CorridorGeometryLibrary.js +++ b/Source/Core/CorridorGeometryLibrary.js @@ -6,6 +6,7 @@ define([ './CornerType', './EllipsoidTangentPlane', './PolylinePipeline', + './PolylineVolumeGeometryLibrary', './Matrix3', './Quaternion', './Math' @@ -16,6 +17,7 @@ define([ CornerType, EllipsoidTangentPlane, PolylinePipeline, + PolylineVolumeGeometryLibrary, Matrix3, Quaternion, CesiumMath) { @@ -44,24 +46,9 @@ define([ var cartesian9 = new Cartesian3(); var cartesian10 = new Cartesian3(); - var originScratch = new Cartesian3(); - var nextScratch = new Cartesian3(); - var prevScratch = new Cartesian3(); - function angleIsGreaterThanPi (forward, backward, position, ellipsoid) { - var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); - var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); - var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); - - prev = Cartesian2.subtract(prev, origin, prev); - next = Cartesian2.subtract(next, origin, next); - - return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; - } - var quaterion = new Quaternion(); var rotMatrix = new Matrix3(); - function computeRoundCorner (cornerPoint, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid) { + function computeRoundCorner (cornerPoint, startPoint, endPoint, cornerType, leftIsOutside) { var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, cornerPoint, scratch1), Cartesian3.subtract(endPoint, cornerPoint, scratch2)); var granularity = (cornerType.value === CornerType.BEVELED.value) ? 1 : Math.ceil(angle / CesiumMath.toRadians(5)) + 1; @@ -100,7 +87,7 @@ define([ startPoint = Cartesian3.fromArray(calculatedPositions[1], leftEdge.length - 3, startPoint); endPoint = Cartesian3.fromArray(calculatedPositions[0], 0, endPoint); cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint); - var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false, ellipsoid); + var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false); var length = calculatedPositions.length - 1; var rightEdge = calculatedPositions[length - 1]; @@ -108,12 +95,12 @@ define([ startPoint = Cartesian3.fromArray(rightEdge, rightEdge.length - 3, startPoint); endPoint = Cartesian3.fromArray(leftEdge, 0, endPoint); cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint); - var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false, ellipsoid); + var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false); return [firstEndCap, lastEndCap]; } - function computeMiteredCorner (position, startPoint, leftCornerDirection, lastPoint, leftIsOutside, granularity, ellipsoid) { + function computeMiteredCorner (position, leftCornerDirection, lastPoint, leftIsOutside) { var cornerPoint = scratch1; if (leftIsOutside) { cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint); @@ -168,14 +155,22 @@ define([ } }; + function scaleToSurface(positions, ellipsoid) { + for(var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + /** * @private */ CorridorGeometryLibrary.computePositions = function (params) { var granularity = params.granularity; var positions = params.positions; - var width = params.width / 2; var ellipsoid = params.ellipsoid; + positions = scaleToSurface(positions, ellipsoid); + var width = params.width / 2; var cornerType = params.cornerType; var saveAttributes = params.saveAttributes; var normal = cartesian1; @@ -219,7 +214,7 @@ define([ cornerDirection = Cartesian3.cross(cornerDirection, normal, cornerDirection); cornerDirection = Cartesian3.cross(normal, cornerDirection, cornerDirection); var scalar = width / Math.max(0.25, Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1))); - var leftIsOutside = angleIsGreaterThanPi(forward, backward, position, ellipsoid); + var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection); if (leftIsOutside) { rightPos = Cartesian3.add(position, cornerDirection, rightPos); @@ -238,9 +233,9 @@ define([ leftPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width * 2, leftPos), leftPos); previousPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width, previousPos), previousPos); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - corners.push({leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside, ellipsoid)}); + corners.push({leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside)}); } else { - corners.push({leftPositions : computeMiteredCorner(position, startPoint, Cartesian3.negate(cornerDirection, cornerDirection), leftPos, leftIsOutside, granularity, ellipsoid)}); + corners.push({leftPositions : computeMiteredCorner(position, Cartesian3.negate(cornerDirection, cornerDirection), leftPos, leftIsOutside)}); } } else { leftPos = Cartesian3.add(position, cornerDirection, leftPos); @@ -259,9 +254,9 @@ define([ rightPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width * 2, rightPos), rightPos), rightPos); previousPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width, previousPos), previousPos), previousPos); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - corners.push({rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside, ellipsoid)}); + corners.push({rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside)}); } else { - corners.push({rightPositions : computeMiteredCorner(position, startPoint, cornerDirection, rightPos, leftIsOutside, granularity, ellipsoid)}); + corners.push({rightPositions : computeMiteredCorner(position, cornerDirection, rightPos, leftIsOutside)}); } } backward = Cartesian3.negate(forward, backward); diff --git a/Source/Core/GeometryAttributes.js b/Source/Core/GeometryAttributes.js index b9e740ce5b46..75563221096a 100644 --- a/Source/Core/GeometryAttributes.js +++ b/Source/Core/GeometryAttributes.js @@ -11,7 +11,7 @@ define(['./defaultValue'], function(defaultValue) { * into the vertex buffer for better rendering performance. *

* - * @alias VertexFormat + * @alias GeometryAttributes * @constructor */ var GeometryAttributes = function(options) { diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 43f0b48c97a0..dcf49e404d3d 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -387,7 +387,7 @@ define([ var after = getNextVertex(a1i, pArray, AFTER); var s1 = Cartesian2.subtract(pArray[before].position, a1.position); - var s2 = Cartesian2.subtract(pArray[after].position, a1.position); + var s2 = Cartesian2.subtract(pArray[after].position, a1.position); var cut = Cartesian2.subtract(a2.position, a1.position); // Convert to 3-dimensional so we can use cross product @@ -515,7 +515,7 @@ define([ } var s1 = Cartesian2.subtract(pArray[before].position, pArray[index].position); - var s2 = Cartesian2.subtract(pArray[after].position, pArray[index].position); + var s2 = Cartesian2.subtract(pArray[after].position, pArray[index].position); // Convert to 3-dimensional so we can use cross product s1 = new Cartesian3(s1.x, s1.y, 0.0); diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 8859d44504b6..abca38d11c6a 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -46,16 +46,20 @@ define([ var carto1 = new Cartographic(); var carto2 = new Cartographic(); var cartesian = new Cartesian3(); + var scaleFirst = new Cartesian3(); + var scaleLast = new Cartesian3(); var ellipsoidGeodesic = new EllipsoidGeodesic(); //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 generateCartesianArc(p1, p2, granularity, ellipsoid) { - var separationAngle = Cartesian3.angleBetween(p1, p2); + var first = ellipsoid.scaleToGeodeticSurface(p1, scaleFirst); + var last = ellipsoid.scaleToGeodeticSurface(p2, scaleLast); + var separationAngle = Cartesian3.angleBetween(first, last); var numPoints = Math.ceil(separationAngle/granularity); var result = new Array(numPoints*3); - var start = ellipsoid.cartesianToCartographic(p1, carto1); - var end = ellipsoid.cartesianToCartographic(p2, carto2); + var start = ellipsoid.cartesianToCartographic(first, carto1); + var end = ellipsoid.cartesianToCartographic(last, carto2); ellipsoidGeodesic.setEndPoints(start, end); var surfaceDistanceBetweenPoints = ellipsoidGeodesic.getSurfaceDistance() / (numPoints); diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js new file mode 100644 index 000000000000..70d315be8055 --- /dev/null +++ b/Source/Core/PolylineVolumeGeometry.js @@ -0,0 +1,260 @@ +/*global define*/ +define([ + './defined', + './DeveloperError', + './Cartesian3', + './CornerType', + './ComponentDatatype', + './Ellipsoid', + './Geometry', + './GeometryPipeline', + './IndexDatatype', + './Math', + './PolygonPipeline', + './PolylineVolumeGeometryLibrary', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './BoundingRectangle', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat', + './WindingOrder' + ], function( + defined, + DeveloperError, + Cartesian3, + CornerType, + ComponentDatatype, + Ellipsoid, + Geometry, + GeometryPipeline, + IndexDatatype, + CesiumMath, + PolygonPipeline, + PolylineVolumeGeometryLibrary, + PrimitiveType, + defaultValue, + BoundingSphere, + BoundingRectangle, + GeometryAttribute, + GeometryAttributes, + VertexFormat, + WindingOrder) { + "use strict"; + + function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { + var attributes = new GeometryAttributes(); + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : combinedPositions + }); + } + var shapeLength = shape.length; + var vertexCount = combinedPositions.length / 3; + var length = (vertexCount - shapeLength * 2) / (shapeLength * 2); + var firstEndIndices = PolygonPipeline.triangulate(shape); + + var indicesCount = (length - 1) * (shapeLength) * 6 + firstEndIndices.length * 2; + var indices = IndexDatatype.createTypedArray(vertexCount, indicesCount); + var i, j; + var ll, ul, ur, lr; + var offset = shapeLength * 2; + var index = 0; + for (i = 0; i < length - 1; i++) { + for (j = 0; j < shapeLength - 1; j++) { + ll = j * 2 + i * shapeLength * 2; + lr = ll + offset; + ul = ll + 1; + ur = ul + offset; + + indices[index++] = ul; + indices[index++] = ll; + indices[index++] = ur; + indices[index++] = ur; + indices[index++] = ll; + indices[index++] = lr; + } + ll = shapeLength * 2 - 2 + i * shapeLength * 2; + ul = ll + 1; + ur = ul + offset; + lr = ll + offset; + + indices[index++] = ul; + indices[index++] = ll; + indices[index++] = ur; + indices[index++] = ur; + indices[index++] = ll; + indices[index++] = lr; + } + + if (vertexFormat.st || vertexFormat.tangent || vertexFormat.binormal) { // st required for tangent/binormal calculation + var st = new Float32Array(vertexCount * 2); + var lengthSt = 1 / (length - 1); + var heightSt = 1 / (boundingRectangle.height); + var heightOffset = boundingRectangle.height / 2; + var s, t; + var stindex = 0; + for (i = 0; i < length; i++) { + s = i * lengthSt; + t = heightSt * (shape[0].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; + for (j = 1; j < shapeLength; j++) { + t = heightSt * (shape[j].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; + st[stindex++] = s; + st[stindex++] = t; + } + t = heightSt * (shape[0].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; + } + for (j = 0; j < shapeLength; j++) { + s = 0; + t = heightSt * (shape[j].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; + } + for (j = 0; j < shapeLength; j++) { + s = (length - 1) * lengthSt; + t = heightSt * (shape[j].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; + } + + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : new Float32Array(st) + }); + } + + var endOffset = vertexCount - shapeLength * 2; + for (i = 0; i < firstEndIndices.length; i += 3) { + var v0 = firstEndIndices[i] + endOffset; + var v1 = firstEndIndices[i + 1] + endOffset; + var v2 = firstEndIndices[i + 2] + endOffset; + + indices[index++] = v0; + indices[index++] = v1; + indices[index++] = v2; + indices[index++] = v2 + shapeLength; + indices[index++] = v1 + shapeLength; + indices[index++] = v0 + shapeLength; + } + + var geometry = new Geometry({ + attributes : attributes, + indices : indices, + boundingSphere : BoundingSphere.fromVertices(combinedPositions), + primitiveType : PrimitiveType.TRIANGLES + }); + + if (vertexFormat.normal) { + geometry = GeometryPipeline.computeNormal(geometry); + } + + if (vertexFormat.tangent || vertexFormat.binormal) { + geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + if (!vertexFormat.tangent) { + geometry.attributes.tangent = undefined; + } + if (!vertexFormat.binormal) { + geometry.attributes.binormal = undefined; + } + if (!vertexFormat.st) { + geometry.attributes.st = undefined; + } + } + + return geometry; + } + + /** + * A description of a polyline with a volume (a 2D shape extruded along a polyline). + * + * @alias PolylineVolumeGeometry + * @constructor + * + * @param {Array} options.polylinePositions An array of {@link Cartesain3} positions that define the center of the polyline volume. + * @param {Number} options.shapePositions An array of {@link Cartesian2} positions that define the shape to be extruded along the polyline + * @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 {Number} [options.height=0] The distance between the ellipsoid surface and the positions. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. + * + * @exception {DeveloperError} options.polylinePositions is required. + * @exception {DeveloperError} options.shapePositions is required. + * + * @see PolylineVolumeGeometry#createGeometry + * + * @example + * var volume = new PolylineVolumeGeometry({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0) + * ]), + * shapePositions : Shapes.compute2DCircle(100000.0) + * }); + */ + var PolylineVolumeGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.polylinePositions; + if (!defined(positions)) { + throw new DeveloperError('options.polylinePositions is required.'); + } + var shape = options.shapePositions; + if (!defined(shape)) { + throw new DeveloperError('options.shapePositions is required.'); + } + + this._positions = positions; + this._shape = shape; + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._height = defaultValue(options.height, 0.0); + this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); + this._vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._workerName = 'createPolylineVolumeGeometry'; + }; + + /** + * Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere. + * @memberof PolylineVolumeGeometry + * + * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume. + * + * @returns {Geometry} The computed vertices and indices. + * + * @exception {DeveloperError} Count of unique polyline positions must be greater than 1. + * @exception {DeveloperError} Count of unique shape positions must be at least 3. + */ + var brScratch = new BoundingRectangle(); + PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) { + var positions = polylineVolumeGeometry._positions; + var cleanPositions = PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions(positions, polylineVolumeGeometry._ellipsoid); + if (cleanPositions.length < 2) { + throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); + } + var shape2D = polylineVolumeGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); + if (shape2D.length < 3) { + throw new DeveloperError('Count of unique shape positions must be at least 3.'); + } + if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { + shape2D.reverse(); + } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); + + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeGeometry, true); + return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeGeometry._vertexFormat, polylineVolumeGeometry._ellipsoid); + }; + + return PolylineVolumeGeometry; +}); \ No newline at end of file diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js new file mode 100644 index 000000000000..a5960cb31abb --- /dev/null +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -0,0 +1,419 @@ +/*global define*/ +define([ + './defined', + './Cartesian2', + './Cartesian3', + './Cartesian4', + './Cartographic', + './CornerType', + './DeveloperError', + './EllipsoidTangentPlane', + './PolylinePipeline', + './Matrix3', + './Matrix4', + './Quaternion', + './Transforms', + './Math' + ], function( + defined, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + CornerType, + DeveloperError, + EllipsoidTangentPlane, + PolylinePipeline, + Matrix3, + Matrix4, + Quaternion, + Transforms, + CesiumMath) { + "use strict"; + + var scratch2Array = [new Cartesian3(), new Cartesian3()]; + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + var scratchCartesian4 = new Cartesian3(); + var scratchCartesian5 = new Cartesian3(); + var scratchCartesian6 = new Cartesian3(); + var scratchCartesian7 = new Cartesian3(); + var scratchCartesian8 = new Cartesian3(); + var scratchCartesian9 = new Cartesian3(); + + var scratch1 = new Cartesian3(); + var scratch2 = new Cartesian3(); + + /** + * @private + */ + var PolylineVolumeGeometryLibrary = {}; + + var cartographic = new Cartographic(); + function scaleToSurface(positions, ellipsoid) { + var heights = new Array(positions.length); + for ( var i = 0; i < positions.length; i++) { + var pos = positions[i]; + cartographic = ellipsoid.cartesianToCartographic(pos, cartographic); + heights[i] = cartographic.height; + positions[i] = ellipsoid.scaleToGeodeticSurface(pos, pos); + } + return heights; + } + + function subdivideHeights(points, h0, h1, granularity) { + var p0 = points[0]; + var p1 = points[1]; + var angleBetween = Cartesian3.angleBetween(p0, p1); + var numPoints = Math.ceil(angleBetween / granularity); + var heights = new Array(numPoints); + var i; + if (h0 === h1) { + for (i = 0; i < numPoints; i++) { + heights[i] = h0; + } + heights.push(h1); + return heights; + } + + var dHeight = h1 - h0; + var heightPerVertex = dHeight / (numPoints); + + for (i = 1; i < numPoints; i++) { + var h = h0 + i * heightPerVertex; + heights[i] = h; + } + + heights[0] = h0; + heights.push(h1); + return heights; + } + + function computeRotationAngle(start, end, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch); + var angle = Cartesian2.angleBetween(next, prev); + + return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle; + } + + var negativeX = new Cartesian4(-1, 0, 0, 0); + var transform = new Matrix4(); + var translation = new Matrix4(); + var rotationZ = new Matrix3(); + var scaleMatrix = Matrix3.IDENTITY.clone(); + var westScratch = new Cartesian4(); + var finalPosScratch = new Cartesian4(); + var heightCartesian = new Cartesian3(); + function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) { + var west = westScratch; + var finalPosition = finalPosScratch; + transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); + + west = Matrix4.multiplyByVector(transform, negativeX, west); + west = Cartesian3.normalize(west, west); + var angle = computeRotationAngle(west, left, center, ellipsoid); + rotationZ = Matrix3.fromRotationZ(angle, rotationZ); + + heightCartesian.z = height; + transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotationZ, heightCartesian, translation), transform); + var scale = scaleMatrix; + scale[0] = xScalar; + + for ( var j = 0; j < repeat; j++) { + for ( var i = 0; i < shape.length; i += 3) { + finalPosition = Cartesian3.fromArray(shape, i, finalPosition); + finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); + finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); + finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); + } + } + + return finalPositions; + } + + var centerScratch = new Cartesian3(); + function addPositions(centers, left, shape, finalPositions, ellipsoid, heights, xScalar) { + for ( var i = 0; i < centers.length; i += 3) { + var center = Cartesian3.fromArray(centers, i, centerScratch); + finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i / 3], xScalar, 1); + } + return finalPositions; + } + + function convertShapeTo3DDuplicate(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points + var length = shape2D.length; + var shape = new Array(length * 6); + var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; + + var point = shape2D[0]; + shape[index++] = point.x - xOffset; + shape[index++] = 0.0; + shape[index++] = point.y - yOffset; + for ( var i = 1; i < length; i++) { + point = shape2D[i]; + var x = point.x - xOffset; + var z = point.y - yOffset; + shape[index++] = x; + shape[index++] = 0.0; + shape[index++] = z; + + shape[index++] = x; + shape[index++] = 0.0; + shape[index++] = z; + } + point = shape2D[0]; + shape[index++] = point.x - xOffset; + shape[index++] = 0.0; + shape[index++] = point.y - yOffset; + + return shape; + } + + function convertShapeTo3D(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) + var length = shape2D.length; + var shape = new Array(length * 3); + var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; + + for ( var i = 0; i < length; i++) { + shape[index++] = shape2D[i].x - xOffset; + shape[index++] = 0; + shape[index++] = shape2D[i].y - yOffset; + } + + return shape; + } + + var quaterion = new Quaternion(); + var startPointScratch = new Cartesian3(); + var rotMatrix = new Matrix3(); + function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height, duplicatePoints) { + var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2)); + var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5)); + + var m; + if (leftIsOutside) { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix); + } else { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix); + } + + var left; + var surfacePoint; + startPoint = Cartesian3.clone(startPoint, startPointScratch); + if (granularity > 0) { + var repeat = duplicatePoints ? 2 : 1; + for ( var i = 0; i < granularity; i++) { + startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint); + left = Cartesian3.subtract(startPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat); + } + } else { + left = Cartesian3.subtract(startPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + + endPoint = Cartesian3.clone(endPoint, startPointScratch); + left = Cartesian3.subtract(endPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + surfacePoint = ellipsoid.scaleToGeodeticSurface(endPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + } + + return finalPositions; + } + + PolylineVolumeGeometryLibrary.removeDuplicatesFromShape = function(shapePositions) { + var length = shapePositions.length; + var cleanedPositions = []; + for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { + var v0 = shapePositions[i0]; + var v1 = shapePositions[i1]; + + if (!Cartesian2.equals(v0, v1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; + + var nextScratch = new Cartesian3(); + var prevScratch = new Cartesian3(); + PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); + + return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; + }; + + function latLonEquals(c0, c1) { + return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); + } + var carto0 = new Cartographic(); + var carto1 = new Cartographic(); + PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions = function(positions, ellipsoid) { + var length = positions.length; + if (length < 2) { + return positions.slice(0); + } + + var cleanedPositions = []; + cleanedPositions.push(positions[0]); + + for (var i = 1; i < length; ++i) { + var v0 = positions[i - 1]; + var v1 = positions[i]; + var c0 = ellipsoid.cartesianToCartographic(v0, carto0); + var c1 = ellipsoid.cartesianToCartographic(v1, carto1); + + if (!latLonEquals(c0, c1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; + + PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) { + var ellipsoid = geometry._ellipsoid; + var heights = scaleToSurface(positions, ellipsoid); + var granularity = geometry._granularity; + var cornerType = geometry._cornerType; + var shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle); + var shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined; + var heightOffset = boundingRectangle.height / 2; + var width = boundingRectangle.width / 2; + var length = positions.length; + var finalPositions = []; + var ends = duplicatePoints ? [] : undefined; + + var forward = scratchCartesian1; + var backward = scratchCartesian2; + var cornerDirection = scratchCartesian3; + var surfaceNormal = scratchCartesian4; + var pivot = scratchCartesian5; + var start = scratchCartesian6; + var end = scratchCartesian7; + var left = scratchCartesian8; + var previousPosition = scratchCartesian9; + + var position = positions[0]; + var nextPosition = positions[1]; + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + var h0 = heights[0]; + var h1 = heights[1]; + if (duplicatePoints) { + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h0 + heightOffset, 1, 1); + } + previousPosition = Cartesian3.clone(position, previousPosition); + position = nextPosition; + backward = Cartesian3.negate(forward, backward); + var subdividedHeights; + var subdividedPositions; + for ( var i = 1; i < length - 1; i++) { + var repeat = duplicatePoints ? 2 : 1; + nextPosition = positions[i + 1]; + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + cornerDirection = Cartesian3.add(forward, backward, cornerDirection); + cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + var doCorner = !Cartesian3.equalsEpsilon(Cartesian3.negate(cornerDirection, scratch1), surfaceNormal, CesiumMath.EPSILON2); + if (doCorner) { + cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection); + cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection); + cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); + var scalar = 1 / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); + var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); + if (leftIsOutside) { + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); + } else { + cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat); + } + previousPosition = Cartesian3.clone(end, previousPosition); + } else { + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); + } else { + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat); + } + previousPosition = Cartesian3.clone(end, previousPosition); + } + backward = Cartesian3.negate(forward, backward); + } else { + finalPositions = addPosition(previousPosition, left, shapeForSides, finalPositions, ellipsoid, h0 + heightOffset, 1, 1); + previousPosition = position; + } + h0 = h1; + h1 = heights[i + 1]; + position = nextPosition; + } + + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); + if (duplicatePoints) { + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h1 + heightOffset, 1, 1); + } + + length = finalPositions.length; + var posLength = duplicatePoints ? length + ends.length : length; + var combinedPositions = new Float64Array(posLength); + combinedPositions.set(finalPositions); + if (duplicatePoints) { + combinedPositions.set(ends, length); + } + + return combinedPositions; + }; + + return PolylineVolumeGeometryLibrary; +}); \ No newline at end of file diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js new file mode 100644 index 000000000000..28fd389e53d8 --- /dev/null +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -0,0 +1,174 @@ +/*global define*/ +define([ + './defined', + './DeveloperError', + './Cartesian3', + './CornerType', + './ComponentDatatype', + './Ellipsoid', + './Geometry', + './GeometryPipeline', + './IndexDatatype', + './Math', + './PolygonPipeline', + './PolylineVolumeGeometryLibrary', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './BoundingRectangle', + './GeometryAttribute', + './GeometryAttributes', + './WindingOrder' + ], function( + defined, + DeveloperError, + Cartesian3, + CornerType, + ComponentDatatype, + Ellipsoid, + Geometry, + GeometryPipeline, + IndexDatatype, + CesiumMath, + PolygonPipeline, + PolylineVolumeGeometryLibrary, + PrimitiveType, + defaultValue, + BoundingSphere, + BoundingRectangle, + GeometryAttribute, + GeometryAttributes, + WindingOrder) { + "use strict"; + + function computeAttributes(positions, shape, boundingRectangle, ellipsoid) { + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + var shapeLength = shape.length; + var vertexCount = attributes.position.values.length / 3; + var positionLength = positions.length / 3; + var shapeCount = positionLength / shapeLength; + var indices = IndexDatatype.createTypedArray(vertexCount, 2 * shapeLength * (shapeCount + 1)); + var i, j; + var index = 0; + i = 0; + var offset = i * shapeLength; + for (j = 0; j < shapeLength - 1; j++) { + indices[index++] = j + offset; + indices[index++] = j + offset + 1; + } + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; + + i = shapeCount - 1; + offset = i * shapeLength; + for (j = 0; j < shapeLength - 1; j++) { + indices[index++] = j + offset; + indices[index++] = j + offset + 1; + } + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; + + for (i = 0; i < shapeCount - 1; i++) { + var firstOffset = shapeLength * i; + var secondOffset = firstOffset + shapeLength; + for (j = 0; j < shapeLength; j++) { + indices[index++] = j + firstOffset; + indices[index++] = j + secondOffset; + } + } + + var geometry = new Geometry({ + attributes : attributes, + indices : IndexDatatype.createTypedArray(vertexCount, indices), + boundingSphere : BoundingSphere.fromVertices(positions), + primitiveType : PrimitiveType.LINES + }); + + return geometry; + } + + /** + * A description of a polyline with a volume (a 2D shape extruded along a polyline). + * + * @alias PolylineVolumeOutlineGeometry + * @constructor + * + * @param {Array} options.polylinePositions An array of {Cartesain3} positions that define the center of the polyline volume. + * @param {Number} options.shapePositions An array of {Cartesian2} positions that define the shape to be extruded along the polyline + * @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.cornerType = CornerType.ROUNDED] Determines the style of the corners. + * + * @exception {DeveloperError} options.polylinePositions is required. + * @exception {DeveloperError} options.shapePositions is required. + * + * @see PolylineVolumeOutlineGeometry#createGeometry + * + * @example + * var volumeOutline = new PolylineVolumeOutlineGeometry({ + * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0) + * ]), + * shapePositions : Shapes.compute2DCircle(100000.0) + * }); + */ + var PolylineVolumeOutlineGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.polylinePositions; + if (!defined(positions)) { + throw new DeveloperError('options.polylinePositions is required.'); + } + var shape = options.shapePositions; + if (!defined(shape)) { + throw new DeveloperError('options.shapePositions is required.'); + } + + this._positions = positions; + this._shape = shape; + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._workerName = 'createPolylineVolumeOutlineGeometry'; + }; + + /** + * Computes the geometric representation of the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere. + * @memberof PolylineVolumeOutlineGeometry + * + * @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume outline. + * + * @returns {Geometry} The computed vertices and indices. + * + * @exception {DeveloperError} Count of unique polyline positions must be greater than 1. + * @exception {DeveloperError} Count of unique shape positions must be at least 3. + */ + var brScratch = new BoundingRectangle(); + PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) { + var positions = polylineVolumeOutlineGeometry._positions; + var cleanPositions = PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions(positions, polylineVolumeOutlineGeometry._ellipsoid); + if (cleanPositions.length < 2) { + throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); + } + var shape2D = polylineVolumeOutlineGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); + if (shape2D.length < 3) { + throw new DeveloperError('Count of unique shape positions must be at least 3.'); + } + if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { + shape2D.reverse(); + } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); + + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false); + return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry._ellipsoid); + }; + + return PolylineVolumeOutlineGeometry; +}); \ No newline at end of file diff --git a/Source/Core/Shapes.js b/Source/Core/Shapes.js index e65384b7a407..152d921302d3 100644 --- a/Source/Core/Shapes.js +++ b/Source/Core/Shapes.js @@ -4,6 +4,7 @@ define([ './defined', './DeveloperError', './Math', + './Cartesian2', './Cartesian3', './Quaternion', './Matrix3' @@ -12,6 +13,7 @@ define([ defined, DeveloperError, CesiumMath, + Cartesian2, Cartesian3, Quaternion, Matrix3) { @@ -227,6 +229,29 @@ define([ ellipsePts.push(Cartesian3.clone(ellipsePts[0])); // Duplicates first and last point for polyline return ellipsePts; + }, + + /** + * Computes a 2D circle about the origin. + * + * @param {Number} [radius = 1.0] The radius of the circle + * @param {Number} [granularity = Cesium.RADIANS_PER_DEGREE*2] The radius of the circle + * + * @returns The set of points that form the ellipse's boundary. + * + * @example + * var circle = Shapes.compute2DCircle(100000.0); + */ + compute2DCircle : function(radius, granularity) { + radius = defaultValue(radius, 1.0); + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE*2); + var positions = []; + var theta = CesiumMath.toRadians(1.0); + var posCount = Math.PI*2/theta; + for (var i = 0; i < posCount; i++) { + positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); + } + return positions; } }; diff --git a/Source/Workers/createPolylineVolumeGeometry.js b/Source/Workers/createPolylineVolumeGeometry.js new file mode 100644 index 000000000000..68bbe1bf4e83 --- /dev/null +++ b/Source/Workers/createPolylineVolumeGeometry.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + '../Core/PolylineVolumeGeometry', + '../Core/Ellipsoid', + '../Scene/PrimitivePipeline', + './createTaskProcessorWorker' + ], function( + PolylineVolumeGeometry, + Ellipsoid, + PrimitivePipeline, + createTaskProcessorWorker) { + "use strict"; + + function createPolylineVolumeGeometry(parameters, transferableObjects) { + var polylineVolumeGeometry = parameters.geometry; + polylineVolumeGeometry._ellipsoid = Ellipsoid.clone(polylineVolumeGeometry._ellipsoid); + + var geometry = PolylineVolumeGeometry.createGeometry(polylineVolumeGeometry); + PrimitivePipeline.transferGeometry(geometry, transferableObjects); + + return { + geometry : geometry, + index : parameters.index + }; + } + + return createTaskProcessorWorker(createPolylineVolumeGeometry); +}); diff --git a/Source/Workers/createPolylineVolumeOutlineGeometry.js b/Source/Workers/createPolylineVolumeOutlineGeometry.js new file mode 100644 index 000000000000..7d86c89a3dd5 --- /dev/null +++ b/Source/Workers/createPolylineVolumeOutlineGeometry.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + '../Core/PolylineVolumeOutlineGeometry', + '../Core/Ellipsoid', + '../Scene/PrimitivePipeline', + './createTaskProcessorWorker' + ], function( + PolylineVolumeOutlineGeometry, + Ellipsoid, + PrimitivePipeline, + createTaskProcessorWorker) { + "use strict"; + + function createPolylineVolumeOutlineGeometry(parameters, transferableObjects) { + var polylineVolumeOutlineGeometry = parameters.geometry; + polylineVolumeOutlineGeometry._ellipsoid = Ellipsoid.clone(polylineVolumeOutlineGeometry._ellipsoid); + + var geometry = PolylineVolumeOutlineGeometry.createGeometry(polylineVolumeOutlineGeometry); + PrimitivePipeline.transferGeometry(geometry, transferableObjects); + + return { + geometry : geometry, + index : parameters.index + }; + } + + return createTaskProcessorWorker(createPolylineVolumeOutlineGeometry); +}); diff --git a/Specs/Core/PolylineVolumeGeometrySpec.js b/Specs/Core/PolylineVolumeGeometrySpec.js new file mode 100644 index 000000000000..a62cea5b6bcd --- /dev/null +++ b/Specs/Core/PolylineVolumeGeometrySpec.js @@ -0,0 +1,182 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolylineVolumeGeometry', + 'Core/CornerType', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + PolylineVolumeGeometry, + CornerType, + Cartesian2, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + var shape; + + beforeAll(function() { + shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; + }); + + it('throws without polyline positions', function() { + expect(function() { + return new PolylineVolumeGeometry({}); + }).toThrow(); + }); + + it('throws without shape positions', function() { + expect(function() { + return new PolylineVolumeGeometry({ + polylinePositions: [new Cartesian3()] + }); + }).toThrow(); + }); + + it('throws without 2 unique polyline positions', function() { + expect(function() { + return PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + polylinePositions: [new Cartesian3()], + shapePositions: shape + })); + }).toThrow(); + }); + + it('throws without 3 unique shape positions', function() { + expect(function() { + return PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + polylinePositions: [Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + shapePositions: [Cartesian2.UNIT_X, Cartesian2.UNIT_X, Cartesian2.UNIT_X] + })); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes positions, clockwise shape', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape.reverse() + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('compute all vertex attributes', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.ALL, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.st.values.length).toEqual(2 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.normal.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.tangent.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.binormal.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes right turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(91.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes left turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes with rounded corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.ROUNDED, + shapePositions: shape + })); + + var corners = 90/5 * 2; + expect(m.attributes.position.values.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 2 * 9)); + expect(m.indices.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 7 * 2 + 4)); + }); + + it('computes with beveled corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.BEVELED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (2 * 8 + 4 * 2 * 9)); + expect(m.indices.length).toEqual(3 * (8 * 2 + 4 * 7 * 2 + 4)); + }); +}); diff --git a/Specs/Core/PolylineVolumeOutlineGeometrySpec.js b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js new file mode 100644 index 000000000000..550d77acd4e3 --- /dev/null +++ b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js @@ -0,0 +1,155 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolylineVolumeOutlineGeometry', + 'Core/CornerType', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math' + ], function( + PolylineVolumeOutlineGeometry, + CornerType, + Cartesian2, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var shape; + + beforeAll(function() { + shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; + }); + + it('throws without polyline positions', function() { + expect(function() { + return new PolylineVolumeOutlineGeometry({}); + }).toThrow(); + }); + + it('throws without shape positions', function() { + expect(function() { + return new PolylineVolumeOutlineGeometry({ + polylinePositions: [new Cartesian3()] + }); + }).toThrow(); + }); + + it('throws without 2 unique polyline positions', function() { + expect(function() { + return PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions: [new Cartesian3()], + shapePositions: shape + })); + }).toThrow(); + }); + + it('throws without 3 unique shape positions', function() { + expect(function() { + return PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions: [Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + shapePositions: [Cartesian2.UNIT_X, Cartesian2.UNIT_X, Cartesian2.UNIT_X] + })); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + shapePositions: shape, + cornerType: CornerType.MITERED + })); + + expect(m.attributes.position.values.length).toEqual(3 * 6 * 4); + expect(m.indices.length).toEqual(2 * 24 + 8); + }); + + it('computes positions, clockwise shape', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + shapePositions: shape.reverse(), + cornerType: CornerType.MITERED + })); + + expect(m.attributes.position.values.length).toEqual(3 * 6 * 4); + expect(m.indices.length).toEqual(2 * 24 + 8); + }); + + it('computes right turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(91.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 5 * 4); + expect(m.indices.length).toEqual(2 * 24); + }); + + it('computes left turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 5 * 4); + expect(m.indices.length).toEqual(2 * 24); + }); + + it('computes with rounded corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.ROUNDED, + shapePositions: shape + })); + + var corners = 90/5*2; + expect(m.attributes.position.values.length).toEqual(3 * (corners * 4 + 7 * 4)); + expect(m.indices.length).toEqual(2 * (corners * 4 + 6 * 4 + 8)); + }); + + it('computes with beveled corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.BEVELED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 20 * 2); + expect(m.indices.length).toEqual(2 * 20 * 2 + 8); + }); +}); \ No newline at end of file diff --git a/Specs/Core/ShapesSpec.js b/Specs/Core/ShapesSpec.js index 39fe89161dfe..9f14705da045 100644 --- a/Specs/Core/ShapesSpec.js +++ b/Specs/Core/ShapesSpec.js @@ -119,4 +119,18 @@ defineSuite([ Shapes.computeEllipseBoundary(ellipsoid, center, 1.0, 1.0, 0, -1.0); }).toThrow(); }); + + it('compute2DCircle returns a unit circle by default', function() { + var points = Shapes.compute2DCircle(); + expect(points.length).toBeGreaterThan(0); + expect(points[0].x).toEqual(1); + expect(points[0].y).toEqual(0); + }); + + it('compute2DCircle returns an circle with radius 5', function() { + var points = Shapes.compute2DCircle(5.0); + expect(points.length).toBeGreaterThan(0); + expect(points[0].x).toEqual(5); + expect(points[0].y).toEqual(0); + }); }); diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js index bd997dc39e59..5445d23ab8f7 100644 --- a/Specs/Scene/GeometryRenderingSpec.js +++ b/Specs/Scene/GeometryRenderingSpec.js @@ -21,11 +21,13 @@ defineSuite([ 'Core/ColorGeometryInstanceAttribute', 'Core/GeometryInstanceAttribute', 'Core/ComponentDatatype', + 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Matrix4', 'Core/Extent', 'Core/Ellipsoid', 'Core/PrimitiveType', + 'Core/PolylineVolumeGeometry', 'Core/Transforms', 'Core/Cartographic', 'Core/BoundingSphere', @@ -66,11 +68,13 @@ defineSuite([ ColorGeometryInstanceAttribute, GeometryInstanceAttribute, ComponentDatatype, + Cartesian2, Cartesian3, Matrix4, Extent, Ellipsoid, PrimitiveType, + PolylineVolumeGeometry, Transforms, Cartographic, BoundingSphere, @@ -1250,6 +1254,98 @@ defineSuite([ }); }, 'WebGL'); + describe('PolylineVolumeGeometry', function() { + var instance; + var geometryHeight; + var positions; + var shape; + beforeAll(function() { + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, -1.0), + Cartographic.fromDegrees(0.0, 1.0) + ]); + shape = [new Cartesian2(-100000, -100000), new Cartesian2(100000, -100000), new Cartesian2(100000, 100000), new Cartesian2(-100000, 100000)]; + geometryHeight = 150000.0; + instance = new GeometryInstance({ + geometry : new PolylineVolumeGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + polylinePositions: positions, + shapePositions: shape, + cornerType: CornerType.MITERED, + height: geometryHeight + }), + id : 'polylineVolume', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + }); + + it('3D', function() { + render3D(instance); + }); + + it('Columbus view', function() { + renderCV(instance); + }); + + it('2D', function() { + render2D(instance); + }); + + it('pick', function() { + pickGeometry(instance); + }); + + it('async', function() { + renderAsync(instance); + }); + + it('renders bottom', function() { + var afterView = function(frameState, primitive) { + var height = geometryHeight * 0.5; + var transform = Matrix4.multiplyByTranslation( + Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center), + new Cartesian3(0.0, 0.0, height)); + frameState.camera.controller.rotateDown(CesiumMath.PI, transform); + }; + render3D(instance, afterView); + }); + + it('renders north wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateDown(-CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders south wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateDown(CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders west wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateRight(-CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders east wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateRight(CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + }, 'WebGL'); + describe('SimplePolylineGeometry', function() { var instance;