diff --git a/Apps/Sandcastle/gallery/Animations.html b/Apps/Sandcastle/gallery/Animations.html index b40c6109f0a6..b6fbabfcf6af 100644 --- a/Apps/Sandcastle/gallery/Animations.html +++ b/Apps/Sandcastle/gallery/Animations.html @@ -34,7 +34,7 @@ { "use strict"; - var polygon; + var extent; var rectangularSensor; function addAlphaAnimation(primitive, scene) { @@ -49,22 +49,14 @@ }); } - function addHeightAnimation(primitive, scene) { - Sandcastle.declare(addHeightAnimation); // For highlighting in Sandcastle. - scene.getAnimations().addProperty(primitive, 'height', 5000000.0, 0.0, { - duration : 1000 - }); - } - function addStripeAnimation(primitive, scene) { Sandcastle.declare(addStripeAnimation); // For highlighting in Sandcastle. scene.getAnimations().addOffsetIncrement(primitive.material); } - function resetPolygonPropeties(polygon) { - polygon.height = 0.0; - polygon.material.uniforms.time = 1.0; - polygon.material.uniforms.color = new Cesium.Color(1.0, 0.0, 0.0, 0.5); + function resetExtentPropeties(extent) { + extent.material.uniforms.time = 1.0; + extent.material.uniforms.color = new Cesium.Color(1.0, 0.0, 0.0, 0.5); } function createPrimitives(widget) { @@ -72,14 +64,15 @@ var scene = widget.scene; var primitives = scene.getPrimitives(); - polygon = new Cesium.Polygon(); - polygon.configureExtent(new Cesium.Extent( - Cesium.Math.toRadians(-120.0), - Cesium.Math.toRadians(20.0), - Cesium.Math.toRadians(-80.0), - Cesium.Math.toRadians(50.0))); - polygon.material = Cesium.Material.fromType(scene.getContext(), 'Erosion'); - primitives.add(polygon); + extent = new Cesium.ExtentPrimitive({ + extent : new Cesium.Extent( + Cesium.Math.toRadians(-120.0), + Cesium.Math.toRadians(20.0), + Cesium.Math.toRadians(-80.0), + Cesium.Math.toRadians(50.0)), + material : Cesium.Material.fromType(scene.getContext(), 'Erosion') + }); + primitives.add(extent); var modelMatrix = Cesium.Transforms.northEastDownToFixedFrame( ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-45.0, 45.0))); @@ -105,8 +98,8 @@ label : 'Alpha Animation', onClick : function() { scene.getAnimations().removeAll(); - resetPolygonPropeties(polygon); - addAlphaAnimation(polygon, scene); + resetExtentPropeties(extent); + addAlphaAnimation(extent, scene); Sandcastle.highlight(addAlphaAnimation); } }).placeAt('toolbar'); @@ -115,22 +108,12 @@ label : 'Erosion Animation', onClick : function() { scene.getAnimations().removeAll(); - resetPolygonPropeties(polygon); - addErosionAnimation(polygon, scene); + resetExtentPropeties(extent); + addErosionAnimation(extent, scene); Sandcastle.highlight(addErosionAnimation); } }).placeAt('toolbar'); - new Button({ - label : 'Height Animation', - onClick : function() { - scene.getAnimations().removeAll(); - resetPolygonPropeties(polygon); - addHeightAnimation(polygon, scene); - Sandcastle.highlight(addHeightAnimation); - } - }).placeAt('toolbar'); - new Button({ label : 'Stripe Animation', onClick : function() { diff --git a/Apps/Sandcastle/gallery/Custom Rendering.html b/Apps/Sandcastle/gallery/Custom Rendering.html deleted file mode 100644 index dcde55549600..000000000000 --- a/Apps/Sandcastle/gallery/Custom Rendering.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - - Cesium Demo - - - - - - - -
-

Loading...

-
- - - diff --git a/Apps/Sandcastle/gallery/Custom Rendering.jpg b/Apps/Sandcastle/gallery/Custom Rendering.jpg deleted file mode 100644 index 5595afcb6157..000000000000 Binary files a/Apps/Sandcastle/gallery/Custom Rendering.jpg and /dev/null differ diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html new file mode 100644 index 000000000000..52774d993063 --- /dev/null +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -0,0 +1,248 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.jpg b/Apps/Sandcastle/gallery/Geometry and Appearances.jpg new file mode 100644 index 000000000000..9b2e98e760f2 Binary files /dev/null and b/Apps/Sandcastle/gallery/Geometry and Appearances.jpg differ diff --git a/Apps/Sandcastle/gallery/Materials.html b/Apps/Sandcastle/gallery/Materials.html index 708a1a50af38..237bd45e33e9 100644 --- a/Apps/Sandcastle/gallery/Materials.html +++ b/Apps/Sandcastle/gallery/Materials.html @@ -35,8 +35,8 @@ { "use strict"; - var polygon; - var worldPolygon; + var extent; + var worldExtent; var polyline; function applyAlphaMapMaterial(primitive, scene) { @@ -496,29 +496,29 @@ var compositeMaterialMenu = new DropDownMenu({ style: "display: none;"}); var polylineMaterialMenu = new DropDownMenu({ style: "display: none;"}); - function togglePolygonVisibility() { - polygon.show = true; - worldPolygon.show = false; + function toggleExtentVisibility() { + extent.show = true; + worldExtent.show = false; polyline.setShow(false); } - function toggleWorldPolygonVisibility() { - worldPolygon.show = true; - polygon.show = false; + function toggleWorldExtentVisibility() { + worldExtent.show = true; + extent.show = false; polyline.setShow(false); } function togglePolylineVisibility() { polyline.setShow(true); - worldPolygon.show = false; - polygon.show = false; + worldExtent.show = false; + extent.show = false; } baseMaterialMenu.addChild(new MenuItem({ label: 'Alpha Map', onClick: function() { - togglePolygonVisibility(); - applyAlphaMapMaterial(polygon, scene); + toggleExtentVisibility(); + applyAlphaMapMaterial(extent, scene); Sandcastle.highlight(applyAlphaMapMaterial); } })); @@ -526,8 +526,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Asphalt', onClick: function() { - togglePolygonVisibility(); - applyAsphaltMaterial(polygon, scene); + toggleExtentVisibility(); + applyAsphaltMaterial(extent, scene); Sandcastle.highlight(applyAsphaltMaterial); } })); @@ -535,8 +535,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Blob', onClick: function() { - togglePolygonVisibility(); - applyBlobMaterial(polygon, scene); + toggleExtentVisibility(); + applyBlobMaterial(extent, scene); Sandcastle.highlight(applyBlobMaterial); } })); @@ -544,8 +544,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Brick', onClick: function() { - togglePolygonVisibility(); - applyBrickMaterial(polygon, scene); + toggleExtentVisibility(); + applyBrickMaterial(extent, scene); Sandcastle.highlight(applyBrickMaterial); } })); @@ -553,8 +553,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Bump Map', onClick: function() { - togglePolygonVisibility(); - applyBumpMapMaterial(polygon, scene); + toggleExtentVisibility(); + applyBumpMapMaterial(extent, scene); Sandcastle.highlight(applyBumpMapMaterial); } })); @@ -562,8 +562,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Cement', onClick: function() { - togglePolygonVisibility(); - applyCementMaterial(polygon, scene); + toggleExtentVisibility(); + applyCementMaterial(extent, scene); Sandcastle.highlight(applyCementMaterial); } })); @@ -571,8 +571,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Checkerboard', onClick: function() { - togglePolygonVisibility(); - applyCheckerboardMaterial(polygon, scene); + toggleExtentVisibility(); + applyCheckerboardMaterial(extent, scene); Sandcastle.highlight(applyCheckerboardMaterial); } })); @@ -580,8 +580,8 @@ compositeMaterialMenu.addChild(new MenuItem({ label: 'Composite Example 1', onClick: function() { - togglePolygonVisibility(); - applyCompositeMaterial1(polygon, scene); + toggleExtentVisibility(); + applyCompositeMaterial1(extent, scene); Sandcastle.highlight(applyCompositeMaterial1); } })); @@ -589,8 +589,8 @@ compositeMaterialMenu.addChild(new MenuItem({ label: 'Composite Example 2', onClick: function() { - toggleWorldPolygonVisibility(); - applyCompositeMaterial2(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyCompositeMaterial2(worldExtent, scene); Sandcastle.highlight(applyCompositeMaterial2); } })); @@ -598,8 +598,8 @@ commonMaterialMenu.addChild(new MenuItem({ label: 'Color', onClick: function() { - togglePolygonVisibility(); - applyColorMaterial(polygon, scene); + toggleExtentVisibility(); + applyColorMaterial(extent, scene); Sandcastle.highlight(applyColorMaterial); } })); @@ -607,8 +607,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Diffuse', onClick: function() { - togglePolygonVisibility(); - applyDiffuseMaterial(polygon, scene); + toggleExtentVisibility(); + applyDiffuseMaterial(extent, scene); Sandcastle.highlight(applyDiffuseMaterial); } })); @@ -616,8 +616,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Dot', onClick: function() { - togglePolygonVisibility(); - applyDotMaterial(polygon, scene); + toggleExtentVisibility(); + applyDotMaterial(extent, scene); Sandcastle.highlight(applyDotMaterial); } })); @@ -625,8 +625,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Emission Map', onClick: function() { - togglePolygonVisibility(); - applyEmissionMapMaterial(polygon, scene); + toggleExtentVisibility(); + applyEmissionMapMaterial(extent, scene); Sandcastle.highlight(applyEmissionMapMaterial); } })); @@ -634,8 +634,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Facet', onClick: function() { - togglePolygonVisibility(); - applyFacetMaterial(polygon, scene); + toggleExtentVisibility(); + applyFacetMaterial(extent, scene); Sandcastle.highlight(applyFacetMaterial); } })); @@ -643,8 +643,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Fresnel', onClick: function() { - toggleWorldPolygonVisibility(); - applyFresnelMaterial(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyFresnelMaterial(worldExtent, scene); Sandcastle.highlight(applyFresnelMaterial); } })); @@ -652,8 +652,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Grass', onClick: function() { - togglePolygonVisibility(); - applyGrassMaterial(polygon, scene); + toggleExtentVisibility(); + applyGrassMaterial(extent, scene); Sandcastle.highlight(applyGrassMaterial); } })); @@ -661,8 +661,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Grid', onClick: function() { - togglePolygonVisibility(polygon, worldPolygon); - applyGridMaterial(polygon, scene); + toggleExtentVisibility(extent, worldExtent); + applyGridMaterial(extent, scene); Sandcastle.highlight(applyGridMaterial); } })); @@ -670,8 +670,8 @@ commonMaterialMenu.addChild(new MenuItem({ label: 'Image', onClick: function() { - togglePolygonVisibility(); - applyImageMaterial(polygon, scene); + toggleExtentVisibility(); + applyImageMaterial(extent, scene); Sandcastle.highlight(applyImageMaterial); } })); @@ -679,8 +679,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Normal Map', onClick: function() { - togglePolygonVisibility(); - applyNormalMapMaterial(polygon, scene); + toggleExtentVisibility(); + applyNormalMapMaterial(extent, scene); Sandcastle.highlight(applyNormalMapMaterial); } })); @@ -688,8 +688,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Reflection', onClick: function() { - toggleWorldPolygonVisibility(); - applyReflectionMaterial(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyReflectionMaterial(worldExtent, scene); Sandcastle.highlight(applyReflectionMaterial); } })); @@ -697,8 +697,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Refraction', onClick: function() { - toggleWorldPolygonVisibility(); - applyRefractionMaterial(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyRefractionMaterial(worldExtent, scene); Sandcastle.highlight(applyRefractionMaterial); } })); @@ -706,8 +706,8 @@ baseMaterialMenu.addChild(new MenuItem({ label: 'Specular Map', onClick: function() { - togglePolygonVisibility(); - applySpecularMapMaterial(polygon, scene); + toggleExtentVisibility(); + applySpecularMapMaterial(extent, scene); Sandcastle.highlight(applySpecularMapMaterial); } })); @@ -715,8 +715,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Stripe', onClick: function() { - togglePolygonVisibility(); - applyStripeMaterial(polygon, scene); + toggleExtentVisibility(); + applyStripeMaterial(extent, scene); Sandcastle.highlight(applyStripeMaterial); } })); @@ -724,8 +724,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'TieDye', onClick: function() { - togglePolygonVisibility(); - applyTieDyeMaterial(polygon, scene); + toggleExtentVisibility(); + applyTieDyeMaterial(extent, scene); Sandcastle.highlight(applyTieDyeMaterial); } })); @@ -733,8 +733,8 @@ proceduralTextureMenu.addChild(new MenuItem({ label: 'Wood', onClick: function() { - togglePolygonVisibility(); - applyWoodMaterial(polygon, scene); + toggleExtentVisibility(); + applyWoodMaterial(extent, scene); Sandcastle.highlight(applyWoodMaterial); } })); @@ -742,8 +742,8 @@ miscMaterialMenu.addChild(new MenuItem({ label: 'Water', onClick: function() { - toggleWorldPolygonVisibility(); - applyWaterMaterial(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyWaterMaterial(worldExtent, scene); Sandcastle.highlight(applyWaterMaterial); } })); @@ -751,8 +751,8 @@ miscMaterialMenu.addChild(new MenuItem({ label: 'Erosion', onClick: function() { - togglePolygonVisibility(); - applyErosionMaterial(polygon, scene); + toggleExtentVisibility(); + applyErosionMaterial(extent, scene); Sandcastle.highlight(applyErosionMaterial); } })); @@ -760,8 +760,8 @@ miscMaterialMenu.addChild(new MenuItem({ label: 'RimLighting', onClick: function() { - toggleWorldPolygonVisibility(); - applyRimLightingMaterial(worldPolygon, scene); + toggleWorldExtentVisibility(); + applyRimLightingMaterial(worldExtent, scene); Sandcastle.highlight(applyRimLightingMaterial); } })); @@ -835,22 +835,25 @@ var primitives = scene.getPrimitives(); var ellipsoid = widget.centralBody.getEllipsoid(); - polygon = new Cesium.Polygon(); - polygon.configureExtent(new Cesium.Extent( - Cesium.Math.toRadians(-120.0), - Cesium.Math.toRadians(20.0), - Cesium.Math.toRadians(-60.0), - Cesium.Math.toRadians(40.0))); - primitives.add(polygon); - - worldPolygon = new Cesium.Polygon(); - worldPolygon.configureExtent(new Cesium.Extent( - Cesium.Math.toRadians(-180.0), - Cesium.Math.toRadians(-90.0), - Cesium.Math.toRadians(180.0), - Cesium.Math.toRadians(90.0))); - worldPolygon.show = false; - primitives.add(worldPolygon); + extent = new Cesium.ExtentPrimitive({ + extent : new Cesium.Extent( + Cesium.Math.toRadians(-120.0), + Cesium.Math.toRadians(20.0), + Cesium.Math.toRadians(-60.0), + Cesium.Math.toRadians(40.0)) + }); + primitives.add(extent); + + worldExtent = new Cesium.Polygon(); + worldExtent = new Cesium.ExtentPrimitive({ + extent : new Cesium.Extent( + Cesium.Math.toRadians(-180.0), + Cesium.Math.toRadians(-90.0), + Cesium.Math.toRadians(180.0), + Cesium.Math.toRadians(90.0)), + show : false + }); + primitives.add(worldExtent); var polylines = new Cesium.PolylineCollection(); polyline = polylines.add({ diff --git a/Apps/Sandcastle/gallery/Picking.html b/Apps/Sandcastle/gallery/Picking.html index 33e4f8a117c9..388d4758caec 100644 --- a/Apps/Sandcastle/gallery/Picking.html +++ b/Apps/Sandcastle/gallery/Picking.html @@ -267,8 +267,8 @@ this._ellipsoid = scene.getPrimitives().getCentralBody().getEllipsoid(); this._finishHandler = handler; this._mouseHandler = new Cesium.ScreenSpaceEventHandler(this._canvas); - this._poly = new Cesium.Polygon(); - this._scene.getPrimitives().add(this._poly); + this._extentPrimitive = new Cesium.ExtentPrimitive(); + this._scene.getPrimitives().add(this._extentPrimitive); }; DrawExtentHelper.prototype.enableInput = function() { @@ -315,8 +315,7 @@ }; DrawExtentHelper.prototype.setPolyPts = function(mn, mx) { - var e = this.getExtent(mn, mx); - this._poly.configureExtent(e); + this._extentPrimitive.extent = this.getExtent(mn, mx); }; DrawExtentHelper.prototype.setToDegrees = function(w, s, e, n) { diff --git a/Apps/Sandcastle/gallery/Polygons.html b/Apps/Sandcastle/gallery/Polygons.html index 169501d4bd7e..7ff5c89becac 100644 --- a/Apps/Sandcastle/gallery/Polygons.html +++ b/Apps/Sandcastle/gallery/Polygons.html @@ -42,14 +42,14 @@ ])); primitives.add(polygon); - polygon = new Cesium.Polygon(); + polygon = new Cesium.ExtentPrimitive(); Sandcastle.declare(polygon); - polygon.configureExtent(new Cesium.Extent( + polygon.extent = new Cesium.Extent( Cesium.Math.toRadians(-110.0), Cesium.Math.toRadians(0.0), Cesium.Math.toRadians(-90.0), - Cesium.Math.toRadians(20.0)), - 0.0, Cesium.Math.toRadians(45)); + Cesium.Math.toRadians(20.0)); + polygon.rotation = Cesium.Math.toRadians(45); polygon.material = Cesium.Material.fromType(scene.getContext(), Cesium.Material.ColorType); polygon.material.uniforms.color = {red: 1.0, green: 0.0, blue: 1.0, alpha: 0.75}; primitives.add(polygon); @@ -101,14 +101,13 @@ primitives.add(polygonHierarchy); // Create a polygon from an extent - var polygonExtent = new Cesium.Polygon(); + var polygonExtent = new Cesium.ExtentPrimitive(); Sandcastle.declare(polygonExtent); // For highlighting on mouseover in Sandcastle. - polygonExtent.configureExtent( - new Cesium.Extent( + polygonExtent.extent = new Cesium.Extent( Cesium.Math.toRadians(-180.0), Cesium.Math.toRadians(50.0), Cesium.Math.toRadians(180.0), - Cesium.Math.toRadians(90.0))); + Cesium.Math.toRadians(90.0)); primitives.add(polygonExtent); // Apply a material to a polygon diff --git a/CHANGES.md b/CHANGES.md index da25a98b6626..bf03eb977231 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,10 +4,38 @@ Change Log Beta Releases ------------- -### b19 - 2013-08-05 +### b19 - 2013-08-01 +* Breaking changes: + * Replaced tessellators and meshes with geometry. In particular: + * Replaced `CubeMapEllipsoidTessellator` with `EllipsoidGeometry`. + * Replaced `BoxTessellator` with `BoxGeometry`. + * Replaced `ExtentTessletaor` with `ExtentGeometry`. + * Removed `PlaneTessellator`. It was incomplete and not used. + * Renamed `MeshFilters` to `GeometryPipeline`. + * Renamed `MeshFilters.toWireframeInPlace` to `GeometryPipeline.toWireframe`. + * Removed `MeshFilters.mapAttributeIndices`. It was not used. + * Renamed `Context.createVertexArrayFromMesh` to `Context.createVertexArrayFromGeometry`. Likewise, renamed `mesh` constructor property to `geometry`. + * Renamed `ComponentDatatype.*.toTypedArray` to `ComponentDatatype.*.createTypedArray`. + * Removed `Polygon.configureExtent`. Use `ExtentPrimitive` instead. + * Removed `Polygon.bufferUsage`. It is no longer needed. + * Removed `height` and `textureRotationAngle` arguments from `Polygon` `setPositions` and `configureFromPolygonHierarchy` functions. Use `Polygon` `height` and `textureRotationAngle` properties. + * Renamed `PolygonPipeline.cleanUp` to `PolygonPipeline.removeDuplicates`. + * Removed `PolygonPipeline.wrapLongitude`. Use `GeometryPipeline.wrapLongitude` instead. + * Added `surfaceHeight` parameter to `BoundingSphere.fromExtent3D`. + * Added `surfaceHeight` parameter to `Extent.subsample`. + * Renamed `pointInsideTriangle2D` to `pointInsideTriangle`. * Improved performance and visual quality of `CustomSensorVolume` and `RectangularPyramidSensorVolume`. * Added property `intersectionWidth` to `DynamicCone`, `DynamicPyramid`, `CustomSensorVolume`, and `RectangularPyramidSensorVolume`. +* Added `ExtentPrimitive`. +* Added Geometry and Appearances [#911](https://github.com/AnalyticalGraphicsInc/cesium/pull/911). +* Added `PolylinePipeline.removeDuplicates`. +* Added `barycentricCoordinates` to compute the barycentric coordinates of a point in a triangle. +* Added `BoundingSphere.fromEllipsoid`. +* Added `BoundingSphere.projectTo2D`. +* Added `Extent.fromDegrees`. +* Added `czm_tangentToEyeSpaceMatrix` built-in GLSL function. +* Improved the performance of drawing polygons created with `configureFromPolygonHierarchy`. ### b18 - 2013-07-01 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index bc829b69452d..ff17a3b004aa 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,6 +20,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Hannah Pinkos](https://github.com/hpinkos) * [NICTA](http://www.nicta.com.au/) * [Chris Cooper](https://github.com/chris-cooper) +* [EU Edge](http://euedge.com/) + * [Ákos Maróy](https://github.com/akosmaroy) ## [Individual CLA](http://www.agi.com/licenses/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js index 8b4dbad5533d..d683aaad0804 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -285,15 +285,17 @@ define([ * * @param {Extent} extent The valid extent used to create a bounding sphere. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the extent. + * @param {Number} [surfaceHeight=0.0] The height above the surface of the ellipsoid. * @param {BoundingSphere} [result] The object onto which to store the result. * @return {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. */ - BoundingSphere.fromExtent3D = function(extent, ellipsoid, result) { + BoundingSphere.fromExtent3D = function(extent, ellipsoid, surfaceHeight, result) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + surfaceHeight = defaultValue(surfaceHeight, 0.0); var positions; if (typeof extent !== 'undefined') { - positions = extent.subsample(ellipsoid, fromExtent3DScratch); + positions = extent.subsample(ellipsoid, surfaceHeight, fromExtent3DScratch); } return BoundingSphere.fromPoints(positions, result); @@ -521,6 +523,35 @@ define([ return result; }; + /** + * Creates a bounding sphere encompassing an ellipsoid. + * + * @memberof BoundingSphere + * + * @param {Ellipsoid} ellipsoid The ellipsoid around which to create a bounding sphere. + * @param {BoundingSphere} [result] The object onto which to store the result. + * + * @return {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. + * + * @exception {DeveloperError} ellipsoid is required. + * + * @example + * var boundingSphere = BoundingSphere.fromEllipsoid(ellipsoid); + */ + BoundingSphere.fromEllipsoid = function(ellipsoid, result) { + if (typeof ellipsoid === 'undefined') { + throw new DeveloperError('ellipsoid is required.'); + } + + if (typeof result === 'undefined') { + result = new BoundingSphere(); + } + + Cartesian3.clone(Cartesian3.ZERO, result.center); + result.radius = ellipsoid.getMaximumRadius(); + return result; + }; + /** * Duplicates a BoundingSphere instance. * @memberof BoundingSphere @@ -733,6 +764,118 @@ define([ return result; }; + var projectTo2DNormalScratch = new Cartesian3(); + var projectTo2DEastScratch = new Cartesian3(); + var projectTo2DNorthScratch = new Cartesian3(); + var projectTo2DWestScratch = new Cartesian3(); + var projectTo2DSouthScratch = new Cartesian3(); + var projectTo2DCartographicScratch = new Cartographic(); + var projectTo2DPositionsScratch = new Array(8); + for (var n = 0; n < 8; ++n) { + projectTo2DPositionsScratch[n] = new Cartesian3(); + } + var projectTo2DProjection = new GeographicProjection(); + /** + * Creates a bounding sphere in 2D from a bounding sphere in 3D world coordinates. + * @memberof BoundingSphere + * + * @param {BoundingSphere} sphere The bounding sphere to transform to 2D. + * @param {Object} [projection=GeographicProjection] The projection to 2D. + * @param {BoundingSphere} [result] The object onto which to store the result. + * @return {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. + * + * @exception {DeveloperError} sphere is required. + */ + BoundingSphere.projectTo2D = function(sphere, projection, result) { + if (typeof sphere === 'undefined') { + throw new DeveloperError('sphere is required.'); + } + + projection = defaultValue(projection, projectTo2DProjection); + + var ellipsoid = projection.getEllipsoid(); + var center = sphere.center; + var radius = sphere.radius; + + var normal = ellipsoid.geodeticSurfaceNormal(center, projectTo2DNormalScratch); + var east = Cartesian3.cross(Cartesian3.UNIT_Z, normal, projectTo2DEastScratch); + Cartesian3.normalize(east, east); + var north = Cartesian3.cross(normal, east, projectTo2DNorthScratch); + Cartesian3.normalize(north, north); + + Cartesian3.multiplyByScalar(normal, radius, normal); + Cartesian3.multiplyByScalar(north, radius, north); + Cartesian3.multiplyByScalar(east, radius, east); + + var south = Cartesian3.negate(north, projectTo2DSouthScratch); + var west = Cartesian3.negate(east, projectTo2DWestScratch); + + var positions = projectTo2DPositionsScratch; + + // top NE corner + var corner = positions[0]; + Cartesian3.add(normal, north, corner); + Cartesian3.add(corner, east, corner); + + // top NW corner + corner = positions[1]; + Cartesian3.add(normal, north, corner); + Cartesian3.add(corner, west, corner); + + // top SW corner + corner = positions[2]; + Cartesian3.add(normal, south, corner); + Cartesian3.add(corner, west, corner); + + // top SE corner + corner = positions[3]; + Cartesian3.add(normal, south, corner); + Cartesian3.add(corner, east, corner); + + Cartesian3.negate(normal, normal); + + // bottom NE corner + corner = positions[4]; + Cartesian3.add(normal, north, corner); + Cartesian3.add(corner, east, corner); + + // bottom NW corner + corner = positions[5]; + Cartesian3.add(normal, north, corner); + Cartesian3.add(corner, west, corner); + + // bottom SW corner + corner = positions[6]; + Cartesian3.add(normal, south, corner); + Cartesian3.add(corner, west, corner); + + // bottom SE corner + corner = positions[7]; + Cartesian3.add(normal, south, corner); + Cartesian3.add(corner, east, corner); + + var length = positions.length; + for (var i = 0; i < length; ++i) { + var position = positions[i]; + Cartesian3.add(center, position, position); + var cartographic = ellipsoid.cartesianToCartographic(position, projectTo2DCartographicScratch); + projection.project(cartographic, position); + } + + result = BoundingSphere.fromPoints(positions, result); + + // swizzle center components + center = result.center; + var x = center.x; + var y = center.y; + var z = center.z; + center.x = z; + center.y = x; + center.z = y; + + return result; + }; + /** * Compares the provided BoundingSphere componentwise and returns * true if they are equal, false otherwise. @@ -840,6 +983,18 @@ define([ return BoundingSphere.getPlaneDistances(this, position, direction, result); }; + /** + * Creates a bounding sphere in 2D from this bounding sphere. This bounding sphere must be in 3D world coordinates. + * @memberof BoundingSphere + * + * @param {Object} [projection=GeographicProjection] The projection to 2D. + * @param {BoundingSphere} [result] The object onto which to store the result. + * @return {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. + */ + BoundingSphere.prototype.projectTo2D = function(projection, result) { + return BoundingSphere.projectTo2D(this, projection, result); + }; + /** * Compares this BoundingSphere against the provided BoundingSphere componentwise and returns * true if they are equal, false otherwise. diff --git a/Source/Core/BoxGeometry.js b/Source/Core/BoxGeometry.js new file mode 100644 index 000000000000..669b4f75adeb --- /dev/null +++ b/Source/Core/BoxGeometry.js @@ -0,0 +1,727 @@ +/*global define*/ +define([ + './DeveloperError', + './Cartesian3', + './ComponentDatatype', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat' + ], function( + DeveloperError, + Cartesian3, + ComponentDatatype, + PrimitiveType, + defaultValue, + BoundingSphere, + GeometryAttribute, + GeometryAttributes, + VertexFormat) { + "use strict"; + + var diffScratch = new Cartesian3(); + + /** + * A {@link Geometry} that represents vertices and indices for a cube centered at the origin. + * + * @alias BoxGeometry + * @constructor + * + * @param {Cartesian3} options.minimumCorner The minimum x, y, and z coordinates of the box. + * @param {Cartesian3} options.maximumCorner The maximum x, y, and z coordinates of the box. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} options.minimumCorner is required. + * @exception {DeveloperError} options.maximumCorner is required. + * + * @example + * var box = new BoxGeometry({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + * minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + * }); + */ + var BoxGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var min = options.minimumCorner; + var max = options.maximumCorner; + + if (typeof min === 'undefined') { + throw new DeveloperError('options.minimumCorner is required.'); + } + + if (typeof max === 'undefined') { + throw new DeveloperError('options.maximumCorner is required'); + } + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + var attributes = new GeometryAttributes(); + var indices; + var positions; + + if (vertexFormat !== VertexFormat.POSITION_ONLY) { + if (vertexFormat.position) { + // 8 corner points. Duplicated 3 times each for each incident edge/face. + positions = new Float64Array(6 * 4 * 3); + + // +z face + positions[0] = min.x; + positions[1] = min.y; + positions[2] = max.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = max.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = max.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = max.z; + + // -z face + positions[12] = min.x; + positions[13] = min.y; + positions[14] = min.z; + positions[15] = max.x; + positions[16] = min.y; + positions[17] = min.z; + positions[18] = max.x; + positions[19] = max.y; + positions[20] = min.z; + positions[21] = min.x; + positions[22] = max.y; + positions[23] = min.z; + + // +x face + positions[24] = max.x; + positions[25] = min.y; + positions[26] = min.z; + positions[27] = max.x; + positions[28] = max.y; + positions[29] = min.z; + positions[30] = max.x; + positions[31] = max.y; + positions[32] = max.z; + positions[33] = max.x; + positions[34] = min.y; + positions[35] = max.z; + + // -x face + positions[36] = min.x; + positions[37] = min.y; + positions[38] = min.z; + positions[39] = min.x; + positions[40] = max.y; + positions[41] = min.z; + positions[42] = min.x; + positions[43] = max.y; + positions[44] = max.z; + positions[45] = min.x; + positions[46] = min.y; + positions[47] = max.z; + + // +y face + positions[48] = min.x; + positions[49] = max.y; + positions[50] = min.z; + positions[51] = max.x; + positions[52] = max.y; + positions[53] = min.z; + positions[54] = max.x; + positions[55] = max.y; + positions[56] = max.z; + positions[57] = min.x; + positions[58] = max.y; + positions[59] = max.z; + + // -y face + positions[60] = min.x; + positions[61] = min.y; + positions[62] = min.z; + positions[63] = max.x; + positions[64] = min.y; + positions[65] = min.z; + positions[66] = max.x; + positions[67] = min.y; + positions[68] = max.z; + positions[69] = min.x; + positions[70] = min.y; + positions[71] = max.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + } + + if (vertexFormat.normal) { + var normals = new Float32Array(6 * 4 * 3); + + // +z face + normals[0] = 0.0; + normals[1] = 0.0; + normals[2] = 1.0; + normals[3] = 0.0; + normals[4] = 0.0; + normals[5] = 1.0; + normals[6] = 0.0; + normals[7] = 0.0; + normals[8] = 1.0; + normals[9] = 0.0; + normals[10] = 0.0; + normals[11] = 1.0; + + // -z face + normals[12] = 0.0; + normals[13] = 0.0; + normals[14] = -1.0; + normals[15] = 0.0; + normals[16] = 0.0; + normals[17] = -1.0; + normals[18] = 0.0; + normals[19] = 0.0; + normals[20] = -1.0; + normals[21] = 0.0; + normals[22] = 0.0; + normals[23] = -1.0; + + // +x face + normals[24] = 1.0; + normals[25] = 0.0; + normals[26] = 0.0; + normals[27] = 1.0; + normals[28] = 0.0; + normals[29] = 0.0; + normals[30] = 1.0; + normals[31] = 0.0; + normals[32] = 0.0; + normals[33] = 1.0; + normals[34] = 0.0; + normals[35] = 0.0; + + // -x face + normals[36] = -1.0; + normals[37] = 0.0; + normals[38] = 0.0; + normals[39] = -1.0; + normals[40] = 0.0; + normals[41] = 0.0; + normals[42] = -1.0; + normals[43] = 0.0; + normals[44] = 0.0; + normals[45] = -1.0; + normals[46] = 0.0; + normals[47] = 0.0; + + // +y face + normals[48] = 0.0; + normals[49] = 1.0; + normals[50] = 0.0; + normals[51] = 0.0; + normals[52] = 1.0; + normals[53] = 0.0; + normals[54] = 0.0; + normals[55] = 1.0; + normals[56] = 0.0; + normals[57] = 0.0; + normals[58] = 1.0; + normals[59] = 0.0; + + // -y face + normals[60] = 0.0; + normals[61] = -1.0; + normals[62] = 0.0; + normals[63] = 0.0; + normals[64] = -1.0; + normals[65] = 0.0; + normals[66] = 0.0; + normals[67] = -1.0; + normals[68] = 0.0; + normals[69] = 0.0; + normals[70] = -1.0; + normals[71] = 0.0; + + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.st) { + var texCoords = new Float32Array(6 * 4 * 2); + + // +z face + texCoords[0] = 0.0; + texCoords[1] = 0.0; + texCoords[2] = 1.0; + texCoords[3] = 0.0; + texCoords[4] = 1.0; + texCoords[5] = 1.0; + texCoords[6] = 0.0; + texCoords[7] = 1.0; + + // -z face + texCoords[8] = 1.0; + texCoords[9] = 0.0; + texCoords[10] = 0.0; + texCoords[11] = 0.0; + texCoords[12] = 0.0; + texCoords[13] = 1.0; + texCoords[14] = 1.0; + texCoords[15] = 1.0; + + //+x face + texCoords[16] = 0.0; + texCoords[17] = 0.0; + texCoords[18] = 1.0; + texCoords[19] = 0.0; + texCoords[20] = 1.0; + texCoords[21] = 1.0; + texCoords[22] = 0.0; + texCoords[23] = 1.0; + + // -x face + texCoords[24] = 1.0; + texCoords[25] = 0.0; + texCoords[26] = 0.0; + texCoords[27] = 0.0; + texCoords[28] = 0.0; + texCoords[29] = 1.0; + texCoords[30] = 1.0; + texCoords[31] = 1.0; + + // +y face + texCoords[32] = 1.0; + texCoords[33] = 0.0; + texCoords[34] = 0.0; + texCoords[35] = 0.0; + texCoords[36] = 0.0; + texCoords[37] = 1.0; + texCoords[38] = 1.0; + texCoords[39] = 1.0; + + // -y face + texCoords[40] = 0.0; + texCoords[41] = 0.0; + texCoords[42] = 1.0; + texCoords[43] = 0.0; + texCoords[44] = 1.0; + texCoords[45] = 1.0; + texCoords[46] = 0.0; + texCoords[47] = 1.0; + + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : texCoords + }); + } + + if (vertexFormat.tangent) { + var tangents = new Float32Array(6 * 4 * 3); + + // +z face + tangents[0] = 1.0; + tangents[1] = 0.0; + tangents[2] = 0.0; + tangents[3] = 1.0; + tangents[4] = 0.0; + tangents[5] = 0.0; + tangents[6] = 1.0; + tangents[7] = 0.0; + tangents[8] = 0.0; + tangents[9] = 1.0; + tangents[10] = 0.0; + tangents[11] = 0.0; + + // -z face + tangents[12] = -1.0; + tangents[13] = 0.0; + tangents[14] = 0.0; + tangents[15] = -1.0; + tangents[16] = 0.0; + tangents[17] = 0.0; + tangents[18] = -1.0; + tangents[19] = 0.0; + tangents[20] = 0.0; + tangents[21] = -1.0; + tangents[22] = 0.0; + tangents[23] = 0.0; + + // +x face + tangents[24] = 0.0; + tangents[25] = 1.0; + tangents[26] = 0.0; + tangents[27] = 0.0; + tangents[28] = 1.0; + tangents[29] = 0.0; + tangents[30] = 0.0; + tangents[31] = 1.0; + tangents[32] = 0.0; + tangents[33] = 0.0; + tangents[34] = 1.0; + tangents[35] = 0.0; + + // -x face + tangents[36] = 0.0; + tangents[37] = -1.0; + tangents[38] = 0.0; + tangents[39] = 0.0; + tangents[40] = -1.0; + tangents[41] = 0.0; + tangents[42] = 0.0; + tangents[43] = -1.0; + tangents[44] = 0.0; + tangents[45] = 0.0; + tangents[46] = -1.0; + tangents[47] = 0.0; + + // +y face + tangents[48] = -1.0; + tangents[49] = 0.0; + tangents[50] = 0.0; + tangents[51] = -1.0; + tangents[52] = 0.0; + tangents[53] = 0.0; + tangents[54] = -1.0; + tangents[55] = 0.0; + tangents[56] = 0.0; + tangents[57] = -1.0; + tangents[58] = 0.0; + tangents[59] = 0.0; + + // -y face + tangents[60] = 1.0; + tangents[61] = 0.0; + tangents[62] = 0.0; + tangents[63] = 1.0; + tangents[64] = 0.0; + tangents[65] = 0.0; + tangents[66] = 1.0; + tangents[67] = 0.0; + tangents[68] = 0.0; + tangents[69] = 1.0; + tangents[70] = 0.0; + tangents[71] = 0.0; + + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + var binormals = new Float32Array(6 * 4 * 3); + + // +z face + binormals[0] = 0.0; + binormals[1] = 1.0; + binormals[2] = 0.0; + binormals[3] = 0.0; + binormals[4] = 1.0; + binormals[5] = 0.0; + binormals[6] = 0.0; + binormals[7] = 1.0; + binormals[8] = 0.0; + binormals[9] = 0.0; + binormals[10] = 1.0; + binormals[11] = 0.0; + + // -z face + binormals[12] = 0.0; + binormals[13] = 1.0; + binormals[14] = 0.0; + binormals[15] = 0.0; + binormals[16] = 1.0; + binormals[17] = 0.0; + binormals[18] = 0.0; + binormals[19] = 1.0; + binormals[20] = 0.0; + binormals[21] = 0.0; + binormals[22] = 1.0; + binormals[23] = 0.0; + + // +x face + binormals[24] = 0.0; + binormals[25] = 0.0; + binormals[26] = 1.0; + binormals[27] = 0.0; + binormals[28] = 0.0; + binormals[29] = 1.0; + binormals[30] = 0.0; + binormals[31] = 0.0; + binormals[32] = 1.0; + binormals[33] = 0.0; + binormals[34] = 0.0; + binormals[35] = 1.0; + + // -x face + binormals[36] = 0.0; + binormals[37] = 0.0; + binormals[38] = 1.0; + binormals[39] = 0.0; + binormals[40] = 0.0; + binormals[41] = 1.0; + binormals[42] = 0.0; + binormals[43] = 0.0; + binormals[44] = 1.0; + binormals[45] = 0.0; + binormals[46] = 0.0; + binormals[47] = 1.0; + + // +y face + binormals[48] = 0.0; + binormals[49] = 0.0; + binormals[50] = 1.0; + binormals[51] = 0.0; + binormals[52] = 0.0; + binormals[53] = 1.0; + binormals[54] = 0.0; + binormals[55] = 0.0; + binormals[56] = 1.0; + binormals[57] = 0.0; + binormals[58] = 0.0; + binormals[59] = 1.0; + + // -y face + binormals[60] = 0.0; + binormals[61] = 0.0; + binormals[62] = 1.0; + binormals[63] = 0.0; + binormals[64] = 0.0; + binormals[65] = 1.0; + binormals[66] = 0.0; + binormals[67] = 0.0; + binormals[68] = 1.0; + binormals[69] = 0.0; + binormals[70] = 0.0; + binormals[71] = 1.0; + + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + + // 12 triangles: 6 faces, 2 triangles each. + indices = new Uint16Array(6 * 2 * 3); + + // +z face + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 0; + indices[4] = 2; + indices[5] = 3; + + // -z face + indices[6] = 4 + 2; + indices[7] = 4 + 1; + indices[8] = 4 + 0; + indices[9] = 4 + 3; + indices[10] = 4 + 2; + indices[11] = 4 + 0; + + // +x face + indices[12] = 8 + 0; + indices[13] = 8 + 1; + indices[14] = 8 + 2; + indices[15] = 8 + 0; + indices[16] = 8 + 2; + indices[17] = 8 + 3; + + // -x face + indices[18] = 12 + 2; + indices[19] = 12 + 1; + indices[20] = 12 + 0; + indices[21] = 12 + 3; + indices[22] = 12 + 2; + indices[23] = 12 + 0; + + // +y face + indices[24] = 16 + 2; + indices[25] = 16 + 1; + indices[26] = 16 + 0; + indices[27] = 16 + 3; + indices[28] = 16 + 2; + indices[29] = 16 + 0; + + // -y face + indices[30] = 20 + 0; + indices[31] = 20 + 1; + indices[32] = 20 + 2; + indices[33] = 20 + 0; + indices[34] = 20 + 2; + indices[35] = 20 + 3; + } else { + // Positions only - no need to duplicate corner points + positions = new Float64Array(8 * 3); + + positions[0] = min.x; + positions[1] = min.y; + positions[2] = min.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = min.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = min.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = min.z; + positions[12] = min.x; + positions[13] = min.y; + positions[14] = max.z; + positions[15] = max.x; + positions[16] = min.y; + positions[17] = max.z; + positions[18] = max.x; + positions[19] = max.y; + positions[20] = max.z; + positions[21] = min.x; + positions[22] = max.y; + positions[23] = max.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + // 12 triangles: 6 faces, 2 triangles each. + indices = new Uint16Array(6 * 2 * 3); + + // plane z = corner.Z + indices[0] = 4; + indices[1] = 5; + indices[2] = 6; + indices[3] = 4; + indices[4] = 6; + indices[5] = 7; + + // plane z = -corner.Z + indices[6] = 1; + indices[7] = 0; + indices[8] = 3; + indices[9] = 1; + indices[10] = 3; + indices[11] = 2; + + // plane x = corner.X + indices[12] = 1; + indices[13] = 6; + indices[14] = 5; + indices[15] = 1; + indices[16] = 2; + indices[17] = 6; + + // plane y = corner.Y + indices[18] = 2; + indices[19] = 3; + indices[20] = 7; + indices[21] = 2; + indices[22] = 7; + indices[23] = 6; + + // plane x = -corner.X + indices[24] = 3; + indices[25] = 0; + indices[26] = 4; + indices[27] = 3; + indices[28] = 4; + indices[29] = 7; + + // plane y = -corner.Y + indices[30] = 0; + indices[31] = 1; + indices[32] = 5; + indices[33] = 0; + indices[34] = 5; + indices[35] = 4; + } + + var diff = Cartesian3.subtract(max, min, diffScratch); + var radius = diff.magnitude() * 0.5; + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.TRIANGLES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = new BoundingSphere(Cartesian3.ZERO, radius); + }; + + /** + * Creates vertices and indices for a cube centered at the origin given its dimensions. + * @memberof BoxGeometry + * + * @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the Cartesian3, respectively. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} options.dimensions is required. + * @exception {DeveloperError} All dimensions components must be greater than or equal to zero. + * + * @example + * var box = BoxGeometry.fromDimensions({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * dimensions : new Cartesian3(500000.0, 500000.0, 500000.0) + * }); + */ + BoxGeometry.fromDimensions = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var dimensions = options.dimensions; + if (typeof dimensions === 'undefined') { + throw new DeveloperError('options.dimensions is required.'); + } + + if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) { + throw new DeveloperError('All dimensions components must be greater than or equal to zero.'); + } + + var corner = dimensions.multiplyByScalar(0.5); + var min = corner.negate(); + var max = corner; + + var newOptions = { + minimumCorner : min, + maximumCorner : max, + vertexFormat : options.vertexFormat + }; + return new BoxGeometry(newOptions); + }; + + return BoxGeometry; +}); \ No newline at end of file diff --git a/Source/Core/BoxTessellator.js b/Source/Core/BoxTessellator.js deleted file mode 100644 index 3e96251b0457..000000000000 --- a/Source/Core/BoxTessellator.js +++ /dev/null @@ -1,96 +0,0 @@ -/*global define*/ -define([ - './DeveloperError', - './Cartesian3', - './ComponentDatatype', - './PrimitiveType', - './defaultValue' - ], function( - DeveloperError, - Cartesian3, - ComponentDatatype, - PrimitiveType, - defaultValue) { - "use strict"; - - /** - * DOC_TBA - * - * @alias BoxTessellator - * @exports BoxTessellator - * - * @see CubeMapEllipsoidTessellator - * @see PlaneTessellator - */ - var BoxTessellator = { - /** - * DOC_TBA - * - * @exception {DeveloperError} All dimensions' components must be greater than or equal to zero. - */ - compute : function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - var minimumCorner; - var maximumCorner; - - if (typeof options.minimumCorner !== 'undefined' && typeof options.maximumCorner !== 'undefined') { - minimumCorner = options.minimumCorner; - maximumCorner = options.maximumCorner; - } else { - var dimensions = typeof options.dimensions !== 'undefined' ? options.dimensions : new Cartesian3(1.0, 1.0, 1.0); - - if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) { - throw new DeveloperError('All dimensions components must be greater than or equal to zero.'); - } - - var corner = dimensions.multiplyByScalar(0.5); - minimumCorner = corner.negate(); - maximumCorner = corner; - } - - var mesh = {}; - mesh.attributes = {}; - mesh.indexLists = []; - - // 8 corner points. - mesh.attributes.position = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : [ - minimumCorner.x, minimumCorner.y, minimumCorner.z, - maximumCorner.x, minimumCorner.y, minimumCorner.z, - maximumCorner.x, maximumCorner.y, minimumCorner.z, - minimumCorner.x, maximumCorner.y, minimumCorner.z, - minimumCorner.x, minimumCorner.y, maximumCorner.z, - maximumCorner.x, minimumCorner.y, maximumCorner.z, - maximumCorner.x, maximumCorner.y, maximumCorner.z, - minimumCorner.x, maximumCorner.y, maximumCorner.z - ] - }; - - // 12 triangles: 6 faces, 2 triangles each. - mesh.indexLists.push({ - primitiveType : PrimitiveType.TRIANGLES, - values : [ - 4, 5, 6, // Top: plane z = corner.Z - 4, 6, 7, - 1, 0, 3, // Bottom: plane z = -corner.Z - 1, 3, 2, - 1, 6, 5, // Side: plane x = corner.X - 1, 2, 6, - 2, 3, 7, // Side: plane y = corner.Y - 2, 7, 6, - 3, 0, 4, // Side: plane x = -corner.X - 3, 4, 7, - 0, 1, 5, // Side: plane y = -corner.Y - 0, 5, 4 - ] - }); - - return mesh; - } - }; - - return BoxTessellator; -}); \ No newline at end of file diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js new file mode 100644 index 000000000000..53a139a17d32 --- /dev/null +++ b/Source/Core/CircleGeometry.js @@ -0,0 +1,97 @@ +/*global define*/ +define([ + './clone', + './defaultValue', + './DeveloperError', + './EllipseGeometry' + ], function( + clone, + defaultValue, + DeveloperError, + EllipseGeometry) { + "use strict"; + + /** + * A {@link Geometry} that represents vertices and indices for a circle on the ellipsoid. + * + * @alias CircleGeometry + * @constructor + * + * @param {Cartesian3} options.center The circle's center point in the fixed frame. + * @param {Number} options.radius The radius in meters. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on. + * @param {Number} [options.height=0.0] The height above the ellipsoid. + * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} center is required. + * @exception {DeveloperError} radius is required. + * @exception {DeveloperError} radius must be greater than zero. + * @exception {DeveloperError} granularity must be greater than zero. + * + * @example + * // Create a circle. + * var ellipsoid = Ellipsoid.WGS84; + * var circle = new CircleGeometry({ + * ellipsoid : ellipsoid, + * center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883)), + * radius : 100000.0 + * }); + */ + var CircleGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var radius = options.radius; + + if (typeof radius === 'undefined') { + throw new DeveloperError('radius is required.'); + } + + if (radius <= 0.0) { + throw new DeveloperError('radius must be greater than zero.'); + } + + var ellipseGeometryOptions = { + center : options.center, + semiMajorAxis : radius, + semiMinorAxis : radius, + ellipsoid : options.ellipsoid, + height : options.height, + granularity : options.granularity, + vertexFormat : options.vertexFormat + }; + var ellipseGeometry = new EllipseGeometry(ellipseGeometryOptions); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = ellipseGeometry.attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = ellipseGeometry.indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = ellipseGeometry.primitiveType; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = ellipseGeometry.boundingSphere; + }; + + return CircleGeometry; +}); \ No newline at end of file diff --git a/Source/Core/ColorGeometryInstanceAttribute.js b/Source/Core/ColorGeometryInstanceAttribute.js new file mode 100644 index 000000000000..76b68dff32e9 --- /dev/null +++ b/Source/Core/ColorGeometryInstanceAttribute.js @@ -0,0 +1,145 @@ +/*global define*/ +define([ + './defaultValue', + './Color', + './ComponentDatatype', + './DeveloperError' + ], function( + defaultValue, + Color, + ComponentDatatype, + DeveloperError) { + "use strict"; + + /** + * Value and type information for per-instance geometry color. + * + * @alias ColorGeometryInstanceAttribute + * @constructor + * + * @param {Number} [red=1.0] The red component. + * @param {Number} [green=1.0] The green component. + * @param {Number} [blue=1.0] The blue component. + * @param {Number} [alpha=1.0] The alpha component. + * + * @example + * var instance = new GeometryInstance({ + * geometry : new BoxGeometry({ + * dimensions : new Cartesian3(1000000.0, 1000000.0, 500000.0) + * }), + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-0.0, 0.0))), new Cartesian3(0.0, 0.0, 1000000.0)), + * id : 'box', + * attributes : { + * color : new ColorGeometryInstanceAttribute(red, green, blue, alpha) + * } + * }); + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + */ + var ColorGeometryInstanceAttribute = function(red, green, blue, alpha) { + red = defaultValue(red, 1.0); + green = defaultValue(green, 1.0); + blue = defaultValue(blue, 1.0); + alpha = defaultValue(alpha, 1.0); + + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link ColorGeometryInstanceAttribute#value}. + * + * @type ComponentDatatype + * + * @default {@link ComponentDatatype.UNSIGNED_BYTE} + * + * @readonly + */ + this.componentDatatype = ComponentDatatype.UNSIGNED_BYTE; + + /** + * The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}. + * + * @type Number + * + * @default 4 + * + * @readonly + */ + this.componentsPerAttribute = 4; + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @type Boolean + * + * @default true + * + * @readonly + */ + this.normalize = true; + + /** + * The values for the attributes stored in a typed array. + * + * @type Uint8Array + * + * @default [255, 255, 255, 255] + */ + this.value = new Uint8Array([ + Color.floatToByte(red), + Color.floatToByte(green), + Color.floatToByte(blue), + Color.floatToByte(alpha) + ]); + }; + + /** + * Creates a new {@link ColorGeometryInstanceAttribute} instance given the provided {@link Color}. + * + * @param {Color} color The color. + * + * @returns {ColorGeometryInstanceAttribute} The new {@link ColorGeometryInstanceAttribute} instance. + * + * @exception {DeveloperError} color is required. + * + * @example + * var instance = new GeometryInstance({ + * geometry : // ... + * attributes : { + * color : ColorGeometryInstanceAttribute.fromColor(Color.CORNFLOWERBLUE), + * } + * }); + */ + ColorGeometryInstanceAttribute.fromColor = function(color) { + if (typeof color === 'undefined') { + throw new DeveloperError('color is required.'); + } + + return new ColorGeometryInstanceAttribute(color.red, color.green, color.blue, color.alpha); + }; + + /** + * Converts a color to a typed array that can be used to assign a color attribute. + * + * @param {Color} color The color. + * + * @returns {Uint8Array} The typed array in the attribute's format. + * + * @exception {DeveloperError} color is required. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = ColorGeometryInstanceAttribute.toValue(Color.AQUA); + */ + ColorGeometryInstanceAttribute.toValue = function(color) { + if (typeof color === 'undefined') { + throw new DeveloperError('color is required.'); + } + + return new Uint8Array(color.toBytes()); + }; + + return ColorGeometryInstanceAttribute; +}); diff --git a/Source/Core/ComponentDatatype.js b/Source/Core/ComponentDatatype.js index 772249d76cfa..841b56431f12 100644 --- a/Source/Core/ComponentDatatype.js +++ b/Source/Core/ComponentDatatype.js @@ -12,7 +12,8 @@ define(['./Enumeration'], function(Enumeration) { } /** - * DOC_TBA + * Enumerations for WebGL component datatypes. Components are intrinsics, + * which form attributes, which form vertices. * * @alias ComponentDatatype * @enumeration @@ -20,17 +21,19 @@ define(['./Enumeration'], function(Enumeration) { var ComponentDatatype = {}; /** - * DOC_TBA + * 8-bit signed byte enumeration corresponding to gl.BYTE and the type + * of an element in Int8Array. + * + * @memberOf ComponentDatatype * * @type {Enumeration} * @constant * @default 0x1400 - * @memberOf ComponentDatatype */ ComponentDatatype.BYTE = new Enumeration(0x1400, 'BYTE'); ComponentDatatype.BYTE.sizeInBytes = Int8Array.BYTES_PER_ELEMENT; - ComponentDatatype.BYTE.toTypedArray = function(values) { - return new Int8Array(values); + ComponentDatatype.BYTE.createTypedArray = function(valuesOrLength) { + return new Int8Array(valuesOrLength); }; ComponentDatatype.BYTE.createArrayBufferView = function(buffer, byteOffset) { @@ -38,17 +41,19 @@ define(['./Enumeration'], function(Enumeration) { }; /** - * DOC_TBA + * 8-bit unsigned byte enumeration corresponding to UNSIGNED_BYTE and the type + * of an element in Uint8Array. + * + * @memberOf ComponentDatatype * * @type {Enumeration} * @constant * @default 0x1401 - * @memberOf ComponentDatatype */ ComponentDatatype.UNSIGNED_BYTE = new Enumeration(0x1401, 'UNSIGNED_BYTE'); ComponentDatatype.UNSIGNED_BYTE.sizeInBytes = Uint8Array.BYTES_PER_ELEMENT; - ComponentDatatype.UNSIGNED_BYTE.toTypedArray = function(values) { - return new Uint8Array(values); + ComponentDatatype.UNSIGNED_BYTE.createTypedArray = function(valuesOrLength) { + return new Uint8Array(valuesOrLength); }; ComponentDatatype.UNSIGNED_BYTE.createArrayBufferView = function(buffer, byteOffset) { @@ -56,17 +61,19 @@ define(['./Enumeration'], function(Enumeration) { }; /** - * DOC_TBA + * 16-bit signed short enumeration corresponding to SHORT and the type + * of an element in Int16Array. + * + * @memberOf ComponentDatatype * * @type {Enumeration} * @constant * @default 0x1402 - * @memberOf ComponentDatatype */ ComponentDatatype.SHORT = new Enumeration(0x1402, 'SHORT'); ComponentDatatype.SHORT.sizeInBytes = Int16Array.BYTES_PER_ELEMENT; - ComponentDatatype.SHORT.toTypedArray = function(values) { - return new Int16Array(values); + ComponentDatatype.SHORT.createTypedArray = function(valuesOrLength) { + return new Int16Array(valuesOrLength); }; ComponentDatatype.SHORT.createArrayBufferView = function(buffer, byteOffset) { @@ -74,17 +81,19 @@ define(['./Enumeration'], function(Enumeration) { }; /** - * DOC_TBA + * 16-bit unsigned short enumeration corresponding to UNSIGNED_SHORT and the type + * of an element in Uint16Array. + * + * @memberOf ComponentDatatype * * @type {Enumeration} * @constant * @default 0x1403 - * @memberOf ComponentDatatype */ ComponentDatatype.UNSIGNED_SHORT = new Enumeration(0x1403, 'UNSIGNED_SHORT'); ComponentDatatype.UNSIGNED_SHORT.sizeInBytes = Uint16Array.BYTES_PER_ELEMENT; - ComponentDatatype.UNSIGNED_SHORT.toTypedArray = function(values) { - return new Uint16Array(values); + ComponentDatatype.UNSIGNED_SHORT.createTypedArray = function(valuesOrLength) { + return new Uint16Array(valuesOrLength); }; ComponentDatatype.UNSIGNED_SHORT.createArrayBufferView = function(buffer, byteOffset) { @@ -92,17 +101,19 @@ define(['./Enumeration'], function(Enumeration) { }; /** - * DOC_TBA + * 32-bit floating-point enumeration corresponding to FLOAT and the type + * of an element in Float32Array. + * + * @memberOf ComponentDatatype * * @type {Enumeration} * @constant * @default 0x1406 - * @memberOf ComponentDatatype */ ComponentDatatype.FLOAT = new Enumeration(0x1406, 'FLOAT'); ComponentDatatype.FLOAT.sizeInBytes = Float32Array.BYTES_PER_ELEMENT; - ComponentDatatype.FLOAT.toTypedArray = function(values) { - return new Float32Array(values); + ComponentDatatype.FLOAT.createTypedArray = function(valuesOrLength) { + return new Float32Array(valuesOrLength); }; ComponentDatatype.FLOAT.createArrayBufferView = function(buffer, byteOffset) { @@ -110,18 +121,45 @@ define(['./Enumeration'], function(Enumeration) { }; /** - * DOC_TBA + * 64-bit floating-point enumeration corresponding to gl.DOUBLE (in Desktop OpenGL; + * this is not supported in WebGL, and is emulated in Cesium via {@link GeometryPipeline.encodeAttribute}) + * and the type of an element in Float64Array. + * + * @memberOf ComponentDatatype + * + * @type {Enumeration} + * @constant + * @default 0x140A + */ + ComponentDatatype.DOUBLE = new Enumeration(0x140A, 'DOUBLE'); + ComponentDatatype.DOUBLE.sizeInBytes = Float64Array.BYTES_PER_ELEMENT; + ComponentDatatype.DOUBLE.createTypedArray = function(valuesOrLength) { + return new Float64Array(valuesOrLength); + }; + + ComponentDatatype.DOUBLE.createArrayBufferView = function(buffer, byteOffset) { + return new Float64Array(buffer, byteOffset); + }; + + /** + * Validates that the provided component datatype is a valid {@link ComponentDatatype} + * + * @param {ComponentDatatype} componentDatatype The component datatype to validate. * - * @param {ComponentDataType} componentDatatype + * @return {Boolean} true if the provided component datatype is a valid enumeration value; otherwise, false. * - * @returns {Boolean} + * @example + * if (!ComponentDatatype.validate(componentDatatype)) { + * throw new DeveloperError('componentDatatype must be a valid enumeration value.'); + * } */ ComponentDatatype.validate = function(componentDatatype) { return ((componentDatatype === ComponentDatatype.BYTE) || (componentDatatype === ComponentDatatype.UNSIGNED_BYTE) || (componentDatatype === ComponentDatatype.SHORT) || (componentDatatype === ComponentDatatype.UNSIGNED_SHORT) || - (componentDatatype === ComponentDatatype.FLOAT)); + (componentDatatype === ComponentDatatype.FLOAT) || + (componentDatatype === ComponentDatatype.DOUBLE)); }; return ComponentDatatype; diff --git a/Source/Core/CubeMapEllipsoidTessellator.js b/Source/Core/CubeMapEllipsoidTessellator.js deleted file mode 100644 index 56d5d7a5d3a4..000000000000 --- a/Source/Core/CubeMapEllipsoidTessellator.js +++ /dev/null @@ -1,200 +0,0 @@ -/*global define*/ -define([ - './defaultValue', - './DeveloperError', - './Cartesian3', - './ComponentDatatype', - './PrimitiveType' - ], function( - defaultValue, - DeveloperError, - Cartesian3, - ComponentDatatype, - PrimitiveType) { - "use strict"; - - /** - * DOC_TBA - * - * @exports CubeMapEllipsoidTessellator - * - * @see BoxTessellator - */ - var CubeMapEllipsoidTessellator = {}; - - /** - * DOC_TBA - * - * @param {Ellipsoid} ellipsoid DOC_TBA. - * @param {Number} numberOfPartitions DOC_TBA. - * @param {String} attributeName DOC_TBA. - * - * @exception {DeveloperError} numberOfPartitions must be greater than zero. - */ - CubeMapEllipsoidTessellator.compute = function(ellipsoid, numberOfPartitions, attributeName) { - if (numberOfPartitions <= 0) { - throw new DeveloperError('numberOfPartitions must be greater than zero.'); - } - - attributeName = defaultValue(attributeName, 'position'); - - var positions = []; - var indices = []; - - function addEdgePositions(i0, i1) { - var indices = []; - indices[0] = i0; - indices[2 + (numberOfPartitions - 1) - 1] = i1; - - var origin = positions[i0]; - var direction = positions[i1].subtract(positions[i0]); - - for ( var i = 1; i < numberOfPartitions; ++i) { - var delta = i / numberOfPartitions; - - indices[i] = positions.length; - positions.push(origin.add(direction.multiplyByScalar(delta))); - } - - return indices; - } - - function addFaceTriangles(leftBottomToTop, bottomLeftToRight, rightBottomToTop, topLeftToRight) { - var origin = positions[bottomLeftToRight[0]]; - var x = positions[bottomLeftToRight[bottomLeftToRight.length - 1]].subtract(origin); - var y = positions[topLeftToRight[0]].subtract(origin); - - var bottomIndicesBuffer = []; - var topIndicesBuffer = []; - - var bottomIndices = bottomLeftToRight; - var topIndices = topIndicesBuffer; - - for ( var j = 1; j <= numberOfPartitions; ++j) { - if (j !== numberOfPartitions) { - if (j !== 1) { - // - // This copy could be avoided by ping ponging buffers. - // - bottomIndicesBuffer = topIndicesBuffer.slice(0); - bottomIndices = bottomIndicesBuffer; - } - - topIndicesBuffer[0] = leftBottomToTop[j]; - topIndicesBuffer[numberOfPartitions] = rightBottomToTop[j]; - - var deltaY = j / numberOfPartitions; - var offsetY = y.multiplyByScalar(deltaY); - - for ( var i = 1; i < numberOfPartitions; ++i) { - var deltaX = i / numberOfPartitions; - var offsetX = x.multiplyByScalar(deltaX); - - topIndicesBuffer[i] = positions.length; - positions.push(origin.add(offsetX).add(offsetY)); - } - } else { - if (j !== 1) { - bottomIndices = topIndicesBuffer; - } - topIndices = topLeftToRight; - } - - for ( var k = 0; k < numberOfPartitions; ++k) { - indices.push(bottomIndices[k]); - indices.push(bottomIndices[k + 1]); - indices.push(topIndices[k + 1]); - - indices.push(bottomIndices[k]); - indices.push(topIndices[k + 1]); - indices.push(topIndices[k]); - } - } - } - - // - // Initial cube. In the plane, z = -1: - // - // +y - // | - // Q2 * p3 Q1 - // / | \ - // p0 *--+--* p2 +x - // \ | / - // Q3 * p1 Q4 - // | - // - // Similarly, p4 to p7 are in the plane z = 1. - // - positions.push(new Cartesian3(-1, 0, -1)); - positions.push(new Cartesian3(0, -1, -1)); - positions.push(new Cartesian3(1, 0, -1)); - positions.push(new Cartesian3(0, 1, -1)); - positions.push(new Cartesian3(-1, 0, 1)); - positions.push(new Cartesian3(0, -1, 1)); - positions.push(new Cartesian3(1, 0, 1)); - positions.push(new Cartesian3(0, 1, 1)); - - // - // Edges - // - // 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 0. Plane z = -1 - // 4 -> 5, 5 -> 6, 6 -> 7, 7 -> 4. Plane z = 1 - // 0 -> 4, 1 -> 5, 2 -> 6, 3 -> 7. From plane z = -1 to plane z - 1 - // - var edge0to1 = addEdgePositions(0, 1); - var edge1to2 = addEdgePositions(1, 2); - var edge2to3 = addEdgePositions(2, 3); - var edge3to0 = addEdgePositions(3, 0); - - var edge4to5 = addEdgePositions(4, 5); - var edge5to6 = addEdgePositions(5, 6); - var edge6to7 = addEdgePositions(6, 7); - var edge7to4 = addEdgePositions(7, 4); - - var edge0to4 = addEdgePositions(0, 4); - var edge1to5 = addEdgePositions(1, 5); - var edge2to6 = addEdgePositions(2, 6); - var edge3to7 = addEdgePositions(3, 7); - - addFaceTriangles(edge0to4, edge0to1, edge1to5, edge4to5); // Q3 Face - addFaceTriangles(edge1to5, edge1to2, edge2to6, edge5to6); // Q4 Face - addFaceTriangles(edge2to6, edge2to3, edge3to7, edge6to7); // Q1 Face - addFaceTriangles(edge3to7, edge3to0, edge0to4, edge7to4); // Q2 Face - addFaceTriangles(edge7to4.slice(0).reverse(), edge4to5, edge5to6, edge6to7.slice(0).reverse()); // Plane z = 1 - addFaceTriangles(edge1to2, edge0to1.slice(0).reverse(), edge3to0.slice(0).reverse(), edge2to3); // Plane z = -1 - - // Expand cube into ellipsoid and flatten values - var radii = ellipsoid.getRadii(); - var length = positions.length; - var q = 0; - var flattenedPositions = new Array(length * 3); - for ( var i = 0; i < length; ++i) { - var item = positions[i]; - Cartesian3.normalize(item, item); - Cartesian3.multiplyComponents(item, radii, item); - flattenedPositions[q++] = item.x; - flattenedPositions[q++] = item.y; - flattenedPositions[q++] = item.z; - } - - var mesh = {}; - mesh.attributes = {}; - mesh.indexLists = []; - - mesh.attributes[attributeName] = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : flattenedPositions - }; - - mesh.indexLists.push({ - primitiveType : PrimitiveType.TRIANGLES, - values : indices - }); - - return mesh; - }; - - return CubeMapEllipsoidTessellator; -}); \ No newline at end of file diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js new file mode 100644 index 000000000000..153ae6b9df45 --- /dev/null +++ b/Source/Core/EllipseGeometry.js @@ -0,0 +1,478 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingSphere', + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './IndexDatatype', + './DeveloperError', + './Ellipsoid', + './GeographicProjection', + './GeometryAttribute', + './GeometryAttributes', + './Math', + './Matrix3', + './PrimitiveType', + './Quaternion', + './VertexFormat' + ], function( + defaultValue, + BoundingSphere, + Cartesian3, + Cartographic, + ComponentDatatype, + IndexDatatype, + DeveloperError, + Ellipsoid, + GeographicProjection, + GeometryAttribute, + GeometryAttributes, + CesiumMath, + Matrix3, + PrimitiveType, + Quaternion, + VertexFormat) { + "use strict"; + + var rotAxis = new Cartesian3(); + var tempVec = new Cartesian3(); + var unitQuat = new Quaternion(); + var rotMtx = new Matrix3(); + + function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) { + var azimuth = theta + rotation; + + Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis); + Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec); + Cartesian3.add(rotAxis, tempVec, rotAxis); + + var cosThetaSquared = Math.cos(theta); + cosThetaSquared = cosThetaSquared * cosThetaSquared; + + var sinThetaSquared = Math.sin(theta); + sinThetaSquared = sinThetaSquared * sinThetaSquared; + + var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared); + var angle = radius / mag; + + // Create the quaternion to rotate the position vector to the boundary of the ellipse. + Quaternion.fromAxisAngle(rotAxis, angle, unitQuat); + Matrix3.fromQuaternion(unitQuat, rotMtx); + + Matrix3.multiplyByVector(rotMtx, unitPos, result); + Cartesian3.normalize(result, result); + Cartesian3.multiplyByScalar(result, mag, result); + return result; + } + + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + var scratchCartesian4 = new Cartesian3(); + var unitPosScratch = new Cartesian3(); + var eastVecScratch = new Cartesian3(); + var northVecScratch = new Cartesian3(); + var scratchCartographic = new Cartographic(); + var projectedCenterScratch = new Cartesian3(); + + /** + * A {@link Geometry} that represents vertices and indices for an ellipse on the ellipsoid. + * + * @alias EllipseGeometry + * @constructor + * + * @param {Cartesian3} options.center The ellipse's center point in the fixed frame. + * @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters. + * @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on. + * @param {Number} [options.height=0.0] The height above the ellipsoid. + * @param {Number} [options.rotation=0.0] The angle from north (clockwise) in radians. The default is zero. + * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} center is required. + * @exception {DeveloperError} semiMajorAxis is required. + * @exception {DeveloperError} semiMinorAxis is required. + * @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero. + * @exception {DeveloperError} semiMajorAxis must be larger than the semiMajorAxis. + * @exception {DeveloperError} granularity must be greater than zero. + * + * @example + * // Create an ellipse. + * var ellipsoid = Ellipsoid.WGS84; + * var ellipse = new EllipseGeometry({ + * ellipsoid : ellipsoid, + * center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883)), + * semiMajorAxis : 500000.0, + * semiMinorAxis : 300000.0, + * rotation : CesiumMath.toRadians(60.0) + * }); + */ + var EllipseGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var center = options.center; + var semiMajorAxis = options.semiMajorAxis; + var semiMinorAxis = options.semiMinorAxis; + + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var rotation = defaultValue(options.rotation, 0.0); + var height = defaultValue(options.height, 0.0); + var granularity = defaultValue(options.granularity, 0.02); + + if (typeof center === 'undefined') { + throw new DeveloperError('center is required.'); + } + + if (typeof semiMajorAxis === 'undefined') { + throw new DeveloperError('semiMajorAxis is required.'); + } + + if (typeof semiMinorAxis === 'undefined') { + throw new DeveloperError('semiMinorAxis is required.'); + } + + if (semiMajorAxis <= 0.0 || semiMinorAxis <= 0.0) { + throw new DeveloperError('Semi-major and semi-minor axes must be greater than zero.'); + } + + if (granularity <= 0.0) { + throw new DeveloperError('granularity must be greater than zero.'); + } + + if (semiMajorAxis < semiMinorAxis) { + throw new DeveloperError('semiMajorAxis must be larger than the semiMajorAxis.'); + } + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + var MAX_ANOMALY_LIMIT = 2.31; + + var aSqr = semiMinorAxis * semiMinorAxis; + var bSqr = semiMajorAxis * semiMajorAxis; + var ab = semiMajorAxis * semiMinorAxis; + + var mag = center.magnitude(); + + var unitPos = Cartesian3.normalize(center, unitPosScratch); + var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch); + Cartesian3.normalize(eastVec, eastVec); + var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch); + + // The number of points in the first quadrant + var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity); + var deltaTheta = MAX_ANOMALY_LIMIT / (numPts - 1); + + // If the number of points were three, the ellipse + // would be tessellated like below: + // + // *---* + // / | \ | \ + // *---*---*---* + // / | \ | \ | \ | \ + // *---*---*---*---*---* + // | \ | \ | \ | \ | \ | + // *---*---*---*---*---* + // \ | \ | \ | \ | / + // *---*---*---* + // \ | \ | / + // *---* + // Notice each vertical column contains an even number of positions. + // The sum of the first n even numbers is n * (n + 1). Double it for the number of points + // for the whole ellipse. Note: this is just an estimate and may actually be less depending + // on the number of iterations before the angle reaches pi/2. + var size = 2 * numPts * (numPts + 1); + var positions = new Array(size * 3); + var positionIndex = 0; + var position = scratchCartesian1; + var reflectedPosition = scratchCartesian2; + + var i; + var j; + var numInterior; + var t; + var interiorPosition; + + // Compute points in the 'northern' half of the ellipse + var theta = CesiumMath.PI_OVER_TWO; + for (i = 0; i < numPts && theta > 0; ++i) { + pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + positions[positionIndex++] = position.x; + positions[positionIndex++] = position.y; + positions[positionIndex++] = position.z; + + numInterior = 2 * i + 2; + for (j = 1; j < numInterior - 1; ++j) { + t = j / (numInterior - 1); + interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); + positions[positionIndex++] = interiorPosition.x; + positions[positionIndex++] = interiorPosition.y; + positions[positionIndex++] = interiorPosition.z; + } + + positions[positionIndex++] = reflectedPosition.x; + positions[positionIndex++] = reflectedPosition.y; + positions[positionIndex++] = reflectedPosition.z; + + theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta; + } + + // Set numPts if theta reached zero + numPts = i; + + // Compute points in the 'northern' half of the ellipse + for (i = numPts; i > 0; --i) { + theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta; + + pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position); + pointOnEllipsoid( theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition); + + positions[positionIndex++] = position.x; + positions[positionIndex++] = position.y; + positions[positionIndex++] = position.z; + + numInterior = 2 * (i - 1) + 2; + for (j = 1; j < numInterior - 1; ++j) { + t = j / (numInterior - 1); + interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3); + positions[positionIndex++] = interiorPosition.x; + positions[positionIndex++] = interiorPosition.y; + positions[positionIndex++] = interiorPosition.z; + } + + positions[positionIndex++] = reflectedPosition.x; + positions[positionIndex++] = reflectedPosition.y; + positions[positionIndex++] = reflectedPosition.z; + } + + // The original length may have been an over-estimate + if (positions.length !== positionIndex) { + size = positionIndex / 3; + positions.length = positionIndex; + } + + positions = new Float64Array(positions); + + var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined; + var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined; + var binormals = (vertexFormat.binormal) ? new Float32Array(size * 3) : undefined; + + var textureCoordIndex = 0; + + // Raise positions to a height above the ellipsoid and compute the + // texture coordinates, normals, tangents, and binormals. + var normal; + var tangent; + var binormal; + + var projection = new GeographicProjection(ellipsoid); + var projectedCenter = projection.project(ellipsoid.cartesianToCartographic(center, scratchCartographic), projectedCenterScratch); + + var length = positions.length; + for (i = 0; i < length; i += 3) { + position = Cartesian3.fromArray(positions, i, scratchCartesian2); + + if (vertexFormat.st) { + var projectedPoint = projection.project(ellipsoid.cartesianToCartographic(position, scratchCartographic), scratchCartesian3); + var relativeToCenter = Cartesian3.subtract(projectedPoint, projectedCenter, projectedPoint); + textureCoordinates[textureCoordIndex++] = (relativeToCenter.x + semiMajorAxis) / (2.0 * semiMajorAxis); + textureCoordinates[textureCoordIndex++] = (relativeToCenter.y + semiMinorAxis) / (2.0 * semiMinorAxis); + } + + ellipsoid.scaleToGeodeticSurface(position, position); + Cartesian3.add(position, Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(position), height), position); + + if (vertexFormat.position) { + positions[i] = position.x; + positions[i + 1] = position.y; + positions[i + 2] = position.z; + } + + if (vertexFormat.normal) { + normal = ellipsoid.geodeticSurfaceNormal(position, scratchCartesian3); + + normals[i] = normal.x; + normals[i + 1] = normal.y; + normals[i + 2] = normal.z; + } + + if (vertexFormat.tangent) { + normal = ellipsoid.geodeticSurfaceNormal(position, scratchCartesian3); + tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, scratchCartesian3); + + tangents[i] = tangent.x; + tangents[i + 1] = tangent.y; + tangents[i + 2] = tangent.z; + } + + if (vertexFormat.binormal) { + normal = ellipsoid.geodeticSurfaceNormal(position, scratchCartesian3); + tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, scratchCartesian4); + binormal = Cartesian3.cross(normal, tangent, scratchCartesian3); + + binormals[i] = binormal.x; + binormals[i + 1] = binormal.y; + binormals[i + 2] = binormal.z; + } + } + + var attributes = new GeometryAttributes(); + + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + } + + if (vertexFormat.st) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); + } + + if (vertexFormat.normal) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.tangent) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + + // The number of triangles in the ellipse on the positive x half-space and for + // the column of triangles in the middle is: + // + // numTriangles = 4 + 8 + 12 + ... = 4 + (4 + 4) + (4 + 4 + 4) + ... = 4 * (1 + 2 + 3 + ...) + // = 4 * ((n * ( n + 1)) / 2) + // numColumnTriangles = 2 * 2 * n + // total = 2 * numTrangles + numcolumnTriangles + // + // Substitute (numPts - 1.0) for n above + var indicesSize = 2 * numPts * (numPts + 1); + var indices = new Array(indicesSize); + var indicesIndex = 0; + var prevIndex; + + // Indices triangles to the 'left' of the north vector + for (i = 1; i < numPts; ++i) { + positionIndex = i * (i + 1); + prevIndex = (i - 1) * i; + + indices[indicesIndex++] = positionIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + + numInterior = 2 * i; + for (j = 0; j < numInterior - 1; ++j) { + + indices[indicesIndex++] = positionIndex; + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = prevIndex; + + indices[indicesIndex++] = positionIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + } + + indices[indicesIndex++] = positionIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + } + + // Indices for central column of triangles + numInterior = numPts * 2; + ++positionIndex; + ++prevIndex; + for (i = 0; i < numInterior - 1; ++i) { + indices[indicesIndex++] = positionIndex; + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = prevIndex; + + indices[indicesIndex++] = positionIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + } + + // Reverse the process creating indices to the 'right' of the north vector + ++prevIndex; + ++positionIndex; + for (i = numPts - 1; i > 0; --i) { + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + + numInterior = 2 * i; + for (j = 0; j < numInterior - 1; ++j) { + indices[indicesIndex++] = positionIndex; + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = prevIndex; + + indices[indicesIndex++] = positionIndex++; + indices[indicesIndex++] = prevIndex; + indices[indicesIndex++] = positionIndex; + } + + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = prevIndex++; + indices[indicesIndex++] = positionIndex++; + } + + var boundingSphereCenter = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center), height); + Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter); + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = IndexDatatype.createTypedArray(positions.length / 3, indices); + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.TRIANGLES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = new BoundingSphere(boundingSphereCenter, semiMajorAxis); + }; + + return EllipseGeometry; +}); \ No newline at end of file diff --git a/Source/Core/EllipsoidGeometry.js b/Source/Core/EllipsoidGeometry.js new file mode 100644 index 000000000000..861cd3ad1c70 --- /dev/null +++ b/Source/Core/EllipsoidGeometry.js @@ -0,0 +1,344 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError', + './Cartesian3', + './Math', + './Ellipsoid', + './ComponentDatatype', + './IndexDatatype', + './PrimitiveType', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat' + ], function( + defaultValue, + DeveloperError, + Cartesian3, + CesiumMath, + Ellipsoid, + ComponentDatatype, + IndexDatatype, + PrimitiveType, + BoundingSphere, + GeometryAttribute, + GeometryAttributes, + VertexFormat) { + "use strict"; + + var scratchDirection = new Cartesian3(); + + function addEdgePositions(i0, i1, numberOfPartitions, positions) { + var indices = new Array(2 + numberOfPartitions - 1); + indices[0] = i0; + + var origin = positions[i0]; + var direction = Cartesian3.subtract(positions[i1], positions[i0], scratchDirection); + + for ( var i = 1; i < numberOfPartitions; ++i) { + var delta = i / numberOfPartitions; + var position = Cartesian3.multiplyByScalar(direction, delta); + Cartesian3.add(origin, position, position); + + indices[i] = positions.length; + positions.push(position); + } + + indices[2 + (numberOfPartitions - 1) - 1] = i1; + + return indices; + } + + var scratchX = new Cartesian3(); + var scratchY = new Cartesian3(); + var scratchOffsetX = new Cartesian3(); + var scratchOffsetY = new Cartesian3(); + + function addFaceTriangles(leftBottomToTop, bottomLeftToRight, rightBottomToTop, topLeftToRight, numberOfPartitions, positions, indices) { + var origin = positions[bottomLeftToRight[0]]; + var x = Cartesian3.subtract(positions[bottomLeftToRight[bottomLeftToRight.length - 1]], origin, scratchX); + var y = Cartesian3.subtract(positions[topLeftToRight[0]], origin, scratchY); + + var bottomIndicesBuffer = []; + var topIndicesBuffer = []; + + var bottomIndices = bottomLeftToRight; + var topIndices = topIndicesBuffer; + + for ( var j = 1; j <= numberOfPartitions; ++j) { + if (j !== numberOfPartitions) { + if (j !== 1) { + // + // This copy could be avoided by ping ponging buffers. + // + bottomIndicesBuffer = topIndicesBuffer.slice(0); + bottomIndices = bottomIndicesBuffer; + } + + topIndicesBuffer[0] = leftBottomToTop[j]; + topIndicesBuffer[numberOfPartitions] = rightBottomToTop[j]; + + var deltaY = j / numberOfPartitions; + var offsetY = Cartesian3.multiplyByScalar(y, deltaY, scratchOffsetY); + + for ( var i = 1; i < numberOfPartitions; ++i) { + var deltaX = i / numberOfPartitions; + var offsetX = Cartesian3.multiplyByScalar(x, deltaX, scratchOffsetX); + var position = Cartesian3.add(origin, offsetX); + Cartesian3.add(position, offsetY, position); + + topIndicesBuffer[i] = positions.length; + positions.push(position); + } + } else { + if (j !== 1) { + bottomIndices = topIndicesBuffer; + } + topIndices = topLeftToRight; + } + + for ( var k = 0; k < numberOfPartitions; ++k) { + indices.push(bottomIndices[k]); + indices.push(bottomIndices[k + 1]); + indices.push(topIndices[k + 1]); + + indices.push(bottomIndices[k]); + indices.push(topIndices[k + 1]); + indices.push(topIndices[k]); + } + } + } + + var sphericalNormal = new Cartesian3(); + var normal = new Cartesian3(); + var tangent = new Cartesian3(); + var binormal = new Cartesian3(); + var defaultRadii = new Cartesian3(1.0, 1.0, 1.0); + + /** + * A {@link Geometry} that represents vertices and indices for an ellipsoid centered at the origin. + * + * @alias EllipsoidGeometry + * @constructor + * + * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions. + * @param {Number} [options.numberOfPartitions=32] The number of times to partition the ellipsoid in a plane formed by two radii in a single quadrant. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} options.numberOfPartitions must be greater than zero. + * + * @example + * var ellipsoid = new EllipsoidGeometry({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * radii : new Cartesian3(1000000.0, 500000.0, 500000.0) + * }); + */ + var EllipsoidGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var radii = defaultValue(options.radii, defaultRadii); + var ellipsoid = Ellipsoid.fromCartesian3(radii); + var numberOfPartitions = defaultValue(options.numberOfPartitions, 32); + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + if (numberOfPartitions <= 0) { + throw new DeveloperError('options.numberOfPartitions must be greater than zero.'); + } + + var positions = []; + var indices = []; + + // + // Initial cube. In the plane, z = -1: + // + // +y + // | + // Q2 * p3 Q1 + // / | \ + // p0 *--+--* p2 +x + // \ | / + // Q3 * p1 Q4 + // | + // + // Similarly, p4 to p7 are in the plane z = 1. + // + positions.push(new Cartesian3(-1, 0, -1)); + positions.push(new Cartesian3(0, -1, -1)); + positions.push(new Cartesian3(1, 0, -1)); + positions.push(new Cartesian3(0, 1, -1)); + positions.push(new Cartesian3(-1, 0, 1)); + positions.push(new Cartesian3(0, -1, 1)); + positions.push(new Cartesian3(1, 0, 1)); + positions.push(new Cartesian3(0, 1, 1)); + + // + // Edges + // + // 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 0. Plane z = -1 + // 4 -> 5, 5 -> 6, 6 -> 7, 7 -> 4. Plane z = 1 + // 0 -> 4, 1 -> 5, 2 -> 6, 3 -> 7. From plane z = -1 to plane z - 1 + // + var edge0to1 = addEdgePositions(0, 1, numberOfPartitions, positions); + var edge1to2 = addEdgePositions(1, 2, numberOfPartitions, positions); + var edge2to3 = addEdgePositions(2, 3, numberOfPartitions, positions); + var edge3to0 = addEdgePositions(3, 0, numberOfPartitions, positions); + + var edge4to5 = addEdgePositions(4, 5, numberOfPartitions, positions); + var edge5to6 = addEdgePositions(5, 6, numberOfPartitions, positions); + var edge6to7 = addEdgePositions(6, 7, numberOfPartitions, positions); + var edge7to4 = addEdgePositions(7, 4, numberOfPartitions, positions); + + var edge0to4 = addEdgePositions(0, 4, numberOfPartitions, positions); + var edge1to5 = addEdgePositions(1, 5, numberOfPartitions, positions); + var edge2to6 = addEdgePositions(2, 6, numberOfPartitions, positions); + var edge3to7 = addEdgePositions(3, 7, numberOfPartitions, positions); + + // Q3 Face + addFaceTriangles(edge0to4, edge0to1, edge1to5, edge4to5, numberOfPartitions, positions, indices); + // Q4 Face + addFaceTriangles(edge1to5, edge1to2, edge2to6, edge5to6, numberOfPartitions, positions, indices); + // Q1 Face + addFaceTriangles(edge2to6, edge2to3, edge3to7, edge6to7, numberOfPartitions, positions, indices); + // Q2 Face + addFaceTriangles(edge3to7, edge3to0, edge0to4, edge7to4, numberOfPartitions, positions, indices); + // Plane z = 1 + addFaceTriangles(edge7to4.slice(0).reverse(), edge4to5, edge5to6, edge6to7.slice(0).reverse(), numberOfPartitions, positions, indices); + // Plane z = -1 + addFaceTriangles(edge1to2, edge0to1.slice(0).reverse(), edge3to0.slice(0).reverse(), edge2to3, numberOfPartitions, positions, indices); + + var attributes = new GeometryAttributes(); + + var length = positions.length; + var i; + var j; + + if (vertexFormat.position) { + // Expand cube into ellipsoid and flatten values + var flattenedPositions = new Float64Array(length * 3); + + for (i = j = 0; i < length; ++i) { + var item = positions[i]; + Cartesian3.normalize(item, item); + Cartesian3.multiplyComponents(item, radii, item); + + flattenedPositions[j++] = item.x; + flattenedPositions[j++] = item.y; + flattenedPositions[j++] = item.z; + } + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : flattenedPositions + }); + } + + if (vertexFormat.st) { + var texCoords = new Float32Array(length * 2); + var oneOverRadii = ellipsoid.getOneOverRadii(); + + for (i = j = 0; i < length; ++i) { + Cartesian3.multiplyComponents(positions[i], oneOverRadii, sphericalNormal); + Cartesian3.normalize(sphericalNormal, sphericalNormal); + + texCoords[j++] = Math.atan2(sphericalNormal.y, sphericalNormal.x) * CesiumMath.ONE_OVER_TWO_PI + 0.5; + texCoords[j++] = Math.asin(sphericalNormal.z) * CesiumMath.ONE_OVER_PI + 0.5; + } + + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : texCoords + }); + } + + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { + var normals = (vertexFormat.normal) ? new Float32Array(length * 3) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(length * 3) : undefined; + var binormals = (vertexFormat.binormal) ? new Float32Array(length * 3) : undefined; + + for (i = j = 0; i < length; ++i, j += 3) { + ellipsoid.geodeticSurfaceNormal(positions[i], normal); + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent).normalize(tangent); + Cartesian3.cross(normal, tangent, binormal).normalize(binormal); + + if (vertexFormat.normal) { + normals[j] = normal.x; + normals[j + 1] = normal.y; + normals[j + 2] = normal.z; + } + + if (vertexFormat.tangent) { + tangents[j] = tangent.x; + tangents[j + 1] = tangent.y; + tangents[j + 2] = tangent.z; + } + + if (vertexFormat.binormal) { + binormals[j] = binormal.x; + binormals[j + 1] = binormal.y; + binormals[j + 2] = binormal.z; + } + } + + if (vertexFormat.normal) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.tangent) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type Object + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = IndexDatatype.createTypedArray(length, indices); + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.TRIANGLES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = BoundingSphere.fromEllipsoid(ellipsoid); + }; + + return EllipsoidGeometry; +}); \ No newline at end of file diff --git a/Source/Core/EllipsoidalOccluder.js b/Source/Core/EllipsoidalOccluder.js index 7a4fb62b3459..283f8f295e0f 100644 --- a/Source/Core/EllipsoidalOccluder.js +++ b/Source/Core/EllipsoidalOccluder.js @@ -253,7 +253,7 @@ define([ throw new DeveloperError('extent is required.'); } - var positions = extent.subsample(ellipsoid, subsampleScratch); + var positions = extent.subsample(ellipsoid, 0.0, subsampleScratch); var bs = BoundingSphere.fromPoints(positions); // If the bounding sphere center is too close to the center of the occluder, it doesn't make diff --git a/Source/Core/Extent.js b/Source/Core/Extent.js index 022ede46c830..a7c2288c34bf 100644 --- a/Source/Core/Extent.js +++ b/Source/Core/Extent.js @@ -17,44 +17,83 @@ define([ /** * A two dimensional region specified as longitude and latitude coordinates. + * * @alias Extent * @constructor * - * @param {Number} [west=0.0] The westernmost longitude in the range [-Pi, Pi]. - * @param {Number} [south=0.0] The southernmost latitude in the range [-Pi/2, Pi/2]. - * @param {Number} [east=0.0] The easternmost longitude in the range [-Pi, Pi]. - * @param {Number} [north=0.0] The northernmost latitude in the range [-Pi/2, Pi/2]. + * @param {Number} [west=0.0] The westernmost longitude, in radians, in the range [-Pi, Pi]. + * @param {Number} [south=0.0] The southernmost latitude, in radians, in the range [-Pi/2, Pi/2]. + * @param {Number} [east=0.0] The easternmost longitude, in radians, in the range [-Pi, Pi]. + * @param {Number} [north=0.0] The northernmost latitude, in radians, in the range [-Pi/2, Pi/2]. */ var Extent = function(west, south, east, north) { /** - * The westernmost longitude in the range [-Pi, Pi]. + * The westernmost longitude in radians in the range [-Pi, Pi]. + * * @type {Number} * @default 0.0 */ this.west = defaultValue(west, 0.0); /** - * The southernmost latitude in the range [-Pi/2, Pi/2]. + * The southernmost latitude in radians in the range [-Pi/2, Pi/2]. + * * @type {Number} * @default 0.0 */ this.south = defaultValue(south, 0.0); /** - * The easternmost longitude in the range [-Pi, Pi]. + * The easternmost longitude in radians in the range [-Pi, Pi]. + * * @type {Number} * @default 0.0 */ this.east = defaultValue(east, 0.0); /** - * The northernmost latitude in the range [-Pi/2, Pi/2]. + * The northernmost latitude in radians in the range [-Pi/2, Pi/2]. + * * @type {Number} * @default 0.0 */ this.north = defaultValue(north, 0.0); }; + /** + * Creates an extent given the boundary longitude and latitude in degrees. + * + * @memberof Extent + * + * @param {Number} [west=0.0] The westernmost longitude in degrees in the range [-180.0, 180.0]. + * @param {Number} [south=0.0] The southernmost latitude in degrees in the range [-90.0, 90.0]. + * @param {Number} [east=0.0] The easternmost longitude in degrees in the range [-180.0, 180.0]. + * @param {Number} [north=0.0] The northernmost latitude in degrees in the range [-90.0, 90.0]. + * @param {Extent} [result] The object onto which to store the result, or undefined if a new instance should be created. + * + * @return {Extent} The modified result parameter or a new Extent instance if none was provided. + * + * @example + * var extent = Extent.fromDegrees(0.0, 20.0, 10.0, 30.0); + */ + Extent.fromDegrees = function(west, south, east, north, result) { + west = CesiumMath.toRadians(defaultValue(west, 0.0)); + south = CesiumMath.toRadians(defaultValue(south, 0.0)); + east = CesiumMath.toRadians(defaultValue(east, 0.0)); + north = CesiumMath.toRadians(defaultValue(north, 0.0)); + + if (typeof result === 'undefined') { + return new Extent(west, south, east, north); + } + + result.west = west; + result.south = south; + result.east = east; + result.north = north; + + return result; + }; + /** * Creates the smallest possible Extent that encloses all positions in the provided array. * @memberof Extent @@ -105,9 +144,11 @@ define([ if (typeof extent === 'undefined') { return undefined; } + if (typeof result === 'undefined') { return new Extent(extent.west, extent.south, extent.east, extent.north); } + result.west = extent.west; result.south = extent.south; result.east = extent.east; @@ -136,11 +177,28 @@ define([ * @return {Boolean} true if the Extents are equal, false otherwise. */ Extent.prototype.equals = function(other) { - return typeof other !== 'undefined' && - this.west === other.west && - this.south === other.south && - this.east === other.east && - this.north === other.north; + return Extent.equals(this, other); + }; + + /** + * Compares the provided extents and returns true if they are equal, + * false otherwise. + * + * @memberof Extent + * + * @param {Extent} [left] The first Extent. + * @param {Extent} [right] The second Extent. + * + * @return {Boolean} true if left and right are equal; otherwise false. + */ + Extent.equals = function(left, right) { + return (left === right) || + ((typeof left !== 'undefined') && + (typeof right !== 'undefined') && + (left.west === right.west) && + (left.south === right.south) && + (left.east === right.east) && + (left.north === right.north)); }; /** @@ -363,16 +421,18 @@ define([ var subsampleLlaScratch = new Cartographic(); /** - * Samples this Extent so that it includes a list of Cartesian points suitable for passing to + * Samples this extent so that it includes a list of Cartesian points suitable for passing to * {@link BoundingSphere#fromPoints}. Sampling is necessary to account * for extents that cover the poles or cross the equator. * * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use. + * @param {Number} [surfaceHeight=0.0] The height of the extent above the ellipsoid. * @param {Array} [result] The array of Cartesians onto which to store the result. * @return {Array} The modified result parameter or a new Array of Cartesians instances if none was provided. */ - Extent.prototype.subsample = function(ellipsoid, result) { + Extent.prototype.subsample = function(ellipsoid, surfaceHeight, result) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + surfaceHeight = defaultValue(surfaceHeight, 0.0); if (typeof result === 'undefined') { result = []; @@ -385,6 +445,8 @@ define([ var west = this.west; var lla = subsampleLlaScratch; + lla.height = surfaceHeight; + lla.longitude = west; lla.latitude = north; result[length] = ellipsoid.cartographicToCartesian(lla, result[length]); diff --git a/Source/Core/ExtentGeometry.js b/Source/Core/ExtentGeometry.js new file mode 100644 index 000000000000..77718ed61960 --- /dev/null +++ b/Source/Core/ExtentGeometry.js @@ -0,0 +1,329 @@ +/*global define*/ +define([ + './clone', + './defaultValue', + './BoundingSphere', + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './IndexDatatype', + './DeveloperError', + './Ellipsoid', + './Extent', + './GeographicProjection', + './GeometryAttribute', + './GeometryAttributes', + './Math', + './Matrix2', + './PrimitiveType', + './VertexFormat' + ], function( + clone, + defaultValue, + BoundingSphere, + Cartesian3, + Cartographic, + ComponentDatatype, + IndexDatatype, + DeveloperError, + Ellipsoid, + Extent, + GeographicProjection, + GeometryAttribute, + GeometryAttributes, + CesiumMath, + Matrix2, + PrimitiveType, + VertexFormat) { + "use strict"; + + function isValidLatLon(latitude, longitude) { + if (latitude < -CesiumMath.PI_OVER_TWO || latitude > CesiumMath.PI_OVER_TWO) { + return false; + } + if (longitude > CesiumMath.PI || longitude < -CesiumMath.PI) { + return false; + } + return true; + } + + var nw = new Cartesian3(); + var nwCartographic = new Cartographic(); + var centerCartographic = new Cartographic(); + var center = new Cartesian3(); + var rotationMatrix = new Matrix2(); + var proj = new GeographicProjection(); + var position = new Cartesian3(); + var normal = new Cartesian3(); + var tangent = new Cartesian3(); + var binormal = new Cartesian3(); + + /** + * A {@link Geometry} that represents geometry for a cartographic extent on an ellipsoid centered at the origin. + * + * @alias ExtentGeometry + * @constructor + * + * @param {Extent} options.extent A cartographic extent with north, south, east and west properties in radians. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the extent lies. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * @param {Number} [options.height=0.0] The height from the surface of the ellipsoid. + * @param {Number} [options.rotation=0.0] The rotation of the extent in radians. A positive rotation is counter-clockwise. + * + * @exception {DeveloperError} options.extent is required and must have north, south, east and west attributes. + * @exception {DeveloperError} options.extent.north must be in the interval [-Pi/2, Pi/2]. + * @exception {DeveloperError} options.extent.south must be in the interval [-Pi/2, Pi/2]. + * @exception {DeveloperError} options.extent.east must be in the interval [-Pi, Pi]. + * @exception {DeveloperError} options.extent.west must be in the interval [-Pi, Pi]. + * @exception {DeveloperError} options.extent.north must be greater than extent.south. + * @exception {DeveloperError} options.extent.east must be greater than extent.west. + * @exception {DeveloperError} Rotated extent is invalid. + * + * @example + * var extent = new ExtentGeometry({ + * ellipsoid : Ellipsoid.WGS84, + * extent : Extent.fromDegrees(-80.0, 39.0, -74.0, 42.0), + * height : 10000.0 + * }); + */ + var ExtentGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var extent = options.extent; + if (typeof extent === 'undefined') { + throw new DeveloperError('extent is required.'); + } + + extent.validate(); + + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var width = Math.ceil((extent.east - extent.west) / granularity) + 1; + var height = Math.ceil((extent.north - extent.south) / granularity) + 1; + var granularityX = (extent.east - extent.west) / (width - 1); + var granularityY = (extent.north - extent.south) / (height - 1); + + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var radiiSquared = ellipsoid.getRadiiSquared(); + var radiiSquaredX = radiiSquared.x; + var radiiSquaredY = radiiSquared.y; + var radiiSquaredZ = radiiSquared.z; + + var surfaceHeight = defaultValue(options.height, 0.0); + var rotation = defaultValue(options.rotation, 0.0); + + var cos = Math.cos; + var sin = Math.sin; + var sqrt = Math.sqrt; + + // for computing texture coordinates + var lonScalar = 1.0 / (extent.east - extent.west); + var latScalar = 1.0 / (extent.north - extent.south); + + extent.getNorthwest(nwCartographic); + extent.getCenter(centerCartographic); + var latitude, longitude; + + var granYCos = granularityY * cos(rotation); + var granYSin = granularityY * sin(rotation); + var granXCos = granularityX * cos(rotation); + var granXSin = granularityX * sin(rotation); + + if (rotation !== 0) { + proj.project(nwCartographic, nw); + proj.project(centerCartographic, center); + nw.subtract(center, nw); + Matrix2.fromRotation(rotation, rotationMatrix); + rotationMatrix.multiplyByVector(nw, nw); + nw.add(center, nw); + proj.unproject(nw, nwCartographic); + latitude = nwCartographic.latitude; + longitude = nwCartographic.longitude; + + if (!isValidLatLon(latitude, longitude) || + !isValidLatLon(latitude + (width-1)*granXSin, longitude + (width-1)*granXCos) || + !isValidLatLon(latitude - granYCos*(height-1), longitude + (height-1)*granYSin) || + !isValidLatLon(latitude - granYCos*(height-1) + (width-1)*granXSin, longitude + (height-1)*granYSin + (width-1)*granXCos)) { + throw new DeveloperError('Rotated extent is invalid.'); + } + } + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + var attributes = new GeometryAttributes(); + + var positionIndex = 0; + var stIndex = 0; + var normalIndex = 0; + var tangentIndex = 0; + var binormalIndex = 0; + + var size = width * height; + var positions = (vertexFormat.position) ? new Float64Array(size * 3) : undefined; + var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined; + var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined; + var binormals = (vertexFormat.binormal) ? new Float32Array(size * 3) : undefined; + + for ( var row = 0; row < height; ++row) { + for ( var col = 0; col < width; ++col) { + latitude = nwCartographic.latitude - granYCos*row + col*granXSin; + var cosLatitude = cos(latitude); + var nZ = sin(latitude); + var kZ = radiiSquaredZ * nZ; + + longitude = nwCartographic.longitude + row*granYSin + col*granXCos; + + var nX = cosLatitude * cos(longitude); + var nY = cosLatitude * sin(longitude); + + var kX = radiiSquaredX * nX; + var kY = radiiSquaredY * nY; + + var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ)); + + var rSurfaceX = kX / gamma; + var rSurfaceY = kY / gamma; + var rSurfaceZ = kZ / gamma; + + position.x = rSurfaceX + nX * surfaceHeight; + position.y = rSurfaceY + nY * surfaceHeight; + position.z = rSurfaceZ + nZ * surfaceHeight; + + if (vertexFormat.position) { + positions[positionIndex++] = position.x; + positions[positionIndex++] = position.y; + positions[positionIndex++] = position.z; + } + + if (vertexFormat.st) { + textureCoordinates[stIndex++] = (longitude - extent.west) * lonScalar; + textureCoordinates[stIndex++] = (latitude - extent.south) * latScalar; + } + + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { + ellipsoid.geodeticSurfaceNormal(position, normal); + + if (vertexFormat.normal) { + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; + } + + if (vertexFormat.tangent) { + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + + tangents[tangentIndex++] = tangent.x; + tangents[tangentIndex++] = tangent.y; + tangents[tangentIndex++] = tangent.z; + } + + if (vertexFormat.binormal) { + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + Cartesian3.cross(normal, tangent, binormal); + + binormals[binormalIndex++] = binormal.x; + binormals[binormalIndex++] = binormal.y; + binormals[binormalIndex++] = binormal.z; + } + } + } + } + + var indicesSize = 6 * (width - 1) * (height - 1); + var indices = IndexDatatype.createTypedArray(size, indicesSize); + + var index = 0; + var indicesIndex = 0; + for ( var i = 0; i < height - 1; ++i) { + for ( var j = 0; j < width - 1; ++j) { + var upperLeft = index; + var lowerLeft = upperLeft + width; + var lowerRight = lowerLeft + 1; + var upperRight = upperLeft + 1; + + indices[indicesIndex++] = upperLeft; + indices[indicesIndex++] = lowerLeft; + indices[indicesIndex++] = upperRight; + indices[indicesIndex++] = upperRight; + indices[indicesIndex++] = lowerLeft; + indices[indicesIndex++] = lowerRight; + + ++index; + } + ++index; + } + + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + } + + if (vertexFormat.st) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); + } + + if (vertexFormat.normal) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.tangent) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.TRIANGLES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = BoundingSphere.fromExtent3D(extent, ellipsoid, surfaceHeight); + }; + + return ExtentGeometry; +}); diff --git a/Source/Core/ExtentTessellator.js b/Source/Core/ExtentTessellator.js deleted file mode 100644 index d944142792d6..000000000000 --- a/Source/Core/ExtentTessellator.js +++ /dev/null @@ -1,446 +0,0 @@ -/*global define*/ -define([ - './clone', - './defaultValue', - './Math', - './Ellipsoid', - './Cartesian3', - './Cartographic', - './Matrix2', - './GeographicProjection', - './ComponentDatatype', - './PrimitiveType' - ], function( - clone, - defaultValue, - CesiumMath, - Ellipsoid, - Cartesian3, - Cartographic, - Matrix2, - GeographicProjection, - ComponentDatatype, - PrimitiveType) { - "use strict"; - - /** - * Contains class functions to create a mesh or vertex array from a cartographic extent. - * - * @exports ExtentTessellator - * - * @see HeightmapTessellator - * @see CubeMapEllipsoidTessellator - * @see BoxTessellator - * @see PlaneTessellator - */ - var ExtentTessellator = {}; - - function isValidLatLon(latitude, longitude) { - if (latitude < -CesiumMath.PI_OVER_TWO || latitude > CesiumMath.PI_OVER_TWO) { - return false; - } - if (longitude > CesiumMath.PI || longitude < -CesiumMath.PI) { - return false; - } - return true; - } - /** - * Compute vertices from a cartographic extent. This function is different from - * {@link ExtentTessellator#compute} and {@link ExtentTessellator#computeBuffers} - * in that it assumes that you have already allocated output arrays of the correct size. - * - * @param {Extent} description.extent A cartographic extent with north, south, east and west properties in radians. - * @param {Number} description.rotation The rotation of the extent in radians. - * @param {Number} description.width The number of vertices in the longitude direction. - * @param {Number} description.height The number of vertices in the latitude direction. - * @param {Number} description.surfaceHeight The height from the surface of the ellipsoid. - * @param {Boolean} description.generateTextureCoordinates Whether to generate texture coordinates. - * @param {Boolean} description.interleaveTextureCoordinates Whether to interleave the texture coordinates into the vertex array. - * @param {Cartesian3} description.relativetoCenter The positions will be computed as worldPosition.subtract(relativeToCenter). - * @param {Cartesian3} description.radiiSquared The radii squared of the ellipsoid to use. - * @param {Array|Float32Array} description.vertices The array to use to store computed vertices. - * @param {Array|Float32Array} description.textureCoordinates The array to use to store computed texture coordinates, unless interleaved. - * @param {Array|Float32Array} [description.indices] The array to use to store computed indices. If undefined, indices will be not computed. - */ - var nw = new Cartesian3(); - var nwCartographic = new Cartographic(); - var centerCartographic = new Cartographic(); - var center = new Cartesian3(); - var rotationMatrix = new Matrix2(); - var proj = new GeographicProjection(); - ExtentTessellator.computeVertices = function(description) { - description = defaultValue(description, defaultValue.EMPTY_OBJECT); - var extent = description.extent; - extent.validate(); - var rotation = description.rotation; - var surfaceHeight = description.surfaceHeight; - var width = description.width; - var height = description.height; - - var granularityX = (extent.east - extent.west) / (width - 1); - var granularityY = (extent.north - extent.south) / (height - 1); - var generateTextureCoordinates = description.generateTextureCoordinates; - var interleaveTextureCoordinates = description.interleaveTextureCoordinates; - var relativeToCenter = description.relativeToCenter; - - var vertices = description.vertices; - var textureCoordinates = description.textureCoordinates; - var indices = description.indices; - - var radiiSquared = description.radiiSquared; - var radiiSquaredX = radiiSquared.x; - var radiiSquaredY = radiiSquared.y; - var radiiSquaredZ = radiiSquared.z; - - var cos = Math.cos; - var sin = Math.sin; - var sqrt = Math.sqrt; - - // for computing texture coordinates - var lonScalar = 1.0 / (extent.east - extent.west); - var latScalar = 1.0 / (extent.north - extent.south); - - var vertexArrayIndex = 0; - var textureCoordinatesIndex = 0; - - extent.getNorthwest(nwCartographic); - extent.getCenter(centerCartographic); - var latitude, longitude; - - if (typeof rotation === 'undefined') { - rotation = 0; - } - - var granYCos = granularityY * cos(rotation); - var granYSin = granularityY * sin(rotation); - var granXCos = granularityX * cos(rotation); - var granXSin = granularityX * sin(rotation); - - if (rotation !== 0) { - proj.project(nwCartographic, nw); - proj.project(centerCartographic, center); - nw.subtract(center, nw); - Matrix2.fromRotation(rotation, rotationMatrix); - rotationMatrix.multiplyByVector(nw, nw); - nw.add(center, nw); - proj.unproject(nw, nwCartographic); - latitude = nwCartographic.latitude; - longitude = nwCartographic.longitude; - if (!isValidLatLon(latitude, longitude)) { //NW corner - return; - } - if (!isValidLatLon(latitude + (width-1)*granXSin, longitude + (width-1)*granXCos)) { //NE corner - return; - } - if (!isValidLatLon(latitude - granYCos*(height-1), longitude + (height-1)*granYSin)) { //SW corner - return; - } - if (!isValidLatLon(latitude - granYCos*(height-1) + (width-1)*granXSin, longitude + (height-1)*granYSin + (width-1)*granXCos)) { //SE corner - return; - } - } - - for ( var row = 0; row < height; ++row) { - for ( var col = 0; col < width; ++col) { - latitude = nwCartographic.latitude - granYCos*row + col*granXSin; - var cosLatitude = cos(latitude); - var nZ = sin(latitude); - var kZ = radiiSquaredZ * nZ; - - longitude = nwCartographic.longitude + row*granYSin + col*granXCos; - - var nX = cosLatitude * cos(longitude); - var nY = cosLatitude * sin(longitude); - - var kX = radiiSquaredX * nX; - var kY = radiiSquaredY * nY; - - var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ)); - - var rSurfaceX = kX / gamma; - var rSurfaceY = kY / gamma; - var rSurfaceZ = kZ / gamma; - - vertices[vertexArrayIndex++] = rSurfaceX + nX * surfaceHeight - relativeToCenter.x; - vertices[vertexArrayIndex++] = rSurfaceY + nY * surfaceHeight - relativeToCenter.y; - vertices[vertexArrayIndex++] = rSurfaceZ + nZ * surfaceHeight - relativeToCenter.z; - - if (generateTextureCoordinates) { - var u = (longitude - extent.west) * lonScalar; - var v = (latitude - extent.south) * latScalar; - - if (interleaveTextureCoordinates) { - vertices[vertexArrayIndex++] = u; - vertices[vertexArrayIndex++] = v; - } else { - textureCoordinates[textureCoordinatesIndex++] = u; - textureCoordinates[textureCoordinatesIndex++] = v; - } - } - } - } - - if (typeof indices !== 'undefined') { - var index = 0; - var indicesIndex = 0; - for ( var i = 0; i < height - 1; ++i) { - for ( var j = 0; j < width - 1; ++j) { - var upperLeft = index; - var lowerLeft = upperLeft + width; - var lowerRight = lowerLeft + 1; - var upperRight = upperLeft + 1; - - indices[indicesIndex++] = upperLeft; - indices[indicesIndex++] = lowerLeft; - indices[indicesIndex++] = upperRight; - indices[indicesIndex++] = upperRight; - indices[indicesIndex++] = lowerLeft; - indices[indicesIndex++] = lowerRight; - - ++index; - } - ++index; - } - } - }; - - /** - * Creates a mesh from a cartographic extent. - * - * @param {Extent} description.extent A cartographic extent with north, south, east and west properties in radians. - * @param {Ellipsoid} [description.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the extent lies. - * @param {Number} [description.granularity=0.1] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. - * @param {Number} [description.surfaceHeight=0.0] The height from the surface of the ellipsoid. - * @param {Cartesian3} [description.relativetoCenter=Cartesian3.ZERO] The positions will be computed as worldPosition.subtract(relativeToCenter). - * @param {Boolean} [description.generateTextureCoordinates=false] Whether to generate texture coordinates. - * - * @exception {DeveloperError} description.extent is required and must have north, south, east and west attributes. - * @exception {DeveloperError} description.extent.north must be in the interval [-Pi/2, Pi/2]. - * @exception {DeveloperError} description.extent.south must be in the interval [-Pi/2, Pi/2]. - * @exception {DeveloperError} description.extent.east must be in the interval [-Pi, Pi]. - * @exception {DeveloperError} description.extent.west must be in the interval [-Pi, Pi]. - * @exception {DeveloperError} description.extent.north must be greater than extent.south. - * @exception {DeveloperError} description.extent.east must be greater than extent.west. - * @exception {DeveloperError} description.context is required. - * - * @return {Object} A mesh containing attributes for positions, possibly texture coordinates and indices - * from the extent for creating a vertex array. (returns undefined if no indices are found) - * - * @see Context#createVertexArrayFromMesh - * @see MeshFilters.createAttributeIndices - * @see MeshFilters.toWireframeInPlace - * @see Extent - * - * @example - * // Create a vertex array for rendering a wireframe extent. - * var mesh = ExtentTessellator.compute({ - * ellipsoid : Ellipsoid.WGS84, - * extent : new Extent( - * CesiumMath.toRadians(-80.0), - * CesiumMath.toRadians(39.0), - * CesiumMath.toRadians(-74.0), - * CesiumMath.toRadians(42.0) - * ), - * granularity : 0.01, - * surfaceHeight : 10000.0 - * }); - * mesh = MeshFilters.toWireframeInPlace(mesh); - * var va = context.createVertexArrayFromMesh({ - * mesh : mesh, - * attributeIndices : MeshFilters.createAttributeIndices(mesh) - * }); - */ - ExtentTessellator.compute = function(description) { - description = defaultValue(description, defaultValue.EMPTY_OBJECT); - - // make a copy of description to allow us to change values before passing to computeVertices - var computeVerticesDescription = clone(description); - - var extent = description.extent; - extent.validate(); - - var ellipsoid = defaultValue(description.ellipsoid, Ellipsoid.WGS84); - computeVerticesDescription.radiiSquared = ellipsoid.getRadiiSquared(); - computeVerticesDescription.relativeToCenter = defaultValue(description.relativeToCenter, Cartesian3.ZERO); - - var granularity = defaultValue(description.granularity, 0.1); - computeVerticesDescription.surfaceHeight = defaultValue(description.surfaceHeight, 0.0); - - computeVerticesDescription.width = Math.ceil((extent.east - extent.west) / granularity) + 1; - computeVerticesDescription.height = Math.ceil((extent.north - extent.south) / granularity) + 1; - - var vertices = []; - var indices = []; - var textureCoordinates = []; - - computeVerticesDescription.generateTextureCoordinates = defaultValue(computeVerticesDescription.generateTextureCoordinates, false); - computeVerticesDescription.interleaveTextureCoordinates = false; - computeVerticesDescription.vertices = vertices; - computeVerticesDescription.textureCoordinates = textureCoordinates; - computeVerticesDescription.indices = indices; - - ExtentTessellator.computeVertices(computeVerticesDescription); - - if (indices.length === 0) { - return undefined; - } - - var mesh = { - attributes : {}, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : indices - }] - }; - - var positionName = defaultValue(description.positionName, 'position'); - mesh.attributes[positionName] = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : vertices - }; - - if (description.generateTextureCoordinates) { - var textureCoordinatesName = defaultValue(description.textureCoordinatesName, 'textureCoordinates'); - mesh.attributes[textureCoordinatesName] = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : textureCoordinates - }; - } - - return mesh; - }; - - /** - * Creates arrays of vertex attributes and indices from a cartographic extent. - * - * @param {Extent} description.extent A cartographic extent with north, south, east and west properties in radians. - * @param {Ellipsoid} [description.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the extent lies. - * @param {Number} [description.granularity=0.1] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. - * @param {Number} [description.surfaceHeight=0.0] The height from the surface of the ellipsoid. - * @param {Cartesian3} [description.relativetoCenter=Cartesian3.ZERO] The positions will be computed as worldPosition.subtract(relativeToCenter). - * @param {Boolean} [description.generateTextureCoordinates=false] Whether to generate texture coordinates. - * @param {Boolean} [description.interleaveTextureCoordinates=false] If texture coordinates are generated, whether to interleave the positions and texture coordinates in a single buffer. - * - * @exception {DeveloperError} description.extent is required and must have north, south, east and west attributes. - * @exception {DeveloperError} description.extent.north must be in the interval [-Pi/2, Pi/2]. - * @exception {DeveloperError} description.extent.south must be in the interval [-Pi/2, Pi/2]. - * @exception {DeveloperError} description.extent.east must be in the interval [-Pi, Pi]. - * @exception {DeveloperError} description.extent.west must be in the interval [-Pi, Pi]. - * @exception {DeveloperError} description.extent.north must be greater than extent.south. * - * @exception {DeveloperError} description.extent.east must be greater than extent.west. - * - * @return {Object} An object with flattened arrays for vertex attributes and indices. - * - * @example - * // Example 1: - * // Create a vertex array for a solid extent, with separate positions and texture coordinates. - * var buffers = ExtentTessellator.computeBuffers({ - * ellipsoid : ellipsoid, - * extent : extent, - * generateTextureCoordinates : true - * }); - * - * var datatype = ComponentDatatype.FLOAT; - * var usage = BufferUsage.STATIC_DRAW; - * var positionBuffer = context.createVertexBuffer(datatype.toTypedArray(buffers.positions), usage); - * var textureCoordinateBuffer = context.createVertexBuffer(datatype.toTypedArray(buffers.textureCoordinates), usage); - * attributes = [{ - * index : attributeIndices.position, - * vertexBuffer : positionBuffer, - * componentDatatype : datatype, - * componentsPerAttribute : 3 - * }, { - * index : attributeIndices.textureCoordinates, - * vertexBuffer : textureCoordinateBuffer, - * componentDatatype : datatype, - * componentsPerAttribute : 2 - * }]; - * var indexBuffer = context.createIndexBuffer(new Uint16Array(buffers.indices), usage, IndexDatatype.UNSIGNED_SHORT); - * var va = context.createVertexArray(attributes, indexBuffer); - * - * @example - * // Example 2: - * // Create a vertex array for a solid extent, with interleaved positions and texture coordinates. - * var buffers = ExtentTessellator.computeBuffers({ - * ellipsoid : ellipsoid, - * extent : extent, - * generateTextureCoordinates : true, - * interleaveTextureCoordinates : true - * }); - * - * var datatype = ComponentDatatype.FLOAT; - * var usage = BufferUsage.STATIC_DRAW; - * var typedArray = datatype.toTypedArray(buffers.vertices); - * var buffer = context.createVertexBuffer(typedArray, usage); - * var stride = 5 * datatype.sizeInBytes; - * var attributes = [{ - * index : attributeIndices.position3D, - * vertexBuffer : buffer, - * componentDatatype : datatype, - * componentsPerAttribute : 3, - * normalize : false, - * offsetInBytes : 0, - * strideInBytes : stride - * }, { - * index : attributeIndices.textureCoordinates, - * vertexBuffer : buffer, - * componentDatatype : datatype, - * componentsPerAttribute : 2, - * normalize : false, - * offsetInBytes : 3 * datatype.sizeInBytes, - * strideInBytes : stride - * }]; - * var indexBuffer = context.createIndexBuffer(new Uint16Array(buffers.indices), usage, IndexDatatype.UNSIGNED_SHORT); - * var vacontext.createVertexArray(attributes, indexBuffer); - */ - ExtentTessellator.computeBuffers = function(description) { - description = defaultValue(description, defaultValue.EMPTY_OBJECT); - - // make a copy of description to allow us to change values before passing to computeVertices - var computeVerticesDescription = clone(description); - - var extent = description.extent; - extent.validate(); - - var ellipsoid = defaultValue(description.ellipsoid, Ellipsoid.WGS84); - computeVerticesDescription.radiiSquared = ellipsoid.getRadiiSquared(); - computeVerticesDescription.relativeToCenter = defaultValue(description.relativeToCenter, Cartesian3.ZERO); - - var granularity = defaultValue(description.granularity, 0.1); - computeVerticesDescription.surfaceHeight = defaultValue(description.surfaceHeight, 0.0); - - computeVerticesDescription.width = Math.ceil((extent.east - extent.west) / granularity) + 1; - computeVerticesDescription.height = Math.ceil((extent.north - extent.south) / granularity) + 1; - - var vertices = []; - var indices = []; - var textureCoordinates = []; - - computeVerticesDescription.generateTextureCoordinates = defaultValue(description.generateTextureCoordinates, false); - computeVerticesDescription.interleaveTextureCoordinates = defaultValue(description.interleaveTextureCoordinates, false); - computeVerticesDescription.vertices = vertices; - computeVerticesDescription.textureCoordinates = textureCoordinates; - computeVerticesDescription.indices = indices; - - ExtentTessellator.computeVertices(computeVerticesDescription); - - var result = { - indices : indices - }; - - if (description.interleaveTextureCoordinates) { - result.vertices = vertices; - } else { - result.positions = vertices; - if (description.generateTextureCoordinates) { - result.textureCoordinates = textureCoordinates; - } - } - - return result; - }; - - return ExtentTessellator; -}); diff --git a/Source/Core/Geometry.js b/Source/Core/Geometry.js new file mode 100644 index 000000000000..424e08319f5c --- /dev/null +++ b/Source/Core/Geometry.js @@ -0,0 +1,195 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError', + './BoundingSphere' + ], function( + defaultValue, + DeveloperError, + BoundingSphere) { + "use strict"; + + /** + * A geometry representation with attributes forming vertices and optional index data + * defining primitives. Geometries and an {@link Appearance}, which describes the shading, + * can be assigned to a {@link Primitive} for visualization. A Primitive can + * be created from many heterogeneous - in many cases - geometries for performance. + *

+ * In low-level rendering code, a vertex array can be created from a geometry using + * {@link Context#createVertexArrayFromGeometry}. + *

+ *

+ * Geometries can be transformed and optimized using functions in {@link GeometryPipeline}. + *

+ * + * @alias Geometry + * @constructor + * + * @param {GeometryAttributes} options.attributes Attributes, which make up the geometry's vertices. + * @param {PrimitiveType} options.primitiveType The type of primitives in the geometry. + * @param {Array} [options.indices] Optional index data that determines the primitives in the geometry. + * @param {BoundingSphere} [options.boundingSphere] An optional bounding sphere that fully enclosed the geometry. + * + * @example + * // Create geometry with a position attribute and indexed lines. + * var positions = new Float64Array([ + * 0.0, 0.0, 0.0, + * 7500000.0, 0.0, 0.0, + * 0.0, 7500000.0, 0.0 + * ]); + * + * var geometry = new Geometry({ + * attributes : { + * position : new GeometryAttribute({ + * componentDatatype : ComponentDatatype.DOUBLE, + * componentsPerAttribute : 3, + * values : positions + * }) + * }, + * indices : new Uint16Array([0, 1, 1, 2, 2, 0]), + * primitiveType : PrimitiveType.LINES, + * boundingSphere : BoundingSphere.fromVertices(positions) + * }); + * + * @demo Geometry and Appearances Demo + * + * @see PolygonGeometry + * @see ExtentGeometry + * @see EllipseGeometry + * @see CircleGeometry + * @see WallGeometry + * @see SimplePolylineGeometry + * @see BoxGeometry + * @see EllipsoidGeometry + */ + var Geometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.attributes === 'undefined') { + throw new DeveloperError('options.attributes is required.'); + } + + if (typeof options.primitiveType === 'undefined') { + throw new DeveloperError('options.primitiveType is required.'); + } + + /** + * Attributes, which make up the geometry's vertices. Each property in this object corresponds to a + * {@link GeometryAttribute} containing the attribute's data. + *

+ * Attributes are always stored non-interleaved in a Geometry. When geometry is prepared for rendering + * with {@link Context#createVertexArrayFromGeometry}, attributes are generally written interleaved + * into the vertex buffer for better rendering performance. + *

+ *

+ * There are reserved attribute names with well-known semantics. The following attributes + * are created by a Geometry (depending on the provided {@link VertexFormat}. + *

+ *

+ *

+ * The following attribute names are generally not created by a Geometry, but are added + * to a Geometry by a {@link Primitive} or {@link GeometryPipeline} functions to prepare + * the geometry for rendering. + *

+ *

+ * + * @type GeometryAttributes + * + * @default undefined + * + * @example + * geometry.attributes.position = new GeometryAttribute({ + * componentDatatype : ComponentDatatype.FLOAT, + * componentsPerAttribute : 3, + * values : new Float32Array() + * }); + * + * @see GeometryAttribute + * @see VertexFormat + */ + this.attributes = options.attributes; + + /** + * Optional index data that - along with {@link Geometry#primitiveType} - + * determines the primitives in the geometry. + * + * @type Array + * + * @default undefined + */ + this.indices = options.indices; + + /** + * The type of primitives in the geometry. This is most often {@link PrimitiveType.TRIANGLES}, + * but can varying based on the specific geometry. + * + * @type PrimitiveType + * + * @default undefined + */ + this.primitiveType = options.primitiveType; + + /** + * An optional bounding sphere that fully encloses the geometry. This is + * commonly used for culling. + * + * @type BoundingSphere + * + * @default undefined + */ + this.boundingSphere = options.boundingSphere; + }; + + /** + * Computes the number of vertices in a geometry. The runtime is linear with + * respect to the number of attributes in a vertex, not the number of vertices. + * + * @memberof Geometry + * + * @param {Cartesian3} geometry The geometry. + * + * @return {Number} The number of vertices in the geometry. + * + * @exception {DeveloperError} geometries is required. + * + * @example + * var numVertices = Geometry.computeNumberOfVertices(geometry); + */ + Geometry.computeNumberOfVertices = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var numberOfVertices = -1; + for ( var property in geometry.attributes) { + if (geometry.attributes.hasOwnProperty(property) && + typeof geometry.attributes[property] !== 'undefined' && + typeof geometry.attributes[property].values !== 'undefined') { + + var attribute = geometry.attributes[property]; + var num = attribute.values.length / attribute.componentsPerAttribute; + if ((numberOfVertices !== num) && (numberOfVertices !== -1)) { + throw new DeveloperError('All attribute lists must have the same number of attributes.'); + } + numberOfVertices = num; + } + } + + return numberOfVertices; + }; + + return Geometry; +}); diff --git a/Source/Core/GeometryAttribute.js b/Source/Core/GeometryAttribute.js new file mode 100644 index 000000000000..f79cbab7a795 --- /dev/null +++ b/Source/Core/GeometryAttribute.js @@ -0,0 +1,142 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError' + ], function( + defaultValue, + DeveloperError) { + "use strict"; + + /** + * Values and type information for geometry attributes. A {@link Geometry} + * generally contains one or more attributes. All attributes together form + * the geometry's vertices. + * + * @alias GeometryAttribute + * @constructor + * + * @param {ComponentDatatype} [options.componentDatatype=undefined] The datatype of each component in the attribute, e.g., individual elements in values. + * @param {Number} [options.componentsPerAttribute=undefined] A number between 1 and 4 that defines the number of components in an attributes. + * @param {Boolean} [options.normalize=false] When true and componentDatatype is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * @param {Array} [options.values=undefined] The values for the attributes stored in a typed array. + * + * @exception {DeveloperError} options.componentDatatype is required. + * @exception {DeveloperError} options.componentsPerAttribute is required. + * @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4. + * @exception {DeveloperError} options.values is required. + * + * @example + * var geometry = new Geometry({ + * attributes : { + * position : new GeometryAttribute({ + * componentDatatype : ComponentDatatype.FLOAT, + * componentsPerAttribute : 3, + * values : [ + * 0.0, 0.0, 0.0, + * 7500000.0, 0.0, 0.0, + * 0.0, 7500000.0, 0.0 + * ] + * }) + * }, + * primitiveType : PrimitiveType.LINE_LOOP + * }); + * + * @see Geometry + */ + var GeometryAttribute = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.componentDatatype === 'undefined') { + throw new DeveloperError('options.componentDatatype is required.'); + } + + if (typeof options.componentsPerAttribute === 'undefined') { + throw new DeveloperError('options.componentsPerAttribute is required.'); + } + + if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) { + throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.'); + } + + if (typeof options.values === 'undefined') { + throw new DeveloperError('options.values is required.'); + } + + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@see GeometryAttribute#values}. + * + * @type ComponentDatatype + * + * @default undefined + */ + this.componentDatatype = options.componentDatatype; + + /** + * A number between 1 and 4 that defines the number of components in an attributes. + * For example, a position attribute with x, y, and z components would have 3 as + * shown in the code example. + * + * @type Number + * + * @default undefined + * + * @example + * attribute.componentDatatype : ComponentDatatype.FLOAT, + * attribute.componentsPerAttribute : 3, + * attribute.values = new Float32Array([ + * 0.0, 0.0, 0.0, + * 7500000.0, 0.0, 0.0, + * 0.0, 7500000.0, 0.0 + * ]); + */ + this.componentsPerAttribute = options.componentsPerAttribute; + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + *

+ * This is commonly used when storing colors using {@ ComponentDatatype.UNSIGNED_BYTE}. + *

+ * + * @type Boolean + * + * @default false + * + * @example + * attribute.componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * attribute.componentsPerAttribute : 4, + * attribute.normalize = true; + * attribute.values = new Uint8Array([ + * Color.floatToByte(color.red) + * Color.floatToByte(color.green) + * Color.floatToByte(color.blue) + * Color.floatToByte(color.alpha) + * ]); + */ + this.normalize = defaultValue(options.normalize, false); + + /** + * The values for the attributes stored in a typed array. In the code example, + * every three elements in values defines one attributes since + * componentsPerAttribute is 3. + * + * @type Array + * + * @default undefined + * + * @example + * attribute.componentDatatype : ComponentDatatype.FLOAT, + * attribute.componentsPerAttribute : 3, + * attribute.values = new Float32Array([ + * 0.0, 0.0, 0.0, + * 7500000.0, 0.0, 0.0, + * 0.0, 7500000.0, 0.0 + * ]); + */ + this.values = options.values; + }; + + return GeometryAttribute; +}); diff --git a/Source/Core/GeometryAttributes.js b/Source/Core/GeometryAttributes.js new file mode 100644 index 000000000000..a2670915c22d --- /dev/null +++ b/Source/Core/GeometryAttributes.js @@ -0,0 +1,82 @@ +/*global define*/ +define(['./defaultValue'], function(defaultValue) { + "use strict"; + + /** + * Attributes, which make up a geometry's vertices. Each property in this object corresponds to a + * {@link GeometryAttribute} containing the attribute's data. + *

+ * Attributes are always stored non-interleaved in a Geometry. When geometry is prepared for rendering + * with {@link Context#createVertexArrayFromGeometry}, attributes are generally written interleaved + * into the vertex buffer for better rendering performance. + *

+ * + * @alias VertexFormat + * @constructor + */ + var GeometryAttributes = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The 3D position attribute. + *

+ * 64-bit floating-point (for precision). 3 components per attribute. + *

+ * + * @type GeometryAttribute + * + * @default undefined + */ + this.position = options.position; + + /** + * The normal attribute (normalized), which is commonly used for lighting. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type GeometryAttribute + * + * @default undefined + */ + this.normal = options.normal; + + /** + * The 2D texture coordinate attribute. + *

+ * 32-bit floating-point. 2 components per attribute + *

+ * + * @type GeometryAttribute + * + * @default undefined + */ + this.st = options.st; + + /** + * The binormal attribute (normalized), which is used for tangent-space effects like bump mapping. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type GeometryAttribute + * + * @default undefined + */ + this.binormal = options.binormal; + + /** + * The tangent attribute (normalized), which is used for tangent-space effects like bump mapping. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type GeometryAttribute + * + * @default undefined + */ + this.tangent = options.tangent; + }; + + return GeometryAttributes; +}); \ No newline at end of file diff --git a/Source/Core/GeometryInstance.js b/Source/Core/GeometryInstance.js new file mode 100644 index 000000000000..fc19edca2b63 --- /dev/null +++ b/Source/Core/GeometryInstance.js @@ -0,0 +1,113 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError', + './Matrix4', + './Geometry', + './GeometryInstanceAttribute' + ], function( + defaultValue, + DeveloperError, + Matrix4, + Geometry, + GeometryInstanceAttribute) { + "use strict"; + + /** + * Geometry instancing allows one {@link Geometry} object to be positions in several + * different locations and colored uniquely. For example, one {@link BoxGeometry} can + * be instanced several times, each with a different modelMatrix to change + * its position, rotation, and scale. + * + * @alias GeometryInstance + * @constructor + * + * @param {Geometry} options.geometry The geometry to instance. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates. + * @param {Object} [options.id=undefined] A user-defined object to return when the instance is picked with {@link Context#pick} or get/set per-instance attributes with {@link Primitive#getGeometryInstanceAttributes}. + * @param {Object} [options.attributes] Per-instance attributes like a show or color attribute shown in the example below. + * + * @exception {DeveloperError} options.geometry is required. + * + * @example + * // Create geometry for a box, and two instances that refer to it. + * // One instance positions the box on the bottom and colored aqua. + * // The other instance positions the box on the top and color white. + * var geometry = new BoxGeometry({ + * vertexFormat : VertexFormat.POSITION_AND_NORMAL, + * dimensions : new Cartesian3(1000000.0, 1000000.0, 500000.0) + * }), + * var instanceBottom = new GeometryInstance({ + * geometry : geometry, + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883))), new Cartesian3(0.0, 0.0, 1000000.0)), + * attributes : { + * color : new ColorGeometryInstanceAttribute(Color.AQUA) + * } + * id : 'bottom' + * }); + * var instanceTop = new GeometryInstance({ + * geometry : geometry, + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883))), new Cartesian3(0.0, 0.0, 3000000.0)), + * attributes : { + * color : new ColorGeometryInstanceAttribute(Color.AQUA) + * } + * id : 'top' + * }); + * + * @see Geometry + */ + var GeometryInstance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.geometry === 'undefined') { + throw new DeveloperError('options.geometry is required.'); + } + + /** + * The geometry being instanced. + * + * @type Geometry + * + * @default undefined + */ + this.geometry = options.geometry; + + /** + * The 4x4 transformation matrix that transforms the geometry from model to world coordinates. + * When this is the identity matrix, the geometry is drawn in world coordinates, i.e., Earth's WGS84 coordinates. + * Local reference frames can be used by providing a different transformation matrix, like that returned + * by {@link Transforms.eastNorthUpToFixedFrame}. + * + * @type Matrix4 + * + * @default Matrix4.IDENTITY + */ + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + + /** + * User-defined object returned when the instance is picked or used to get/set per-instance attributes. + * + * @type Object + * + * @default undefined + * + * @see Context#pick + * @see Primitive#getGeometryInstanceAttributes + */ + this.id = options.id; + + /** + * Per-instance attributes like {@link ColorGeometryInstanceAttribute} or {@link ShowGeometryInstanceAttribute}. + * {@link Geometry} attributes varying per vertex; these attributes are constant for the entire instance. + * + * @type Object + * + * @default undefined + */ + this.attributes = defaultValue(options.attributes, {}); + }; + + return GeometryInstance; +}); diff --git a/Source/Core/GeometryInstanceAttribute.js b/Source/Core/GeometryInstanceAttribute.js new file mode 100644 index 000000000000..3958dab12a44 --- /dev/null +++ b/Source/Core/GeometryInstanceAttribute.js @@ -0,0 +1,139 @@ +/*global define*/ +define([ + './defaultValue', + './DeveloperError' + ], function( + defaultValue, + DeveloperError) { + "use strict"; + + /** + * Values and type information for per-instance geometry attributes. + * + * @alias GeometryInstanceAttribute + * @constructor + * + * @param {ComponentDatatype} [options.componentDatatype=undefined] The datatype of each component in the attribute, e.g., individual elements in values. + * @param {Number} [options.componentsPerAttribute=undefined] A number between 1 and 4 that defines the number of components in an attributes. + * @param {Boolean} [options.normalize=false] When true and componentDatatype is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * @param {Array} [options.value=undefined] The value for the attribute. + * + * @exception {DeveloperError} options.componentDatatype is required. + * @exception {DeveloperError} options.componentsPerAttribute is required. + * @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4. + * @exception {DeveloperError} options.value is required. + * + * @example + * var instance = new GeometryInstance({ + * geometry : new BoxGeometry({ + * dimensions : new Cartesian3(1000000.0, 1000000.0, 500000.0) + * }), + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-0.0, 0.0))), new Cartesian3(0.0, 0.0, 1000000.0)), + * id : 'box', + * attributes : { + * color : new GeometryInstanceAttribute({ + * componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * componentsPerAttribute : 4, + * normalize : true, + * value : [255, 255, 0 255] + * } + * } + * }); + * + * @see ColorGeometryInstanceAttribute + * @see ShowGeometryInstanceAttribute + */ + var GeometryInstanceAttribute = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.componentDatatype === 'undefined') { + throw new DeveloperError('options.componentDatatype is required.'); + } + + if (typeof options.componentsPerAttribute === 'undefined') { + throw new DeveloperError('options.componentsPerAttribute is required.'); + } + + if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) { + throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.'); + } + + if (typeof options.value === 'undefined') { + throw new DeveloperError('options.value is required.'); + } + + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@see GeometryInstanceAttribute#value}. + * + * @type ComponentDatatype + * + * @default undefined + */ + this.componentDatatype = options.componentDatatype; + + /** + * A number between 1 and 4 that defines the number of components in an attributes. + * For example, a position attribute with x, y, and z components would have 3 as + * shown in the code example. + * + * @type Number + * + * @default undefined + * + * @example + * show : new GeometryInstanceAttribute({ + * componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * componentsPerAttribute : 1, + * normalize : true, + * value : 1.0 + * } + */ + this.componentsPerAttribute = options.componentsPerAttribute; + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + *

+ * This is commonly used when storing colors using {@ ComponentDatatype.UNSIGNED_BYTE}. + *

+ * + * @type Boolean + * + * @default false + * + * @example + * attribute.componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * attribute.componentsPerAttribute : 4, + * attribute.normalize = true; + * attribute.value = [ + * Color.floatToByte(color.red) + * Color.floatToByte(color.green) + * Color.floatToByte(color.blue) + * Color.floatToByte(color.alpha) + * ]; + */ + this.normalize = defaultValue(options.normalize, false); + + /** + * The values for the attributes stored in a typed array. In the code example, + * every three elements in values defines one attributes since + * componentsPerAttribute is 3. + * + * @default undefined + * + * @example + * show : new GeometryInstanceAttribute({ + * componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * componentsPerAttribute : 1, + * normalize : true, + * value : [1.0] + * } + */ + this.value = options.value; + }; + + return GeometryInstanceAttribute; +}); diff --git a/Source/Core/GeometryPipeline.js b/Source/Core/GeometryPipeline.js new file mode 100644 index 000000000000..88c9b9d5c8f2 --- /dev/null +++ b/Source/Core/GeometryPipeline.js @@ -0,0 +1,1886 @@ +/*global define*/ +define([ + './barycentricCoordinates', + './defaultValue', + './DeveloperError', + './Cartesian2', + './Cartesian3', + './Cartesian4', + './Cartographic', + './EncodedCartesian3', + './Intersect', + './IntersectionTests', + './Math', + './Matrix3', + './Matrix4', + './Plane', + './GeographicProjection', + './ComponentDatatype', + './IndexDatatype', + './PrimitiveType', + './Tipsify', + './BoundingSphere', + './Geometry', + './GeometryAttribute' + ], function( + barycentricCoordinates, + defaultValue, + DeveloperError, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + EncodedCartesian3, + Intersect, + IntersectionTests, + CesiumMath, + Matrix3, + Matrix4, + Plane, + GeographicProjection, + ComponentDatatype, + IndexDatatype, + PrimitiveType, + Tipsify, + BoundingSphere, + Geometry, + GeometryAttribute) { + "use strict"; + + /** + * Content pipeline functions for geometries. + * + * @exports GeometryPipeline + * + * @see Geometry + * @see Context#createVertexArrayFromGeometry + */ + var GeometryPipeline = {}; + + function addTriangle(lines, index, i0, i1, i2) { + lines[index++] = i0; + lines[index++] = i1; + + lines[index++] = i1; + lines[index++] = i2; + + lines[index++] = i2; + lines[index] = i0; + } + + function trianglesToLines(triangles) { + var count = triangles.length; + var size = (count / 3) * 6; + var lines = IndexDatatype.createTypedArray(count, size); + + var index = 0; + for ( var i = 0; i < count; i += 3, index += 6) { + addTriangle(lines, index, triangles[i], triangles[i + 1], triangles[i + 2]); + } + + return lines; + } + + function triangleStripToLines(triangles) { + var count = triangles.length; + if (count >= 3) { + var size = (count - 2) * 6; + var lines = IndexDatatype.createTypedArray(count, size); + + addTriangle(lines, 0, triangles[0], triangles[1], triangles[2]); + var index = 6; + + for ( var i = 3; i < count; ++i, index += 6) { + addTriangle(lines, index, triangles[i - 1], triangles[i], triangles[i - 2]); + } + + return lines; + } + + return new Uint16Array(); + } + + function triangleFanToLines(triangles) { + if (triangles.length > 0) { + var count = triangles.length - 1; + var size = (count - 1) * 6; + var lines = IndexDatatype.createTypedArray(count, size); + + var base = triangles[0]; + var index = 0; + for ( var i = 1; i < count; ++i, index += 6) { + addTriangle(lines, index, base, triangles[i], triangles[i + 1]); + } + + return lines; + } + + return new Uint16Array(); + } + + /** + * Converts a geometry's triangle indices to line indices. If the geometry has an indices + * and its primitiveType is TRIANGLES, TRIANGLE_STRIP, + * TRIANGLE_FAN, it is converted to LINES; otherwise, the geometry is not changed. + *

+ * This is commonly used to create a wireframe geometry for visual debugging. + *

+ * + * @param {Geometry} geometry The geometry to modify. + * + * @returns {Geometry} The modified geometry argument, with its triangle indices converted to lines. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN. + * + * @example + * geometry = GeometryPipeline.toWireframe(geometry); + */ + GeometryPipeline.toWireframe = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var indices = geometry.indices; + if (typeof indices !== 'undefined') { + switch (geometry.primitiveType) { + case PrimitiveType.TRIANGLES: + geometry.indices = trianglesToLines(indices); + break; + case PrimitiveType.TRIANGLE_STRIP: + geometry.indices = triangleStripToLines(indices); + break; + case PrimitiveType.TRIANGLE_FAN: + geometry.indices = triangleFanToLines(indices); + break; + default: + throw new DeveloperError('geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.'); + } + + geometry.primitiveType = PrimitiveType.LINES; + } + + return geometry; + }; + + /** + * Creates a new {@link Geometry} with LINES representing the provided + * attribute (attributeName) for the provided geometry. This is used to + * visualize vector attributes like normals, binormals, and tangents. + * + * @param {Geometry} geometry The Geometry instance with the attribute. + * @param {String} [attributeName='normal'] The name of the attribute. + * @param {Number} [length=10000.0] The length of each line segment in meters. This can be negative to point the vector in the opposite direction. + * + * @returns {Geometry} A new Geometry instance with line segments for the vector. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} geometry.attributes.position is required. + * @exception {DeveloperError} geometry.attributes must have an attribute with the same name as the attributeName parameter. + * + * @example + * var geometry = GeometryPipeline.createLineSegmentsForVectors(instance.geometry, 'binormal', 100000.0), + */ + GeometryPipeline.createLineSegmentsForVectors = function(geometry, attributeName, length) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + if (typeof geometry.attributes.position === 'undefined') { + throw new DeveloperError('geometry.attributes.position is required.'); + } + + attributeName = defaultValue(attributeName, 'normal'); + + if (typeof geometry.attributes[attributeName] === 'undefined') { + throw new DeveloperError('geometry.attributes must have an attribute with the same name as the attributeName parameter, ' + attributeName + '.'); + } + + length = defaultValue(length, 10000.0); + + var positions = geometry.attributes.position.values; + var vectors = geometry.attributes[attributeName].values; + var positionsLength = positions.length; + + var newPositions = new Float64Array(2 * positionsLength); + + var j = 0; + for (var i = 0; i < positionsLength; i += 3) { + newPositions[j++] = positions[i]; + newPositions[j++] = positions[i + 1]; + newPositions[j++] = positions[i + 2]; + + newPositions[j++] = positions[i] + (vectors[i] * length); + newPositions[j++] = positions[i + 1] + (vectors[i + 1] * length); + newPositions[j++] = positions[i + 2] + (vectors[i + 2] * length); + } + + var newBoundingSphere; + var bs = geometry.boundingSphere; + if (typeof bs !== 'undefined') { + newBoundingSphere = new BoundingSphere(bs.center, bs.radius + length); + } + + return new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : newPositions + }) + }, + primitiveType : PrimitiveType.LINES, + boundingSphere : newBoundingSphere + }); + }; + + /** + * Creates an object that maps attribute names to unique indices for matching + * vertex attributes and shader programs. + * + * @param {Geometry} geometry The geometry, which is not modified, to create the object for. + * + * @returns {Object} An object with attribute name / index pairs. + * + * @exception {DeveloperError} geometry is required. + * + * @example + * var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + * // Example output + * // { + * // 'position' : 0, + * // 'normal' : 1 + * // } + * + * @see Context#createVertexArrayFromGeometry + * @see ShaderCache + */ + GeometryPipeline.createAttributeIndices = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + // There can be a WebGL performance hit when attribute 0 is disabled, so + // assign attribute locations to well-known attributes. + var semantics = [ + 'position', + 'positionHigh', + 'positionLow', + + // From VertexFormat.position - after 2D projection and high-precision encoding + 'position3DHigh', + 'position3DLow', + 'position2DHigh', + 'position2DLow', + + // From Primitive + 'pickColor', + + // From VertexFormat + 'normal', + 'st', + 'binormal', + 'tangent' + ]; + + var attributes = geometry.attributes; + var indices = {}; + var j = 0; + var i; + var len = semantics.length; + + // Attribute locations for well-known attributes + for (i = 0; i < len; ++i) { + var semantic = semantics[i]; + + if (typeof attributes[semantic] !== 'undefined') { + indices[semantic] = j++; + } + } + + // Locations for custom attributes + for (var name in attributes) { + if (attributes.hasOwnProperty(name) && (typeof indices[name] === 'undefined')) { + indices[name] = j++; + } + } + + return indices; + }; + + /** + * Reorders a geometry's attributes and indices to achieve better performance from the GPU's pre-vertex-shader cache. + * + * @param {Geometry} geometry The geometry to modify. + * + * @returns {Geometry} The modified geometry argument, with its attributes and indices reordered for the GPU's pre-vertex-shader cache. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} Each attribute array in geometry.attributes must have the same number of attributes. + * + * @example + * geometry = GeometryPipeline.reorderForPreVertexCache(geometry); + * + * @see GeometryPipeline.reorderForPostVertexCache + */ + GeometryPipeline.reorderForPreVertexCache = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var numVertices = Geometry.computeNumberOfVertices(geometry); + + var indices = geometry.indices; + if (typeof indices !== 'undefined') { + var indexCrossReferenceOldToNew = new Int32Array(numVertices); + for ( var i = 0; i < numVertices; i++) { + indexCrossReferenceOldToNew[i] = -1; + } + + // Construct cross reference and reorder indices + var indicesIn = indices; + var numIndices = indicesIn.length; + var indicesOut = IndexDatatype.createTypedArray(numVertices, numIndices); + + var intoIndicesIn = 0; + var intoIndicesOut = 0; + var nextIndex = 0; + var tempIndex; + while (intoIndicesIn < numIndices) { + tempIndex = indexCrossReferenceOldToNew[indicesIn[intoIndicesIn]]; + if (tempIndex !== -1) { + indicesOut[intoIndicesOut] = tempIndex; + } else { + tempIndex = indicesIn[intoIndicesIn]; + indexCrossReferenceOldToNew[tempIndex] = nextIndex; + + indicesOut[intoIndicesOut] = nextIndex; + ++nextIndex; + } + ++intoIndicesIn; + ++intoIndicesOut; + } + geometry.indices = indicesOut; + + // Reorder attributes + var attributes = geometry.attributes; + for ( var property in attributes) { + if (attributes.hasOwnProperty(property) && + typeof attributes[property] !== 'undefined' && + typeof attributes[property].values !== 'undefined') { + + var attribute = attributes[property]; + var elementsIn = attribute.values; + var intoElementsIn = 0; + var numComponents = attribute.componentsPerAttribute; + var elementsOut = attribute.componentDatatype.createTypedArray(elementsIn.length); + while (intoElementsIn < numVertices) { + var temp = indexCrossReferenceOldToNew[intoElementsIn]; + for (i = 0; i < numComponents; i++) { + elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i]; + } + ++intoElementsIn; + } + attribute.values = elementsOut; + } + } + } + + return geometry; + }; + + /** + * Reorders a geometry's indices to achieve better performance from the GPU's + * post vertex-shader cache by using the Tipsify algorithm. If the geometry primitiveType + * is not TRIANGLES or the geometry does not have an indices, this function has no effect. + * + * @param {Geometry} geometry The geometry to modify. + * @param {Number} [cacheCapacity=24] The number of vertices that can be held in the GPU's vertex cache. + * + * @returns {Geometry} The modified geometry argument, with its indices reordered for the post-vertex-shader cache. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} cacheCapacity must be greater than two. + * + * @example + * geometry = GeometryPipeline.reorderForPostVertexCache(geometry); + * + * @see GeometryPipeline.reorderForPreVertexCache + * @see + * Fast Triangle Reordering for Vertex Locality and Reduced Overdraw + * by Sander, Nehab, and Barczak + */ + GeometryPipeline.reorderForPostVertexCache = function(geometry, cacheCapacity) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var indices = geometry.indices; + if ((geometry.primitiveType === PrimitiveType.TRIANGLES) && (typeof indices !== 'undefined')) { + var numIndices = indices.length; + var maximumIndex = 0; + for ( var j = 0; j < numIndices; j++) { + if (indices[j] > maximumIndex) { + maximumIndex = indices[j]; + } + } + geometry.indices = Tipsify.tipsify({ + indices : indices, + maximumIndex : maximumIndex, + cacheSize : cacheCapacity + }); + } + + return geometry; + }; + + function copyAttributesDescriptions(attributes) { + var newAttributes = {}; + + for ( var attribute in attributes) { + if (attributes.hasOwnProperty(attribute) && + typeof attributes[attribute] !== 'undefined' && + typeof attributes[attribute].values !== 'undefined') { + + var attr = attributes[attribute]; + newAttributes[attribute] = new GeometryAttribute({ + componentDatatype : attr.componentDatatype, + componentsPerAttribute : attr.componentsPerAttribute, + normalize : attr.normalize, + values : [] + }); + } + } + + return newAttributes; + } + + function copyVertex(destinationAttributes, sourceAttributes, index) { + for ( var attribute in sourceAttributes) { + if (sourceAttributes.hasOwnProperty(attribute) && + typeof sourceAttributes[attribute] !== 'undefined' && + typeof sourceAttributes[attribute].values !== 'undefined') { + + var attr = sourceAttributes[attribute]; + + for ( var k = 0; k < attr.componentsPerAttribute; ++k) { + destinationAttributes[attribute].values.push(attr.values[(index * attr.componentsPerAttribute) + k]); + } + } + } + } + + /** + * Splits a geometry into multiple geometries, if necessary, to ensure that indices in the + * indices fit into unsigned shorts. This is used to meet the WebGL requirements + * when {@link Context#getElementIndexUint} is false. + *

+ * If the geometry does not have any indices, this function has no effect. + *

+ * + * @param {Geometry} geometry The geometry to be split into multiple geometries. + * + * @returns {Array} An array of geometries, each with indices that fit into unsigned shorts. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS + * @exception {DeveloperError} All geometry attribute lists must have the same number of attributes. + * + * @example + * var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + */ + GeometryPipeline.fitToUnsignedShortIndices = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + if ((typeof geometry.indices !== 'undefined') && + ((geometry.primitiveType !== PrimitiveType.TRIANGLES) && + (geometry.primitiveType !== PrimitiveType.LINES) && + (geometry.primitiveType !== PrimitiveType.POINTS))) { + throw new DeveloperError('geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS.'); + } + + var geometries = []; + + // If there's an index list and more than 64K attributes, it is possible that + // some indices are outside the range of unsigned short [0, 64K - 1] + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (typeof geometry.indices !== 'undefined' && (numberOfVertices > CesiumMath.SIXTY_FOUR_KILOBYTES)) { + var oldToNewIndex = []; + var newIndices = []; + var currentIndex = 0; + var newAttributes = copyAttributesDescriptions(geometry.attributes); + + var originalIndices = geometry.indices; + var numberOfIndices = originalIndices.length; + + var indicesPerPrimitive; + + if (geometry.primitiveType === PrimitiveType.TRIANGLES) { + indicesPerPrimitive = 3; + } else if (geometry.primitiveType === PrimitiveType.LINES) { + indicesPerPrimitive = 2; + } else if (geometry.primitiveType === PrimitiveType.POINTS) { + indicesPerPrimitive = 1; + } + + for ( var j = 0; j < numberOfIndices; j += indicesPerPrimitive) { + for (var k = 0; k < indicesPerPrimitive; ++k) { + var x = originalIndices[j + k]; + var i = oldToNewIndex[x]; + if (typeof i === 'undefined') { + i = currentIndex++; + oldToNewIndex[x] = i; + copyVertex(newAttributes, geometry.attributes, x); + } + newIndices.push(i); + } + + if (currentIndex + indicesPerPrimitive > CesiumMath.SIXTY_FOUR_KILOBYTES) { + geometries.push(new Geometry({ + attributes : newAttributes, + indices : newIndices, + primitiveType : geometry.primitiveType, + boundingSphere : geometry.boundingSphere + })); + + // Reset for next vertex-array + oldToNewIndex = []; + newIndices = []; + currentIndex = 0; + newAttributes = copyAttributesDescriptions(geometry.attributes); + } + } + + if (newIndices.length !== 0) { + geometries.push(new Geometry({ + attributes : newAttributes, + indices : newIndices, + primitiveType : geometry.primitiveType, + boundingSphere : geometry.boundingSphere + })); + } + } else { + // No need to split into multiple geometries + geometries.push(geometry); + } + + return geometries; + }; + + var scratchProjectTo2DCartesian3 = new Cartesian3(); + var scratchProjectTo2DCartographic = new Cartographic(); + + /** + * Projects a geometry's 3D position attribute to 2D, replacing the position + * attribute with separate position3D and position2D attributes. + *

+ * If the geometry does not have a position, this function has no effect. + *

+ * + * @param {Geometry} geometry The geometry to modify. + * @param {Object} [projection=new GeographicProjection()] The projection to use. + * + * @returns {Geometry} The modified geometry argument with position3D and position2D attributes. + * + * @exception {DeveloperError} geometry is required. + * + * @example + * geometry = GeometryPipeline.projectTo2D(geometry); + */ + GeometryPipeline.projectTo2D = function(geometry, projection) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + if (typeof geometry.attributes.position !== 'undefined') { + projection = (typeof projection !== 'undefined') ? projection : new GeographicProjection(); + var ellipsoid = projection.getEllipsoid(); + + // Project original positions to 2D. + var positions3D = geometry.attributes.position.values; + var projectedPositions = new Float64Array(positions3D.length); + var index = 0; + + for ( var i = 0; i < positions3D.length; i += 3) { + var position = Cartesian3.fromArray(positions3D, i, scratchProjectTo2DCartesian3); + var lonLat = ellipsoid.cartesianToCartographic(position, scratchProjectTo2DCartographic); + var projectedLonLat = projection.project(lonLat, scratchProjectTo2DCartesian3); + + projectedPositions[index++] = projectedLonLat.x; + projectedPositions[index++] = projectedLonLat.y; + projectedPositions[index++] = projectedLonLat.z; + } + + // Rename original positions to WGS84 Positions. + geometry.attributes.position3D = geometry.attributes.position; + + // Replace original positions with 2D projected positions + geometry.attributes.position2D = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : projectedPositions + }); + delete geometry.attributes.position; + } + + return geometry; + }; + + var encodedResult = { + high : 0.0, + low : 0.0 + }; + + /** + * Encodes floating-point geometry attribute values as two separate attributes to improve + * rendering precision using the same encoding as {@link EncodedCartesian3}. + *

+ * This is commonly used to create high-precision position vertex attributes. + *

+ * + * @param {Geometry} geometry The geometry to modify. + * @param {String} attributeName The name of the attribute. + * @param {String} attributeHighName The name of the attribute for the encoded high bits. + * @param {String} attributeLowName The name of the attribute for the encoded low bits. + * + * @returns {Geometry} The modified geometry argument, with its encoded attribute. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} attributeName is required. + * @exception {DeveloperError} attributeHighName is required. + * @exception {DeveloperError} attributeLowName is required. + * @exception {DeveloperError} geometry must have attribute matching the attributeName argument. + * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE. + * + * @example + * geometry = GeometryPipeline.encodeAttribute(geometry, 'position3D', 'position3DHigh', 'position3DLow'); + * + * @see EncodedCartesian3 + */ + GeometryPipeline.encodeAttribute = function(geometry, attributeName, attributeHighName, attributeLowName) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + if (typeof attributeName === 'undefined') { + throw new DeveloperError('attributeName is required.'); + } + + if (typeof attributeHighName === 'undefined') { + throw new DeveloperError('attributeHighName is required.'); + } + + if (typeof attributeLowName === 'undefined') { + throw new DeveloperError('attributeLowName is required.'); + } + + var attribute = geometry.attributes[attributeName]; + + if (typeof attribute === 'undefined') { + throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.'); + } + + if (attribute.componentDatatype !== ComponentDatatype.DOUBLE) { + throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.'); + } + + var values = attribute.values; + var length = values.length; + var highValues = new Float32Array(length); + var lowValues = new Float32Array(length); + + for (var i = 0; i < length; ++i) { + EncodedCartesian3.encode(values[i], encodedResult); + highValues[i] = encodedResult.high; + lowValues[i] = encodedResult.low; + } + + var componentsPerAttribute = attribute.componentsPerAttribute; + + geometry.attributes[attributeHighName] = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : componentsPerAttribute, + values : highValues + }); + geometry.attributes[attributeLowName] = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : componentsPerAttribute, + values : lowValues + }); + delete geometry.attributes[attributeName]; + + return geometry; + }; + + var scratch = new Cartesian3(); + + function transformPoint(matrix, attribute) { + if (typeof attribute !== 'undefined') { + var values = attribute.values; + var length = values.length; + for (var i = 0; i < length; i += 3) { + Cartesian3.fromArray(values, i, scratch); + Matrix4.multiplyByPoint(matrix, scratch, scratch); + values[i] = scratch.x; + values[i + 1] = scratch.y; + values[i + 2] = scratch.z; + } + } + } + + function transformVector(matrix, attribute) { + if (typeof attribute !== 'undefined') { + var values = attribute.values; + var length = values.length; + for (var i = 0; i < length; i += 3) { + Cartesian3.fromArray(values, i, scratch); + Matrix3.multiplyByVector(matrix, scratch, scratch); + values[i] = scratch.x; + values[i + 1] = scratch.y; + values[i + 2] = scratch.z; + } + } + } + + var inverseTranspose = new Matrix4(); + var normalMatrix = new Matrix3(); + + /** + * Transforms a geometry instance to world coordinates. This is used as a prerequisite + * to batch together several instances with {@link GeometryPipeline.combine}. This changes + * the instance's modelMatrix to {@see Matrix4.IDENTITY} and transforms the + * following attributes if they are present: position, normal, + * binormal, and tangent. + * + * @param {GeometryInstance} instance The geometry instance to modify. + * + * @returns {GeometryInstance} The modified instance argument, with its attributes transforms to world coordinates. + * + * @exception {DeveloperError} instance is required. + * + * @example + * for (var i = 0; i < instances.length; ++i) { + * GeometryPipeline.transformToWorldCoordinates(instances[i]); + * } + * var geometry = GeometryPipeline.combine(instances); + * + * @see GeometryPipeline.combine + */ + GeometryPipeline.transformToWorldCoordinates = function(instance) { + if (typeof instance === 'undefined') { + throw new DeveloperError('instance is required.'); + } + + var modelMatrix = instance.modelMatrix; + + if (modelMatrix.equals(Matrix4.IDENTITY)) { + // Already in world coordinates + return instance; + } + + var attributes = instance.geometry.attributes; + + // Transform attributes in known vertex formats + transformPoint(modelMatrix, attributes.position); + + if ((typeof attributes.normal !== 'undefined') || + (typeof attributes.binormal !== 'undefined') || + (typeof attributes.tangent !== 'undefined')) { + + Matrix4.inverse(modelMatrix, inverseTranspose); + Matrix4.transpose(inverseTranspose, inverseTranspose); + Matrix4.getRotation(inverseTranspose, normalMatrix); + + transformVector(normalMatrix, attributes.normal); + transformVector(normalMatrix, attributes.binormal); + transformVector(normalMatrix, attributes.tangent); + } + + var boundingSphere = instance.geometry.boundingSphere; + + if (typeof boundingSphere !== 'undefined') { + Matrix4.multiplyByPoint(modelMatrix, boundingSphere.center, boundingSphere.center); + boundingSphere.center = Cartesian3.fromCartesian4(boundingSphere.center); + } + + instance.modelMatrix = Matrix4.IDENTITY.clone(); + + return instance; + }; + + function findAttributesInAllGeometries(instances) { + var length = instances.length; + + var attributesInAllGeometries = {}; + + var attributes0 = instances[0].geometry.attributes; + var name; + + for (name in attributes0) { + if (attributes0.hasOwnProperty(name) && + typeof attributes0[name] !== 'undefined' && + typeof attributes0[name].values !== 'undefined') { + + var attribute = attributes0[name]; + var numberOfComponents = attribute.values.length; + var inAllGeometries = true; + + // Does this same attribute exist in all geometries? + for (var i = 1; i < length; ++i) { + var otherAttribute = instances[i].geometry.attributes[name]; + + if ((typeof otherAttribute === 'undefined') || + (attribute.componentDatatype !== otherAttribute.componentDatatype) || + (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || + (attribute.normalize !== otherAttribute.normalize)) { + + inAllGeometries = false; + break; + } + + numberOfComponents += otherAttribute.values.length; + } + + if (inAllGeometries) { + attributesInAllGeometries[name] = new GeometryAttribute({ + componentDatatype : attribute.componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize, + values : attribute.componentDatatype.createTypedArray(numberOfComponents) + }); + } + } + } + + return attributesInAllGeometries; + } + + /** + * Combines geometry from several {@link GeometryInstance} objects into one geometry. + * This concatenates the attributes, concatenates and adjusts the indices, and creates + * a bounding sphere encompassing all instances. + *

+ * If the instances do not have the same attributes, a subset of attributes common + * to all instances is used, and the others are ignored. + *

+ *

+ * This is used by {@link Primitive} to efficiently render a large amount of static data. + *

+ * + * @param {Array} [instances] The array of {@link GeometryInstance} objects whose geometry will be combined. + * + * @returns {Geometry} A single geometry created from the provided geometry instances. + * + * @exception {DeveloperError} instances is required and must have length greater than zero. + * @exception {DeveloperError} All instances must have the same modelMatrix. + * @exception {DeveloperError} All instance geometries must have an indices or not have one. + * @exception {DeveloperError} All instance geometries must have the same primitiveType. + * + * @example + * for (var i = 0; i < instances.length; ++i) { + * GeometryPipeline.transformToWorldCoordinates(instances[i]); + * } + * var geometry = GeometryPipeline.combine(instances); + * + * @see GeometryPipeline.transformToWorldCoordinates + */ + GeometryPipeline.combine = function(instances) { + if ((typeof instances === 'undefined') || (instances.length < 1)) { + throw new DeveloperError('instances is required and must have length greater than zero.'); + } + + var length = instances.length; + + var name; + var i; + var j; + var k; + + var m = instances[0].modelMatrix; + var haveIndices = (typeof instances[0].geometry.indices !== 'undefined'); + var primitiveType = instances[0].geometry.primitiveType; + + for (i = 1; i < length; ++i) { + if (!Matrix4.equals(instances[i].modelMatrix, m)) { + throw new DeveloperError('All instances must have the same modelMatrix.'); + } + + if ((typeof instances[i].geometry.indices !== 'undefined') !== haveIndices) { + throw new DeveloperError('All instance geometries must have an indices or not have one.'); + } + + if (instances[i].geometry.primitiveType !== primitiveType) { + throw new DeveloperError('All instance geometries must have the same primitiveType.'); + } + } + + // Find subset of attributes in all geometries + var attributes = findAttributesInAllGeometries(instances); + var values; + var sourceValues; + var sourceValuesLength; + + // Combine attributes from each geometry into a single typed array + for (name in attributes) { + if (attributes.hasOwnProperty(name)) { + values = attributes[name].values; + + k = 0; + for (i = 0; i < length; ++i) { + sourceValues = instances[i].geometry.attributes[name].values; + sourceValuesLength = sourceValues.length; + + for (j = 0; j < sourceValuesLength; ++j) { + values[k++] = sourceValues[j]; + } + } + } + } + + // Combine index lists + var indices; + + if (haveIndices) { + var numberOfIndices = 0; + for (i = 0; i < length; ++i) { + numberOfIndices += instances[i].geometry.indices.length; + } + + var numberOfVertices = Geometry.computeNumberOfVertices(new Geometry({ + attributes : attributes, + primitiveType : PrimitiveType.POINTS + })); + var destIndices = IndexDatatype.createTypedArray(numberOfVertices, numberOfIndices); + + var destOffset = 0; + var offset = 0; + + for (i = 0; i < length; ++i) { + var sourceIndices = instances[i].geometry.indices; + var sourceIndicesLen = sourceIndices.length; + + for (k = 0; k < sourceIndicesLen; ++k) { + destIndices[destOffset++] = offset + sourceIndices[k]; + } + + offset += Geometry.computeNumberOfVertices(instances[i].geometry); + } + + indices = destIndices; + } + + // Create bounding sphere that includes all instances + var boundingSphere; + + for (i = 0; i < length; ++i) { + var bs = instances[i].geometry.boundingSphere; + if (typeof bs === 'undefined') { + // If any geometries have an undefined bounding sphere, then so does the combined geometry + boundingSphere = undefined; + break; + } + + if (typeof boundingSphere === 'undefined') { + boundingSphere = bs.clone(); + } else { + BoundingSphere.union(boundingSphere, bs, boundingSphere); + } + } + + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : primitiveType, + boundingSphere : boundingSphere + }); + }; + + var normal = new Cartesian3(); + var v0 = new Cartesian3(); + var v1 = new Cartesian3(); + var v2 = new Cartesian3(); + + /** + * Computes per-vertex normals for a geometry containing TRIANGLES by averaging the normals of + * all triangles incident to the vertex. The result is a new normal attribute added to the geometry. + * This assumes a counter-clockwise winding order. + * + * @param {Geometry} geometry The geometry to modify. + * + * @returns {Geometry} The modified geometry argument with the computed normal attribute. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} geometry.attributes.position.values is required. + * @exception {DeveloperError} geometry.indices is required. + * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3. + * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}. + * + * @example + * GeometryPipeline.computeNormal(geometry); + */ + GeometryPipeline.computeNormal = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var attributes = geometry.attributes; + var indices = geometry.indices; + + if (typeof attributes.position === 'undefined' || typeof attributes.position.values === 'undefined') { + throw new DeveloperError('geometry.attributes.position.values is required.'); + } + + if (typeof indices === 'undefined') { + throw new DeveloperError('geometry.indices is required.'); + } + + if (indices.length < 2 || indices.length % 3 !== 0) { + throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.'); + } + + if (geometry.primitiveType !== PrimitiveType.TRIANGLES) { + throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.'); + } + + var vertices = geometry.attributes.position.values; + var numVertices = geometry.attributes.position.values.length / 3; + var numIndices = indices.length; + var normalsPerVertex = new Array(numVertices); + var normalsPerTriangle = new Array(numIndices / 3); + var normalIndices = new Array(numIndices); + + for ( var i = 0; i < numVertices; i++) { + normalsPerVertex[i] = { + indexOffset : 0, + count : 0, + currentCount : 0 + }; + } + + var j = 0; + for (i = 0; i < numIndices; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + var i2 = indices[i + 2]; + var i03 = i0 * 3; + var i13 = i1 * 3; + var i23 = i2 * 3; + + v0.x = vertices[i03]; + v0.y = vertices[i03 + 1]; + v0.z = vertices[i03 + 2]; + v1.x = vertices[i13]; + v1.y = vertices[i13 + 1]; + v1.z = vertices[i13 + 2]; + v2.x = vertices[i23]; + v2.y = vertices[i23 + 1]; + v2.z = vertices[i23 + 2]; + + normalsPerVertex[i0].count++; + normalsPerVertex[i1].count++; + normalsPerVertex[i2].count++; + + v1.subtract(v0, v1); + v2.subtract(v0, v2); + normalsPerTriangle[j] = v1.cross(v2); + j++; + } + + var indexOffset = 0; + for (i = 0; i < numVertices; i++) { + normalsPerVertex[i].indexOffset += indexOffset; + indexOffset += normalsPerVertex[i].count; + } + + j = 0; + var vertexNormalData; + for (i = 0; i < numIndices; i += 3) { + vertexNormalData = normalsPerVertex[indices[i]]; + var index = vertexNormalData.indexOffset + vertexNormalData.currentCount; + normalIndices[index] = j; + vertexNormalData.currentCount++; + + vertexNormalData = normalsPerVertex[indices[i + 1]]; + index = vertexNormalData.indexOffset + vertexNormalData.currentCount; + normalIndices[index] = j; + vertexNormalData.currentCount++; + + vertexNormalData = normalsPerVertex[indices[i + 2]]; + index = vertexNormalData.indexOffset + vertexNormalData.currentCount; + normalIndices[index] = j; + vertexNormalData.currentCount++; + + j++; + } + + var normalValues = new Float32Array(numVertices * 3); + for (i = 0; i < numVertices; i++) { + var i3 = i * 3; + vertexNormalData = normalsPerVertex[i]; + if (vertexNormalData.count > 0) { + Cartesian3.ZERO.clone(normal); + for (j = 0; j < vertexNormalData.count; j++) { + normal.add(normalsPerTriangle[normalIndices[vertexNormalData.indexOffset + j]], normal); + } + normal.normalize(normal); + normalValues[i3] = normal.x; + normalValues[i3 + 1] = normal.y; + normalValues[i3 + 2] = normal.z; + } else { + normalValues[i3] = 0.0; + normalValues[i3 + 1] = 0.0; + normalValues[i3 + 2] = 1.0; + } + } + + geometry.attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normalValues + }); + + return geometry; + }; + + var normalScratch = new Cartesian3(); + var normalScale = new Cartesian3(); + var tScratch = new Cartesian3(); + + /** + * Computes per-vertex binormals and tangents for a geometry containing TRIANGLES. + * The result is new binormal and tangent attributes added to the geometry. + * This assumes a counter-clockwise winding order. + *

+ * Based on Computing Tangent Space Basis Vectors + * for an Arbitrary Mesh by Eric Lengyel. + *

+ * + * @param {Geometry} geometry The geometry to modify. + * + * @returns {Geometry} The modified geometry argument with the computed binormal and tangent attributes. + * + * @exception {DeveloperError} geometry is required. + * @exception {DeveloperError} geometry.attributes.position.values is required. + * @exception {DeveloperError} geometry.attributes.normal.values is required. + * @exception {DeveloperError} geometry.attributes.st.values is required. + * @exception {DeveloperError} geometry.indices is required. + * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3. + * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}. + * + * @example + * GeometryPipeline.computeBinormalAndTangent(geometry); + */ + GeometryPipeline.computeBinormalAndTangent = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var attributes = geometry.attributes; + var indices = geometry.indices; + + if (typeof attributes.position === 'undefined' || typeof attributes.position.values === 'undefined') { + throw new DeveloperError('geometry.attributes.position.values is required.'); + } + + if (typeof attributes.normal === 'undefined' || typeof attributes.normal.values === 'undefined') { + throw new DeveloperError('geometry.attributes.normal.values is required.'); + } + + if (typeof attributes.st === 'undefined' || typeof attributes.st.values === 'undefined') { + throw new DeveloperError('geometry.attributes.st.values is required.'); + } + + if (typeof indices === 'undefined') { + throw new DeveloperError('geometry.indices is required.'); + } + + if (indices.length < 2 || indices.length % 3 !== 0) { + throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.'); + } + + if (geometry.primitiveType !== PrimitiveType.TRIANGLES) { + throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.'); + } + + var vertices = geometry.attributes.position.values; + var normals = geometry.attributes.normal.values; + var st = geometry.attributes.st.values; + + var numVertices = geometry.attributes.position.values.length / 3; + var numIndices = indices.length; + var tan1 = new Array(numVertices * 3); + + for ( var i = 0; i < tan1.length; i++) { + tan1[i] = 0; + } + + var i03; + var i13; + var i23; + for (i = 0; i < numIndices; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + var i2 = indices[i + 2]; + i03 = i0 * 3; + i13 = i1 * 3; + i23 = i2 * 3; + var i02 = i0 * 2; + var i12 = i1 * 2; + var i22 = i2 * 2; + + var ux = vertices[i03]; + var uy = vertices[i03 + 1]; + var uz = vertices[i03 + 2]; + + var wx = st[i02]; + var wy = st[i02 + 1]; + var t1 = st[i12 + 1] - wy; + var t2 = st[i22 + 1] - wy; + + var r = 1.0 / ((st[i12] - wx) * t2 - (st[i22] - wx) * t1); + var sdirx = (t2 * (vertices[i13] - ux) - t1 * (vertices[i23] - ux)) * r; + var sdiry = (t2 * (vertices[i13 + 1] - uy) - t1 * (vertices[i23 + 1] - uy)) * r; + var sdirz = (t2 * (vertices[i13 + 2] - uz) - t1 * (vertices[i23 + 2] - uz)) * r; + + tan1[i03] += sdirx; + tan1[i03 + 1] += sdiry; + tan1[i03 + 2] += sdirz; + + tan1[i13] += sdirx; + tan1[i13 + 1] += sdiry; + tan1[i13 + 2] += sdirz; + + tan1[i23] += sdirx; + tan1[i23 + 1] += sdiry; + tan1[i23 + 2] += sdirz; + } + + var binormalValues = new Float32Array(numVertices * 3); + var tangentValues = new Float32Array(numVertices * 3); + + for (i = 0; i < numVertices; i++) { + i03 = i * 3; + i13 = i03 + 1; + i23 = i03 + 2; + + var n = Cartesian3.fromArray(normals, i03, normalScratch); + var t = Cartesian3.fromArray(tan1, i03, tScratch); + var scalar = n.dot(t); + n.multiplyByScalar(scalar, normalScale); + t.subtract(normalScale, t).normalize(t); + + tangentValues[i03] = t.x; + tangentValues[i13] = t.y; + tangentValues[i23] = t.z; + + n.cross(t, t).normalize(t); + + binormalValues[i03] = t.x; + binormalValues[i13] = t.y; + binormalValues[i23] = t.z; + } + + geometry.attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangentValues + }); + + geometry.attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormalValues + }); + + return geometry; + }; + + function indexTriangles(geometry) { + if (typeof geometry.indices !== 'undefined') { + return geometry; + } + + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 3) { + throw new DeveloperError('The number of vertices must be at least three.'); + } + + if (numberOfVertices % 3 !== 0) { + throw new DeveloperError('The number of vertices must be a multiple of three.'); + } + + var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices); + for (var i = 0; i < numberOfVertices; ++i) { + indices[i] = i; + } + + geometry.indices = indices; + return geometry; + } + + function indexTriangleFan(geometry) { + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 3) { + throw new DeveloperError('The number of vertices must be at least three.'); + } + + var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3); + indices[0] = 1; + indices[1] = 0; + indices[2] = 2; + + var indicesIndex = 3; + for (var i = 3; i < numberOfVertices; ++i) { + indices[indicesIndex++] = i - 1; + indices[indicesIndex++] = 0; + indices[indicesIndex++] = i; + } + + geometry.indices = indices; + geometry.primitiveType = PrimitiveType.TRIANGLES; + return geometry; + } + + function indexTriangleStrip(geometry) { + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 3) { + throw new DeveloperError('The number of vertices must be at least 3.'); + } + + var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3); + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + + if (numberOfVertices > 3) { + indices[3] = 0; + indices[4] = 2; + indices[5] = 3; + } + + var indicesIndex = 6; + for (var i = 3; i < numberOfVertices - 1; i += 2) { + indices[indicesIndex++] = i; + indices[indicesIndex++] = i - 1; + indices[indicesIndex++] = i + 1; + + if (i + 2 < numberOfVertices) { + indices[indicesIndex++] = i; + indices[indicesIndex++] = i + 1; + indices[indicesIndex++] = i + 2; + } + } + + geometry.indices = indices; + geometry.primitiveType = PrimitiveType.TRIANGLES; + return geometry; + } + + function indexLines(geometry) { + if (typeof geometry.indices !== 'undefined') { + return geometry; + } + + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 2) { + throw new DeveloperError('The number of vertices must be at least two.'); + } + + if (numberOfVertices % 2 !== 0) { + throw new DeveloperError('The number of vertices must be a multiple of 2.'); + } + + var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices); + for (var i = 0; i < numberOfVertices; ++i) { + indices[i] = i; + } + + geometry.indices = indices; + return geometry; + } + + function indexLineStrip(geometry) { + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 2) { + throw new DeveloperError('The number of vertices must be at least two.'); + } + var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 1) * 2); + + indices[0] = 0; + indices[1] = 1; + + var indicesIndex = 2; + for (var i = 2; i < numberOfVertices; ++i) { + indices[indicesIndex++] = i - 1; + indices[indicesIndex++] = i; + } + + geometry.indices = indices; + geometry.primitiveType = PrimitiveType.LINES; + return geometry; + } + + function indexLineLoop(geometry) { + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + if (numberOfVertices < 2) { + throw new DeveloperError('The number of vertices must be at least two.'); + } + + var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices * 2); + + indices[0] = 0; + indices[1] = 1; + + var indicesIndex = 2; + for (var i = 2; i < numberOfVertices; ++i) { + indices[indicesIndex++] = i - 1; + indices[indicesIndex++] = i; + } + + indices[indicesIndex++] = numberOfVertices - 1; + indices[indicesIndex] = 0; + + geometry.indices = indices; + geometry.primitiveType = PrimitiveType.LINES; + return geometry; + } + + function indexPrimitive(geometry) { + switch (geometry.primitiveType) { + case PrimitiveType.TRIANGLE_FAN: + return indexTriangleFan(geometry); + case PrimitiveType.TRIANGLE_STRIP: + return indexTriangleStrip(geometry); + case PrimitiveType.TRIANGLES: + return indexTriangles(geometry); + case PrimitiveType.LINE_STRIP: + return indexLineStrip(geometry); + case PrimitiveType.LINE_LOOP: + return indexLineLoop(geometry); + case PrimitiveType.LINES: + return indexLines(geometry); + } + + return geometry; + } + + function offsetPointFromXZPlane(p, isBehind) { + if (Math.abs(p.y) < CesiumMath.EPSILON11){ + if (isBehind) { + p.y = -CesiumMath.EPSILON11; + } else { + p.y = CesiumMath.EPSILON11; + } + } + } + + var c3 = new Cartesian3(); + function getXZIntersectionOffsetPoints(p, p1, u1, v1) { + p.add(p1.subtract(p, c3).multiplyByScalar(p.y/(p.y-p1.y), c3), u1); + Cartesian3.clone(u1, v1); + offsetPointFromXZPlane(u1, true); + offsetPointFromXZPlane(v1, false); + } + + var u1 = new Cartesian3(); + var u2 = new Cartesian3(); + var q1 = new Cartesian3(); + var q2 = new Cartesian3(); + + var splitTriangleResult = { + positions : new Array(7), + indices : new Array(3 * 3) + }; + + function splitTriangle(p0, p1, p2) { + // In WGS84 coordinates, for a triangle approximately on the + // ellipsoid to cross the IDL, first it needs to be on the + // negative side of the plane x = 0. + if ((p0.x >= 0.0) || (p1.x >= 0.0) || (p2.x >= 0.0)) { + return undefined; + } + + var p0Behind = p0.y < 0.0; + var p1Behind = p1.y < 0.0; + var p2Behind = p2.y < 0.0; + + offsetPointFromXZPlane(p0, p0Behind); + offsetPointFromXZPlane(p1, p1Behind); + offsetPointFromXZPlane(p2, p2Behind); + + var numBehind = 0; + numBehind += p0Behind ? 1 : 0; + numBehind += p1Behind ? 1 : 0; + numBehind += p2Behind ? 1 : 0; + + var indices = splitTriangleResult.indices; + + if (numBehind === 1) { + indices[1] = 3; + indices[2] = 4; + indices[5] = 6; + indices[7] = 6; + indices[8] = 5; + + if (p0Behind) { + getXZIntersectionOffsetPoints(p0, p1, u1, q1); + getXZIntersectionOffsetPoints(p0, p2, u2, q2); + + indices[0] = 0; + indices[3] = 1; + indices[4] = 2; + indices[6] = 1; + } else if (p1Behind) { + getXZIntersectionOffsetPoints(p1, p0, u1, q1); + getXZIntersectionOffsetPoints(p1, p2, u2, q2); + + indices[0] = 1; + indices[3] = 2; + indices[4] = 0; + indices[6] = 2; + } else if (p2Behind) { + getXZIntersectionOffsetPoints(p2, p0, u1, q1); + getXZIntersectionOffsetPoints(p2, p1, u2, q2); + + indices[0] = 2; + indices[3] = 0; + indices[4] = 1; + indices[6] = 0; + } + } else if (numBehind === 2) { + indices[2] = 4; + indices[4] = 4; + indices[5] = 3; + indices[7] = 5; + indices[8] = 6; + + if (!p0Behind) { + getXZIntersectionOffsetPoints(p0, p1, u1, q1); + getXZIntersectionOffsetPoints(p0, p2, u2, q2); + + indices[0] = 1; + indices[1] = 2; + indices[3] = 1; + indices[6] = 0; + } else if (!p1Behind) { + getXZIntersectionOffsetPoints(p1, p2, u1, q1); + getXZIntersectionOffsetPoints(p1, p0, u2, q2); + + indices[0] = 2; + indices[1] = 0; + indices[3] = 2; + indices[6] = 1; + } else if (!p2Behind) { + getXZIntersectionOffsetPoints(p2, p0, u1, q1); + getXZIntersectionOffsetPoints(p2, p1, u2, q2); + + indices[0] = 0; + indices[1] = 1; + indices[3] = 0; + indices[6] = 2; + } + } + + var positions = splitTriangleResult.positions; + positions[0] = p0; + positions[1] = p1; + positions[2] = p2; + splitTriangleResult.length = 3; + + if (numBehind === 1 || numBehind === 2) { + positions[3] = u1; + positions[4] = u2; + positions[5] = q1; + positions[6] = q2; + splitTriangleResult.length = 7; + } + + return splitTriangleResult; + } + + function computeTriangleAttributes(i0, i1, i2, dividedTriangle, normals, binormals, tangents, texCoords) { + if (typeof normals === 'undefined' && typeof binormals === 'undefined' && + typeof tangents === 'undefined' && typeof texCoords === 'undefined') { + return; + } + + var positions = dividedTriangle.positions; + var p0 = positions[0]; + var p1 = positions[1]; + var p2 = positions[2]; + + var n0, n1, n2; + var b0, b1, b2; + var t0, t1, t2; + var s0, s1, s2; + var v0, v1, v2; + var u0, u1, u2; + + if (typeof normals !== 'undefined') { + n0 = Cartesian3.fromArray(normals, i0 * 3); + n1 = Cartesian3.fromArray(normals, i1 * 3); + n2 = Cartesian3.fromArray(normals, i2 * 3); + } + + if (typeof binormals !== 'undefined') { + b0 = Cartesian3.fromArray(binormals, i0 * 3); + b1 = Cartesian3.fromArray(binormals, i1 * 3); + b2 = Cartesian3.fromArray(binormals, i2 * 3); + } + + if (typeof tangents !== 'undefined') { + t0 = Cartesian3.fromArray(tangents, i0 * 3); + t1 = Cartesian3.fromArray(tangents, i1 * 3); + t2 = Cartesian3.fromArray(tangents, i2 * 3); + } + + if (typeof texCoords !== 'undefined') { + s0 = Cartesian2.fromArray(texCoords, i0 * 2); + s1 = Cartesian2.fromArray(texCoords, i1 * 2); + s2 = Cartesian2.fromArray(texCoords, i2 * 2); + } + + for (var i = 3; i < positions.length; ++i) { + var point = positions[i]; + var coords = barycentricCoordinates(point, p0, p1, p2); + + if (typeof normals !== 'undefined') { + v0 = Cartesian3.multiplyByScalar(n0, coords.x, v0); + v1 = Cartesian3.multiplyByScalar(n1, coords.y, v1); + v2 = Cartesian3.multiplyByScalar(n2, coords.z, v2); + + var normal = Cartesian3.add(v0, v1); + Cartesian3.add(normal, v2, normal); + Cartesian3.normalize(normal, normal); + + normals.push(normal.x, normal.y, normal.z); + } + + if (typeof binormals !== 'undefined') { + v0 = Cartesian3.multiplyByScalar(b0, coords.x, v0); + v1 = Cartesian3.multiplyByScalar(b1, coords.y, v1); + v2 = Cartesian3.multiplyByScalar(b2, coords.z, v2); + + var binormal = Cartesian3.add(v0, v1); + Cartesian3.add(binormal, v2, binormal); + Cartesian3.normalize(binormal, binormal); + + binormals.push(binormal.x, binormal.y, binormal.z); + } + + if (typeof tangents !== 'undefined') { + v0 = Cartesian3.multiplyByScalar(t0, coords.x, v0); + v1 = Cartesian3.multiplyByScalar(t1, coords.y, v1); + v2 = Cartesian3.multiplyByScalar(t2, coords.z, v2); + + var tangent = Cartesian3.add(v0, v1); + Cartesian3.add(tangent, v2, tangent); + Cartesian3.normalize(tangent, tangent); + + tangents.push(tangent.x, tangent.y, tangent.z); + } + + if (typeof texCoords !== 'undefined') { + u0 = Cartesian2.multiplyByScalar(s0, coords.x, u0); + u1 = Cartesian2.multiplyByScalar(s1, coords.y, u1); + u2 = Cartesian2.multiplyByScalar(s2, coords.z, u2); + + var texCoord = Cartesian2.add(u0, u1); + Cartesian2.add(texCoord, u2, texCoord); + + texCoords.push(texCoord.x, texCoord.y); + } + } + } + + function wrapLongitudeTriangles(geometry) { + var attributes = geometry.attributes; + var positions = attributes.position.values; + var normals = (typeof attributes.normal !== 'undefined') ? attributes.normal.values : undefined; + var binormals = (typeof attributes.binormal !== 'undefined') ? attributes.binormal.values : undefined; + var tangents = (typeof attributes.tangent !== 'undefined') ? attributes.tangent.values : undefined; + var texCoords = (typeof attributes.st !== 'undefined') ? attributes.st.values : undefined; + var indices = geometry.indices; + + var newPositions = Array.prototype.slice.call(positions, 0); + var newNormals = (typeof normals !== 'undefined') ? Array.prototype.slice.call(normals, 0) : undefined; + var newBinormals = (typeof binormals !== 'undefined') ? Array.prototype.slice.call(binormals, 0) : undefined; + var newTangents = (typeof tangents !== 'undefined') ? Array.prototype.slice.call(tangents, 0) : undefined; + var newTexCoords = (typeof texCoords !== 'undefined') ? Array.prototype.slice.call(texCoords, 0) : undefined; + var newIndices = []; + + var len = indices.length; + for (var i = 0; i < len; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + var i2 = indices[i + 2]; + + var p0 = Cartesian3.fromArray(positions, i0 * 3); + var p1 = Cartesian3.fromArray(positions, i1 * 3); + var p2 = Cartesian3.fromArray(positions, i2 * 3); + + var result = splitTriangle(p0, p1, p2); + if (typeof result !== 'undefined') { + newPositions[i0 * 3 + 1] = result.positions[0].y; + newPositions[i1 * 3 + 1] = result.positions[1].y; + newPositions[i2 * 3 + 1] = result.positions[2].y; + + if (result.length > 3) { + var positionsLength = newPositions.length / 3; + for(var j = 0; j < result.indices.length; ++j) { + var index = result.indices[j]; + if (index < 3) { + newIndices.push(indices[i + index]); + } else { + newIndices.push(index - 3 + positionsLength); + } + } + + for (var k = 3; k < result.positions.length; ++k) { + var position = result.positions[k]; + newPositions.push(position.x, position.y, position.z); + } + computeTriangleAttributes(i0, i1, i2, result, newNormals, newBinormals, newTangents, newTexCoords); + } else { + newIndices.push(i0, i1, i2); + } + } else { + newIndices.push(i0, i1, i2); + } + } + + geometry.attributes.position.values = new Float64Array(newPositions); + + if (typeof newNormals !== 'undefined') { + attributes.normal.values = attributes.normal.componentDatatype.createTypedArray(newNormals); + } + + if (typeof newBinormals !== 'undefined') { + attributes.binormal.values = attributes.binormal.componentDatatype.createTypedArray(newBinormals); + } + + if (typeof newTangents !== 'undefined') { + attributes.tangent.values = attributes.tangent.componentDatatype.createTypedArray(newTangents); + } + + if (typeof newTexCoords !== 'undefined') { + attributes.st.values = attributes.st.componentDatatype.createTypedArray(newTexCoords); + } + + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + geometry.indices = IndexDatatype.createTypedArray(numberOfVertices, newIndices); + } + + function wrapLongitudeLines(geometry) { + var attributes = geometry.attributes; + var positions = attributes.position.values; + var indices = geometry.indices; + + var newPositions = Array.prototype.slice.call(positions, 0); + var newIndices = []; + + var xzPlane = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y); + + var length = indices.length; + for ( var i = 0; i < length; i += 2) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + + var prev = Cartesian3.fromArray(positions, i0 * 3); + var cur = Cartesian3.fromArray(positions, i1 * 3); + + if (Math.abs(prev.y) < CesiumMath.EPSILON6){ + if (prev.y < 0.0) { + prev.y = -CesiumMath.EPSILON6; + } else { + prev.y = CesiumMath.EPSILON6; + } + + newPositions[i0 * 3 + 1] = prev.y; + } + + if (Math.abs(cur.y) < CesiumMath.EPSILON6){ + if (cur.y < 0.0) { + cur.y = -CesiumMath.EPSILON6; + } else { + cur.y = CesiumMath.EPSILON6; + } + + newPositions[i1 * 3 + 1] = cur.y; + } + + newIndices.push(i0); + + // intersects the IDL if either endpoint is on the negative side of the yz-plane + if (prev.x < 0.0 || cur.x < 0.0) { + // and intersects the xz-plane + var intersection = IntersectionTests.lineSegmentPlane(prev, cur, xzPlane); + if (typeof intersection !== 'undefined') { + // move point on the xz-plane slightly away from the plane + var offset = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Y, 5.0 * CesiumMath.EPSILON9); + if (prev.y < 0.0) { + Cartesian3.negate(offset, offset); + } + + var index = newPositions.length / 3; + newIndices.push(index, index + 1); + + var offsetPoint = Cartesian3.add(intersection, offset); + newPositions.push(offsetPoint.x, offsetPoint.y, offsetPoint.z); + + Cartesian3.negate(offset, offset); + Cartesian3.add(intersection, offset, offsetPoint); + newPositions.push(offsetPoint.x, offsetPoint.y, offsetPoint.z); + } + } + + newIndices.push(i1); + } + + geometry.attributes.position.values = new Float64Array(newPositions); + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + geometry.indices = IndexDatatype.createTypedArray(numberOfVertices, newIndices); + } + + /** + * Splits the geometry's primitives, by introducing new vertices and indices,that + * intersect the International Date Line so that no primitives cross longitude + * -180/180 degrees. This is not required for 3D drawing, but is required for + * correcting drawing in 2D and Columbus view. + * + * @param {Geometry} geometry The geometry to modify. + * + * @returns {Geometry} The modified geometry argument, with it's primitives split at the International Date Line. + * + * @exception {DeveloperError} geometry is required. + * + * @example + * geometry = GeometryPipeline.wrapLongitude(geometry); + */ + GeometryPipeline.wrapLongitude = function(geometry) { + if (typeof geometry === 'undefined') { + throw new DeveloperError('geometry is required.'); + } + + var boundingSphere = geometry.boundingSphere; + if (typeof boundingSphere !== 'undefined') { + var minX = boundingSphere.center.x - boundingSphere.radius; + if (minX > 0 || BoundingSphere.intersect(boundingSphere, Cartesian4.UNIT_Y) !== Intersect.INTERSECTING) { + return geometry; + } + } + + indexPrimitive(geometry); + if (geometry.primitiveType === PrimitiveType.TRIANGLES) { + wrapLongitudeTriangles(geometry); + } else if (geometry.primitiveType === PrimitiveType.LINES) { + wrapLongitudeLines(geometry); + } + + return geometry; + }; + + return GeometryPipeline; +}); diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index 71fb70041ce0..7963fbcab876 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -19,11 +19,6 @@ define([ * Contains functions to create a mesh from a heightmap image. * * @exports HeightmapTessellator - * - * @see ExtentTessellator - * @see CubeMapEllipsoidTessellator - * @see BoxTessellator - * @see PlaneTessellator */ var HeightmapTessellator = {}; diff --git a/Source/Core/IndexDatatype.js b/Source/Core/IndexDatatype.js index 31143e2b4654..b32c8c7f5678 100644 --- a/Source/Core/IndexDatatype.js +++ b/Source/Core/IndexDatatype.js @@ -1,29 +1,100 @@ /*global define*/ -define(['./Enumeration'], function(Enumeration) { +define([ + './Enumeration', + './DeveloperError', + './Math' + ], function( + Enumeration, + DeveloperError, + CesiumMath) { "use strict"; /** - * DOC_TBA + * Enumerations for WebGL index datatypes. These corresponds to the + * type parameter of drawElements. * - * @exports IndexDatatype + * @alias IndexDatatype + * @enumeration */ var IndexDatatype = { /** - * DOC_TBA + * 8-bit unsigned byte enumeration corresponding to UNSIGNED_BYTE and the type + * of an element in Uint8Array. * * @type {Enumeration} * @constant * @default 0x1401 */ UNSIGNED_BYTE : new Enumeration(0x1401, 'UNSIGNED_BYTE'), + /** - * DOC_TBA + * 16-bit unsigned short enumeration corresponding to UNSIGNED_SHORT and the type + * of an element in Uint16Array. * * @type {Enumeration} * @constant * @default 0x1403 */ - UNSIGNED_SHORT : new Enumeration(0x1403, 'UNSIGNED_SHORT') + UNSIGNED_SHORT : new Enumeration(0x1403, 'UNSIGNED_SHORT'), + + /** + * 32-bit unsigned int enumeration corresponding to UNSIGNED_INT and the type + * of an element in Uint32Array. + * + * @memberOf ComponentDatatype + * + * @constant + * @type {Enumeration} + */ + UNSIGNED_INT : new Enumeration(0x1405, 'UNSIGNED_INT') + }; + + IndexDatatype.UNSIGNED_BYTE.sizeInBytes = Uint8Array.BYTES_PER_ELEMENT; + IndexDatatype.UNSIGNED_SHORT.sizeInBytes = Uint16Array.BYTES_PER_ELEMENT; + IndexDatatype.UNSIGNED_INT.sizeInBytes = Uint32Array.BYTES_PER_ELEMENT; + + /** + * Validates that the provided index datatype is a valid {@link IndexDatatype} + * + * @param {IndexDatatype} indexDatatype The index datatype to validate. + * + * @return {Boolean} true if the provided index datatype is a valid enumeration value; otherwise, false. + * + * @example + * if (!IndexDatatype.validate(indexDatatype)) { + * throw new DeveloperError('indexDatatype must be a valid enumeration value.'); + * } + */ + IndexDatatype.validate = function(indexDatatype) { + return ((indexDatatype === IndexDatatype.UNSIGNED_BYTE) || + (indexDatatype === IndexDatatype.UNSIGNED_SHORT) || + (indexDatatype === IndexDatatype.UNSIGNED_INT)); + }; + + /** + * Creates a typed array that will store indices, using either + * or Uint32Array depending on the number of vertices. + * + * @param {Number} numberOfVertices Number of vertices that the indices will reference. + * @param {Any} indicesLengthOrArray Passed through to the typed array constructor. + * + * @return {Array} A Uint16Array or Uint32Array constructed with indicesLengthOrArray. + * + * @exception {DeveloperError} center is required. + * + * @example + * this.indices = IndexDatatype.createTypedArray(positions.length / 3, numberOfIndices); + */ + IndexDatatype.createTypedArray = function(numberOfVertices, indicesLengthOrArray) { + if (typeof numberOfVertices === 'undefined') { + throw new DeveloperError('numberOfVertices is required.'); + } + + if (numberOfVertices > CesiumMath.SIXTY_FOUR_KILOBYTES) { + return new Uint32Array(indicesLengthOrArray); + } + + return new Uint16Array(indicesLengthOrArray); }; return IndexDatatype; diff --git a/Source/Core/Math.js b/Source/Core/Math.js index d5939acfedf5..3e304ad8fbfa 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -164,10 +164,16 @@ define([ * Radius of the sun in meters: 6.995e8 * @type {Number} * @constant - * @default */ CesiumMath.SOLAR_RADIUS = 6.995e8; + /** + * 64 * 1024 + * @type {Number} + * @constant + */ + CesiumMath.SIXTY_FOUR_KILOBYTES = 64 * 1024; + /** * Returns the sign of the value; 1 if the value is positive, -1 if the value is * negative, or 0 if the value is 0. diff --git a/Source/Core/Matrix4.js b/Source/Core/Matrix4.js index 16530291b2ec..f2087331b199 100644 --- a/Source/Core/Matrix4.js +++ b/Source/Core/Matrix4.js @@ -87,32 +87,32 @@ define([ * @param {Matrix4} [result] The object onto which to store the result. * @return {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided. (Returns undefined if matrix is undefined) */ - Matrix4.clone = function(values, result) { - if (typeof values === 'undefined') { + Matrix4.clone = function(matrix, result) { + if (typeof matrix === 'undefined') { return undefined; } if (typeof result === 'undefined') { - return new Matrix4(values[0], values[4], values[8], values[12], - values[1], values[5], values[9], values[13], - values[2], values[6], values[10], values[14], - values[3], values[7], values[11], values[15]); + return new Matrix4(matrix[0], matrix[4], matrix[8], matrix[12], + matrix[1], matrix[5], matrix[9], matrix[13], + matrix[2], matrix[6], matrix[10], matrix[14], + matrix[3], matrix[7], matrix[11], matrix[15]); } - result[0] = values[0]; - result[1] = values[1]; - result[2] = values[2]; - result[3] = values[3]; - result[4] = values[4]; - result[5] = values[5]; - result[6] = values[6]; - result[7] = values[7]; - result[8] = values[8]; - result[9] = values[9]; - result[10] = values[10]; - result[11] = values[11]; - result[12] = values[12]; - result[13] = values[13]; - result[14] = values[14]; - result[15] = values[15]; + result[0] = matrix[0]; + result[1] = matrix[1]; + result[2] = matrix[2]; + result[3] = matrix[3]; + result[4] = matrix[4]; + result[5] = matrix[5]; + result[6] = matrix[6]; + result[7] = matrix[7]; + result[8] = matrix[8]; + result[9] = matrix[9]; + result[10] = matrix[10]; + result[11] = matrix[11]; + result[12] = matrix[12]; + result[13] = matrix[13]; + result[14] = matrix[14]; + result[15] = matrix[15]; return result; }; diff --git a/Source/Core/MeshFilters.js b/Source/Core/MeshFilters.js deleted file mode 100644 index 4040b55d7946..000000000000 --- a/Source/Core/MeshFilters.js +++ /dev/null @@ -1,568 +0,0 @@ -/*global define*/ -define([ - './defaultValue', - './DeveloperError', - './Cartesian3', - './EncodedCartesian3', - './GeographicProjection', - './ComponentDatatype', - './PrimitiveType', - './Tipsify' - ], function( - defaultValue, - DeveloperError, - Cartesian3, - EncodedCartesian3, - GeographicProjection, - ComponentDatatype, - PrimitiveType, - Tipsify) { - "use strict"; - - /** - * DOC_TBA - * - * @exports MeshFilters - * - * @see Context#createVertexArrayFromMesh - */ - var MeshFilters = {}; - - /** - * Converts a mesh's triangle indices to line indices. Each list of indices in the mesh's indexList with - * a primitive type of triangles, triangleStrip, or trangleFan is converted to a - * list of indices with a primitive type of lines. Lists of indices with other primitive types remain unchanged. - *

- * The mesh argument should use the standard layout like the mesh returned by {@link BoxTessellator}. - *

- * This filter is commonly used to create a wireframe mesh for visual debugging. - * - * @param {Object} mesh The mesh to filter, which is modified in place. - * - * @returns The modified mesh argument, with its triangle indices converted to lines. - * - * @see BoxTessellator - * - * @example - * var mesh = BoxTessellator.compute(); - * mesh = MeshFilters.toWireframeInPlace(mesh); - */ - MeshFilters.toWireframeInPlace = function(mesh) { - function addTriangle(lines, i0, i1, i2) { - lines.push(i0); - lines.push(i1); - - lines.push(i1); - lines.push(i2); - - lines.push(i2); - lines.push(i0); - } - - function trianglesToLines(triangles) { - var lines = []; - var count = triangles.length; - for ( var i = 0; i < count; i += 3) { - addTriangle(lines, triangles[i], triangles[i + 1], triangles[i + 2]); - } - - return lines; - } - - function triangleStripToLines(triangles) { - var lines = []; - var count = triangles.length; - - if (count >= 3) { - addTriangle(lines, triangles[0], triangles[1], triangles[2]); - - for ( var i = 3; i < count; ++i) { - addTriangle(lines, triangles[i - 1], triangles[i], triangles[i - 2]); - } - } - - return lines; - } - - function triangleFanToLines(triangles) { - var lines = []; - - if (triangles.length > 0) { - var base = triangles[0]; - var count = triangles.length - 1; - for ( var i = 1; i < count; ++i) { - addTriangle(lines, base, triangles[i], triangles[i + 1]); - } - } - - return lines; - } - - if (typeof mesh !== 'undefined') { - var indexLists = mesh.indexLists; - if (typeof indexLists !== 'undefined') { - var count = indexLists.length; - for ( var i = 0; i < count; ++i) { - var indices = indexLists[i]; - - switch (indices.primitiveType) { - case PrimitiveType.TRIANGLES: - indices.primitiveType = PrimitiveType.LINES; - indices.values = trianglesToLines(indices.values); - break; - case PrimitiveType.TRIANGLE_STRIP: - indices.primitiveType = PrimitiveType.LINES; - indices.values = triangleStripToLines(indices.values); - break; - case PrimitiveType.TRIANGLE_FAN: - indices.primitiveType = PrimitiveType.LINES; - indices.values = triangleFanToLines(indices.values); - break; - } - } - } - } - - return mesh; - }; - - /** - * DOC_TBA - */ - MeshFilters.createAttributeIndices = function(mesh) { - var indices = {}; - - if (typeof mesh !== 'undefined') { - var attributes = mesh.attributes; - var j = 0; - - for ( var name in attributes) { - if (attributes.hasOwnProperty(name)) { - indices[name] = j++; - } - } - } - - return indices; - }; - - /** - * DOC_TBA - */ - MeshFilters.mapAttributeIndices = function(indices, map) { - var mappedIndices = {}; - - if (typeof indices !== 'undefined' && typeof map !== 'undefined') { - for ( var name in map) { - if (map.hasOwnProperty(name)) { - mappedIndices[map[name]] = indices[name]; - } - } - } - - return mappedIndices; - }; - - MeshFilters._computeNumberOfAttributes = function(mesh) { - var numberOfVertices = -1; - for ( var property in mesh.attributes) { - if (mesh.attributes.hasOwnProperty(property) && mesh.attributes[property].values) { - var attribute = mesh.attributes[property]; - var num = attribute.values.length / attribute.componentsPerAttribute; - if ((numberOfVertices !== num) && (numberOfVertices !== -1)) { - throw new DeveloperError('All mesh attribute lists must have the same number of attributes.'); - } - numberOfVertices = num; - } - } - - return numberOfVertices; - }; - - /** - * Reorders a mesh's indices to achieve better performance from the GPU's pre-vertex-shader cache. - * Each list of indices in the mesh's indexList is reordered to keep the same index-vertex correspondence. - *

- * The mesh argument should use the standard layout like the mesh returned by {@link BoxTessellator}. - *

- - * @param {Object} mesh The mesh to filter, which is modified in place. - * - * @exception {DeveloperError} All mesh attribute lists must have the same number of attributes. - * - * @returns The modified mesh argument, with its vertices and indices reordered for the GPU's pre-vertex-shader cache. - * - * @see MeshFilters.reorderForPostVertexCache - * - * @example - * var mesh = CubeMapEllipsoidTessellator.compute(...); - * mesh = MeshFilters.reorderForPreVertexCache(mesh); - */ - MeshFilters.reorderForPreVertexCache = function(mesh) { - if (typeof mesh !== 'undefined') { - var numVertices = MeshFilters._computeNumberOfAttributes(mesh); - - var indexCrossReferenceOldToNew = []; - for ( var i = 0; i < numVertices; i++) { - indexCrossReferenceOldToNew[i] = -1; - } - - //Construct cross reference and reorder indices - var indexLists = mesh.indexLists; - if (typeof indexLists !== 'undefined') { - var count = indexLists.length; - for ( var j = 0; j < count; ++j) { - var indicesIn = indexLists[j].values; - var numIndices = indicesIn.length; - var indicesOut = []; - var intoIndicesIn = 0; - var intoIndicesOut = 0; - var nextIndex = 0; - var tempIndex; - while (intoIndicesIn < numIndices) { - tempIndex = indexCrossReferenceOldToNew[indicesIn[intoIndicesIn]]; - if (tempIndex !== -1) { - indicesOut[intoIndicesOut] = tempIndex; - } else { - tempIndex = indicesIn[intoIndicesIn]; - if (tempIndex >= numVertices) { - throw new DeveloperError('Input indices contains a value greater than or equal to the number of vertices'); - } - indexCrossReferenceOldToNew[tempIndex] = nextIndex; - - indicesOut[intoIndicesOut] = nextIndex; - ++nextIndex; - } - ++intoIndicesIn; - ++intoIndicesOut; - } - indexLists[j].values = indicesOut; - } - } - - //Reorder Vertices - var attributes = mesh.attributes; - if (typeof attributes !== 'undefined') { - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && attributes[property].values) { - var elementsIn = attributes[property].values; - var intoElementsIn = 0; - var numComponents = attributes[property].componentsPerAttribute; - var elementsOut = []; - while (intoElementsIn < numVertices) { - var temp = indexCrossReferenceOldToNew[intoElementsIn]; - for (i = 0; i < numComponents; i++) { - elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i]; - } - ++intoElementsIn; - } - attributes[property].values = elementsOut; - } - } - } - } - return mesh; - }; - - /** - * Reorders a mesh's indices to achieve better performance from the GPU's post vertex-shader cache by using the Tipsify algorithm. - * Each list of indices in the mesh's indexList is optimally reordered. - *

- * The mesh argument should use the standard layout like the mesh returned by {@link BoxTessellator}. - *

- - * @param {Object} mesh The mesh to filter, which is modified in place. - * @param {Number} [cacheCapacity=24] The number of vertices that can be held in the GPU's vertex cache. - * - * @exception {DeveloperError} Mesh's index list must be defined. - * @exception {DeveloperError} Mesh's index lists' lengths must each be a multiple of three. - * @exception {DeveloperError} Mesh's index list's maximum index value must be greater than zero. - * @exception {DeveloperError} cacheCapacity must be greater than two. - * - * @returns The modified mesh argument, with its indices optimally reordered for the post-vertex-shader cache. - * - * @see MeshFilters.reorderForPreVertexCache - * @see Tipsify - * @see - * Fast Triangle Reordering for Vertex Locality and Reduced Overdraw - * by Sander, Nehab, and Barczak - * - * @example - * var mesh = CubeMapEllipsoidTessellator.compute(...); - * mesh = MeshFilters.reorderForPostVertexCache(mesh); - */ - MeshFilters.reorderForPostVertexCache = function(mesh, cacheCapacity) { - if (typeof mesh !== 'undefined') { - var indexLists = mesh.indexLists; - if (typeof indexLists !== 'undefined') { - var count = indexLists.length; - for ( var i = 0; i < count; i++) { - var indices = indexLists[i].values; - var numIndices = indices.length; - var maximumIndex = 0; - for ( var j = 0; j < numIndices; j++) { - if (indices[j] > maximumIndex) { - maximumIndex = indices[j]; - } - } - indexLists[i].values = Tipsify.tipsify({indices : indices, - maximumIndex : maximumIndex, - cacheSize : cacheCapacity}); - } - } - } - return mesh; - }; - - MeshFilters._verifyTrianglesPrimitiveType = function(indexLists) { - var length = indexLists.length; - for ( var i = 0; i < length; ++i) { - if (indexLists[i].primitiveType !== PrimitiveType.TRIANGLES) { - throw new DeveloperError('indexLists must have PrimitiveType equal to PrimitiveType.TRIANGLES.'); - } - } - }; - - MeshFilters._copyAttributesDescriptions = function(attributes) { - var newAttributes = {}; - - for ( var attribute in attributes) { - if (attributes.hasOwnProperty(attribute) && attributes[attribute].values) { - var attr = attributes[attribute]; - newAttributes[attribute] = { - componentDatatype : attr.componentDatatype, - componentsPerAttribute : attr.componentsPerAttribute, - values : [] - }; - } - } - - return newAttributes; - }; - - function copyVertex(destinationAttributes, sourceAttributes, index) { - for ( var attribute in sourceAttributes) { - if (sourceAttributes.hasOwnProperty(attribute) && sourceAttributes[attribute].values) { - var attr = sourceAttributes[attribute]; - - for ( var k = 0; k < attr.componentsPerAttribute; ++k) { - destinationAttributes[attribute].values.push(attr.values[(index * attr.componentsPerAttribute) + k]); - } - } - } - } - - /** - * DOC_TBA. Old mesh is not guaranteed to be copied. - * - * @exception {DeveloperError} The mesh's index-lists must have PrimitiveType equal to PrimitiveType.TRIANGLES. - * @exception {DeveloperError} All mesh attribute lists must have the same number of attributes. - */ - MeshFilters.fitToUnsignedShortIndices = function(mesh) { - function createMesh(attributes, primitiveType, indices) { - return { - attributes : attributes, - indexLists : [{ - primitiveType : primitiveType, - values : indices - }] - }; - } - - var meshes = []; - - if (typeof mesh !== 'undefined') { - MeshFilters._verifyTrianglesPrimitiveType(mesh.indexLists); - - var numberOfVertices = MeshFilters._computeNumberOfAttributes(mesh); - - // If there's an index list and more than 64K attributes, it is possible that - // some indices are outside the range of unsigned short [0, 64K - 1] - var sixtyFourK = 64 * 1024; - var indexLists = mesh.indexLists; - if (typeof indexLists !== 'undefined' && (numberOfVertices > sixtyFourK)) { - // PERFORMANCE_IDEA: If an input mesh has more than one index-list. This creates - // at least one vertex-array per index-list. A more sophisticated implementation - // may create less vertex-arrays. - var length = indexLists.length; - for ( var i = 0; i < length; ++i) { - var oldToNewIndex = []; - var newIndices = []; - var currentIndex = 0; - var newAttributes = MeshFilters._copyAttributesDescriptions(mesh.attributes); - - var originalIndices = indexLists[i].values; - var numberOfIndices = originalIndices.length; - - for ( var j = 0; j < numberOfIndices; j += 3) { - // It would be easy to extend this inter-loop to support all primitive-types. - - var x0 = originalIndices[j]; - var x1 = originalIndices[j + 1]; - var x2 = originalIndices[j + 2]; - - var i0 = oldToNewIndex[x0]; - if (typeof i0 === 'undefined') { - i0 = currentIndex++; - oldToNewIndex[x0] = i0; - - copyVertex(newAttributes, mesh.attributes, x0); - } - - var i1 = oldToNewIndex[x1]; - if (typeof i1 === 'undefined') { - i1 = currentIndex++; - oldToNewIndex[x1] = i1; - - copyVertex(newAttributes, mesh.attributes, x1); - } - - var i2 = oldToNewIndex[x2]; - if (typeof i2 === 'undefined') { - i2 = currentIndex++; - oldToNewIndex[x2] = i2; - - copyVertex(newAttributes, mesh.attributes, x2); - } - - newIndices.push(i0); - newIndices.push(i1); - newIndices.push(i2); - - if (currentIndex + 3 > sixtyFourK) { - meshes.push(createMesh(newAttributes, indexLists[i].primitiveType, newIndices)); - - // Reset for next vertex-array - oldToNewIndex = []; - newIndices = []; - currentIndex = 0; - newAttributes = MeshFilters._copyAttributesDescriptions(mesh.attributes); - } - } - - if (newIndices.length !== 0) { - meshes.push(createMesh(newAttributes, indexLists[i].primitiveType, newIndices)); - } - } - } else { - // No need to split into multiple meshes - meshes.push(mesh); - } - } - - return meshes; - }; - - /** - * DOC_TBA - */ - MeshFilters.projectTo2D = function(mesh, projection) { - if (typeof mesh !== 'undefined' && typeof mesh.attributes !== 'undefined' && typeof mesh.attributes.position !== 'undefined') { - projection = typeof projection !== 'undefined' ? projection : new GeographicProjection(); - var ellipsoid = projection.getEllipsoid(); - - // Project original positions to 2D. - var wgs84Positions = mesh.attributes.position.values; - var projectedPositions = []; - - for ( var i = 0; i < wgs84Positions.length; i += 3) { - var lonLat = ellipsoid.cartesianToCartographic(new Cartesian3(wgs84Positions[i], wgs84Positions[i + 1], wgs84Positions[i + 2])); - var projectedLonLat = projection.project(lonLat); - projectedPositions.push(projectedLonLat.x, projectedLonLat.y); - } - - // Rename original positions to WGS84 Positions. - mesh.attributes.position3D = mesh.attributes.position; - - // Replace original positions with 2D projected positions - mesh.attributes.position2D = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : projectedPositions - }; - delete mesh.attributes.position; - } - - return mesh; - }; - - var encodedResult = { - high : 0.0, - low : 0.0 - }; - - /** - * Encodes floating-point mesh attribute values as two separate attributes to improve - * rendering precision using the same encoding as {@link EncodedCartesian3}. - *

- * This is commonly used to create high-precision position vertex attributes. - *

- * - * @param {Object} mesh The mesh to filter, which is modified in place. - * @param {String} [attributeName='position'] The name of the attribute. - * @param {String} [attributeHighName='positionHigh'] The name of the attribute for the encoded high bits. - * @param {String} [attributeLowName='positionLow'] The name of the attribute for the encoded low bits. - * - * @returns The modified mesh argument, with its encoded attribute. - * - * @exception {DeveloperError} mesh is required. - * @exception {DeveloperError} mesh must have an attributes property. - * @exception {DeveloperError} mesh must have attribute matching the attributeName argument. - * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.FLOAT. - * - * @example - * mesh = MeshFilters.encodeAttribute(mesh, 'position3D', 'position3DHigh', 'position3DLow'); - * - * @see EncodedCartesian3 - */ - MeshFilters.encodeAttribute = function(mesh, attributeName, attributeHighName, attributeLowName) { - attributeName = defaultValue(attributeName, 'position'); - attributeHighName = defaultValue(attributeHighName, 'positionHigh'); - attributeLowName = defaultValue(attributeLowName, 'positionLow'); - - if (typeof mesh === 'undefined') { - throw new DeveloperError('mesh is required.'); - } - - if (typeof mesh.attributes === 'undefined') { - throw new DeveloperError('mesh must have an attributes property.'); - } - - var attribute = mesh.attributes[attributeName]; - - if (typeof attribute === 'undefined') { - throw new DeveloperError('mesh must have attribute matching the attributeName argument: ' + attributeName + '.'); - } - - if (attribute.componentDatatype !== ComponentDatatype.FLOAT) { - throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.FLOAT.'); - } - - var values = attribute.values; - var length = values.length; - var highValues = new Array(length); - var lowValues = new Array(length); - - for (var i = 0; i < length; ++i) { - EncodedCartesian3.encode(values[i], encodedResult); - highValues[i] = encodedResult.high; - lowValues[i] = encodedResult.low; - } - - mesh.attributes[attributeHighName] = { - componentDatatype : attribute.componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - values : highValues - }; - mesh.attributes[attributeLowName] = { - componentDatatype : attribute.componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - values : lowValues - }; - delete mesh.attributes[attributeName]; - - return mesh; - }; - - return MeshFilters; -}); diff --git a/Source/Core/Occluder.js b/Source/Core/Occluder.js index 5564fbb6bb78..a234fc63a725 100644 --- a/Source/Core/Occluder.js +++ b/Source/Core/Occluder.js @@ -374,7 +374,7 @@ define([ } ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var positions = extent.subsample(ellipsoid, computeOccludeePointFromExtentScratch); + var positions = extent.subsample(ellipsoid, 0.0, computeOccludeePointFromExtentScratch); var bs = BoundingSphere.fromPoints(positions); // TODO: get correct ellipsoid center diff --git a/Source/Core/PlaneTessellator.js b/Source/Core/PlaneTessellator.js deleted file mode 100644 index 4e1544134ec6..000000000000 --- a/Source/Core/PlaneTessellator.js +++ /dev/null @@ -1,76 +0,0 @@ -/*global define*/ -define([ - './DeveloperError', - './Cartesian2', - './PrimitiveType', - './defaultValue' - ], function( - DeveloperError, - Cartesian2, - PrimitiveType, - defaultValue) { - "use strict"; - - /** - * DOC_TBA - * - * @exports PlaneTessellator - * - * @see CubeMapEllipsoidTessellator - * @see BoxTessellator - */ - var PlaneTessellator = { - /** - * DOC_TBA - * - * @exception {DeveloperError} Resolution must be greater than one in both the x and y directions. - */ - compute : function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var resolution = typeof options.resolution !== "undefined" ? options.resolution : new Cartesian2(2, 2); - var onInterpolation = options.onInterpolation; // Can be undefined - - if (resolution.x <= 1 || resolution.y <= 1) { - throw new DeveloperError('Resolution must be greater than one in both the x and y directions.'); - } - - var i; - var j; - - // To allow computing custom attributes, e.g., texture coordinates, etc. - if (onInterpolation) { - for (j = 0; j < resolution.y; ++j) { - var yTime = j / (resolution.y - 1); - - for (i = 0; i < resolution.x; ++i) { - var xTime = i / (resolution.x - 1); - onInterpolation(new Cartesian2(xTime, yTime)); - } - } - } - - var indices = []; - - // Counterclockwise winding order - for (j = 0; j < resolution.y - 1; ++j) { - var row = j * resolution.x; - var aboveRow = (j + 1) * resolution.x; - - for (i = 0; i < resolution.x - 1; ++i) { - indices.push(row + i, row + i + 1, aboveRow + i + 1); - indices.push(row + i, aboveRow + i + 1, aboveRow + i); - } - } - - return { - attributes : {}, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : indices - }] - }; - } - }; - - return PlaneTessellator; -}); \ No newline at end of file diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js new file mode 100644 index 000000000000..661e34e0f61b --- /dev/null +++ b/Source/Core/PolygonGeometry.js @@ -0,0 +1,477 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingRectangle', + './BoundingSphere', + './Cartesian2', + './Cartesian3', + './Cartesian4', + './ComponentDatatype', + './DeveloperError', + './Ellipsoid', + './EllipsoidTangentPlane', + './GeometryAttribute', + './GeometryAttributes', + './GeometryInstance', + './GeometryPipeline', + './IndexDatatype', + './Intersect', + './Math', + './Matrix3', + './PolygonPipeline', + './Quaternion', + './Queue', + './VertexFormat', + './WindingOrder' + ], function( + defaultValue, + BoundingRectangle, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartesian4, + ComponentDatatype, + DeveloperError, + Ellipsoid, + EllipsoidTangentPlane, + GeometryAttribute, + GeometryAttributes, + GeometryInstance, + GeometryPipeline, + IndexDatatype, + Intersect, + CesiumMath, + Matrix3, + PolygonPipeline, + Quaternion, + Queue, + VertexFormat, + WindingOrder) { + "use strict"; + + var computeBoundingRectangleCartesian2 = new Cartesian2(); + var computeBoundingRectangleCartesian3 = new Cartesian3(); + var computeBoundingRectangleQuaternion = new Quaternion(); + var computeBoundingRectangleMatrix3 = new Matrix3(); + + function computeBoundingRectangle(tangentPlane, positions, angle, result) { + var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, angle, computeBoundingRectangleQuaternion); + var textureMatrix = Matrix3.fromQuaternion(rotation, computeBoundingRectangleMatrix3); + + var minX = Number.POSITIVE_INFINITY; + var maxX = Number.NEGATIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxY = Number.NEGATIVE_INFINITY; + + var length = positions.length; + for ( var i = 0; i < length; ++i) { + var p = Cartesian3.clone(positions[i], computeBoundingRectangleCartesian3); + Matrix3.multiplyByVector(textureMatrix, p, p); + var st = tangentPlane.projectPointOntoPlane(p, computeBoundingRectangleCartesian2); + + if (typeof st !== 'undefined') { + minX = Math.min(minX, st.x); + maxX = Math.max(maxX, st.x); + + minY = Math.min(minY, st.y); + maxY = Math.max(maxY, st.y); + } + } + + result.x = minX; + result.y = minY; + result.width = maxX - minX; + result.height = maxY - minY; + return result; + } + + var createGeometryFromPositionsPositions = []; + + function createGeometryFromPositions(ellipsoid, positions, granularity) { + var cleanedPositions = PolygonPipeline.removeDuplicates(positions); + if (cleanedPositions.length < 3) { + throw new DeveloperError('Duplicate positions result in not enough positions to form a polygon.'); + } + + var tangentPlane = EllipsoidTangentPlane.fromPoints(cleanedPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(cleanedPositions, createGeometryFromPositionsPositions); + + var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + cleanedPositions.reverse(); + } + + var indices = PolygonPipeline.earClip2D(positions2D); + return new GeometryInstance({ + geometry : PolygonPipeline.computeSubdivision(cleanedPositions, indices, granularity) + }); + } + + var scratchBoundingRectangle = new BoundingRectangle(); + var scratchPosition = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchTangent = new Cartesian3(); + var scratchBinormal = new Cartesian3(); + + var appendTextureCoordinatesOrigin = new Cartesian2(); + var appendTextureCoordinatesCartesian2 = new Cartesian2(); + var appendTextureCoordinatesCartesian3 = new Cartesian3(); + var appendTextureCoordinatesQuaternion = new Quaternion(); + var appendTextureCoordinatesMatrix3 = new Matrix3(); + + /** + * A {@link Geometry} that represents vertices and indices for a polygon on the ellipsoid. The polygon is either defined + * by an array of Cartesian points, or a polygon hierarchy. + * + * @alias PolygonGeometry + * @constructor + * + * @param {Object} options.polygonHierarchy A polygon hierarchy that can include holes. + * @param {Number} [options.height=0.0] The height of the polygon. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Number} [options.stRotation=0.0] The rotation of the texture coordiantes, in radians. A positive rotation is counter-clockwise. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * + * @exception {DeveloperError} polygonHierarchy is required. + * @exception {DeveloperError} At least three positions are required. + * @exception {DeveloperError} Duplicate positions result in not enough positions to form a polygon. + * + * @example + * // create a polygon from points + * var geometry = new PolygonGeometry({ + * polygonHierarchy : { + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0), + * Cartographic.fromDegrees(-75.0, 30.0), + * Cartographic.fromDegrees(-70.0, 30.0), + * Cartographic.fromDegrees(-68.0, 40.0) + * ]) + * } + * }); + * + * // create a nested polygon with holes + * var geometryWithHole = new PolygonGeometry({ + * polygonHierarchy : { + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-109.0, 30.0), + * Cartographic.fromDegrees(-95.0, 30.0), + * Cartographic.fromDegrees(-95.0, 40.0), + * Cartographic.fromDegrees(-109.0, 40.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-107.0, 31.0), + * Cartographic.fromDegrees(-107.0, 39.0), + * Cartographic.fromDegrees(-97.0, 39.0), + * Cartographic.fromDegrees(-97.0, 31.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-105.0, 33.0), + * Cartographic.fromDegrees(-99.0, 33.0), + * Cartographic.fromDegrees(-99.0, 37.0), + * Cartographic.fromDegrees(-105.0, 37.0) + * ]), + * holes : [{ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-103.0, 34.0), + * Cartographic.fromDegrees(-101.0, 34.0), + * Cartographic.fromDegrees(-101.0, 36.0), + * Cartographic.fromDegrees(-103.0, 36.0) + * ]) + * }] + * }] + * }] + * } + * }); + */ + var PolygonGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var stRotation = defaultValue(options.stRotation, 0.0); + var height = defaultValue(options.height, 0.0); + + var polygonHierarchy = options.polygonHierarchy; + if (typeof polygonHierarchy === 'undefined') { + throw new DeveloperError('options.polygonHierarchy is required.'); + } + + // create from a polygon hierarchy + // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf + var polygons = []; + var queue = new Queue(); + queue.enqueue(polygonHierarchy); + + var i; + + while (queue.length !== 0) { + var outerNode = queue.dequeue(); + var outerRing = outerNode.positions; + + if (outerRing.length < 3) { + throw new DeveloperError('At least three positions are required.'); + } + + var numChildren = outerNode.holes ? outerNode.holes.length : 0; + if (numChildren === 0) { + // The outer polygon is a simple polygon with no nested inner polygon. + polygons.push(outerNode.positions); + } else { + // The outer polygon contains inner polygons + var holes = []; + for (i = 0; i < numChildren; i++) { + var hole = outerNode.holes[i]; + holes.push(hole.positions); + + var numGrandchildren = 0; + if (typeof hole.holes !== 'undefined') { + numGrandchildren = hole.holes.length; + } + + for (var j = 0; j < numGrandchildren; j++) { + queue.enqueue(hole.holes[j]); + } + } + var combinedPolygon = PolygonPipeline.eliminateHoles(outerRing, holes); + polygons.push(combinedPolygon); + } + } + + var outerPositions = polygons[0]; + // The bounding volume is just around the boundary points, so there could be cases for + // contrived polygons on contrived ellipsoids - very oblate ones - where the bounding + // volume doesn't cover the polygon. + var boundingSphere = BoundingSphere.fromPoints(outerPositions); + + var geometry; + var geometries = []; + for (var k = 0; k < polygons.length; k++) { + geometry = createGeometryFromPositions(ellipsoid, polygons[k], granularity); + if (typeof geometry !== 'undefined') { + geometries.push(geometry); + } + } + + geometry = GeometryPipeline.combine(geometries); + geometry = PolygonPipeline.scaleToGeodeticHeight(geometry, height, ellipsoid); + + var center = boundingSphere.center; + var mag = center.magnitude(); + ellipsoid.geodeticSurfaceNormal(center, center); + Cartesian3.multiplyByScalar(center, mag + height, center); + + var attributes = new GeometryAttributes(); + + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array(geometry.attributes.position.values) + }); + } + + if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { + // PERFORMANCE_IDEA: Compute before subdivision, then just interpolate during subdivision. + // PERFORMANCE_IDEA: Compute with createGeometryFromPositions() for fast path when there's no holes. + var cleanedPositions = PolygonPipeline.removeDuplicates(outerPositions); + var tangentPlane = EllipsoidTangentPlane.fromPoints(cleanedPositions, ellipsoid); + var boundingRectangle = computeBoundingRectangle(tangentPlane, outerPositions, stRotation, scratchBoundingRectangle); + + var origin = appendTextureCoordinatesOrigin; + origin.x = boundingRectangle.x; + origin.y = boundingRectangle.y; + + var flatPositions = geometry.attributes.position.values; + var length = flatPositions.length; + + var textureCoordinates = vertexFormat.st ? new Float32Array(2 * (length / 3)) : undefined; + var normals = vertexFormat.normal ? new Float32Array(length) : undefined; + var tangents = vertexFormat.tangent ? new Float32Array(length) : undefined; + var binormals = vertexFormat.binormal ? new Float32Array(length) : undefined; + + var textureCoordIndex = 0; + var normalIndex = 0; + var tangentIndex = 0; + var binormalIndex = 0; + + var normal = scratchNormal; + var tangent = scratchTangent; + + var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, stRotation, appendTextureCoordinatesQuaternion); + var textureMatrix = Matrix3.fromQuaternion(rotation, appendTextureCoordinatesMatrix3); + + for (i = 0; i < length; i += 3) { + var position = Cartesian3.fromArray(flatPositions, i, appendTextureCoordinatesCartesian3); + + if (vertexFormat.st) { + var p = Matrix3.multiplyByVector(textureMatrix, position, scratchPosition); + var st = tangentPlane.projectPointOntoPlane(p, appendTextureCoordinatesCartesian2); + Cartesian2.subtract(st, origin, st); + + textureCoordinates[textureCoordIndex++] = st.x / boundingRectangle.width; + textureCoordinates[textureCoordIndex++] = st.y / boundingRectangle.height; + } + + if (vertexFormat.normal) { + normal = ellipsoid.geodeticSurfaceNormal(position, normal); + + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; + } + + if (vertexFormat.tangent) { + if (!vertexFormat.normal) { + ellipsoid.geodeticSurfaceNormal(position, normal); + } + + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + Matrix3.multiplyByVector(textureMatrix, tangent, tangent); + Cartesian3.normalize(tangent, tangent); + + tangents[tangentIndex++] = tangent.x; + tangents[tangentIndex++] = tangent.y; + tangents[tangentIndex++] = tangent.z; + } + + if (vertexFormat.binormal) { + if (!vertexFormat.normal) { + ellipsoid.geodeticSurfaceNormal(position, normal); + } + + if (!vertexFormat.tangent) { + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + Matrix3.multiplyByVector(textureMatrix, tangent, tangent); + } + + var binormal = Cartesian3.cross(tangent, normal, scratchBinormal); + Cartesian3.normalize(binormal, binormal); + + binormals[binormalIndex++] = binormal.x; + binormals[binormalIndex++] = binormal.y; + binormals[binormalIndex++] = binormal.z; + } + } + + if (vertexFormat.st) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); + } + + if (vertexFormat.normal) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.tangent) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = IndexDatatype.createTypedArray(geometry.attributes.position.values / 3, geometry.indices); + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = geometry.primitiveType; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = boundingSphere; + }; + + /** + * Creates a polygon from an array of positions. + * + * @memberof PolygonGeometry + * + * @param {Array} options.positions An array of positions that defined the corner points of the polygon. + * @param {Number} [options.height=0.0] The height of the polygon. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Number} [options.stRotation=0.0] The rotation of the texture coordiantes, in radians. A positive rotation is counter-clockwise. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * + * @exception {DeveloperError} options.positions is required. + * @exception {DeveloperError} At least three positions are required. + * @exception {DeveloperError} Duplicate positions result in not enough positions to form a polygon. + * + * @example + * // create a polygon from points + * var geometry = new PolygonGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0), + * Cartographic.fromDegrees(-75.0, 30.0), + * Cartographic.fromDegrees(-70.0, 30.0), + * Cartographic.fromDegrees(-68.0, 40.0) + * ]) + * }); + */ + PolygonGeometry.fromPositions = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (typeof options.positions === 'undefined') { + throw new DeveloperError('options.positions is required.'); + } + + var newOptions = { + polygonHierarchy : { + positions : options.positions + }, + height : options.height, + vertexFormat : options.vertexFormat, + stRotation : options.stRotation, + ellipsoid : options.ellipsoid, + granularity : options.granularity + }; + return new PolygonGeometry(newOptions); + }; + + return PolygonGeometry; +}); + diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index ef99c8f45110..441177800c51 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -4,10 +4,12 @@ define([ './Math', './Cartesian2', './Cartesian3', + './Geometry', + './GeometryAttribute', './Ellipsoid', './EllipsoidTangentPlane', './defaultValue', - './pointInsideTriangle2D', + './pointInsideTriangle', './ComponentDatatype', './PrimitiveType', './Queue', @@ -17,10 +19,12 @@ define([ CesiumMath, Cartesian2, Cartesian3, + Geometry, + GeometryAttribute, Ellipsoid, EllipsoidTangentPlane, defaultValue, - pointInsideTriangle2D, + pointInsideTriangle, ComponentDatatype, PrimitiveType, Queue, @@ -153,7 +157,7 @@ define([ * Returns true if the given point is contained in the list of positions. * * @param {Array} positions A list of Cartesian elements defining a polygon. - * @param {Cartesian} point The point to check. + * @param {Cartesian2} point The point to check. * @returns {Boolean} true> if point is found in polygon, false otherwise. * * @private @@ -170,11 +174,11 @@ define([ /** * Given a point inside a polygon, find the nearest point directly to the right that lies on one of the polygon's edges. * - * @param {Cartesian} point A point inside the polygon defined by ring. + * @param {Cartesian2} point A point inside the polygon defined by ring. * @param {Array} ring A list of Cartesian points defining a polygon. * @param {Array} [edgeIndices] An array containing the indices two endpoints of the edge containing the intersection. * - * @returns {Cartesian} The intersection point. + * @returns {Cartesian2} The intersection point. * @private */ function intersectPointWithRing(point, ring, edgeIndices) { @@ -182,7 +186,7 @@ define([ var minDistance = Number.MAX_VALUE; var rightmostVertexIndex = getRightmostPositionIndex(ring); - var intersection = new Cartesian3(ring[rightmostVertexIndex].x, point.y, 0.0); + var intersection = new Cartesian2(ring[rightmostVertexIndex].x, point.y); edgeIndices.push(rightmostVertexIndex); edgeIndices.push((rightmostVertexIndex + 1) % ring.length); @@ -263,7 +267,7 @@ define([ var pointsInside = []; for ( var i = 0; i < reflexVertices.length; i++) { var vertex = reflexVertices[i]; - if (pointInsideTriangle2D(vertex, innerRingVertex, intersection, p)) { + if (pointInsideTriangle(vertex, innerRingVertex, intersection, p)) { pointsInside.push(vertex); } } @@ -299,7 +303,7 @@ define([ * @param {Array} outerRing An array of Cartesian points defining the outer boundary of the polygon. * @param {Array} innerRings An array of arrays of Cartesian points, where each array represents a hole in the polygon. * - * @return A single list of Cartesian points defining the polygon, including the eliminated inner ring. + * @return {Array} A single list of Cartesian points defining the polygon, including the eliminated inner ring. * * @private */ @@ -370,24 +374,6 @@ define([ return newPolygonVertices; } - var c3 = new Cartesian3(); - function getXZIntersectionOffsetPoints(p, p1, u1, v1) { - p.add(p1.subtract(p, c3).multiplyByScalar(p.y/(p.y-p1.y), c3), u1); - Cartesian3.clone(u1, v1); - offsetPointFromXZPlane(u1, true); - offsetPointFromXZPlane(v1, false); - } - - function offsetPointFromXZPlane(p, isBehind) { - if (Math.abs(p.y) < CesiumMath.EPSILON11){ - if (isBehind) { - p.y = -CesiumMath.EPSILON11; - } else { - p.y = CesiumMath.EPSILON11; - } - } - } - var scaleToGeodeticHeightN = new Cartesian3(); var scaleToGeodeticHeightP = new Cartesian3(); @@ -406,7 +392,7 @@ define([ * @exception {DeveloperError} positions is required. * @exception {DeveloperError} At least three positions are required. */ - cleanUp : function(positions) { + removeDuplicates : function(positions) { if (typeof positions === 'undefined') { throw new DeveloperError('positions is required.'); } @@ -518,7 +504,7 @@ define([ var isEar = true; for ( var n = (nextNode.next ? nextNode.next : remainingPositions.head); n !== previousNode; n = (n.next ? n.next : remainingPositions.head)) { - if (pointInsideTriangle2D(n.item.position, p0, p1, p2)) { + if (pointInsideTriangle(n.item.position, p0, p1, p2)) { isEar = false; break; } @@ -556,122 +542,6 @@ define([ return indices; }, - /** - * Subdivides a {@link Polygon} such that no triangles cross the ±180 degree meridian of an ellipsoid. - * @memberof PolygonPipeline - * - * @param {Array} positions The Cartesian positions of triangles that make up a polygon. - * @param {Array} indices The indices of positions in the positions array that make up triangles - * - * @returns {Object} The full set of indices, including those for positions added for newly created triangles - * - * @exception {DeveloperError} positions and indices are required - * @exception {DeveloperError} At least three indices are required. - * @exception {DeveloperError} The number of indices must be divisable by three. - * - * @see Polygon - * - * @example - * var positions = [new Cartesian3(-1, -1, 0), new Cartesian3(-1, 1, 2), new Cartesian3(-1, 2, 2)]; - * var indices = [0, 1, 2]; - * indices = PolygonPipeline.wrapLongitude(positions, indices); - */ - - wrapLongitude : function(positions, indices) { - if ((typeof positions === 'undefined') || - (typeof indices === 'undefined')) { - throw new DeveloperError('positions and indices are required.'); - } - - if (indices.length < 3) { - throw new DeveloperError('At least three indices are required.'); - } - - if (indices.length % 3 !== 0) { - throw new DeveloperError('The number of indices must be divisable by three.'); - } - - var newIndices = []; - - var len = indices.length; - for (var i = 0; i < len; i += 3) { - var i0 = indices[i]; - var i1 = indices[i + 1]; - var i2 = indices[i + 2]; - var p0 = positions[i0]; - var p1 = positions[i1]; - var p2 = positions[i2]; - - // In WGS84 coordinates, for a triangle approximately on the - // ellipsoid to cross the IDL, first it needs to be on the - // negative side of the plane x = 0. - if ((p0.x < 0.0) && (p1.x < 0.0) && (p2.x < 0.0)) { - var p0Behind = p0.y < 0.0; - var p1Behind = p1.y < 0.0; - var p2Behind = p2.y < 0.0; - - offsetPointFromXZPlane(p0, p0Behind); - offsetPointFromXZPlane(p1, p1Behind); - offsetPointFromXZPlane(p2, p2Behind); - - var numBehind = 0; - numBehind += p0Behind ? 1 : 0; - numBehind += p1Behind ? 1 : 0; - numBehind += p2Behind ? 1 : 0; - - var u1, u2, v1, v2; - - if (numBehind === 1 || numBehind === 2) { - u1 = new Cartesian3(); - u2 = new Cartesian3(); - v1 = new Cartesian3(); - v2 = new Cartesian3(); - } - var iu1 = positions.length; - if (numBehind === 1) { - if (p0Behind) { - getXZIntersectionOffsetPoints(p0, p1, u1, v1); - getXZIntersectionOffsetPoints(p0, p2, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i0, iu1, iu1+1, i1, i2, iu1+3, i1, iu1+3, iu1+2); - } else if (p1Behind) { - getXZIntersectionOffsetPoints(p1, p0, u1, v1); - getXZIntersectionOffsetPoints(p1, p2, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i1, iu1, iu1+1, i2, i0, iu1+3, i2, iu1+3, iu1+2); - } else if (p2Behind) { - getXZIntersectionOffsetPoints(p2, p0, u1, v1); - getXZIntersectionOffsetPoints(p2, p1, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i2, iu1, iu1+1, i0, i1, iu1+3, i0, iu1+3, iu1+2); - } - } else if (numBehind === 2) { - if (!p0Behind) { - getXZIntersectionOffsetPoints(p0, p1, u1, v1); - getXZIntersectionOffsetPoints(p0, p2, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i1, i2, iu1+1, i1, iu1+1, iu1, i0, iu1+2, iu1+3); - } else if (!p1Behind) { - getXZIntersectionOffsetPoints(p1, p2, u1, v1); - getXZIntersectionOffsetPoints(p1, p0, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i2, i0, iu1+1, i2, iu1+1, iu1, i1, iu1+2, iu1+3); - } else if (!p2Behind) { - getXZIntersectionOffsetPoints(p2, p0, u1, v1); - getXZIntersectionOffsetPoints(p2, p1, u2, v2); - positions.push(u1, u2, v1, v2); - newIndices.push(i0, i1, iu1+1, i0, iu1+1, iu1, i2, iu1+2, iu1+3); - } - } else { - newIndices.push(i0, i1, i2); - } - } else { - newIndices.push(i0, i1, i2); - } - } - return newIndices; - }, - /** * DOC_TBA * @@ -702,7 +572,7 @@ define([ throw new DeveloperError('The number of indices must be divisable by three.'); } - granularity = defaultValue(granularity, CesiumMath.toRadians(1.0)); + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE); if (granularity <= 0.0) { throw new DeveloperError('granularity must be greater than zero.'); } @@ -823,20 +693,17 @@ define([ flattenedPositions[q++] = item.z; } - return { + return new Geometry({ attributes : { - position : { - componentDatatype : ComponentDatatype.FLOAT, + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, values : flattenedPositions - } + }) }, - - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : subdividedIndices - }] - }; + indices : subdividedIndices, + primitiveType : PrimitiveType.TRIANGLES + }); }, /** @@ -844,7 +711,7 @@ define([ * * @exception {DeveloperError} ellipsoid is required. */ - scaleToGeodeticHeight : function(mesh, height, ellipsoid) { + scaleToGeodeticHeight : function(geometry, height, ellipsoid) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); var n = scaleToGeodeticHeightN; @@ -852,8 +719,8 @@ define([ height = defaultValue(height, 0.0); - if (typeof mesh !== 'undefined' && typeof mesh.attributes !== 'undefined' && typeof mesh.attributes.position !== 'undefined') { - var positions = mesh.attributes.position.values; + if (typeof geometry !== 'undefined' && typeof geometry.attributes !== 'undefined' && typeof geometry.attributes.position !== 'undefined') { + var positions = geometry.attributes.position.values; var length = positions.length; for ( var i = 0; i < length; i += 3) { @@ -872,7 +739,7 @@ define([ } } - return mesh; + return geometry; }, /** diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 51e1d4750a07..147a6414f1a8 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -1,6 +1,7 @@ /*global define*/ define([ './defaultValue', + './DeveloperError', './Cartesian3', './Cartesian4', './IntersectionTests', @@ -8,6 +9,7 @@ define([ './Plane' ], function( defaultValue, + DeveloperError, Cartesian3, Cartesian4, IntersectionTests, @@ -112,5 +114,49 @@ define([ }; }; + /** + * Removes adjacent duplicate positions in an array of positions. + * + * @memberof PolylinePipeline + * + * @param {Array} positions The array of positions. Each element is usually a {@see Cartesian3}, but all that is required is that the object have an equals function. + * + * @returns {Array} A new array of positions with no adjacent duplicate positions. Positions are shallow copied. + * + * @exception {DeveloperError} positions is required. + * + * @example + * // Returns [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0)] + * var positions = [ + * new Cartesian3(1.0, 1.0, 1.0), + * new Cartesian3(1.0, 1.0, 1.0), + * new Cartesian3(2.0, 2.0, 2.0)]; + * var nonDuplicatePositions = PolylinePipeline.removeDuplicates(positions); + */ + PolylinePipeline.removeDuplicates = function(positions) { + if (typeof positions === 'undefined') { + throw new DeveloperError('positions is required.'); + } + + 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]; + + if (!v0.equals(v1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; + return PolylinePipeline; }); diff --git a/Source/Core/Shapes.js b/Source/Core/Shapes.js index 296b1c052f7e..fa62956ca3bb 100644 --- a/Source/Core/Shapes.js +++ b/Source/Core/Shapes.js @@ -110,7 +110,7 @@ define([ throw new DeveloperError('radius must be greater than zero.'); } - granularity = defaultValue(granularity, CesiumMath.toRadians(1.0)); + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE); if (granularity <= 0.0) { throw new DeveloperError('granularity must be greater than zero.'); } @@ -162,7 +162,7 @@ define([ } bearing = bearing || 0.0; - granularity = defaultValue(granularity, CesiumMath.toRadians(1.0)); + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE); if (granularity <= 0.0) { throw new DeveloperError('granularity must be greater than zero.'); diff --git a/Source/Core/ShowGeometryInstanceAttribute.js b/Source/Core/ShowGeometryInstanceAttribute.js new file mode 100644 index 000000000000..c82a823ed5b8 --- /dev/null +++ b/Source/Core/ShowGeometryInstanceAttribute.js @@ -0,0 +1,108 @@ +/*global define*/ +define([ + './defaultValue', + './ComponentDatatype', + './DeveloperError' + ], function( + defaultValue, + ComponentDatatype, + DeveloperError) { + "use strict"; + + /** + * Value and type information for per-instance geometry attribute that determines if the geometry instance will be shown. + * + * @alias ShowGeometryInstanceAttribute + * @constructor + * + * @param {Boolean} [show=true] Determines if the geometry instance will be shown. + * + * @example + * var instance = new GeometryInstance({ + * geometry : new BoxGeometry({ + * vertexFormat : VertexFormat.POSITION_AND_NORMAL, + * dimensions : new Cartesian3(1000000.0, 1000000.0, 500000.0) + * }), + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883))), new Cartesian3(0.0, 0.0, 1000000.0)), + * id : 'box', + * attributes : { + * show : new ShowGeometryInstanceAttribute(false) + * } + * }); + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + */ + var ShowGeometryInstanceAttribute = function(show) { + show = defaultValue(show, true); + + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link ShowGeometryInstanceAttribute#value}. + * + * @type ComponentDatatype + * + * @default {@link ComponentDatatype.UNSIGNED_BYTE} + * + * @readonly + */ + this.componentDatatype = ComponentDatatype.UNSIGNED_BYTE; + + /** + * The number of components in the attributes, i.e., {@link ShowGeometryInstanceAttribute#value}. + * + * @type Number + * + * @default 1 + * + * @readonly + */ + this.componentsPerAttribute = 1; + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @type Boolean + * + * @default true + * + * @readonly + */ + this.normalize = true; + + /** + * The values for the attributes stored in a typed array. + * + * @type Uint8Array + * + * @default [1.0] + */ + this.value = ShowGeometryInstanceAttribute.toValue(show); + }; + + /** + * Converts a boolean show to a typed array that can be used to assign a show attribute. + * + * @param {Boolean} show The show value. + * + * @returns {Uint8Array} The typed array in the attribute's format. + * + * @exception {DeveloperError} show is required. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.show = ShowGeometryInstanceAttribute.toValue(true); + */ + ShowGeometryInstanceAttribute.toValue = function(show) { + if (typeof show === 'undefined') { + throw new DeveloperError('show is required.'); + } + + return new Uint8Array([show]); + }; + + return ShowGeometryInstanceAttribute; +}); diff --git a/Source/Core/SimplePolylineGeometry.js b/Source/Core/SimplePolylineGeometry.js new file mode 100644 index 000000000000..fa7d8718a07f --- /dev/null +++ b/Source/Core/SimplePolylineGeometry.js @@ -0,0 +1,114 @@ +/*global define*/ +define([ + './DeveloperError', + './ComponentDatatype', + './IndexDatatype', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './GeometryAttribute', + './GeometryAttributes' + ], function( + DeveloperError, + ComponentDatatype, + IndexDatatype, + PrimitiveType, + defaultValue, + BoundingSphere, + GeometryAttribute, + GeometryAttributes) { + "use strict"; + + /** + * A {@link Geometry} that represents a polyline modeled as a line strip; the first two positions define a line segment, + * and each additional position defines a line segment from the previous position. + * + * @alias SimplePolylineGeometry + * @constructor + * + * @param {Array} [options.positions] An array of {@link Cartesian3} defining the positions in the polyline as a line strip. + * + * @exception {DeveloperError} At least two positions are required. + * + * @example + * // A polyline with two connected line segments + * var geometry = new SimplePolylineGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(0.0, 0.0), + * Cartographic.fromDegrees(5.0, 0.0), + * Cartographic.fromDegrees(5.0, 5.0) + * ]) + * }); + */ + var SimplePolylineGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.positions; + + if ((typeof positions === 'undefined') || (positions.length < 2)) { + throw new DeveloperError('At least two positions are required.'); + } + + var i; + var j = 0; + var numberOfPositions = positions.length; + var positionValues = new Float64Array(numberOfPositions * 3); + + for (i = 0; i < numberOfPositions; ++i) { + var p = positions[i]; + + positionValues[j++] = p.x; + positionValues[j++] = p.y; + positionValues[j++] = p.z; + } + + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positionValues + }); + + // From line strip to lines + var numberOfIndices = 2 * (numberOfPositions - 1); + var indices = IndexDatatype.createTypedArray(numberOfPositions, numberOfIndices); + + j = 0; + for (i = 0; i < numberOfPositions - 1; ++i) { + indices[j++] = i; + indices[j++] = i + 1; + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type Object + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.LINES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.LINES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = BoundingSphere.fromPoints(positions); + }; + + return SimplePolylineGeometry; +}); \ No newline at end of file diff --git a/Source/Core/Tipsify.js b/Source/Core/Tipsify.js index c62884a5dc00..2adde8cd21aa 100644 --- a/Source/Core/Tipsify.js +++ b/Source/Core/Tipsify.js @@ -25,7 +25,7 @@ define([ * Calculates the average cache miss ratio (ACMR) for a given set of indices. * * @param {Array} description.indices Lists triads of numbers corresponding to the indices of the vertices - * in the vertex buffer that define the mesh's triangles. + * in the vertex buffer that define the geometry's triangles. * @param {Number} [description.maximumIndex] The maximum value of the elements in args.indices. * If not supplied, this value will be computed. * @param {Number} [description.cacheSize=24] The number of vertices that can be stored in the cache at any one time. @@ -100,7 +100,7 @@ define([ * Optimizes triangles for the post-vertex shader cache. * * @param {Array} description.indices Lists triads of numbers corresponding to the indices of the vertices - * in the vertex buffer that define the mesh's triangles. + * in the vertex buffer that define the geometry's triangles. * @param {Number} [description.maximumIndex] The maximum value of the elements in args.indices. * If not supplied, this value will be computed. * @param {Number} [description.cacheSize=24] The number of vertices that can be stored in the cache at any one time. diff --git a/Source/Core/VertexFormat.js b/Source/Core/VertexFormat.js new file mode 100644 index 000000000000..6cf0197673ac --- /dev/null +++ b/Source/Core/VertexFormat.js @@ -0,0 +1,182 @@ +/*global define*/ +define([ + './defaultValue', + './freezeObject' + ], function( + defaultValue, + freezeObject) { + "use strict"; + + /** + * A vertex format defines what attributes make up a vertex. A VertexFormat can be provided + * to a {@link Geometry} to request that certain properties be computed, e.g., just position, + * position and normal, etc. + * + * @param {Object} [options=undefined] An object with boolean properties corresponding to VertexFormat properties as shown in the code example. + * + * @alias VertexFormat + * @constructor + * + * @example + * // Create a vertex format with position and 2D texture coordinate attributes. + * var format = new VertexFormat({ + * position : true, + * st : true + * }); + * + * @see Geometry#attributes + */ + var VertexFormat = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * When true, the vertex has a 3D position attribute. + *

+ * 64-bit floating-point (for precision). 3 components per attribute. + *

+ * + * @type Boolean + * + * @default false + */ + this.position = defaultValue(options.position, false); + + /** + * When true, the vertex has a normal attribute (normalized), which is commonly used for lighting. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type Boolean + * + * @default false + */ + this.normal = defaultValue(options.normal, false); + + /** + * When true, the vertex has a 2D texture coordinate attribute. + *

+ * 32-bit floating-point. 2 components per attribute + *

+ * + * @type Boolean + * + * @default false + */ + this.st = defaultValue(options.st, false); + + /** + * When true, the vertex has a binormal attribute (normalized), which is used for tangent-space effects like bump mapping. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type Boolean + * + * @default false + */ + this.binormal = defaultValue(options.binormal, false); + + /** + * When true, the vertex has a tangent attribute (normalized), which is used for tangent-space effects like bump mapping. + *

+ * 32-bit floating-point. 3 components per attribute. + *

+ * + * @type Boolean + * + * @default false + */ + this.tangent = defaultValue(options.tangent, false); + }; + + /** + * An immutable vertex format with only a position attribute. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + */ + VertexFormat.POSITION_ONLY = freezeObject(new VertexFormat({ + position : true + })); + + /** + * An immutable vertex format with position and normal attributes. + * This is compatible with per-instance color appearances like {@link PerInstanceColorAppearance}. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + * @see VertexFormat#normal + */ + VertexFormat.POSITION_AND_NORMAL = freezeObject(new VertexFormat({ + position : true, + normal : true + })); + + /** + * An immutable vertex format with position, normal, and st attributes. + * This is compatible with {@link MaterialAppearance} when {@link MaterialAppearance#materialSupport} + * is TEXTURED/code>. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + * @see VertexFormat#normal + * @see VertexFormat#st + */ + VertexFormat.POSITION_NORMAL_AND_ST = freezeObject(new VertexFormat({ + position : true, + normal : true, + st : true + })); + + /** + * An immutable vertex format with position and st attributes. + * This is compatible with {@link EllipsoidSurfaceAppearance}. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + * @see VertexFormat#st + */ + VertexFormat.POSITION_AND_ST = freezeObject(new VertexFormat({ + position : true, + st : true + })); + + /** + * An immutable vertex format with all well-known attributes: position, normal, st, binormal, and tangent. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + * @see VertexFormat#normal + * @see VertexFormat#st + * @see VertexFormat#binormal + * @see VertexFormat#tangent + */ + VertexFormat.ALL = freezeObject(new VertexFormat({ + position : true, + normal : true, + st : true, + binormal : true, + tangent : true + })); + + /** + * An immutable vertex format with position, normal, and st attributes. + * This is compatible with most appearances and materials; however + * normal and st attributes are not always required. When this is + * known in advance, another VertexFormat should be used. + * + * @memberof VertexFormat + * + * @see VertexFormat#position + * @see VertexFormat#normal + */ + VertexFormat.DEFAULT = VertexFormat.POSITION_NORMAL_AND_ST; + + return VertexFormat; +}); \ No newline at end of file diff --git a/Source/Core/WallGeometry.js b/Source/Core/WallGeometry.js new file mode 100644 index 000000000000..aa6d0b65907c --- /dev/null +++ b/Source/Core/WallGeometry.js @@ -0,0 +1,467 @@ +/*global define*/ +define([ + './defaultValue', + './BoundingSphere', + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './IndexDatatype', + './DeveloperError', + './Ellipsoid', + './EllipsoidTangentPlane', + './GeometryAttribute', + './GeometryAttributes', + './Math', + './PolylinePipeline', + './PolygonPipeline', + './PrimitiveType', + './VertexFormat', + './WindingOrder' + ], function( + defaultValue, + BoundingSphere, + Cartesian3, + Cartographic, + ComponentDatatype, + IndexDatatype, + DeveloperError, + Ellipsoid, + EllipsoidTangentPlane, + GeometryAttribute, + GeometryAttributes, + CesiumMath, + PolylinePipeline, + PolygonPipeline, + PrimitiveType, + VertexFormat, + WindingOrder) { + "use strict"; + + function removeDuplicates(positions, topHeights, bottomHeights, cleanedPositions, cleanedTopHeights, cleanedBottomHeights) { + var length = positions.length; + + cleanedPositions.push(positions[0]); + + if (typeof topHeights !== 'undefined') { + cleanedTopHeights.push(topHeights[0]); + } + + if (typeof bottomHeights !== 'undefined') { + cleanedBottomHeights.push(bottomHeights[0]); + } + + for (var i = 1; i < length; ++i) { + var v0 = positions[i - 1]; + var v1 = positions[i]; + + if (!Cartesian3.equals(v0, v1)) { + cleanedPositions.push(v1); // Shallow copy! + + if (typeof topHeights !== 'undefined') { + cleanedTopHeights.push(topHeights[i]); + } + + if (typeof bottomHeights !== 'undefined') { + cleanedBottomHeights.push(bottomHeights[i]); + } + } + } + } + + var scratchCartographic = new Cartographic(); + var scratchCartesian3Position1 = new Cartesian3(); + var scratchCartesian3Position2 = new Cartesian3(); + var scratchCartesian3Position3 = new Cartesian3(); + var scratchBinormal = new Cartesian3(); + var scratchTangent = new Cartesian3(); + var scratchNormal = new Cartesian3(); + + /** + * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, + * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. + * + * @alias WallGeometry + * @constructor + * + * @param {Array} positions An array of Cartesian objects, which are the points of the wall. + * @param {Array} [maximumHeights] An array parallel to positions that give the maximum height of the + * wall at positions. If undefined, the height of each position in used. + * @param {Array} [minimumHeights] An array parallel to positions that give the minimum height of the + * wall at positions. If undefined, the height at each position is 0.0. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} positions is required. + * @exception {DeveloperError} positions and maximumHeights must have the same length. + * @exception {DeveloperError} positions and minimumHeights must have the same length. + * + * @example + * var positions = [ + * Cartographic.fromDegrees(19.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 47.0, 10000.0) + * ]; + * + * // create a wall that spans from ground level to 10000 meters + * var wall = new WallGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray(positions) + * }); + */ + var WallGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var wallPositions = options.positions; + var maximumHeights = options.maximumHeights; + var minimumHeights = options.minimumHeights; + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + if (typeof wallPositions === 'undefined') { + throw new DeveloperError('positions is required.'); + } + + if (typeof maximumHeights !== 'undefined' && maximumHeights.length !== wallPositions.length) { + throw new DeveloperError('positions and maximumHeights must have the same length.'); + } + + if (typeof minimumHeights !== 'undefined' && minimumHeights.length !== wallPositions.length) { + throw new DeveloperError('positions and minimumHeights must have the same length.'); + } + + var cleanedPositions = []; + var cleanedMaximumHeights = (typeof maximumHeights !== 'undefined') ? [] : undefined; + var cleanedMinimumHeights = (typeof minimumHeights !== 'undefined') ? [] : undefined; + removeDuplicates(wallPositions, maximumHeights, minimumHeights, cleanedPositions, cleanedMaximumHeights, cleanedMinimumHeights); + + wallPositions = cleanedPositions; + maximumHeights = cleanedMaximumHeights; + minimumHeights = cleanedMinimumHeights; + + if (wallPositions.length >= 3) { + // Order positions counter-clockwise + var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); + + if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { + wallPositions.reverse(); + + if (typeof maximumHeights !== 'undefined') { + maximumHeights.reverse(); + } + + if (typeof minimumHeights !== 'undefined') { + minimumHeights.reverse(); + } + } + } + + var i; + var length = wallPositions.length; + var size = 2 * (length - 1) * 2 * 3; + + var positions = vertexFormat.position ? new Float64Array(size) : undefined; + var normals = vertexFormat.normal ? new Float32Array(size) : undefined; + var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined; + var binormals = vertexFormat.binormal ? new Float32Array(size) : undefined; + var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined; + + var positionIndex = 0; + var normalIndex = 0; + var tangentIndex = 0; + var binormalIndex = 0; + var textureCoordIndex = 0; + + // add lower and upper points one after the other, lower + // points being even and upper points being odd + var normal = scratchNormal; + var tangent = scratchTangent; + var binormal = scratchBinormal; + var recomputeNormal = true; + for (i = 0; i < length - 1; ++i) { + for (var j = 0; j < 2; ++j) { + var index = i + j; + + var pos = wallPositions[index]; + var c = ellipsoid.cartesianToCartographic(pos, scratchCartographic); + + if (typeof maximumHeights !== 'undefined') { + c.height = maximumHeights[index]; + } + var topPosition = ellipsoid.cartographicToCartesian(c, scratchCartesian3Position1); + + c.height = 0.0; + if (typeof minimumHeights !== 'undefined') { + c.height += minimumHeights[index]; + } else { + c.height = 0.0; + } + + var bottomPosition = ellipsoid.cartographicToCartesian(c, scratchCartesian3Position2); + + if (vertexFormat.position) { + // insert the lower point + positions[positionIndex++] = bottomPosition.x; + positions[positionIndex++] = bottomPosition.y; + positions[positionIndex++] = bottomPosition.z; + + // insert the upper point + positions[positionIndex++] = topPosition.x; + positions[positionIndex++] = topPosition.y; + positions[positionIndex++] = topPosition.z; + } + + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) { + var nextPosition; + if (index + 1 < length) { + nextPosition = Cartesian3.clone(wallPositions[index + 1], scratchCartesian3Position3); + } + + if (recomputeNormal) { + nextPosition = nextPosition.subtract(topPosition, nextPosition); + bottomPosition = bottomPosition.subtract(topPosition, bottomPosition); + normal = Cartesian3.cross(bottomPosition, nextPosition, normal).normalize(normal); + if (vertexFormat.tangent) { + tangent = nextPosition.normalize(tangent); + } + if (vertexFormat.binormal) { + binormal = Cartesian3.cross(normal, tangent, binormal).normalize(binormal); + } + recomputeNormal = false; + } + + if (j === 1) { + recomputeNormal = true; + } + + if (vertexFormat.normal) { + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; + + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; + } + + if (vertexFormat.tangent) { + tangents[tangentIndex++] = tangent.x; + tangents[tangentIndex++] = tangent.y; + tangents[tangentIndex++] = tangent.z; + + tangents[tangentIndex++] = tangent.x; + tangents[tangentIndex++] = tangent.y; + tangents[tangentIndex++] = tangent.z; + } + + if (vertexFormat.binormal) { + binormals[binormalIndex++] = binormal.x; + binormals[binormalIndex++] = binormal.y; + binormals[binormalIndex++] = binormal.z; + + binormals[binormalIndex++] = binormal.x; + binormals[binormalIndex++] = binormal.y; + binormals[binormalIndex++] = binormal.z; + } + } + + if (vertexFormat.st) { + var s = index / (length - 1); + + textureCoordinates[textureCoordIndex++] = s; + textureCoordinates[textureCoordIndex++] = 0.0; + + textureCoordinates[textureCoordIndex++] = s; + textureCoordinates[textureCoordIndex++] = 1.0; + } + } + } + + var attributes = new GeometryAttributes(); + + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + } + + if (vertexFormat.normal) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.tangent) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.binormal) { + attributes.binormal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : binormals + }); + } + + if (vertexFormat.st) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); + } + + + // prepare the side walls, two triangles for each wall + // + // A (i+1) B (i+3) E + // +--------+-------+ + // | / | /| triangles: A C B + // | / | / | B C D + // | / | / | + // | / | / | + // | / | / | + // | / | / | + // +--------+-------+ + // C (i) D (i+2) F + // + + var numVertices = size / 3; + size -= 6; + var indices = IndexDatatype.createTypedArray(numVertices, size); + + var edgeIndex = 0; + for (i = 0; i < numVertices - 2; i += 2) { + var LL = i; + var LR = i + 2; + var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1); + var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2); + if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON6)) { + continue; + } + var UL = i + 1; + var UR = i + 3; + + indices[edgeIndex++] = UL; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = UR; + indices[edgeIndex++] = UR; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = LR; + } + + /** + * An object containing {@link GeometryAttribute} properties named after each of the + * true values of the {@link VertexFormat} option. + * + * @type GeometryAttributes + * + * @see Geometry#attributes + */ + this.attributes = attributes; + + /** + * Index data that, along with {@link Geometry#primitiveType}, determines the primitives in the geometry. + * + * @type Array + */ + this.indices = indices; + + /** + * The type of primitives in the geometry. For this geometry, it is {@link PrimitiveType.TRIANGLES}. + * + * @type PrimitiveType + */ + this.primitiveType = PrimitiveType.TRIANGLES; + + /** + * A tight-fitting bounding sphere that encloses the vertices of the geometry. + * + * @type BoundingSphere + */ + this.boundingSphere = new BoundingSphere.fromVertices(positions); + }; + + /** + * A {@link Geometry} that represents a wall, which is similar to a KML line string. A wall is defined by a series of points, + * which extrude down to the ground. Optionally, they can extrude downwards to a specified height. + * + * @memberof WallGeometry + * + * @param {Array} positions An array of Cartesian objects, which are the points of the wall. + * @param {Number} [maximumHeight] A constant that defines the maximum height of the + * wall at positions. If undefined, the height of each position in used. + * @param {Number} [minimumHeight] A constant that defines the minimum height of the + * wall at positions. If undefined, the height at each position is 0.0. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @exception {DeveloperError} positions is required. + * + * @example + * var positions = [ + * Cartographic.fromDegrees(19.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 48.0, 10000.0), + * Cartographic.fromDegrees(20.0, 47.0, 10000.0), + * Cartographic.fromDegrees(19.0, 47.0, 10000.0) + * ]; + * + * // create a wall that spans from 10000 meters to 20000 meters + * var wall = new WallGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray(positions), + * topHeight : 20000.0, + * bottomHeight : 10000.0 + * }); + */ + WallGeometry.fromConstantHeights = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var positions = options.positions; + if (typeof positions === 'undefined') { + throw new DeveloperError('options.positions is required.'); + } + + var minHeights; + var maxHeights; + + var min = options.minimumHeight; + var max = options.maximumHeight; + if (typeof min !== 'undefined' || typeof max !== 'undefined') { + var length = positions.length; + minHeights = (typeof min !== 'undefined') ? new Array(length) : undefined; + maxHeights = (typeof max !== 'undefined') ? new Array(length) : undefined; + + for (var i = 0; i < length; ++i) { + if (typeof min !== 'undefined') { + minHeights[i] = min; + } + + if (typeof max !== 'undefined') { + maxHeights[i] = max; + } + } + } + + var newOptions = { + positions : positions, + maximumHeights : maxHeights, + minimumHeights : minHeights, + ellipsoid : options.ellipsoid, + vertexFormat : options.vertexFormat + }; + return new WallGeometry(newOptions); + }; + + return WallGeometry; +}); + diff --git a/Source/Core/barycentricCoordinates.js b/Source/Core/barycentricCoordinates.js new file mode 100644 index 000000000000..f01baa85e342 --- /dev/null +++ b/Source/Core/barycentricCoordinates.js @@ -0,0 +1,65 @@ +/*global define*/ +define([ + './Cartesian3', + './DeveloperError' + ], function( + Cartesian3, + DeveloperError) { + "use strict"; + + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + + /** + * Computes the barycentric coordinates for a point with respect to a triangle. + * + * @exports pointInsideTriangle + * + * @param {Cartesian2|Cartesian3} point The point to test. + * @param {Cartesian2|Cartesian3} p0 The first point of the triangle, corresponding to the barycentric x-axis. + * @param {Cartesian2|Cartesian3} p1 The second point of the triangle, corresponding to the barycentric y-axis. + * @param {Cartesian2|Cartesian3} p2 The third point of the triangle, corresponding to the barycentric z-axis. + * @param {Cartesian3} [result] The object onto which to store the result. + * + * @return {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. + * + * @exception {DeveloperError} point, p0, p1, and p2 are required. + * + * @example + * // Returns Cartesian3.UNIT_X + * var p = new Cartesian3(-1.0, 0.0, 0.0); + * var b = barycentricCoordinates(p, + * new Cartesian3(-1.0, 0.0, 0.0), + * new Cartesian3( 1.0, 0.0, 0.0), + * new Cartesian3( 0.0, 1.0, 1.0)); + */ + var barycentricCoordinates = function(point, p0, p1, p2, result) { + if (typeof point === 'undefined' || typeof p0 === 'undefined' || typeof p1 === 'undefined' || typeof p2 === 'undefined') { + throw new DeveloperError('point, p0, p1, and p2 are required.'); + } + + if (typeof result === 'undefined') { + result = new Cartesian3(); + } + + // Implementation based on http://www.blackpawn.com/texts/pointinpoly/default.html. + var v0 = p1.subtract(p0, scratchCartesian1); + var v1 = p2.subtract(p0, scratchCartesian2); + var v2 = point.subtract(p0, scratchCartesian3); + + var dot00 = v0.dot(v0); + var dot01 = v0.dot(v1); + var dot02 = v0.dot(v2); + var dot11 = v1.dot(v1); + var dot12 = v1.dot(v2); + + var q = 1.0 / (dot00 * dot11 - dot01 * dot01); + result.y = (dot11 * dot02 - dot01 * dot12) * q; + result.z = (dot00 * dot12 - dot01 * dot02) * q; + result.x = 1.0 - result.y - result.z; + return result; + }; + + return barycentricCoordinates; +}); \ No newline at end of file diff --git a/Source/Core/pointInsideTriangle.js b/Source/Core/pointInsideTriangle.js new file mode 100644 index 000000000000..9f2cfcca3d6b --- /dev/null +++ b/Source/Core/pointInsideTriangle.js @@ -0,0 +1,42 @@ +/*global define*/ +define([ + './barycentricCoordinates', + './Cartesian3', + './DeveloperError' + ], function( + barycentricCoordinates, + Cartesian3, + DeveloperError) { + "use strict"; + + var coords = new Cartesian3(); + + /** + * Determines if a point is inside a triangle. + * + * @exports pointInsideTriangle + * + * @param {Cartesian2|Cartesian3} point The point to test. + * @param {Cartesian2|Cartesian3} p0 The first point of the triangle. + * @param {Cartesian2|Cartesian3} p1 The second point of the triangle. + * @param {Cartesian2|Cartesian3} p2 The third point of the triangle. + * + * @return {Boolean} true if the point is inside the triangle; otherwise, false. + * + * @exception {DeveloperError} point, p0, p1, and p2 are required. + * + * @example + * // Returns true + * var p = new Cartesian2(0.25, 0.25); + * var b = pointInsideTriangle(p, + * new Cartesian2(0.0, 0.0), + * new Cartesian2(1.0, 0.0), + * new Cartesian2(0.0, 1.0)); + */ + var pointInsideTriangle = function(point, p0, p1, p2) { + barycentricCoordinates(point, p0, p1, p2, coords); + return (coords.x > 0.0) && (coords.y > 0.0) && (coords.z > 0); + }; + + return pointInsideTriangle; +}); diff --git a/Source/Core/pointInsideTriangle2D.js b/Source/Core/pointInsideTriangle2D.js deleted file mode 100644 index e03c54f5144b..000000000000 --- a/Source/Core/pointInsideTriangle2D.js +++ /dev/null @@ -1,41 +0,0 @@ -/*global define*/ -define(['./DeveloperError'], function(DeveloperError) { - "use strict"; - - /** - * DOC_TBA - * - * @param point - * @param p0 - * @param p1 - * @param p2 - * - * @exports pointInsideTriangle2D - * - * @exception {DeveloperError} point, p0, p1, and p2 are required. - */ - var pointInsideTriangle2D = function(point, p0, p1, p2) { - if (typeof point === 'undefined' || typeof p0 === 'undefined' || typeof p1 === 'undefined' || typeof p2 === 'undefined') { - throw new DeveloperError('point, p0, p1, and p2 are required.'); - } - - // Implementation based on http://www.blackpawn.com/texts/pointinpoly/default.html. - var v0 = p1.subtract(p0); - var v1 = p2.subtract(p0); - var v2 = point.subtract(p0); - - var dot00 = v0.dot(v0); - var dot01 = v0.dot(v1); - var dot02 = v0.dot(v2); - var dot11 = v1.dot(v1); - var dot12 = v1.dot(v2); - - var q = 1.0 / (dot00 * dot11 - dot01 * dot01); - var u = (dot11 * dot02 - dot01 * dot12) * q; - var v = (dot00 * dot12 - dot01 * dot02) * q; - - return (u > 0) && (v > 0) && (u + v < 1); - }; - - return pointInsideTriangle2D; -}); diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 3b6cde2e9b37..9fb8864ba3b9 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -4,11 +4,14 @@ define([ '../Core/DeveloperError', '../Core/destroyObject', '../Core/Color', + '../Core/ComponentDatatype', '../Core/IndexDatatype', '../Core/RuntimeError', '../Core/PrimitiveType', + '../Core/Geometry', '../Core/createGuid', '../Core/Matrix4', + '../Core/Math', './Buffer', './BufferUsage', './CubeMap', @@ -36,11 +39,14 @@ define([ DeveloperError, destroyObject, Color, + ComponentDatatype, IndexDatatype, RuntimeError, PrimitiveType, + Geometry, createGuid, Matrix4, + CesiumMath, Buffer, BufferUsage, CubeMap, @@ -219,6 +225,7 @@ define([ // Query and initialize extensions this._standardDerivatives = gl.getExtension('OES_standard_derivatives'); + this._elementIndexUint = gl.getExtension('OES_element_index_uint'); this._depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture') || gl.getExtension('MOZ_WEBGL_depth_texture'); this._textureFloat = gl.getExtension('OES_texture_float'); var textureFilterAnisotropic = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic'); @@ -697,6 +704,21 @@ define([ return !!this._standardDerivatives; }; + /** + * Returns true if the OES_element_index_uint extension is supported. This + * extension allows the use of unsigned int indices, which can improve performance by + * eliminating batch breaking caused by unsigned short indices. + * + * @memberof Context + * + * @returns {Boolean} true if OES_element_index_uint is supported; otherwise, false. + * + * @see OES_element_index_uint + */ + Context.prototype.getElementIndexUint = function() { + return !!this._elementIndexUint; + }; + /** * Returns true if WEBGL_depth_texture is supported. This extension provides * access to depth textures that, for example, can be attached to framebuffers for shadow mapping. @@ -1062,6 +1084,7 @@ define([ * * @return {IndexBuffer} The index buffer, ready to be attached to a vertex array. * + * @exception {RuntimeError} IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. * @exception {DeveloperError} The size in bytes must be greater than zero. * @exception {DeveloperError} Invalid usage. * @exception {DeveloperError} Invalid indexDatatype. @@ -1087,16 +1110,16 @@ define([ * BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT) */ Context.prototype.createIndexBuffer = function(typedArrayOrSizeInBytes, usage, indexDatatype) { - var bytesPerIndex; - - if (indexDatatype === IndexDatatype.UNSIGNED_BYTE) { - bytesPerIndex = Uint8Array.BYTES_PER_ELEMENT; - } else if (indexDatatype === IndexDatatype.UNSIGNED_SHORT) { - bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT; - } else { + if (!IndexDatatype.validate(indexDatatype)) { throw new DeveloperError('Invalid indexDatatype.'); } + if ((indexDatatype === IndexDatatype.UNSIGNED_INT) && !this.getElementIndexUint()) { + throw new RuntimeError('IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system.'); + } + + var bytesPerIndex = indexDatatype.sizeInBytes; + var gl = this._gl; var buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, typedArrayOrSizeInBytes, usage); var numberOfIndices = buffer.getSizeInBytes() / bytesPerIndex; @@ -1133,7 +1156,7 @@ define([ * @exception {DeveloperError} Attribute must have a strideInBytes less than or equal to 255 or not specify it. * @exception {DeveloperError} Index n is used by more than one attribute. * - * @see Context#createVertexArrayFromMesh + * @see Context#createVertexArrayFromGeometry * @see Context#createVertexBuffer * @see Context#createIndexBuffer * @see Context#draw @@ -2180,8 +2203,15 @@ define([ var names = []; for (name in attributes) { // Attribute needs to have per-vertex values; not a constant value for all vertices. - if (attributes.hasOwnProperty(name) && attributes[name].values) { + if (attributes.hasOwnProperty(name) && + typeof attributes[name] !== 'undefined' && + typeof attributes[name].values !== 'undefined') { names.push(name); + + if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) { + attributes[name].componentDatatype = ComponentDatatype.FLOAT; + attributes[name].values = ComponentDatatype.FLOAT.createTypedArray(attributes[name].values); + } } } @@ -2244,7 +2274,7 @@ define([ var sizeInBytes = attributes[name].componentDatatype.sizeInBytes; views[name] = { - pointer : attributes[name].componentDatatype.toTypedArray(buffer), + pointer : attributes[name].componentDatatype.createTypedArray(buffer), index : offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType strideInComponentType : vertexSizeInBytes / sizeInBytes }; @@ -2281,54 +2311,53 @@ define([ } /** - * Creates a vertex array from a mesh. A mesh contains vertex attributes and optional index data + * Creates a vertex array from a geometry. A geometry contains vertex attributes and optional index data * in system memory, whereas a vertex array contains vertex buffers and an optional index buffer in WebGL * memory for use with rendering. *

- * The mesh argument should use the standard layout like the mesh returned by {@link BoxTessellator}. + * The geometry argument should use the standard layout like the geometry returned by {@link BoxGeometry}. *

* creationArguments can have four properties: *
    - *
  • mesh: The source mesh containing data used to create the vertex array.
  • - *
  • attributeIndices: An object that maps mesh attribute names to vertex shader attribute indices.
  • + *
  • geometry: The source geometry containing data used to create the vertex array.
  • + *
  • attributeIndices: An object that maps geometry attribute names to vertex shader attribute indices.
  • *
  • bufferUsage: The expected usage pattern of the vertex array's buffers. On some WebGL implementations, this can significantly affect performance. See {@link BufferUsage}. Default: BufferUsage.DYNAMIC_DRAW.
  • *
  • vertexLayout: Determines if all attributes are interleaved in a single vertex buffer or if each attribute is stored in a separate vertex buffer. Default: VertexLayout.SEPARATE.
  • *
*
- * If creationArguments is not specified or the mesh contains no data, the returned vertex array is empty. + * If creationArguments is not specified or the geometry contains no data, the returned vertex array is empty. * * @memberof Context * - * @param {Object} [creationArguments=undefined] An object defining the mesh, attribute indices, buffer usage, and vertex layout used to create the vertex array. + * @param {Object} [creationArguments=undefined] An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array. * * @exception {RuntimeError} Each attribute list must have the same number of vertices. - * @exception {DeveloperError} The mesh must have zero or one index lists. + * @exception {DeveloperError} The geometry must have zero or one index lists. * @exception {DeveloperError} Index n is used by more than one attribute. * * @see Context#createVertexArray * @see Context#createVertexBuffer * @see Context#createIndexBuffer - * @see MeshFilters.createAttributeIndices + * @see GeometryPipeline.createAttributeIndices * @see ShaderProgram - * @see BoxTessellator * * @example * // Example 1. Creates a vertex array for rendering a box. The default dynamic draw * // usage is used for the created vertex and index buffer. The attributes are not * // interleaved by default. - * var mesh = BoxTessellator.compute(); - * var va = context.createVertexArrayFromMesh({ - * mesh : mesh, - * attributeIndices : MeshFilters.createAttributeIndices(mesh), + * var geometry = new BoxGeometry(); + * var va = context.createVertexArrayFromGeometry({ + * geometry : geometry, + * attributeIndices : GeometryPipeline.createAttributeIndices(geometry), * }); * * //////////////////////////////////////////////////////////////////////////////// * * // Example 2. Creates a vertex array with interleaved attributes in a * // single vertex buffer. The vertex and index buffer have static draw usage. - * var va = context.createVertexArrayFromMesh({ - * mesh : mesh, - * attributeIndices : MeshFilters.createAttributeIndices(mesh), + * var va = context.createVertexArrayFromGeometry({ + * geometry : geometry, + * attributeIndices : GeometryPipeline.createAttributeIndices(geometry), * bufferUsage : BufferUsage.STATIC_DRAW, * vertexLayout : VertexLayout.INTERLEAVED * }); @@ -2339,41 +2368,35 @@ define([ * // attached vertex buffer(s) and index buffer. * va = va.destroy(); */ - Context.prototype.createVertexArrayFromMesh = function(creationArguments) { + Context.prototype.createVertexArrayFromGeometry = function(creationArguments) { var ca = defaultValue(creationArguments, defaultValue.EMPTY_OBJECT); - var mesh = defaultValue(ca.mesh, defaultValue.EMPTY_OBJECT); + var geometry = defaultValue(ca.geometry, defaultValue.EMPTY_OBJECT); var bufferUsage = defaultValue(ca.bufferUsage, BufferUsage.DYNAMIC_DRAW); - var indexLists; - - if (mesh.indexLists) { - indexLists = mesh.indexLists; - if (indexLists.length !== 1) { - throw new DeveloperError('The mesh must have zero or one index lists. This mesh has ' + indexLists.length.toString() + ' index lists.'); - } - } var attributeIndices = defaultValue(ca.attributeIndices, defaultValue.EMPTY_OBJECT); - var interleave = ca.vertexLayout && (ca.vertexLayout === VertexLayout.INTERLEAVED); + var interleave = (typeof ca.vertexLayout !== 'undefined') && (ca.vertexLayout === VertexLayout.INTERLEAVED); + var createdVAAttributes = ca.vertexArrayAttributes; var name; var attribute; - var vaAttributes = []; - var attributes = mesh.attributes; + var vertexBuffer; + var vaAttributes = (typeof createdVAAttributes !== 'undefined') ? createdVAAttributes : []; + var attributes = geometry.attributes; if (interleave) { // Use a single vertex buffer with interleaved vertices. var interleavedAttributes = interleaveAttributes(attributes); - if (interleavedAttributes) { - var vertexBuffer = this.createVertexBuffer(interleavedAttributes.buffer, bufferUsage); + if (typeof interleavedAttributes !== 'undefined') { + vertexBuffer = this.createVertexBuffer(interleavedAttributes.buffer, bufferUsage); var offsetsInBytes = interleavedAttributes.offsetsInBytes; var strideInBytes = interleavedAttributes.vertexSizeInBytes; for (name in attributes) { - if (attributes.hasOwnProperty(name)) { + if (attributes.hasOwnProperty(name) && typeof attributes[name] !== 'undefined') { attribute = attributes[name]; - if (attribute.values) { + if (typeof attribute.values !== 'undefined') { // Common case: per-vertex attributes vaAttributes.push({ index : attributeIndices[name], @@ -2399,13 +2422,24 @@ define([ } else { // One vertex buffer per attribute. for (name in attributes) { - if (attributes.hasOwnProperty(name)) { + if (attributes.hasOwnProperty(name) && typeof attributes[name] !== 'undefined') { attribute = attributes[name]; + + var componentDatatype = attribute.componentDatatype; + if (componentDatatype === ComponentDatatype.DOUBLE) { + componentDatatype = ComponentDatatype.FLOAT; + } + + vertexBuffer = undefined; + if (typeof attribute.values !== 'undefined') { + vertexBuffer = this.createVertexBuffer(componentDatatype.createTypedArray(attribute.values), bufferUsage); + } + vaAttributes.push({ index : attributeIndices[name], - vertexBuffer : attribute.values ? this.createVertexBuffer(attribute.componentDatatype.toTypedArray(attribute.values), bufferUsage) : undefined, - value : attribute.value ? attribute.value : undefined, - componentDatatype : attribute.componentDatatype, + vertexBuffer : vertexBuffer, + value : attribute.value, + componentDatatype : componentDatatype, componentsPerAttribute : attribute.componentsPerAttribute, normalize : attribute.normalize }); @@ -2414,8 +2448,13 @@ define([ } var indexBuffer; - if (typeof indexLists !== 'undefined') { - indexBuffer = this.createIndexBuffer(new Uint16Array(indexLists[0].values), bufferUsage, IndexDatatype.UNSIGNED_SHORT); + var indices = geometry.indices; + if (typeof indices !== 'undefined') { + if ((Geometry.computeNumberOfVertices(geometry) > CesiumMath.SIXTY_FOUR_KILOBYTES) && this.getElementIndexUint()) { + indexBuffer = this.createIndexBuffer(new Uint32Array(indices), bufferUsage, IndexDatatype.UNSIGNED_INT); + } else{ + indexBuffer = this.createIndexBuffer(new Uint16Array(indices), bufferUsage, IndexDatatype.UNSIGNED_SHORT); + } } return this.createVertexArray(vaAttributes, indexBuffer); diff --git a/Source/Renderer/ShaderCache.js b/Source/Renderer/ShaderCache.js index 2aed6c6533e6..d83519e2f8c4 100644 --- a/Source/Renderer/ShaderCache.js +++ b/Source/Renderer/ShaderCache.js @@ -61,8 +61,7 @@ define([ * @see ShaderCache#replaceShaderProgram */ ShaderCache.prototype.getShaderProgram = function(vertexShaderSource, fragmentShaderSource, attributeLocations) { - // TODO: compare attributeLocations! - var keyword = vertexShaderSource + fragmentShaderSource; + var keyword = vertexShaderSource + fragmentShaderSource + JSON.stringify(attributeLocations); var cachedShader; if (this._shaders[keyword]) { diff --git a/Source/Renderer/ShaderProgram.js b/Source/Renderer/ShaderProgram.js index 9034d1a86394..0d7e401e0769 100644 --- a/Source/Renderer/ShaderProgram.js +++ b/Source/Renderer/ShaderProgram.js @@ -759,8 +759,8 @@ define([ * * void main() * { - * vec3 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_projection * (czm_modelViewRelativeToEye * vec4(p, 1.0)); + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_projection * (czm_modelViewRelativeToEye * p); * } * * @see czm_modelViewProjectionRelativeToEye @@ -963,8 +963,8 @@ define([ * * void main() * { - * vec3 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_modelViewProjectionRelativeToEye * vec4(p, 1.0); + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_modelViewProjectionRelativeToEye * p; * } * * @see czm_modelViewRelativeToEye @@ -2596,7 +2596,7 @@ define([ gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); - if (attributeLocations) { + if (typeof attributeLocations !== 'undefined') { for ( var attribute in attributeLocations) { if (attributeLocations.hasOwnProperty(attribute)) { gl.bindAttribLocation(program, attributeLocations[attribute], attribute); diff --git a/Source/Renderer/VertexArray.js b/Source/Renderer/VertexArray.js index 7527067af2fe..d7834a8a2fff 100644 --- a/Source/Renderer/VertexArray.js +++ b/Source/Renderer/VertexArray.js @@ -122,7 +122,7 @@ define([ * @internalConstructor * * @see {@link Context#createVertexArray} - * @see {@link Context#createVertexArrayFromMesh} + * @see {@link Context#createVertexArrayFromGeometry} */ var VertexArray = function(gl, vertexArrayObject, attributes, indexBuffer) { var vaAttributes = []; diff --git a/Source/Renderer/VertexArrayFacade.js b/Source/Renderer/VertexArrayFacade.js index 00d587a4fb7f..223b06e6d0bd 100644 --- a/Source/Renderer/VertexArrayFacade.js +++ b/Source/Renderer/VertexArrayFacade.js @@ -4,12 +4,14 @@ define([ '../Core/defaultValue', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/Math', './BufferUsage' ], function( ComponentDatatype, defaultValue, destroyObject, DeveloperError, + CesiumMath, BufferUsage) { "use strict"; @@ -371,9 +373,6 @@ define([ } }; - // Using unsigned short indices, 64K vertices can be indexed by one index buffer - var sixtyFourK = 64 * 1024; - /** * DOC_TBA * @@ -403,7 +402,7 @@ define([ var buffersByUsage = buffersByPurposeAndUsage[purpose]; var va = []; - var numberOfVertexArrays = Math.ceil(this._size / sixtyFourK); + var numberOfVertexArrays = Math.ceil(this._size / CesiumMath.SIXTY_FOUR_KILOBYTES); for ( var k = 0; k < numberOfVertexArrays; ++k) { var attributes = []; @@ -413,7 +412,7 @@ define([ for (var allPurposeUsage in allPurposeBuffersByUsage) { if (allPurposeBuffersByUsage.hasOwnProperty(allPurposeUsage)) { var allPurposeBuffer = allPurposeBuffersByUsage[allPurposeUsage]; - VertexArrayFacade._appendAttributes(attributes, allPurposeBuffer, k * (allPurposeBuffer.vertexSizeInBytes * sixtyFourK)); + VertexArrayFacade._appendAttributes(attributes, allPurposeBuffer, k * (allPurposeBuffer.vertexSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES)); } } } @@ -422,7 +421,7 @@ define([ for (var usage in buffersByUsage) { if (buffersByUsage.hasOwnProperty(usage)) { buffer = buffersByUsage[usage]; - VertexArrayFacade._appendAttributes(attributes, buffer, k * (buffer.vertexSizeInBytes * sixtyFourK)); + VertexArrayFacade._appendAttributes(attributes, buffer, k * (buffer.vertexSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES)); } } @@ -430,7 +429,7 @@ define([ va.push({ va : this._context.createVertexArray(attributes, indexBuffer), - indicesCount : 1.5 * ((k !== (numberOfVertexArrays - 1)) ? sixtyFourK : (this._size % sixtyFourK)) + indicesCount : 1.5 * ((k !== (numberOfVertexArrays - 1)) ? CesiumMath.SIXTY_FOUR_KILOBYTES : (this._size % CesiumMath.SIXTY_FOUR_KILOBYTES)) // TODO: not hardcode 1.5 }); } diff --git a/Source/Renderer/VertexLayout.js b/Source/Renderer/VertexLayout.js index 7d7ad77617e8..cf425b99004c 100644 --- a/Source/Renderer/VertexLayout.js +++ b/Source/Renderer/VertexLayout.js @@ -7,7 +7,7 @@ define(['../Core/Enumeration'], function(Enumeration) { * * @exports VertexLayout * - * @see Context#createVertexArrayFromMesh + * @see Context#createVertexArrayFromGeometry */ var VertexLayout = { /** diff --git a/Source/Scene/Appearance.js b/Source/Scene/Appearance.js new file mode 100644 index 000000000000..f473d8d3d1e3 --- /dev/null +++ b/Source/Scene/Appearance.js @@ -0,0 +1,119 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Renderer/BlendingState', + '../Renderer/CullFace' + ], function( + defaultValue, + BlendingState, + CullFace) { + "use strict"; + + /** + * An appearance defines the full GLSL vertex and fragment shaders and the + * render state used to draw a {@link Primitive}. All appearances implement + * this base Appearance interface. + * + * @alias Appearance + * @constructor + * + * @see MaterialAppearance + * @see EllipsoidSurfaceAppearance + * @see PerInstanceColorAppearance + * @see DebugAppearance + */ + var Appearance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The material used to determine the fragment color. Unlike other {@link Appearance} + * properties, this is not read-only, so an appearance's material can change on the fly. + * + * @type Material + * + * @see Fabric + */ + this.material = options.material; + + /** + * The GLSL source code for the vertex shader. + * + * @type String + * + * @readonly + */ + this.vertexShaderSource = options.vertexShaderSource; + + /** + * The GLSL source code for the fragment shader. The full fragment shader + * source is built procedurally taking into account the {@link Appearance#material}. + * Use {@link Appearance#getFragmentShaderSource} to get the full source. + * + * @type String + * + * @readonly + */ + this.fragmentShaderSource = options.fragmentShaderSource; + + /** + * The render state. This is not the final {@link RenderState} instance; instead, + * it can contain a subset of render state properties identical to renderState + * passed to {@link Context#createRenderState}. + * + * @type Object + * + * @readonly + */ + this.renderState = options.renderState; + }; + + /** + * Procedurally creates the full GLSL fragment shader source for this appearance + * taking into account {@link Appearance#fragmentShaderSource} and {@link Appearance#material}. + * + * @memberof Appearance + * + * @return String The full GLSL fragment shader source. + */ + Appearance.prototype.getFragmentShaderSource = function() { + var flat = this.flat ? '#define FLAT 1\n#line 0 \n' : '#line 0 \n'; + var faceForward = this.faceForward ? '#define FACE_FORWARD 1\n#line 0 \n' : '#line 0 \n'; + + if (typeof this.material !== 'undefined') { + return '#line 0\n' + + this.material.shaderSource + + flat + + faceForward + + this.fragmentShaderSource; + } + + return flat + faceForward + this.fragmentShaderSource; + }; + + /** + * @private + */ + Appearance.getDefaultRenderState = function(translucent, closed) { + var rs = { + depthTest : { + enabled : true + } + }; + + if (translucent) { + rs.depthMask = false; + rs.blending = BlendingState.ALPHA_BLEND; + } + + if (closed) { + rs.cull = { + enabled : true, + face : CullFace.BACK + }; + } + + return rs; + }; + + return Appearance; +}); \ No newline at end of file diff --git a/Source/Scene/CameraController.js b/Source/Scene/CameraController.js index 3735a4437aec..e212a597387d 100644 --- a/Source/Scene/CameraController.js +++ b/Source/Scene/CameraController.js @@ -890,8 +890,8 @@ define([ if (!positionOnly) { var direction = Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); Cartesian3.negate(direction, direction); - var right = Cartesian3.clone(Cartesian3.UNIT_X, camera.right); - Cartesian3.cross(right, direction, camera.up); + Cartesian3.clone(Cartesian3.UNIT_X, camera.right); + Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); } return result; @@ -947,8 +947,10 @@ define([ frustum.top = top; frustum.bottom = -top; - var cameraRight = Cartesian3.clone(Cartesian3.UNIT_X, camera.right); - Cartesian3.cross(cameraRight, camera.direction, camera.up); + var direction = Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); + Cartesian3.negate(direction, direction); + Cartesian3.clone(Cartesian3.UNIT_X, camera.right); + Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); } return result; diff --git a/Source/Scene/CentralBody.js b/Source/Scene/CentralBody.js index f24a80696479..49bba4bbc346 100644 --- a/Source/Scene/CentralBody.js +++ b/Source/Scene/CentralBody.js @@ -14,6 +14,8 @@ define([ '../Core/Ellipsoid', '../Core/Extent', '../Core/GeographicProjection', + '../Core/Geometry', + '../Core/GeometryAttribute', '../Core/Intersect', '../Core/Math', '../Core/Matrix4', @@ -55,6 +57,8 @@ define([ Ellipsoid, Extent, GeographicProjection, + Geometry, + GeometryAttribute, Intersect, CesiumMath, Matrix4, @@ -337,7 +341,7 @@ define([ var occludeePoint; var occluded; var datatype; - var mesh; + var geometry; var rect; var positions; var occluder = centralBody._occluder; @@ -367,17 +371,17 @@ define([ if (typeof centralBody._northPoleCommand.vertexArray === 'undefined') { centralBody._northPoleCommand.boundingVolume = BoundingSphere.fromExtent3D(extent, centralBody._ellipsoid); - mesh = { + geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : positions - } + }) } - }; - centralBody._northPoleCommand.vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, + }); + centralBody._northPoleCommand.vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : { position : 0 }, @@ -385,7 +389,7 @@ define([ }); } else { datatype = ComponentDatatype.FLOAT; - centralBody._northPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.toTypedArray(positions)); + centralBody._northPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.createTypedArray(positions)); } } } @@ -415,17 +419,17 @@ define([ if (typeof centralBody._southPoleCommand.vertexArray === 'undefined') { centralBody._southPoleCommand.boundingVolume = BoundingSphere.fromExtent3D(extent, centralBody._ellipsoid); - mesh = { + geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : positions - } + }) } - }; - centralBody._southPoleCommand.vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, + }); + centralBody._southPoleCommand.vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : { position : 0 }, @@ -433,7 +437,7 @@ define([ }); } else { datatype = ComponentDatatype.FLOAT; - centralBody._southPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.toTypedArray(positions)); + centralBody._southPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.createTypedArray(positions)); } } } @@ -491,7 +495,7 @@ define([ if (this._mode !== mode || typeof this._rsColor === 'undefined') { modeChanged = true; - if (mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) { + if (mode === SceneMode.SCENE3D || (mode === SceneMode.COLUMBUS_VIEW && !(this.terrainProvider instanceof EllipsoidTerrainProvider))) { this._rsColor = context.createRenderState({ // Write color and depth cull : { enabled : true @@ -547,21 +551,19 @@ define([ // depth plane if (!this._depthCommand.vertexArray) { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : depthQuad - } + }) }, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : [0, 1, 2, 2, 1, 3] - }] - }; - this._depthCommand.vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, + indices : [0, 1, 2, 2, 1, 3], + primitiveType : PrimitiveType.TRIANGLES + }); + this._depthCommand.vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : { position : 0 }, @@ -569,7 +571,7 @@ define([ }); } else { var datatype = ComponentDatatype.FLOAT; - this._depthCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.toTypedArray(depthQuad)); + this._depthCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(datatype.createTypedArray(depthQuad)); } var shaderCache = context.getShaderCache(); diff --git a/Source/Scene/CentralBodySurface.js b/Source/Scene/CentralBodySurface.js index b5e5248107d1..dc47cb7de0f3 100644 --- a/Source/Scene/CentralBodySurface.js +++ b/Source/Scene/CentralBodySurface.js @@ -6,13 +6,13 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', - '../Core/CubeMapEllipsoidTessellator', + '../Core/EllipsoidGeometry', '../Core/DeveloperError', '../Core/Ellipsoid', '../Core/EllipsoidalOccluder', '../Core/Intersect', '../Core/Matrix4', - '../Core/MeshFilters', + '../Core/GeometryPipeline', '../Core/PrimitiveType', '../Core/Queue', '../Core/WebMercatorProjection', @@ -30,13 +30,13 @@ define([ Cartesian2, Cartesian3, Cartesian4, - CubeMapEllipsoidTessellator, + EllipsoidGeometry, DeveloperError, Ellipsoid, EllipsoidalOccluder, Intersect, Matrix4, - MeshFilters, + GeometryPipeline, PrimitiveType, Queue, WebMercatorProjection, @@ -708,11 +708,14 @@ define([ if (typeof surface._debug !== 'undefined' && typeof surface._debug.boundingSphereTile !== 'undefined') { if (!surface._debug.boundingSphereVA) { var radius = surface._debug.boundingSphereTile.boundingSphere3D.radius; - var sphere = CubeMapEllipsoidTessellator.compute(new Ellipsoid(radius, radius, radius), 10); - MeshFilters.toWireframeInPlace(sphere); - surface._debug.boundingSphereVA = context.createVertexArrayFromMesh({ - mesh : sphere, - attributeIndices : MeshFilters.createAttributeIndices(sphere) + var sphere = new EllipsoidGeometry({ + radii : new Cartesian3(radius, radius, radius), + numberOfPartitions : 10 + }); + GeometryPipeline.toWireframe(sphere); + surface._debug.boundingSphereVA = context.createVertexArrayFromGeometry({ + geometry : sphere, + attributeIndices : GeometryPipeline.createAttributeIndices(sphere) }); } diff --git a/Source/Scene/CompositePrimitive.js b/Source/Scene/CompositePrimitive.js index f7b978dc4af8..09d4a5633af2 100644 --- a/Source/Scene/CompositePrimitive.js +++ b/Source/Scene/CompositePrimitive.js @@ -400,8 +400,7 @@ define([ var primitives = this._primitives; var length = primitives.length; for (var i = 0; i < length; ++i) { - var primitive = primitives[i]; - primitive.update(context, frameState, commandList); + primitives[i].update(context, frameState, commandList); } }; diff --git a/Source/Scene/CustomSensorVolume.js b/Source/Scene/CustomSensorVolume.js index a671a8345c51..ea0dd587b079 100644 --- a/Source/Scene/CustomSensorVolume.js +++ b/Source/Scene/CustomSensorVolume.js @@ -80,9 +80,10 @@ define([ this.show = defaultValue(options.show, true); /** - * When true, a polyline is shown where the sensor outline intersections the central body. The default is true. + * When true, a polyline is shown where the sensor outline intersections the central body. * * @type {Boolean} + * * @default true * * @see CustomSensorVolume#intersectionColor @@ -94,9 +95,6 @@ define([ * Determines if a sensor intersecting the ellipsoid is drawn through the ellipsoid and potentially out * to the other side, or if the part of the sensor intersecting the ellipsoid stops at the ellipsoid. *

- *

- * The default is false, meaning the sensor will not go through the ellipsoid. - *

* * @type {Boolean} * @default false diff --git a/Source/Scene/DebugAppearance.js b/Source/Scene/DebugAppearance.js new file mode 100644 index 000000000000..e7174f5c363d --- /dev/null +++ b/Source/Scene/DebugAppearance.js @@ -0,0 +1,170 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/DeveloperError', + './Appearance' + ], function( + defaultValue, + DeveloperError, + Appearance) { + "use strict"; + + /** + * Visualizes a vertex attribute by displaying it as a color for debugging. + *

+ * Components for well-known unit-length vectors, i.e., normal, + * binormal, and tangent, are scaled and biased + * from [-1.0, 1.0] to (-1.0, 1.0). + *

+ * + * @alias DebugAppearance + * @constructor + * + * @param {String} options.attributeName The name of the attribute to visualize. + * @param {String} [options.glslDatatype='vec3'] The GLSL datatype of the attribute. Supported datatypes are float, vec2, vec3, and vec4. + * @param {String} [options.vertexShaderSource=undefined] Optional GLSL vertex shader source to override the default vertex shader. + * @param {String} [options.fragmentShaderSource=undefined] Optional GLSL fragment shader source to override the default fragment shader. + * @param {RenderState} [options.renderState=undefined] Optional render state to override the default render state. + * + * @exception {DeveloperError} options.attributeName is required. + * @exception {DeveloperError} options.glslDatatype must be float, vec2, vec3, or vec4. + * + * @example + * var primitive = new Primitive({ + * geometryInstances : // ... + * appearance : new DebugAppearance({ + * attributeName : 'normal' + * }) + * }); + */ + var DebugAppearance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var attributeName = options.attributeName; + + if (typeof attributeName === 'undefined') { + throw new DeveloperError('options.attributeName is required.'); + } + + var glslDatatype = defaultValue(options.glslDatatype, 'vec3'); + var varyingName = 'v_' + attributeName; + var getColor; + + // Well-known normalized vector attributes in VertexFormat + if ((attributeName === 'normal') || (attributeName === 'binormal') | (attributeName === 'tangent')) { + getColor = 'vec4 getColor() { return vec4((' + varyingName + ' + vec3(1.0)) * 0.5, 1.0); }\n'; + } else { + // All other attributes, both well-known and custom + if (attributeName === 'st') { + glslDatatype = 'vec2'; + } + + switch(glslDatatype) { + case 'float': + getColor = 'vec4 getColor() { return vec4(vec3(' + varyingName + '), 1.0); }\n'; + break; + case 'vec2': + getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 0.0, 1.0); }\n'; + break; + case 'vec3': + getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 1.0); }\n'; + break; + case 'vec4': + getColor = 'vec4 getColor() { return ' + varyingName + '; }\n'; + break; + default: + throw new DeveloperError('options.glslDatatype must be float, vec2, vec3, or vec4.'); + } + } + + var vs = + 'attribute vec3 position3DHigh;\n' + + 'attribute vec3 position3DLow;\n' + + 'attribute ' + glslDatatype + ' ' + attributeName + ';\n' + + 'varying ' + glslDatatype + ' ' + varyingName + ';\n' + + 'void main()\n' + + '{\n' + + 'vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' + + varyingName + ' = ' + attributeName + ';\n' + + 'gl_Position = czm_modelViewProjectionRelativeToEye * p;\n' + + '}'; + var fs = + 'varying ' + glslDatatype + ' ' + varyingName + ';\n' + + getColor + '\n' + + 'void main()\n' + + '{\n' + + 'gl_FragColor = getColor();\n' + + '}'; + + /** + * This property is part of the {@link Appearance} interface, but is not + * used by {@link DebugAppearance} since a fully custom fragment shader is used. + * + * @type Material + * + * @default undefined + */ + this.material = undefined; + + /** + * The GLSL source code for the vertex shader. + * + * @type String + * + * @readonly + */ + this.vertexShaderSource = defaultValue(options.vertexShaderSource, vs); + + /** + * The GLSL source code for the fragment shader. + * + * @type String + * + * @readonly + */ + this.fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs); + + /** + * The render state. This is not the final {@link RenderState} instance; instead, + * it can contain a subset of render state properties identical to renderState + * passed to {@link Context#createRenderState}. + * + * @type Object + * + * @readonly + */ + this.renderState = defaultValue(options.renderState, Appearance.getDefaultRenderState(false, false)); + + // Non-derived members + + /** + * The name of the attribute being visualized. + * + * @type String + * + * @readonly + */ + this.attributeName = attributeName; + + /** + * The GLSL datatype of the attribute being visualized. + * + * @type String + * + * @readonly + */ + this.glslDatatype = glslDatatype; + }; + + /** + * Returns the full GLSL fragment shader source, which for {@link DebugAppearance} is just + * {@link DebugAppearance#fragmentShaderSource}. + * + * @memberof DebugAppearance + * + * @return String The full GLSL fragment shader source. + */ + DebugAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + + return DebugAppearance; +}); \ No newline at end of file diff --git a/Source/Scene/EllipsoidPrimitive.js b/Source/Scene/EllipsoidPrimitive.js index 6c91e314244d..a4644e5c275a 100644 --- a/Source/Scene/EllipsoidPrimitive.js +++ b/Source/Scene/EllipsoidPrimitive.js @@ -1,7 +1,8 @@ /*global define*/ define([ - '../Core/BoxTessellator', + '../Core/BoxGeometry', '../Core/Cartesian3', + '../Core/Cartesian4', '../Core/combine', '../Core/DeveloperError', '../Core/destroyObject', @@ -19,8 +20,9 @@ define([ '../Shaders/EllipsoidVS', '../Shaders/EllipsoidFS' ], function( - BoxTessellator, + BoxGeometry, Cartesian3, + Cartesian4, combine, DeveloperError, destroyObject, @@ -113,13 +115,12 @@ define([ * Local reference frames can be used by providing a different transformation matrix, like that returned * by {@link Transforms.eastNorthUpToFixedFrame}. This matrix is available to GLSL vertex and fragment * shaders via {@link czm_model} and derived uniforms. - *

- * The default is {@link Matrix4.IDENTITY}. - *

* * @type {Matrix4} * @default {@link Matrix4.IDENTITY} * + * @default Matrix4.IDENTITY + * * @example * var origin = ellipsoid.cartographicToCartesian( * Cartographic.fromDegrees(-95.0, 40.0, 200000.0)); @@ -133,9 +134,6 @@ define([ /** * Determines if the ellipsoid primitive will be shown. - *

- * The default is true. - *

* * @type {Boolean} * @default true @@ -199,12 +197,12 @@ define([ return vertexArray; } - var mesh = BoxTessellator.compute({ + var geometry = BoxGeometry.fromDimensions({ dimensions : new Cartesian3(2.0, 2.0, 2.0) }); - vertexArray = context.createVertexArrayFromMesh({ - mesh: mesh, + vertexArray = context.createVertexArrayFromGeometry({ + geometry: geometry, attributeIndices: attributeIndices, bufferUsage: BufferUsage.STATIC_DRAW }); diff --git a/Source/Scene/EllipsoidSurfaceAppearance.js b/Source/Scene/EllipsoidSurfaceAppearance.js new file mode 100644 index 000000000000..37a207f96748 --- /dev/null +++ b/Source/Scene/EllipsoidSurfaceAppearance.js @@ -0,0 +1,189 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/VertexFormat', + './Material', + './Appearance', + './MaterialAppearance', + '../Shaders/Appearances/EllipsoidSurfaceAppearanceVS', + '../Shaders/Appearances/EllipsoidSurfaceAppearanceFS' + ], function( + defaultValue, + VertexFormat, + Material, + Appearance, + MaterialAppearance, + EllipsoidSurfaceAppearanceVS, + EllipsoidSurfaceAppearanceFS) { + "use strict"; + + /** + * An appearance for geometry on the surface of the ellipsoid like {@link PolygonGeometry} + * and {@link ExtentGeometry}, which supports all materials like {@link MaterialAppearance} + * with {@link MaterialAppearance.MaterialSupport.ALL}. However, this appearance requires + * fewer vertex attributes since the fragment shader can procedurally compute normal, + * binormal, and tangent. + * + * @alias EllipsoidSurfaceAppearance + * @constructor + * + * @param {Boolean} [options.flat=false] When true, flat shading is used in the fragment shader, which means lighting is not taking into account. + * @param {Boolean} [options.faceForward=false] When true, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}. + * @param {Boolean} [options.translucent=true] When true, the geometry is expected to appear translucent so {@link EllipsoidSurfaceAppearance#renderState} has alpha blending enabled. + * @param {Boolean} [options.aboveGround=false] When true, the geometry is expected to be on the ellipsoid's surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState} has backface culling enabled. + * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color. + * @param {String} [options.vertexShaderSource=undefined] Optional GLSL vertex shader source to override the default vertex shader. + * @param {String} [options.fragmentShaderSource=undefined] Optional GLSL fragment shader source to override the default fragment shader. + * @param {RenderState} [options.renderState=undefined] Optional render state to override the default render state. + * + * @example + * var primitive = new Primitive({ + * geometryInstances : new GeometryInstance({ + * geometry : new PolygonGeometry({ + * vertexFormat : EllipsoidSurfaceAppearance.VERTEX_FORMAT, + * // ... + * }) + * }), + * appearance : new EllipsoidSurfaceAppearance({ + * material : Material.fromType(scene.getContext(), 'Stripe') + * }) + * }); + * + * @see Fabric + */ + var EllipsoidSurfaceAppearance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var translucent = defaultValue(options.translucent, true); + var aboveGround = defaultValue(options.aboveGround, false); + + /** + * The material used to determine the fragment color. Unlike other {@link EllipsoidSurfaceAppearance} + * properties, this is not read-only, so an appearance's material can change on the fly. + * + * @type Material + * + * @default Material.ColorType + * + * @see Fabric + */ + this.material = (typeof options.material !== 'undefined') ? options.material : Material.fromType(undefined, Material.ColorType); + + /** + * The GLSL source code for the vertex shader. + * + * @type String + * + * @readonly + */ + this.vertexShaderSource = defaultValue(options.vertexShaderSource, EllipsoidSurfaceAppearanceVS); + + /** + * The GLSL source code for the fragment shader. The full fragment shader + * source is built procedurally taking into account {@link EllipsoidSurfaceAppearance#material}, + * {@link EllipsoidSurfaceAppearance#flat}, and {@link EllipsoidSurfaceAppearance#faceForward}. + * Use {@link EllipsoidSurfaceAppearance#getFragmentShaderSource} to get the full source. + * + * @type String + * + * @readonly + */ + this.fragmentShaderSource = defaultValue(options.fragmentShaderSource, EllipsoidSurfaceAppearanceFS); + + /** + * The render state. This is not the final {@link RenderState} instance; instead, + * it can contain a subset of render state properties identical to renderState + * passed to {@link Context#createRenderState}. + *

+ * The render state can be explicitly defined when constructing a {@link EllipsoidSurfaceAppearance} + * instance, or it is set implicitly via {@link EllipsoidSurfaceAppearance#translucent} + * and {@link EllipsoidSurfaceAppearance#aboveGround}. + *

+ * + * @type Object + * + * @readonly + */ + this.renderState = defaultValue(options.renderState, Appearance.getDefaultRenderState(translucent, !aboveGround)); + + // Non-derived members + + /** + * The {@link VertexFormat} that this appearance instance is compatible with. + * A geometry can have more vertex attributes and still be compatible - at a + * potential performance cost - but it can't have less. + * + * @type VertexFormat + * + * @readonly + */ + this.vertexFormat = EllipsoidSurfaceAppearance.VERTEX_FORMAT; + + /** + * When true, flat shading is used in the fragment shader, + * which means lighting is not taking into account. + * + * @readonly + * + * @default false + */ + this.flat = defaultValue(options.flat, false); + + /** + * When true, the fragment shader flips the surface normal + * as needed to ensure that the normal faces the viewer to avoid + * dark spots. This is useful when both sides of a geometry should be + * shaded like {@link WallGeometry}. + * + * @readonly + * + * @default false + */ + this.faceForward = defaultValue(options.faceForward, false); + + /** + * When true, the geometry is expected to appear translucent so + * {@link EllipsoidSurfaceAppearance#renderState} has alpha blending enabled. + * + * @readonly + * + * @default true + */ + this.translucent = translucent; + + /** + * When true, the geometry is expected to be on the ellipsoid's + * surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState} + * has backface culling enabled. + * + * @readonly + * + * @default false + */ + this.aboveGround = aboveGround; + }; + + /** + * The {@link VertexFormat} that all {@link EllipsoidSurfaceAppearance} instances + * are compatible with, which requires only position and st + * attributes. Other attributes are procedurally computed in the fragment shader. + * + * @type VertexFormat + * + * @constant + */ + EllipsoidSurfaceAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_ST; + + /** + * Procedurally creates the full GLSL fragment shader source. For {@link PerInstanceColorAppearance}, + * this is derived from {@link PerInstanceColorAppearance#fragmentShaderSource}, {@link PerInstanceColorAppearance#flat}, + * and {@link PerInstanceColorAppearance#faceForward}. + * + * @memberof EllipsoidSurfaceAppearance + * + * @return String The full GLSL fragment shader source. + */ + EllipsoidSurfaceAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + + return EllipsoidSurfaceAppearance; +}); \ No newline at end of file diff --git a/Source/Scene/ExtentPrimitive.js b/Source/Scene/ExtentPrimitive.js new file mode 100644 index 000000000000..ce1032c3132a --- /dev/null +++ b/Source/Scene/ExtentPrimitive.js @@ -0,0 +1,242 @@ +/*global define*/ +define([ + '../Core/DeveloperError', + '../Core/defaultValue', + '../Core/Color', + '../Core/destroyObject', + '../Core/Math', + '../Core/Extent', + '../Core/Ellipsoid', + '../Core/GeometryInstance', + '../Core/ExtentGeometry', + './EllipsoidSurfaceAppearance', + './Primitive', + './Material' + ], function( + DeveloperError, + defaultValue, + Color, + destroyObject, + CesiumMath, + Extent, + Ellipsoid, + GeometryInstance, + ExtentGeometry, + EllipsoidSurfaceAppearance, + Primitive, + Material) { + "use strict"; + + /** + * A renderable rectangular extent. + * + * @alias ExtentPrimitive + * @constructor + * + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that the extent is drawn on. + * @param {Extent} [extent=undefined] The extent, which defines the rectangular region to draw. + * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude in the underlying geometry. + * @param {Number} [height=0.0] The height, in meters, that the extent is raised above the {@link ExtentPrimitive#ellipsoid}. + * @param {Number} [rotation=0.0] The angle, in radians, relative to north that the extent is rotated. Positive angles rotate counter-clockwise. + * @param {Boolean} [show=true] Determines if this primitive will be shown. + * @param {Material} [material=undefined] The surface appearance of the primitive. + * + * @example + * var extentPrimitive = new ExtentPrimitive({ + * extent : Extent.fromDegrees(0.0, 20.0, 10.0, 30.0) + * }); + * primitives.add(extentPrimitive); + */ + var ExtentPrimitive = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The ellipsoid that the extent is drawn on. + * + * @type Ellipsoid + * + * @default Ellipsoid.WGS84 + */ + this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._ellipsoid = undefined; + + /** + * The extent, which defines the rectangular region to draw. + * + * @type Extent + * + * @default undefined + */ + this.extent = Extent.clone(options.extent); + this._extent = undefined; + + /** + * The distance, in radians, between each latitude and longitude in the underlying geometry. + * A lower granularity fits the curvature of the {@link ExtentPrimitive#ellipsoid} better, + * but uses more triangles. + * + * @type Number + * + * @default CesiumMath.RADIANS_PER_DEGREE + */ + this.granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._granularity = undefined; + + /** + * The height, in meters, that the extent is raised above the {@link ExtentPrimitive#ellipsoid}. + * + * @type Number + * + * @default 0.0 + */ + this.height = defaultValue(options.height, 0.0); + this._height = undefined; + + /** + * The angle, in radians, relative to north that the extent is rotated. + * Positive angles rotate counter-clockwise. + * + * @type Number + * + * @default 0.0 + */ + this.rotation = defaultValue(options.rotation, 0.0); + this._rotation = undefined; + + /** + * Determines if this primitive will be shown. + * + * @type Boolean + * + * @default true + */ + this.show = defaultValue(options.show, true); + + var material = Material.fromType(undefined, Material.ColorType); + material.uniforms.color = new Color(1.0, 1.0, 0.0, 0.5); + + /** + * The surface appearance of the primitive. This can be one of several built-in {@link Material} objects or a custom material, scripted with + * Fabric. + *

+ * The default material is Material.ColorType. + *

+ * + * @type Material + * + * @example + * // 1. Change the color of the default material to yellow + * extent.material.uniforms.color = new Color(1.0, 1.0, 0.0, 1.0); + * + * // 2. Change material to horizontal stripes + * extent.material = Material.fromType(scene.getContext(), Material.StripeType); + * + * @see Fabric + */ + this.material = defaultValue(options.material, material); + + this._primitive = undefined; + }; + + /** + * @private + */ + ExtentPrimitive.prototype.update = function(context, frameState, commandList) { + if (typeof this.ellipsoid === 'undefined') { + throw new DeveloperError('this.ellipsoid must be defined.'); + } + + if (typeof this.material === 'undefined') { + throw new DeveloperError('this.material must be defined.'); + } + + if (this.granularity < 0.0) { + throw new DeveloperError('this.granularity and scene2D/scene3D overrides must be greater than zero.'); + } + + if (!this.show || (typeof this.extent === 'undefined')) { + return; + } + + if (!Extent.equals(this._extent, this.extent) || + (this._ellipsoid !== this.ellipsoid) || + (this._granularity !== this.granularity) || + (this._height !== this.height) || + (this._rotation !== this.rotation)) { + + this._extent = Extent.clone(this.extent, this._extent); + this._ellipsoid = this.ellipsoid; + this._granularity = this.granularity; + this._height = this.height; + this._rotation = this.rotation; + + var instance = new GeometryInstance({ + geometry : new ExtentGeometry({ + extent : this.extent, + vertexFormat : EllipsoidSurfaceAppearance.VERTEX_FORMAT, + ellipsoid : this.ellipsoid, + granularity : this.granularity, + height : this.height, + rotation : this.rotation + }), + id : this + }); + + if (typeof this._primitive !== 'undefined') { + this._primitive.destroy(); + } + + this._primitive = new Primitive({ + geometryInstances : instance, + appearance : new EllipsoidSurfaceAppearance({ + aboveGround : (this.height > 0.0) + }) + }); + } + + this._primitive.appearance.material = this.material; + this._primitive.update(context, frameState, commandList); + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + * + * @memberof Extent + * + * @return {Boolean} true if this object was destroyed; otherwise, false. + * + * @see Extent#destroy + */ + ExtentPrimitive.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + * + * @memberof Extent + * + * @return {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @see Extent#isDestroyed + * + * @example + * extent = extent && extent.destroy(); + */ + ExtentPrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + return destroyObject(this); + }; + + return ExtentPrimitive; +}); diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 05909e335b9e..c02cdba68f95 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -10,6 +10,8 @@ define([ '../Core/Extent', '../Core/Math', '../Core/PrimitiveType', + '../Core/Geometry', + '../Core/GeometryAttribute', '../Renderer/BufferUsage', '../Renderer/MipmapHint', '../Renderer/TextureMagnificationFilter', @@ -37,6 +39,8 @@ define([ Extent, CesiumMath, PrimitiveType, + Geometry, + GeometryAttribute, BufferUsage, MipmapHint, TextureMagnificationFilter, @@ -814,26 +818,24 @@ define([ } } - var reprojectMesh = { + var reprojectGeometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : positions - } + }) }, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : TerrainProvider.getRegularGridIndices(256, 256) - }] - }; + indices : TerrainProvider.getRegularGridIndices(256, 256), + primitiveType : PrimitiveType.TRIANGLES + }); var reprojectAttribInds = { position : 0 }; - reproject.vertexArray = context.createVertexArrayFromMesh({ - mesh : reprojectMesh, + reproject.vertexArray = context.createVertexArrayFromGeometry({ + geometry : reprojectGeometry, attributeIndices : reprojectAttribInds, bufferUsage : BufferUsage.STATIC_DRAW }); diff --git a/Source/Scene/MaterialAppearance.js b/Source/Scene/MaterialAppearance.js new file mode 100644 index 000000000000..9126016e5039 --- /dev/null +++ b/Source/Scene/MaterialAppearance.js @@ -0,0 +1,244 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/freezeObject', + '../Core/VertexFormat', + './Material', + './Appearance', + '../Shaders/Appearances/BasicMaterialAppearanceVS', + '../Shaders/Appearances/BasicMaterialAppearanceFS', + '../Shaders/Appearances/TexturedMaterialAppearanceVS', + '../Shaders/Appearances/TexturedMaterialAppearanceFS', + '../Shaders/Appearances/AllMaterialAppearanceVS', + '../Shaders/Appearances/AllMaterialAppearanceFS' + ], function( + defaultValue, + freezeObject, + VertexFormat, + Material, + Appearance, + BasicMaterialAppearanceVS, + BasicMaterialAppearanceFS, + TexturedMaterialAppearanceVS, + TexturedMaterialAppearanceFS, + AllMaterialAppearanceVS, + AllMaterialAppearanceFS) { + "use strict"; + + /** + * An appearance for arbitrary geometry (as opposed to {@link EllipsoidSurfaceAppearance}, for example) + * that supports shading with materials. + * + * @alias MaterialAppearance + * @constructor + * + * @param {Boolean} [options.flat=false] When true, flat shading is used in the fragment shader, which means lighting is not taking into account. + * @param {Boolean} [options.faceForward=false] When true, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}. + * @param {Boolean} [options.translucent=true] When true, the geometry is expected to appear translucent so {@link MaterialAppearance#renderState} has alpha blending enabled. + * @param {Boolean} [options.closed=false] When true, the geometry is expected to be closed so {@link MaterialAppearance#renderState} has backface culling enabled. + * @param {MaterialAppearance.MaterialSupport} [options.materialSupport=MaterialAppearance.MaterialSupport.TEXTURED] The type of materials that will be supported. + * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color. + * @param {String} [options.vertexShaderSource=undefined] Optional GLSL vertex shader source to override the default vertex shader. + * @param {String} [options.fragmentShaderSource=undefined] Optional GLSL fragment shader source to override the default fragment shader. + * @param {RenderState} [options.renderState=undefined] Optional render state to override the default render state. + * + * @example + * var primitive = new Primitive({ + * geometryInstances : new GeometryInstance({ + * geometry : new WallGeometry({ + materialSupport : MaterialAppearance.MaterialSupport.BASIC.vertexFormat, + * // ... + * }) + * }), + * appearance : new MaterialAppearance({ + * material : Material.fromType(scene.getContext(), 'Color'), + * faceForward : true + * }) + * }); + * + * @see Fabric + */ + var MaterialAppearance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var translucent = defaultValue(options.translucent, true); + var closed = defaultValue(options.closed, false); + var materialSupport = defaultValue(options.materialSupport, MaterialAppearance.MaterialSupport.TEXTURED); + + /** + * The material used to determine the fragment color. Unlike other {@link MaterialAppearance} + * properties, this is not read-only, so an appearance's material can change on the fly. + * + * @type Material + * + * @default Material.ColorType + * + * @see Fabric + */ + this.material = (typeof options.material !== 'undefined') ? options.material : Material.fromType(undefined, Material.ColorType); + + /** + * The GLSL source code for the vertex shader. + * + * @type String + * + * @readonly + */ + this.vertexShaderSource = defaultValue(options.vertexShaderSource, materialSupport.vertexShaderSource); + + /** + * The GLSL source code for the fragment shader. The full fragment shader + * source is built procedurally taking into account {@link MaterialAppearance#material}, + * {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}. + * Use {@link MaterialAppearance#getFragmentShaderSource} to get the full source. + * + * @type String + * + * @readonly + */ + this.fragmentShaderSource = defaultValue(options.fragmentShaderSource, materialSupport.fragmentShaderSource); + + /** + * The render state. This is not the final {@link RenderState} instance; instead, + * it can contain a subset of render state properties identical to renderState + * passed to {@link Context#createRenderState}. + *

+ * The render state can be explicitly defined when constructing a {@link MaterialAppearance} + * instance, or it is set implicitly via {@link MaterialAppearance#translucent} + * and {@link MaterialAppearance#closed}. + *

+ * + * @type Object + * + * @readonly + */ + this.renderState = defaultValue(options.renderState, Appearance.getDefaultRenderState(translucent, closed)); + + // Non-derived members + + /** + * The type of materials supported by this instance. This impacts the required + * {@link VertexFormat} and the complexity of the vertex and fragment shaders. + * + * @type MaterialAppearance.MaterialSupport + * + * @readonly + */ + this.materialSupport = materialSupport; + + /** + * The {@link VertexFormat} that this appearance instance is compatible with. + * A geometry can have more vertex attributes and still be compatible - at a + * potential performance cost - but it can't have less. + * + * @type VertexFormat + * + * @readonly + */ + this.vertexFormat = materialSupport.vertexFormat; + + /** + * When true, flat shading is used in the fragment shader, + * which means lighting is not taking into account. + * + * @readonly + * + * @default false + */ + this.flat = defaultValue(options.flat, false); + + /** + * When true, the fragment shader flips the surface normal + * as needed to ensure that the normal faces the viewer to avoid + * dark spots. This is useful when both sides of a geometry should be + * shaded like {@link WallGeometry}. + * + * @readonly + * + * @default false + */ + this.faceForward = defaultValue(options.faceForward, false); + + /** + * When true, the geometry is expected to appear translucent so + * {@link MaterialAppearance#renderState} has alpha blending enabled. + * + * @readonly + * + * @default true + */ + this.translucent = translucent; + + /** + * When true, the geometry is expected to be closed so + * {@link MaterialAppearance#renderState} has backface culling enabled. + * If the viewer enters the geometry, it will not be visible. + * + * @readonly + * + * @default false + */ + this.closed = closed; + }; + + /** + * Procedurally creates the full GLSL fragment shader source. For {@link MaterialAppearance}, + * this is derived from {@link MaterialAppearance#fragmentShaderSource}, {@link MaterialAppearance#material}, + * {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}. + * + * @memberof MaterialAppearance + * + * @return String The full GLSL fragment shader source. + */ + MaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + + /** + * Determines the type of {@link Material} that is supported by a + * {@link MaterialAppearance} instance. This is a trade-off between + * flexibility (a wide array of materials) and memory/performance + * (required vertex format and GLSL shader complexity. + * + * @memberof MaterialAppearance + * + * @enumeration + */ + MaterialAppearance.MaterialSupport = { + /** + * Only basic materials, which require just position and + * normal vertex attributes, are supported. + * + * @readonly + */ + BASIC : freezeObject({ + vertexFormat : VertexFormat.POSITION_AND_NORMAL, + vertexShaderSource : BasicMaterialAppearanceVS, + fragmentShaderSource : BasicMaterialAppearanceFS + }), + /** + * Materials with textures, which require position, + * normal, and st vertex attributes, + * are supported. The vast majority of materials fall into this category. + * + * @readonly + */ + TEXTURED : freezeObject({ + vertexFormat : VertexFormat.POSITION_NORMAL_AND_ST, + vertexShaderSource : TexturedMaterialAppearanceVS, + fragmentShaderSource : TexturedMaterialAppearanceFS + }), + /** + * All materials, including those that work in tangent space, are supported. + * This requires position, normal, st, + * binormal, and tangent vertex attributes. + * + * @readonly + */ + ALL : freezeObject({ + vertexFormat : VertexFormat.ALL, + vertexShaderSource : AllMaterialAppearanceVS, + fragmentShaderSource : AllMaterialAppearanceFS + }) + }; + + return MaterialAppearance; +}); \ No newline at end of file diff --git a/Source/Scene/PerInstanceColorAppearance.js b/Source/Scene/PerInstanceColorAppearance.js new file mode 100644 index 000000000000..aac5e3bf0fdc --- /dev/null +++ b/Source/Scene/PerInstanceColorAppearance.js @@ -0,0 +1,222 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/VertexFormat', + './Appearance', + '../Shaders/Appearances/PerInstanceColorAppearanceVS', + '../Shaders/Appearances/PerInstanceColorAppearanceFS', + '../Shaders/Appearances/PerInstanceFlatColorAppearanceVS', + '../Shaders/Appearances/PerInstanceFlatColorAppearanceFS' + ], function( + defaultValue, + VertexFormat, + Appearance, + PerInstanceColorAppearanceVS, + PerInstanceColorAppearanceFS, + PerInstanceFlatColorAppearanceVS, + PerInstanceFlatColorAppearanceFS) { + "use strict"; + + /** + * An appearance for {@link GeometryInstance} instances with color attributes. + * This allows several geometry instances, each with a different color, to + * be drawn with the same {@link Primitive} as shown in the second example below. + * + * @alias PerInstanceColorAppearance + * @constructor + * + * @param {Boolean} [options.flat=false] When true, flat shading is used in the fragment shader, which means lighting is not taking into account. + * @param {Boolean} [options.faceForward=false] When true, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}. + * @param {Boolean} [options.translucent=true] When true, the geometry is expected to appear translucent so {@link PerInstanceColorAppearance#renderState} has alpha blending enabled. + * @param {Boolean} [options.closed=false] When true, the geometry is expected to be closed so {@link PerInstanceColorAppearance#renderState} has backface culling enabled. + * @param {String} [options.vertexShaderSource=undefined] Optional GLSL vertex shader source to override the default vertex shader. + * @param {String} [options.fragmentShaderSource=undefined] Optional GLSL fragment shader source to override the default fragment shader. + * @param {RenderState} [options.renderState=undefined] Optional render state to override the default render state. + * + * @example + * // A solid white line segment + * var primitive = new Primitive({ + * geometryInstances : new GeometryInstance({ + * geometry : new SimplePolylineGeometry({ + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(0.0, 0.0), + * Cartographic.fromDegrees(5.0, 0.0) + * ]) + * }), + * color : new Color(1.0, 1.0, 1.0, 1.0) + * }), + * appearance : new PerInstanceColorAppearance({ + * flat : true, + * translucent : false + * }) + * })); + * + * // Two extents in a primitive, each with a different color + * var instance = new GeometryInstance({ + * geometry : new ExtentGeometry({ + * extent : Extent.fromDegrees(0.0, 20.0, 10.0, 30.0) + * }), + * color : new Color(1.0, 0.0, 0.0, 0.5) + * }); + * + * var anotherInstance = new GeometryInstance({ + * geometry : new ExtentGeometry({ + * extent : Extent.fromDegrees(0.0, 40.0, 10.0, 50.0) + * }), + * color : new Color(0.0, 0.0, 1.0, 0.5) + * }); + * + * var extentPrimitive = new Primitive({ + * geometryInstances : [instance, anotherInstance], + * appearance : new PerInstanceColorAppearance() + * }); + */ + var PerInstanceColorAppearance = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var translucent = defaultValue(options.translucent, true); + var closed = defaultValue(options.closed, false); + var flat = defaultValue(options.flat, false); + var vs = flat ? PerInstanceFlatColorAppearanceVS : PerInstanceColorAppearanceVS; + var fs = flat ? PerInstanceFlatColorAppearanceFS : PerInstanceColorAppearanceFS; + var vertexFormat = flat ? PerInstanceColorAppearance.FLAT_VERTEX_FORMAT : PerInstanceColorAppearance.VERTEX_FORMAT; + + /** + * This property is part of the {@link Appearance} interface, but is not + * used by {@link PerInstanceColorAppearance} since a fully custom fragment shader is used. + * + * @type Material + * + * @default undefined + */ + this.material = undefined; + + /** + * The GLSL source code for the vertex shader. + * + * @type String + * + * @readonly + */ + this.vertexShaderSource = defaultValue(options.vertexShaderSource, vs); + + /** + * The GLSL source code for the fragment shader. The full fragment shader + * source is built procedurally taking into account {@link PerInstanceColorAppearance#flat}, + * and {@link PerInstanceColorAppearance#faceForward}. + * Use {@link PerInstanceColorAppearance#getFragmentShaderSource} to get the full source. + * + * @type String + * + * @readonly + */ + this.fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs); + + /** + * The render state. This is not the final {@link RenderState} instance; instead, + * it can contain a subset of render state properties identical to renderState + * passed to {@link Context#createRenderState}. + *

+ * The render state can be explicitly defined when constructing a {@link PerInstanceColorAppearance} + * instance, or it is set implicitly via {@link PerInstanceColorAppearance#translucent} + * and {@link PerInstanceColorAppearance#closed}. + *

+ * + * @type Object + * + * @readonly + */ + this.renderState = defaultValue(options.renderState, Appearance.getDefaultRenderState(translucent, closed)); + + // Non-derived members + + /** + * The {@link VertexFormat} that this appearance instance is compatible with. + * A geometry can have more vertex attributes and still be compatible - at a + * potential performance cost - but it can't have less. + * + * @type VertexFormat + * + * @readonly + */ + this.vertexFormat = vertexFormat; + + /** + * When true, flat shading is used in the fragment shader, + * which means lighting is not taking into account. + * + * @readonly + * + * @default false + */ + this.flat = flat; + + /** + * When true, the fragment shader flips the surface normal + * as needed to ensure that the normal faces the viewer to avoid + * dark spots. This is useful when both sides of a geometry should be + * shaded like {@link WallGeometry}. + * + * @readonly + * + * @default false + */ + this.faceForward = defaultValue(options.faceForward, false); + + /** + * When true, the geometry is expected to appear translucent so + * {@link PerInstanceColorAppearance#renderState} has alpha blending enabled. + * + * @readonly + * + * @default true + */ + this.translucent = translucent; + + /** + * When true, the geometry is expected to be closed so + * {@link PerInstanceColorAppearance#renderState} has backface culling enabled. + * If the viewer enters the geometry, it will not be visible. + * + * @readonly + * + * @default false + */ + this.closed = closed; + }; + + /** + * The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances + * are compatible with. This requires only position and st + * attributes. + * + * @type VertexFormat + * + * @constant + */ + PerInstanceColorAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_NORMAL; + + /** + * The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances + * are compatible with when {@link PerInstanceColorAppearance#flat} is false. + * This requires only a position attribute. + * + * @type VertexFormat + * + * @constant + */ + PerInstanceColorAppearance.FLAT_VERTEX_FORMAT = VertexFormat.POSITION_ONLY; + + /** + * Procedurally creates the full GLSL fragment shader source. For {@link PerInstanceColorAppearance}, + * this is derived from {@link PerInstanceColorAppearance#fragmentShaderSource}, {@link PerInstanceColorAppearance#flat}, + * and {@link PerInstanceColorAppearance#faceForward}. + * + * @memberof PerInstanceColorAppearance + * + * @return String The full GLSL fragment shader source. + */ + PerInstanceColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + + return PerInstanceColorAppearance; +}); \ No newline at end of file diff --git a/Source/Scene/Polygon.js b/Source/Scene/Polygon.js index ff97853ba7a1..f0da0fa4e74c 100644 --- a/Source/Scene/Polygon.js +++ b/Source/Scene/Polygon.js @@ -3,136 +3,34 @@ define([ '../Core/DeveloperError', '../Core/defaultValue', '../Core/Color', - '../Core/combine', '../Core/destroyObject', - '../Core/Cartesian2', '../Core/Math', '../Core/Ellipsoid', - '../Core/BoundingRectangle', - '../Core/BoundingSphere', - '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/ComponentDatatype', - '../Core/MeshFilters', - '../Core/PrimitiveType', - '../Core/EllipsoidTangentPlane', + '../Core/GeometryInstance', + '../Core/PolygonGeometry', '../Core/PolygonPipeline', - '../Core/WindingOrder', - '../Core/ExtentTessellator', - '../Core/Intersect', '../Core/Queue', - '../Core/Matrix3', - '../Core/Quaternion', - '../Renderer/BlendingState', - '../Renderer/BufferUsage', - '../Renderer/CommandLists', - '../Renderer/CullFace', - '../Renderer/DrawCommand', - '../Renderer/VertexLayout', - '../Renderer/createPickFragmentShaderSource', - './Material', - './SceneMode', - '../Shaders/PolygonVS', - '../Shaders/PolygonFS' + './EllipsoidSurfaceAppearance', + './Primitive', + './Material' ], function( DeveloperError, defaultValue, Color, - combine, destroyObject, - Cartesian2, CesiumMath, Ellipsoid, - BoundingRectangle, - BoundingSphere, - Cartesian3, - Cartesian4, - ComponentDatatype, - MeshFilters, - PrimitiveType, - EllipsoidTangentPlane, + GeometryInstance, + PolygonGeometry, PolygonPipeline, - WindingOrder, - ExtentTessellator, - Intersect, Queue, - Matrix3, - Quaternion, - BlendingState, - BufferUsage, - CommandLists, - CullFace, - DrawCommand, - VertexLayout, - createPickFragmentShaderSource, - Material, - SceneMode, - PolygonVS, - PolygonFS) { + EllipsoidSurfaceAppearance, + Primitive, + Material) { "use strict"; - var attributeIndices = { - position3DHigh : 0, - position3DLow : 1, - position2DHigh : 2, - position2DLow : 3, - textureCoordinates : 4 - }; - - function PositionVertices() { - this._va = undefined; - } - - PositionVertices.prototype.getVertexArrays = function() { - return this._va; - }; - - PositionVertices.prototype.update = function(context, meshes, bufferUsage) { - if (typeof meshes !== 'undefined') { - // Initially create or recreate vertex array and buffers - this._destroyVA(); - - var va = []; - - var length = meshes.length; - for ( var i = 0; i < length; ++i) { - va.push(context.createVertexArrayFromMesh({ - mesh : meshes[i], - attributeIndices : attributeIndices, - bufferUsage : bufferUsage, - vertexLayout : VertexLayout.INTERLEAVED - })); - } - - this._va = va; - } else { - this._destroyVA(); - } - }; - - PositionVertices.prototype._destroyVA = function() { - var va = this._va; - if (typeof va !== 'undefined') { - this._va = undefined; - - var length = va.length; - for ( var i = 0; i < length; ++i) { - va[i].destroy(); - } - } - }; - - PositionVertices.prototype.isDestroyed = function() { - return false; - }; - - PositionVertices.prototype.destroy = function() { - this._destroyVA(); - return destroyObject(this); - }; - /** - * DOC_TBA + * A renderable polygon or hierarchy of polygons. * * @alias Polygon * @constructor @@ -153,91 +51,65 @@ define([ * * @demo Cesium Sandcastle Polygons Demo */ - var Polygon = function() { - this._sp = undefined; - this._rs = undefined; - - this._spPick = undefined; - - this._vertices = new PositionVertices(); - this._pickId = undefined; - - this._boundingVolume = new BoundingSphere(); - this._boundingVolume2D = new BoundingSphere(); - - this._commandLists = new CommandLists(); + var Polygon = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); /** - * DOC_TBA + * The ellipsoid that the polygon is drawn on. + * + * @type Ellipsoid + * + * @default Ellipsoid.WGS84 */ - this.ellipsoid = Ellipsoid.WGS84; + this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); this._ellipsoid = undefined; /** - * DOC_TBA - */ - this.height = 0.0; - this._height = undefined; - - /** - * DOC_TBA + * The distance, in radians, between each latitude and longitude in the underlying geometry. + * A lower granularity fits the curvature of the {@link Polygon#ellipsoid} better, + * but uses more triangles. + * + * @type Number + * + * @default CesiumMath.RADIANS_PER_DEGREE */ - this.granularity = CesiumMath.toRadians(1.0); + this.granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._granularity = undefined; /** - * DOC_TBA + * The height, in meters, that the polygon is raised above the {@link Polygon#ellipsoid}. + * + * @type Number + * + * @default 0.0 */ - this.scene2D = { - /** - * DOC_TBA - */ - granularity : CesiumMath.toRadians(30.0) - }; + this.height = defaultValue(options.height, 0.0); + this._height = undefined; /** - * DOC_TBA - */ - this.scene3D = { - /** - * DOC_TBA + * The angle, in radians, relative to north that the polygon's texture is rotated. + * Positive angles rotate counter-clockwise. + * + * @type Number * - * granularity can override object-level granularity + * @default 0.0 */ - }; - - this._positions = undefined; + this.textureRotationAngle = defaultValue(options.textureRotationAngle, 0.0); this._textureRotationAngle = undefined; - this._extent = undefined; - this._polygonHierarchy = undefined; - this._createVertexArray = false; /** - * Determines if this polygon will be shown. + * Determines if this primitive will be shown. * * @type {Boolean} * @default true */ - this.show = true; + this.show = defaultValue(options.show, true); - /** - * The usage hint for the polygon's vertex buffer. - * - * @type {BufferUsage} - * @default {@link BufferUsage.STATIC_DRAW} - * - * @performance If bufferUsage changes, the next time - * {@link Polygon#update} is called, the polygon's vertex buffer - * is rewritten - an O(n) operation that also incurs CPU to GPU overhead. - * For best performance, it is important to provide the proper usage hint. If the polygon - * will not change over several frames, use BufferUsage.STATIC_DRAW. - * If the polygon will change every frame, use BufferUsage.STREAM_DRAW. - */ - this.bufferUsage = BufferUsage.STATIC_DRAW; - this._bufferUsage = BufferUsage.STATIC_DRAW; + var material = Material.fromType(undefined, Material.ColorType); + material.uniforms.color = new Color(1.0, 1.0, 0.0, 0.5); /** - * The surface appearance of the polygon. This can be one of several built-in {@link Material} objects or a custom material, scripted with + * The surface appearance of the primitive. This can be one of several built-in {@link Material} objects or a custom material, scripted with * Fabric. *

* The default material is Material.ColorType. @@ -255,32 +127,18 @@ define([ * * @see Fabric */ - this.material = Material.fromType(undefined, Material.ColorType); - this.material.uniforms.color = new Color(1.0, 1.0, 0.0, 0.5); - this._material = undefined; - - this._mode = SceneMode.SCENE3D; - this._projection = undefined; + this.material = defaultValue(options.material, material); - var that = this; - this._uniforms = { - u_height : function() { - return (that._mode !== SceneMode.SCENE2D) ? that.height : 0.0; - } - }; + this._positions = options.positions; + this._polygonHierarchy = options.polygonHierarchy; + this._createPrimitive = false; - this._pickColorUniform = { - czm_pickColor : function() { - return that._pickId.color; - } - }; - - this._pickUniforms = undefined; - this._drawUniforms = undefined; + this._primitive = undefined; }; /** - * DOC_TBA + * Returns the positions that define the boundary of the polygon. If {@link Polygon#configureFromPolygonHierarchy} + * was used, this returns undefined. * * @memberof Polygon * @@ -293,7 +151,7 @@ define([ }; /** - * DOC_TBA + * Sets positions that define the boundary of the polygon. * * @memberof Polygon * @@ -303,27 +161,22 @@ define([ * @see Polygon#getPositions * * @param {Array} positions The cartesian positions of the polygon. - * @param {Number} [height=0.0] The height of the polygon. - * @param {Number} [textureRotationAngle=0.0] The angle, in radians, to rotate the texture. Positive angles are counter-clockwise. * * @example * polygon.setPositions([ * ellipsoid.cartographicToCartesian(new Cartographic(...)), * ellipsoid.cartographicToCartesian(new Cartographic(...)), * ellipsoid.cartographicToCartesian(new Cartographic(...)) - * ], 10.0); + * ]); */ - Polygon.prototype.setPositions = function(positions, height, textureRotationAngle) { + Polygon.prototype.setPositions = function(positions) { // positions can be undefined if (typeof positions !== 'undefined' && (positions.length < 3)) { throw new DeveloperError('At least three positions are required.'); } - this.height = defaultValue(height, 0.0); - this._textureRotationAngle = defaultValue(textureRotationAngle, 0.0); - this._extent = undefined; - this._polygonHierarchy = undefined; this._positions = positions; - this._createVertexArray = true; + this._polygonHierarchy = undefined; + this._createPrimitive = true; }; /** @@ -354,312 +207,34 @@ define([ * } * * - * @param {Number} [height=0.0] The height of the polygon. - * @param {Number} [textureRotationAngle=0.0] The angle to rotate the texture in radians. * - * @exception {DeveloperError} At least three positions are required. + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @example * // A triangle within a triangle * var hierarchy = { - * positions : [new Cartesian3(-634066.5629045101,-4608738.034138676,4348640.761750969), - * new Cartesian3(-1321523.0597310204,-5108871.981065817,3570395.2500986718), - * new Cartesian3(46839.74837473363,-5303481.972379478,3530933.5841716)], - * holes : [{ - * positions :[new Cartesian3(-646079.44483647,-4811233.11175887,4123187.2266941597), - * new Cartesian3(-1024015.4454943262,-5072141.413164587,3716492.6173834214), - * new Cartesian3(-234678.22583880965,-5189078.820849883,3688809.059214336)] - * }] - * }; + * positions : [ + * new Cartesian3(-634066.5629045101, -4608738.034138676, 4348640.761750969), + * new Cartesian3(-1321523.0597310204, -5108871.981065817, 3570395.2500986718), + * new Cartesian3(46839.74837473363, -5303481.972379478, 3530933.5841716) + * ], + * holes : [{ + * positions :[ + * new Cartesian3(-646079.44483647, -4811233.11175887, 4123187.2266941597), + * new Cartesian3(-1024015.4454943262, -5072141.413164587, 3716492.6173834214), + * new Cartesian3(-234678.22583880965, -5189078.820849883, 3688809.059214336) + * ] + * }] + * }; */ - Polygon.prototype.configureFromPolygonHierarchy = function(hierarchy, height, textureRotationAngle) { - // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf - var polygons = []; - var queue = new Queue(); - queue.enqueue(hierarchy); - - while (queue.length !== 0) { - var outerNode = queue.dequeue(); - var outerRing = outerNode.positions; - - if (outerRing.length < 3) { - throw new DeveloperError('At least three positions are required.'); - } - - var numChildren = outerNode.holes ? outerNode.holes.length : 0; - if (numChildren === 0) { - // The outer polygon is a simple polygon with no nested inner polygon. - polygons.push(outerNode.positions); - } else { - // The outer polygon contains inner polygons - var holes = []; - for ( var i = 0; i < numChildren; i++) { - var hole = outerNode.holes[i]; - holes.push(hole.positions); - - var numGrandchildren = 0; - if (hole.holes) { - numGrandchildren = hole.holes.length; - } - - for ( var j = 0; j < numGrandchildren; j++) { - queue.enqueue(hole.holes[j]); - } - } - var combinedPolygon = PolygonPipeline.eliminateHoles(outerRing, holes); - polygons.push(combinedPolygon); - } - } - - this.height = defaultValue(height, 0.0); - this._textureRotationAngle = defaultValue(textureRotationAngle, 0.0); + Polygon.prototype.configureFromPolygonHierarchy = function(hierarchy) { this._positions = undefined; - this._extent = undefined; - this._polygonHierarchy = polygons; - this._createVertexArray = true; + this._polygonHierarchy = hierarchy; + this._createPrimitive = true; }; /** - * DOC_TBA - * - * @memberof Polygon - * - * @param {extent} extent. The cartographic extent of the tile, with north, south, east and - * west properties in radians. - * - * @param {double} [height=0.0]. The height of the cartographic extent. - * @param {double} [rotation=0.0]. The rotation of the cartographic extent. - * @example - * polygon.configureExtent(new Extent( - * CesiumMath.toRadians(0.0), - * CesiumMath.toRadians(0.0), - * CesiumMath.toRadians(10.0), - * CesiumMath.toRadians(10.0)), - * 0.0, - * CesiumMath.toRadians(45.0), - * ); - */ - Polygon.prototype.configureExtent = function(extent, height, rotation) { - this._extent = extent; - this.height = defaultValue(height, 0.0); - this.rotation = defaultValue(rotation, 0.0); - this._textureRotationAngle = undefined; - this._positions = undefined; - this._polygonHierarchy = undefined; - this._createVertexArray = true; - }; - - var appendTextureCoordinatesCartesian2 = new Cartesian2(); - var appendTextureCoordinatesCartesian3 = new Cartesian3(); - var appendTextureCoordinatesQuaternion = new Quaternion(); - var appendTextureCoordinatesMatrix3 = new Matrix3(); - - function appendTextureCoordinates(tangentPlane, boundingRectangle, mesh, angle) { - var origin = new Cartesian2(boundingRectangle.x, boundingRectangle.y); - - var positions = mesh.attributes.position.values; - var length = positions.length; - - var textureCoordinates = new Float32Array(2 * (length / 3)); - var j = 0; - - var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, angle, appendTextureCoordinatesQuaternion); - var textureMatrix = Matrix3.fromQuaternion(rotation, appendTextureCoordinatesMatrix3); - - // PERFORMANCE_IDEA: Instead of storing texture coordinates per-vertex, we could - // save memory by computing them in the fragment shader. However, projecting - // the point onto the plane may have precision issues. - for ( var i = 0; i < length; i += 3) { - var p = appendTextureCoordinatesCartesian3; - p.x = positions[i]; - p.y = positions[i + 1]; - p.z = positions[i + 2]; - Matrix3.multiplyByVector(textureMatrix, p, p); - var st = tangentPlane.projectPointOntoPlane(p, appendTextureCoordinatesCartesian2); - st.subtract(origin, st); - - textureCoordinates[j++] = st.x / boundingRectangle.width; - textureCoordinates[j++] = st.y / boundingRectangle.height; - } - - mesh.attributes.textureCoordinates = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : textureCoordinates - }; - - return mesh; - } - - var computeBoundingRectangleCartesian2 = new Cartesian2(); - var computeBoundingRectangleCartesian3 = new Cartesian3(); - var computeBoundingRectangleQuaternion = new Quaternion(); - var computeBoundingRectangleMatrix3 = new Matrix3(); - - function computeBoundingRectangle(tangentPlane, positions, angle, result) { - var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, angle, computeBoundingRectangleQuaternion); - var textureMatrix = Matrix3.fromQuaternion(rotation,computeBoundingRectangleMatrix3); - - var minX = Number.POSITIVE_INFINITY; - var maxX = Number.NEGATIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxY = Number.NEGATIVE_INFINITY; - - var length = positions.length; - for ( var i = 0; i < length; ++i) { - var p = Cartesian3.clone(positions[i], computeBoundingRectangleCartesian3); - Matrix3.multiplyByVector(textureMatrix, p, p); - var st = tangentPlane.projectPointOntoPlane(p, computeBoundingRectangleCartesian2); - - if (typeof st !== 'undefined') { - minX = Math.min(minX, st.x); - maxX = Math.max(maxX, st.x); - - minY = Math.min(minY, st.y); - maxY = Math.max(maxY, st.y); - } - } - - if (typeof result === 'undefined') { - result = new BoundingRectangle(); - } - - result.x = minX; - result.y = minY; - result.width = maxX - minX; - result.height = maxY - minY; - return result; - } - - var createMeshFromPositionsPositions = []; - var createMeshFromPositionsBoundingRectangle = new BoundingRectangle(); - - function createMeshFromPositions(polygon, positions, angle, boundingSphere, outerPositions) { - var cleanedPositions = PolygonPipeline.cleanUp(positions); - if (cleanedPositions.length < 3) { - // Duplicate positions result in not enough positions to form a polygon. - return undefined; - } - - var tangentPlane = EllipsoidTangentPlane.fromPoints(cleanedPositions, polygon.ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(cleanedPositions, createMeshFromPositionsPositions); - - var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - positions2D.reverse(); - cleanedPositions.reverse(); - } - var indices = PolygonPipeline.earClip2D(positions2D); - // Checking bounding sphere with plane for quick reject - var minX = boundingSphere.center.x - boundingSphere.radius; - if ((minX < 0) && (BoundingSphere.intersect(boundingSphere, Cartesian4.UNIT_Y) === Intersect.INTERSECTING)) { - indices = PolygonPipeline.wrapLongitude(cleanedPositions, indices); - } - var mesh = PolygonPipeline.computeSubdivision(cleanedPositions, indices, polygon._granularity); - var boundary = outerPositions || cleanedPositions; - var boundingRectangle = computeBoundingRectangle(tangentPlane, boundary, angle, createMeshFromPositionsBoundingRectangle); - mesh = appendTextureCoordinates(tangentPlane, boundingRectangle, mesh, angle); - return mesh; - } - - function createMeshes(polygon) { - // PERFORMANCE_IDEA: Move this to a web-worker. - var i; - var meshes = []; - var mesh; - - if ((typeof polygon._extent !== 'undefined') && !polygon._extent.isEmpty()) { - mesh = ExtentTessellator.compute({extent: polygon._extent, rotation: polygon.rotation, generateTextureCoordinates:true}); - if (typeof mesh !== 'undefined') { - meshes.push(mesh); - } - polygon._boundingVolume = BoundingSphere.fromExtent3D(polygon._extent, polygon._ellipsoid, polygon._boundingVolume); - if (polygon._mode !== SceneMode.SCENE3D) { - polygon._boundingVolume2D = BoundingSphere.fromExtent2D(polygon._extent, polygon._projection, polygon._boundingVolume2D); - var center2D = polygon._boundingVolume2D.center; - polygon._boundingVolume2D.center = new Cartesian3(0.0, center2D.x, center2D.y); - } - } else if (typeof polygon._positions !== 'undefined') { - polygon._boundingVolume = BoundingSphere.fromPoints(polygon._positions, polygon._boundingVolume); - mesh = createMeshFromPositions(polygon, polygon._positions, polygon._textureRotationAngle, polygon._boundingVolume); - if (typeof mesh !== 'undefined') { - meshes.push(mesh); - } - } else if (typeof polygon._polygonHierarchy !== 'undefined') { - var outerPositions = polygon._polygonHierarchy[0]; - // The bounding volume is just around the boundary points, so there could be cases for - // contrived polygons on contrived ellipsoids - very oblate ones - where the bounding - // volume doesn't cover the polygon. - polygon._boundingVolume = BoundingSphere.fromPoints(outerPositions, polygon._boundingVolume); - for (i = 0; i < polygon._polygonHierarchy.length; i++) { - mesh = createMeshFromPositions(polygon, polygon._polygonHierarchy[i], polygon._textureRotationAngle, polygon._boundingVolume, outerPositions); - if (typeof mesh !== 'undefined') { - meshes.push(mesh); - } - } - } - - if (meshes.length === 0) { - return undefined; - } - - var processedMeshes = []; - for (i = 0; i < meshes.length; i++) { - mesh = meshes[i]; - mesh = PolygonPipeline.scaleToGeodeticHeight(mesh, polygon.height, polygon.ellipsoid); - mesh = MeshFilters.reorderForPostVertexCache(mesh); - mesh = MeshFilters.reorderForPreVertexCache(mesh); - - if (polygon._mode === SceneMode.SCENE3D) { - mesh.attributes.position2DHigh = { // Not actually used in shader - value : [0.0, 0.0] - }; - mesh.attributes.position2DLow = { // Not actually used in shader - value : [0.0, 0.0] - }; - mesh = MeshFilters.encodeAttribute(mesh, 'position', 'position3DHigh', 'position3DLow'); - } else { - mesh = MeshFilters.projectTo2D(mesh, polygon._projection); - - if ((i === 0) && (polygon._mode !== SceneMode.SCENE3D)) { - var projectedPositions = mesh.attributes.position2D.values; - var positions = []; - - for (var j = 0; j < projectedPositions.length; j += 2) { - positions.push(new Cartesian3(projectedPositions[j], projectedPositions[j + 1], 0.0)); - } - - polygon._boundingVolume2D = BoundingSphere.fromPoints(positions, polygon._boundingVolume2D); - var center2DPositions = polygon._boundingVolume2D.center; - polygon._boundingVolume2D.center = new Cartesian3(0.0, center2DPositions.x, center2DPositions.y); - } - - mesh = MeshFilters.encodeAttribute(mesh, 'position3D', 'position3DHigh', 'position3DLow'); - mesh = MeshFilters.encodeAttribute(mesh, 'position2D', 'position2DHigh', 'position2DLow'); - } - processedMeshes = processedMeshes.concat(MeshFilters.fitToUnsignedShortIndices(mesh)); - } - - return processedMeshes; - } - - function getGranularity(polygon, mode) { - if (mode === SceneMode.SCENE3D) { - return polygon.scene3D.granularity || polygon.granularity; - } - - return polygon.scene2D.granularity || polygon.granularity; - } - - /** - * Commits changes to properties before rendering by updating the object's WebGL resources. - * - * @memberof Polygon - * - * @exception {DeveloperError} this.ellipsoid must be defined. - * @exception {DeveloperError} this.material must be defined. - * @exception {DeveloperError} this.granularity must be greater than zero. - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * @private */ Polygon.prototype.update = function(context, frameState, commandList) { if (typeof this.ellipsoid === 'undefined') { @@ -670,10 +245,7 @@ define([ throw new DeveloperError('this.material must be defined.'); } - var mode = frameState.mode; - var granularity = getGranularity(this, mode); - - if (granularity < 0.0) { + if (this.granularity < 0.0) { throw new DeveloperError('this.granularity and scene2D/scene3D overrides must be greater than zero.'); } @@ -681,149 +253,66 @@ define([ return; } - if (this._ellipsoid !== this.ellipsoid) { - this._createVertexArray = true; - this._ellipsoid = this.ellipsoid; - } - - if (this._height !== this.height) { - this._createVertexArray = true; - this._height = this.height; - } - - if (this._granularity !== granularity) { - this._createVertexArray = true; - this._granularity = granularity; - } - - if (this._bufferUsage !== this.bufferUsage) { - this._createVertexArray = true; - this._bufferUsage = this.bufferUsage; - } - - var projection = frameState.scene2D.projection; - if (this._projection !== projection) { - this._createVertexArray = true; - this._projection = projection; - } - - if (this._mode !== mode) { - // SCENE2D, COLUMBUS_VIEW, and MORPHING use the same rendering path, so a - // transition only occurs when switching from/to SCENE3D - this._createVertexArray = this._mode === SceneMode.SCENE3D || mode === SceneMode.SCENE3D; - this._mode = mode; - } - - if (this._createVertexArray) { - this._createVertexArray = false; - this._vertices.update(context, createMeshes(this), this.bufferUsage); - } - - if (typeof this._vertices.getVertexArrays() === 'undefined') { + if (!this._createPrimitive && (typeof this._primitive === 'undefined')) { + // No positions/hierarchy to draw return; } - var boundingVolume; - if (mode === SceneMode.SCENE3D) { - boundingVolume = this._boundingVolume; - } else if (mode === SceneMode.COLUMBUS_VIEW || mode === SceneMode.SCENE2D) { - boundingVolume = this._boundingVolume2D; - } else { - boundingVolume = this._boundingVolume.union(this._boundingVolume2D); - } - - var pass = frameState.passes; - var vas = this._vertices.getVertexArrays(); - var length = vas.length; - var commands; - var command; - - var materialChanged = this._material !== this.material; + if (this._createPrimitive || + (this._ellipsoid !== this.ellipsoid) || + (this._granularity !== this.granularity) || + (this._height !== this.height) || + (this._textureRotationAngle !== this.textureRotationAngle)) { - this._commandLists.removeAll(); - if (pass.color) { - if (typeof this._rs === 'undefined') { - // TODO: Should not need this in 2D/columbus view, but is hiding a triangulation issue. - this._rs = context.createRenderState({ - cull : { - enabled : true, - face : CullFace.BACK - }, - blending : BlendingState.ALPHA_BLEND - }); - } - - // Recompile shader when material changes - if (materialChanged) { - this._material = this.material; - - var fsSource = - '#line 0\n' + - this.material.shaderSource + - '#line 0\n' + - PolygonFS; - - this._sp = context.getShaderCache().replaceShaderProgram(this._sp, PolygonVS, fsSource, attributeIndices); - - this._drawUniforms = combine([this._uniforms, this.material._uniforms], false, false); - } - - commands = this._commandLists.colorList; - commands.length = length; + this._createPrimitive = false; + this._ellipsoid = this.ellipsoid; + this._granularity = this.granularity; + this._height = this.height; + this._textureRotationAngle = this.textureRotationAngle; - for (var i = 0; i < length; ++i) { - command = commands[i]; - if (typeof command === 'undefined') { - command = commands[i] = new DrawCommand(); - } + this._primitive = this._primitive && this._primitive.destroy(); - command.boundingVolume = boundingVolume; - command.primitiveType = PrimitiveType.TRIANGLES; - command.shaderProgram = this._sp; - command.uniformMap = this._drawUniforms; - command.vertexArray = vas[i]; - command.renderState = this._rs; + if ((typeof this._positions === 'undefined') && (typeof this._polygonHierarchy === 'undefined')) { + return; } - } - if (pass.pick) { - if (typeof this._pickId === 'undefined') { - this._pickId = context.createPickId(this); - } - - // Recompile shader when material changes - if (materialChanged || typeof this._spPick === 'undefined') { - var pickFS = createPickFragmentShaderSource( - '#line 0\n' + - this.material.shaderSource + - '#line 0\n' + - PolygonFS, 'uniform'); - - this._spPick = context.getShaderCache().replaceShaderProgram(this._spPick, PolygonVS, pickFS, attributeIndices); - this._pickUniforms = combine([this._uniforms, this._pickColorUniform, this.material._uniforms], false, false); + var instance; + if (typeof this._positions !== 'undefined') { + instance = new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + positions : this._positions, + height : this.height, + vertexFormat : EllipsoidSurfaceAppearance.VERTEX_FORMAT, + stRotation : this.textureRotationAngle, + ellipsoid : this.ellipsoid, + granularity : this.granularity + }), + id : this + }); + } else { + instance = new GeometryInstance({ + geometry : new PolygonGeometry({ + polygonHierarchy : this._polygonHierarchy, + height : this.height, + vertexFormat : EllipsoidSurfaceAppearance.VERTEX_FORMAT, + stRotation : this.textureRotationAngle, + ellipsoid : this.ellipsoid, + granularity : this.granularity + }), + id : this + }); } - commands = this._commandLists.pickList; - commands.length = length; - - for (var j = 0; j < length; ++j) { - command = commands[j]; - if (typeof command === 'undefined') { - command = commands[j] = new DrawCommand(); - } - - command.boundingVolume = boundingVolume; - command.primitiveType = PrimitiveType.TRIANGLES; - command.shaderProgram = this._spPick; - command.uniformMap = this._pickUniforms; - command.vertexArray = vas[j]; - command.renderState = this._rs; - } + this._primitive = new Primitive({ + geometryInstances : instance, + appearance : new EllipsoidSurfaceAppearance({ + aboveGround : (this.height > 0.0) + }) + }); } - if (!this._commandLists.empty()) { - commandList.push(this._commandLists); - } + this._primitive.appearance.material = this.material; + this._primitive.update(context, frameState, commandList); }; /** @@ -862,10 +351,7 @@ define([ * polygon = polygon && polygon.destroy(); */ Polygon.prototype.destroy = function() { - this._sp = this._sp && this._sp.release(); - this._spPick = this._spPick && this._spPick.release(); - this._vertices = this._vertices.destroy(); - this._pickId = this._pickId && this._pickId.destroy(); + this._primitive = this._primitive && this._primitive.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index 08f4a448cbba..d4a2cace2a48 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -8,6 +8,7 @@ define([ '../Core/Cartesian4', '../Core/EncodedCartesian3', '../Core/Matrix4', + '../Core/Math', '../Core/ComponentDatatype', '../Core/IndexDatatype', '../Core/PrimitiveType', @@ -32,6 +33,7 @@ define([ Cartesian4, EncodedCartesian3, Matrix4, + CesiumMath, ComponentDatatype, IndexDatatype, PrimitiveType, @@ -57,7 +59,6 @@ define([ //When it does, we need to recreate the indicesBuffer. var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX; var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES; - var SIXTYFOURK = 64 * 1024; var attributeIndices = { position3DHigh : 0, @@ -744,14 +745,14 @@ define([ vbo += vertexBufferOffset[k]; - var positionHighOffset = 6 * (k * (positionSizeInBytes * SIXTYFOURK) - vbo * positionSizeInBytes);//componentsPerAttribute(3) * componentDatatype(4) + var positionHighOffset = 6 * (k * (positionSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * positionSizeInBytes);//componentsPerAttribute(3) * componentDatatype(4) var positionLowOffset = positionSizeInBytes + positionHighOffset; var prevPositionHighOffset = positionSizeInBytes + positionLowOffset; var prevPositionLowOffset = positionSizeInBytes + prevPositionHighOffset; var nextPositionHighOffset = positionSizeInBytes + prevPositionLowOffset; var nextPositionLowOffset = positionSizeInBytes + nextPositionHighOffset; - var vertexPickColorBufferOffset = k * (pickColorSizeInBytes * SIXTYFOURK) - vbo * pickColorSizeInBytes; - var vertexTexCoordExpandWidthAndShowBufferOffset = k * (texCoordExpandWidthAndShowSizeInBytes * SIXTYFOURK) - vbo * texCoordExpandWidthAndShowSizeInBytes; + var vertexPickColorBufferOffset = k * (pickColorSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * pickColorSizeInBytes; + var vertexTexCoordExpandWidthAndShowBufferOffset = k * (texCoordExpandWidthAndShowSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * texCoordExpandWidthAndShowSizeInBytes; var attributes = [{ index : attributeIndices.position3DHigh, @@ -1247,7 +1248,7 @@ define([ for ( var j = 0; j < numberOfSegments; ++j) { var segmentLength = segments[j] - 1.0; for ( var k = 0; k < segmentLength; ++k) { - if (indicesCount + 4 >= SIXTYFOURK - 1) { + if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 1) { polyline._locatorBuckets.push({ locator : bucketLocator, count : segmentIndexCount @@ -1279,7 +1280,7 @@ define([ count : segmentIndexCount }); - if (indicesCount + 4 >= SIXTYFOURK - 1) { + if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 1) { vertexBufferOffset.push(0); indices = []; totalIndices.push(indices); diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js new file mode 100644 index 000000000000..4711e8c65a4a --- /dev/null +++ b/Source/Scene/Primitive.js @@ -0,0 +1,1002 @@ +/*global define*/ +define([ + '../Core/clone', + '../Core/defaultValue', + '../Core/DeveloperError', + '../Core/destroyObject', + '../Core/Matrix4', + '../Core/Color', + '../Core/GeometryPipeline', + '../Core/PrimitiveType', + '../Core/BoundingSphere', + '../Core/Geometry', + '../Core/GeometryAttribute', + '../Core/GeometryAttributes', + '../Core/GeometryInstance', + '../Core/GeometryInstanceAttribute', + '../Core/ComponentDatatype', + '../Core/Cartesian3', + '../Renderer/BufferUsage', + '../Renderer/VertexLayout', + '../Renderer/CommandLists', + '../Renderer/DrawCommand', + '../Renderer/createPickFragmentShaderSource', + './SceneMode' + ], function( + clone, + defaultValue, + DeveloperError, + destroyObject, + Matrix4, + Color, + GeometryPipeline, + PrimitiveType, + BoundingSphere, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryInstance, + GeometryInstanceAttribute, + ComponentDatatype, + Cartesian3, + BufferUsage, + VertexLayout, + CommandLists, + DrawCommand, + createPickFragmentShaderSource, + SceneMode) { + "use strict"; + + /** + * A primitive represents geometry in the {@link Scene}. The geometry can be from a single {@link GeometryInstance} + * as shown in example 1 below, or from an array of instances, even if the geometry is from different + * geometry types, e.g., an {@link ExtentGeometry} and an {@link EllipsoidGeometry} as shown in Code Example 2. + *

+ * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including + * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, + * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix + * and match most of them and add a new geometry or appearance independently of each other. + *

+ *

+ * Combining multiple instances into one primitive is called batching, and significantly improves performance for static data. + * Instances can be individually picked; {@link Context#pick} returns their {@link GeometryInstance#id}. Using + * per-instance appearances like {@link PerInstanceColorAppearance}, each instance can also have a unique color. + *

+ * + * @alias Primitive + * @constructor + * + * @param {Array|GeometryInstance} [options.geometryInstances=undefined] The geometry instances - or a single geometry instance - to render. + * @param {Appearance} [options.appearance=undefined] The appearance used to render the primitive. + * @param {Boolean} [options.vertexCacheOptimize=true] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * @param {Boolean} [options.allow3DOnly=false] When true, each geometry instance will only be rendered in 3D. + * + * @example + * // 1. Draw a translucent ellipse on the surface with a checkerboard pattern + * var instance = new GeometryInstance({ + * geometry : new EllipseGeometry({ + * vertexFormat : VertexFormat.POSITION_AND_ST, + * ellipsoid : ellipsoid, + * center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20)), + * semiMinorAxis : 500000.0, + * semiMajorAxis : 1000000.0, + * bearing : CesiumMath.PI_OVER_FOUR + * }), + * id : 'object returned when this instance is picked and to get/set per-instance attributes' + * }); + * var primitive = new Primitive({ + * geometryInstances : instance, + * appearance : new EllipsoidSurfaceAppearance({ + * material : Material.fromType(scene.getContext(), 'Checkerboard') + * }) + * }); + * scene.getPrimitives().add(primitive); + * + * // 2. Draw different instances each with a unique color + * var extentInstance = new GeometryInstance({ + * geometry : new ExtentGeometry({ + * vertexFormat : VertexFormat.POSITION_AND_NORMAL, + * extent : new Extent( + * CesiumMath.toRadians(-140.0), + * CesiumMath.toRadians(30.0), + * CesiumMath.toRadians(-100.0), + * CesiumMath.toRadians(40.0)) + * }), + * id : 'extent', + * attribute : { + * color : new ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5) + * } + * }); + * var ellipsoidInstance = new GeometryInstance({ + * geometry : new EllipsoidGeometry({ + * vertexFormat : VertexFormat.POSITION_AND_NORMAL, + * radii : new Cartesian3(500000.0, 500000.0, 1000000.0) + * }), + * modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + * ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-95.59777, 40.03883))), new Cartesian3(0.0, 0.0, 500000.0)), + * id : 'ellipsoid', + * attribute : { + * color : ColorGeometryInstanceAttribute.fromColor(Color.AQUA) + * } + * }); + * var primitive = new Primitive({ + * geometryInstances : [extentInstance, ellipsoidInstance], + * appearance : new PerInstanceColorAppearance() + * }); + * scene.getPrimitives().add(primitive); + * + * @see GeometryInstance + * @see Appearance + */ + var Primitive = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The geometry instances rendered with this primitive. This may + * be undefined if options.releaseGeometryInstances + * is true when the primitive is constructed. + *

+ * Changing this property after the primitive is rendered has no effect. + *

+ * + * @type Array + * + * @default undefined + */ + this.geometryInstances = options.geometryInstances; + + /** + * The {@link Appearance} used to shade this primitive. Each geometry + * instance is shaded with the same appearance. Some appearances, like + * {@link PerInstanceColorAppearance} allow giving each instance unique + * properties. + * + * @type Appearance + * + * @default undefined + */ + this.appearance = options.appearance; + this._appearance = undefined; + this._material = undefined; + + /** + * The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates. + * When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth's WGS84 coordinates. + * Local reference frames can be used by providing a different transformation matrix, like that returned + * by {@link Transforms.eastNorthUpToFixedFrame}. This matrix is available to GLSL vertex and fragment + * shaders via {@link czm_model} and derived uniforms. + * + * @type Matrix4 + * + * @default Matrix4.IDENTITY + * + * @example + * var origin = ellipsoid.cartographicToCartesian( + * Cartographic.fromDegrees(-95.0, 40.0, 200000.0)); + * p.modelMatrix = Transforms.eastNorthUpToFixedFrame(origin); + * + * @see czm_model + */ + this.modelMatrix = Matrix4.IDENTITY.clone(); + + /** + * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. + * + * @type Boolean + * + * @default true + */ + this.show = true; + + this._vertexCacheOptimize = defaultValue(options.vertexCacheOptimize, true); + this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true); + // When true, geometry is transformed to world coordinates even if there is a single + // geometry or all geometries are in the same reference frame. + this._allow3DOnly = defaultValue(options.allow3DOnly, false); + this._boundingSphere = undefined; + this._boundingSphere2D = undefined; + this._perInstanceAttributes = {}; + this._lastPerInstanceAttributeIndex = 0; + + this._va = []; + this._attributeIndices = undefined; + + this._rs = undefined; + this._sp = undefined; + + this._pickSP = undefined; + this._pickIds = []; + + this._commandLists = new CommandLists(); + }; + + function cloneAttribute(attribute) { + return new GeometryAttribute({ + componentDatatype : attribute.componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize, + values : new attribute.values.constructor(attribute.values) + }); + } + + function cloneGeometry(geometry) { + var attributes = geometry.attributes; + var newAttributes = new GeometryAttributes(); + for (var property in attributes) { + if (attributes.hasOwnProperty(property) && typeof attributes[property] !== 'undefined') { + newAttributes[property] = cloneAttribute(attributes[property]); + } + } + + var indices; + if (typeof geometry.indices !== 'undefined') { + var sourceValues = geometry.indices; + indices = new sourceValues.constructor(sourceValues); + } + + return new Geometry({ + attributes : newAttributes, + indices : indices, + primitiveType : geometry.primitiveType, + boundingSphere : BoundingSphere.clone(geometry.boundingSphere) + }); + } + + function cloneGeometryInstanceAttribute(attribute) { + return new GeometryInstanceAttribute({ + componentDatatype : attribute.componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize, + value : new attribute.value.constructor(attribute.value) + }); + } + + function cloneInstance(instance) { + var attributes = instance.attributes; + var newAttributes = {}; + for (var property in attributes) { + if (attributes.hasOwnProperty(property)) { + newAttributes[property] = cloneGeometryInstanceAttribute(attributes[property]); + } + } + + return new GeometryInstance({ + geometry : cloneGeometry(instance.geometry), + modelMatrix : Matrix4.clone(instance.modelMatrix), + id : instance.id, // Shallow copy + attributes : newAttributes + }); + } + + function addPickColorAttribute(primitive, instances, context) { + var length = instances.length; + + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var geometry = instance.geometry; + var attributes = geometry.attributes; + var positionAttr = attributes.position; + var numberOfComponents = 4 * (positionAttr.values.length / positionAttr.componentsPerAttribute); + + attributes.pickColor = new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true, + values : new Uint8Array(numberOfComponents) + }); + + var pickId = context.createPickId(defaultValue(instance.id, primitive)); + primitive._pickIds.push(pickId); + + var pickColor = pickId.color; + var red = Color.floatToByte(pickColor.red); + var green = Color.floatToByte(pickColor.green); + var blue = Color.floatToByte(pickColor.blue); + var alpha = Color.floatToByte(pickColor.alpha); + var values = attributes.pickColor.values; + + for (var j = 0; j < numberOfComponents; j += 4) { + values[j] = red; + values[j + 1] = green; + values[j + 2] = blue; + values[j + 3] = alpha; + } + } + } + + function transformToWorldCoordinates(primitive, instances) { + var toWorld = !primitive._allow3DOnly; + var length = instances.length; + var i; + + if (!toWorld && (length > 1)) { + var modelMatrix = instances[0].modelMatrix; + + for (i = 1; i < length; ++i) { + if (!Matrix4.equals(modelMatrix, instances[i].modelMatrix)) { + toWorld = true; + break; + } + } + } + + if (toWorld) { + for (i = 0; i < length; ++i) { + GeometryPipeline.transformToWorldCoordinates(instances[i]); + } + } else { + // Leave geometry in local coordinate system; auto update model-matrix. + Matrix4.clone(instances[0].modelMatrix, primitive.modelMatrix); + } + } + + function getCommonPerInstanceAttributeNames(instances) { + var length = instances.length; + + var attributesInAllInstances = []; + var attributes0 = instances[0].attributes; + var name; + + for (name in attributes0) { + if (attributes0.hasOwnProperty(name)) { + var attribute = attributes0[name]; + var inAllInstances = true; + + // Does this same attribute exist in all instances? + for (var i = 1; i < length; ++i) { + var otherAttribute = instances[i].attributes[name]; + + if ((typeof otherAttribute === 'undefined') || + (attribute.componentDatatype !== otherAttribute.componentDatatype) || + (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || + (attribute.normalize !== otherAttribute.normalize)) { + + inAllInstances = false; + break; + } + } + + if (inAllInstances) { + attributesInAllInstances.push(name); + } + } + } + + return attributesInAllInstances; + } + + function addPerInstanceAttributes(primitive, instances, names) { + var length = instances.length; + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var instanceAttributes = instance.attributes; + var geometry = instance.geometry; + var numberOfVertices = Geometry.computeNumberOfVertices(geometry); + + var namesLength = names.length; + for (var j = 0; j < namesLength; ++j) { + var name = names[j]; + var attribute = instanceAttributes[name]; + var componentDatatype = attribute.componentDatatype; + var value = attribute.value; + var componentsPerAttribute = value.length; + + var buffer = componentDatatype.createTypedArray(numberOfVertices * componentsPerAttribute); + for (var k = 0; k < numberOfVertices; ++k) { + buffer.set(value, k * componentsPerAttribute); + } + + geometry.attributes[name] = new GeometryAttribute({ + componentDatatype : componentDatatype, + componentsPerAttribute : componentsPerAttribute, + normalize : attribute.normalize, + values : buffer + }); + } + } + } + + // PERFORMANCE_IDEA: Move pipeline to a web-worker. + function geometryPipeline(primitive, instances, context, projection) { + var length = instances.length; + var primitiveType = instances[0].geometry.primitiveType; + for (var i = 1; i < length; ++i) { + if (instances[i].geometry.primitiveType !== primitiveType) { + throw new DeveloperError('All instance geometries must have the same primitiveType.'); + } + } + + // Unify to world coordinates before combining. + transformToWorldCoordinates(primitive, instances); + + // Clip to IDL + if (!primitive._allow3DOnly) { + for (i = 0; i < length; ++i) { + GeometryPipeline.wrapLongitude(instances[i].geometry); + } + } + + // Add pickColor attribute for picking individual instances + addPickColorAttribute(primitive, instances, context); + + // add attributes to the geometry for each per-instance attribute + var perInstanceAttributeNames = getCommonPerInstanceAttributeNames(instances); + addPerInstanceAttributes(primitive, instances, perInstanceAttributeNames); + + // Optimize for vertex shader caches + if (primitive._vertexCacheOptimize) { + for (i = 0; i < length; ++i) { + GeometryPipeline.reorderForPostVertexCache(instances[i].geometry); + GeometryPipeline.reorderForPreVertexCache(instances[i].geometry); + } + } + + // Combine into single geometry for better rendering performance. + var geometry = GeometryPipeline.combine(instances); + + // Split positions for GPU RTE + if (!primitive._allow3DOnly) { + // Compute 2D positions + GeometryPipeline.projectTo2D(geometry, projection); + + GeometryPipeline.encodeAttribute(geometry, 'position3D', 'position3DHigh', 'position3DLow'); + GeometryPipeline.encodeAttribute(geometry, 'position2D', 'position2DHigh', 'position2DLow'); + } else { + GeometryPipeline.encodeAttribute(geometry, 'position', 'position3DHigh', 'position3DLow'); + } + + if (!context.getElementIndexUint()) { + // Break into multiple geometries to fit within unsigned short indices if needed + return GeometryPipeline.fitToUnsignedShortIndices(geometry); + } + + // Unsigned int indices are supported. No need to break into multiple geometries. + return [geometry]; + } + + function createPerInstanceVAAttributes(context, geometry, attributeIndices, names) { + var vaAttributes = []; + + var bufferUsage = BufferUsage.DYNAMIC_DRAW; + var attributes = geometry.attributes; + + var length = names.length; + for (var i = 0; i < length; ++i) { + var name = names[i]; + var attribute = attributes[name]; + + var componentDatatype = attribute.componentDatatype; + if (componentDatatype === ComponentDatatype.DOUBLE) { + componentDatatype = ComponentDatatype.FLOAT; + } + + var vertexBuffer = context.createVertexBuffer(componentDatatype.createTypedArray(attribute.values), bufferUsage); + vaAttributes.push({ + index : attributeIndices[name], + vertexBuffer : vertexBuffer, + componentDatatype : componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize + }); + + delete attributes[name]; + } + + return vaAttributes; + } + + function computePerInstanceAttributeIndices(instances, vertexArrays, attributeIndices) { + var ids = []; + var indices = []; + + var names = getCommonPerInstanceAttributeNames(instances); + var length = instances.length; + var offsets = {}; + var vaIndices = {}; + + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var numberOfVertices = Geometry.computeNumberOfVertices(instance.geometry); + + var namesLength = names.length; + for (var j = 0; j < namesLength; ++j) { + var name = names[j]; + var index = attributeIndices[name]; + + var tempVertexCount = numberOfVertices; + while (tempVertexCount > 0) { + var vaIndex = defaultValue(vaIndices[name], 0); + var va = vertexArrays[vaIndex]; + var vaLength = va.getNumberOfAttributes(); + + var attribute; + for (var k = 0; k < vaLength; ++k) { + attribute = va.getAttribute(k); + if (attribute.index === index) { + break; + } + } + + if (typeof ids[i] === 'undefined') { + ids[i] = instance.id; + } + + if (typeof indices[i] === 'undefined') { + indices[i] = {}; + } + + if (typeof indices[i][name] === 'undefined') { + indices[i][name] = { + dirty : false, + value : instance.attributes[name].value, + indices : [] + }; + } + + var size = attribute.vertexBuffer.getSizeInBytes() / attribute.componentDatatype.sizeInBytes; + size /= attribute.componentsPerAttribute; + var offset = defaultValue(offsets[name], 0); + + var count; + if (offset + tempVertexCount < size) { + count = tempVertexCount; + indices[i][name].indices.push({ + attribute : attribute, + offset : offset, + count : count + }); + offsets[name] = offset + tempVertexCount; + } else { + count = size - offset; + indices[i][name].indices.push({ + attribute : attribute, + offset : offset, + count : count + }); + offsets[name] = 0; + vaIndices[name] = vaIndex + 1; + } + + tempVertexCount -= count; + } + } + } + + return { + ids : ids, + indices : indices + }; + } + + function createColumbusViewShader(primitive, vertexShaderSource) { + var attributes; + if (!primitive._allow3DOnly) { + attributes = + 'attribute vec3 position2DHigh;\n' + + 'attribute vec3 position2DLow;\n'; + } else { + attributes = ''; + } + + var computePosition = + '\nvec4 czm_computePosition()\n' + + '{\n'; + if (!primitive._allow3DOnly) { + computePosition += + ' vec4 p;\n' + + ' if (czm_morphTime == 1.0)\n' + + ' {\n' + + ' p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' + + ' }\n' + + ' else if (czm_morphTime == 0.0)\n' + + ' {\n' + + ' p = czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy);\n' + + ' }\n' + + ' else\n' + + ' {\n' + + ' p = czm_columbusViewMorph(\n' + + ' czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),\n' + + ' czm_translateRelativeToEye(position3DHigh, position3DLow),\n' + + ' czm_morphTime);\n' + + ' }\n' + + ' return p;\n'; + } else { + computePosition += ' return czm_translateRelativeToEye(position3DHigh, position3DLow);\n'; + } + computePosition += '}\n\n'; + + return attributes + vertexShaderSource + computePosition; + } + + function createPickVertexShaderSource(vertexShaderSource) { + var renamedVS = vertexShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); + var pickMain = + 'attribute vec4 pickColor; \n' + + 'varying vec4 czm_pickColor; \n' + + 'void main() \n' + + '{ \n' + + ' czm_old_main(); \n' + + ' czm_pickColor = pickColor; \n' + + '}'; + + return renamedVS + '\n' + pickMain; + } + + function appendShow(primitive, vertexShaderSource) { + if (typeof primitive._attributeIndices.show === 'undefined') { + return vertexShaderSource; + } + + var renamedVS = vertexShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_non_show_main()'); + var showMain = + 'attribute float show;\n' + + 'void main() \n' + + '{ \n' + + ' czm_non_show_main(); \n' + + ' gl_Position *= show; \n' + + '}'; + + return renamedVS + '\n' + showMain; + } + + function validateShaderMatching(shaderProgram, attributeIndices) { + // For a VAO and shader program to be compatible, the VAO must have + // all active attribute in the shader program. The VAO may have + // extra attributes with the only concern being a potential + // performance hit due to extra memory bandwidth and cache pollution. + // The shader source could have extra attributes that are not used, + // but there is no guarantee they will be optimized out. + // + // Here, we validate that the VAO has all attributes required + // to match the shader program. + var shaderAttributes = shaderProgram.getVertexAttributes(); + + for (var name in shaderAttributes) { + if (shaderAttributes.hasOwnProperty(name)) { + if (typeof attributeIndices[name] === 'undefined') { + throw new DeveloperError('Appearance/Geometry mismatch. The appearance requires vertex shader attribute input \'' + name + + '\', which was not computed as part of the Geometry. Use the appearance\'s vertexFormat property when constructing the geometry.'); + } + } + } + + } + + /** + * @private + */ + Primitive.prototype.update = function(context, frameState, commandList) { + if (!this.show || + ((typeof this.geometryInstances === 'undefined') && (this._va.length === 0)) || + (typeof this.appearance === 'undefined') || + (frameState.mode !== SceneMode.SCENE3D && this._allow3DOnly) || + (!frameState.passes.color && !frameState.passes.pick)) { + return; + } + + var colorCommands = this._commandLists.colorList; + var pickCommands = this._commandLists.pickList; + var colorCommand; + var pickCommand; + var length; + var i; + + if (this._va.length === 0) { + var projection = frameState.scene2D.projection; + + var instances = (Array.isArray(this.geometryInstances)) ? this.geometryInstances : [this.geometryInstances]; + // Copy instances first since most pipeline operations modify the geometry and instance in-place. + length = instances.length; + var insts = new Array(length); + for (i = 0; i < length; ++i) { + insts[i] = cloneInstance(instances[i]); + } + var geometries = geometryPipeline(this, insts, context, projection); + + this._attributeIndices = GeometryPipeline.createAttributeIndices(geometries[0]); + + this._boundingSphere = geometries[0].boundingSphere; + if (!this._allow3DOnly && typeof this._boundingSphere !== 'undefined') { + this._boundingSphere2D = BoundingSphere.projectTo2D(this._boundingSphere, projection); + } + + var geometry; + var perInstanceAttributeNames = getCommonPerInstanceAttributeNames(insts); + + var va = []; + length = geometries.length; + for (i = 0; i < length; ++i) { + geometry = geometries[i]; + var vaAttributes = createPerInstanceVAAttributes(context, geometry, this._attributeIndices, perInstanceAttributeNames); + va.push(context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : this._attributeIndices, + bufferUsage : BufferUsage.STATIC_DRAW, + vertexLayout : VertexLayout.INTERLEAVED, + vertexArrayAttributes : vaAttributes + })); + } + + this._va = va; + this._perInstanceAttributes = computePerInstanceAttributeIndices(insts, va, this._attributeIndices); + + for (i = 0; i < length; ++i) { + geometry = geometries[i]; + + // renderState, shaderProgram, and uniformMap for commands are set below. + + colorCommand = new DrawCommand(); + colorCommand.primitiveType = geometry.primitiveType; + colorCommand.vertexArray = this._va[i]; + colorCommands.push(colorCommand); + + pickCommand = new DrawCommand(); + pickCommand.primitiveType = geometry.primitiveType; + pickCommand.vertexArray = this._va[i]; + pickCommands.push(pickCommand); + } + + if (this._releaseGeometryInstances) { + this.geometryInstances = undefined; + } + } + + // Create or recreate render state and shader program if appearance/material changed + var appearance = this.appearance; + var material = appearance.material; + var createRS = false; + var createSP = false; + + if (this._appearance !== appearance) { + this._appearance = appearance; + this._material = material; + createRS = true; + createSP = true; + } else if (this._material !== material ) { + this._material = material; + createSP = true; + } + + if (createRS) { + this._rs = context.createRenderState(appearance.renderState); + } + + if (createSP) { + var shaderCache = context.getShaderCache(); + var vs = createColumbusViewShader(this, appearance.vertexShaderSource); + vs = appendShow(this, vs); + var fs = appearance.getFragmentShaderSource(); + + this._sp = shaderCache.replaceShaderProgram(this._sp, vs, fs, this._attributeIndices); + this._pickSP = shaderCache.replaceShaderProgram(this._pickSP, + createPickVertexShaderSource(vs), + createPickFragmentShaderSource(fs, 'varying'), + this._attributeIndices); + + validateShaderMatching(this._sp, this._attributeIndices); + validateShaderMatching(this._pickSP, this._attributeIndices); + } + + if (createRS || createSP) { + var uniforms = (typeof material !== 'undefined') ? material._uniforms : undefined; + + length = colorCommands.length; + for (i = 0; i < length; ++i) { + + colorCommand = colorCommands[i]; + colorCommand.renderState = this._rs; + colorCommand.shaderProgram = this._sp; + colorCommand.uniformMap = uniforms; + + pickCommand = pickCommands[i]; + pickCommand.renderState = this._rs; + pickCommand.shaderProgram = this._pickSP; + pickCommand.uniformMap = uniforms; + } + } + + // Update per-instance attributes + if (this._perInstanceAttributes._dirty) { + var perInstanceIndices = this._perInstanceAttributes.indices; + length = perInstanceIndices.length; + for (i = 0; i < length; ++i) { + var perInstanceAttributes = perInstanceIndices[i]; + for (var name in perInstanceAttributes) { + if (perInstanceAttributes.hasOwnProperty(name)) { + var attribute = perInstanceAttributes[name]; + if (attribute.dirty) { + var value = attribute.value; + var indices = attribute.indices; + var indicesLength = indices.length; + for (var j = 0; j < indicesLength; ++j) { + var index = indices[j]; + var offset = index.offset; + var count = index.count; + + var vaAttribute = index.attribute; + var componentDatatype = vaAttribute.componentDatatype; + var componentsPerAttribute = vaAttribute.componentsPerAttribute; + + var typedArray = componentDatatype.createTypedArray(count * componentsPerAttribute); + for (var k = 0; k < count; ++k) { + typedArray.set(value, k * componentsPerAttribute); + } + + var offsetInBytes = offset * componentsPerAttribute * componentDatatype.sizeInBytes; + vaAttribute.vertexBuffer.copyFromArrayView(typedArray, offsetInBytes); + } + + attribute.dirty = false; + } + } + } + } + + this._perInstanceAttributes._dirty = false; + } + + var boundingSphere; + if (frameState.mode === SceneMode.SCENE3D) { + boundingSphere = this._boundingSphere; + } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + boundingSphere = this._boundingSphere2D; + } else if (frameState.mode === SceneMode.SCENE2D && typeof this._boundingSphere2D !== 'undefined') { + boundingSphere = BoundingSphere.clone(this._boundingSphere2D); + boundingSphere.center.x = 0.0; + } else if (typeof this._boundingSphere !== 'undefined' && typeof this._boundingSphere2D !== 'undefined') { + boundingSphere = BoundingSphere.union(this._boundingSphere, this._boundingSphere2D); + } + + // modelMatrix can change from frame to frame + length = colorCommands.length; + for (i = 0; i < length; ++i) { + colorCommands[i].modelMatrix = this.modelMatrix; + pickCommands[i].modelMatrix = this.modelMatrix; + + colorCommands[i].boundingVolume = boundingSphere; + pickCommands[i].boundingVolume = boundingSphere; + } + + commandList.push(this._commandLists); + }; + + function createGetFunction(name, perInstanceAttributes) { + return function() { + return perInstanceAttributes[name].value; + }; + } + + function createSetFunction(name, perInstanceAttributes, container) { + return function (value) { + if (typeof value === 'undefined' || typeof value.length === 'undefined' || value.length < 1 || value.length > 4) { + throw new DeveloperError('value must be and array with length between 1 and 4.'); + } + + perInstanceAttributes[name].value = value; + perInstanceAttributes[name].dirty = true; + container._dirty = true; + }; + } + + /** + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. + * + * @param {Object} id The id of the {@link GeometryInstance}. + * + * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id. + * + * @exception {DeveloperError} id is required. + * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = ColorGeometryInstanceAttribute.toValue(Color.AQUA); + * attributes.show = ShowGeometryInstanceAttribute.toValue(true); + */ + Primitive.prototype.getGeometryInstanceAttributes = function(id) { + if (typeof id === 'undefined') { + throw new DeveloperError('id is required'); + } + + if (typeof this._perInstanceAttributes === 'undefined') { + throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); + } + + var index = -1; + var lastIndex = this._lastPerInstanceAttributeIndex; + var ids = this._perInstanceAttributes.ids; + var length = ids.length; + for (var i = 0; i < length; ++i) { + var curIndex = (lastIndex + i) % length; + if (id === ids[curIndex]) { + index = curIndex; + break; + } + } + + if (index === -1) { + return undefined; + } + + var perInstanceAttributes = this._perInstanceAttributes.indices[index]; + var attributes = {}; + + for (var name in perInstanceAttributes) { + if (perInstanceAttributes.hasOwnProperty(name)) { + Object.defineProperty(attributes, name, { + get : createGetFunction(name, perInstanceAttributes, this._perInstanceAttributes), + set : createSetFunction(name, perInstanceAttributes, this._perInstanceAttributes) + }); + } + } + + this._lastPerInstanceAttributeIndex = index; + + return attributes; + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + *

+ * + * @memberof Primitive + * + * @return {Boolean} true if this object was destroyed; otherwise, false. + * + * @see Primitive#destroy + */ + Primitive.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + *

+ * + * @memberof Primitive + * + * @return {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @see Primitive#isDestroyed + * + * @example + * e = e && e.destroy(); + */ + Primitive.prototype.destroy = function() { + var length; + var i; + + this._sp = this._sp && this._sp.release(); + this._pickSP = this._pickSP && this._pickSP.release(); + + var va = this._va; + length = va.length; + for (i = 0; i < length; ++i) { + va[i].destroy(); + } + this._va = undefined; + + var pickIds = this._pickIds; + length = pickIds.length; + for (i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + this._pickIds = undefined; + + return destroyObject(this); + }; + + return Primitive; +}); diff --git a/Source/Scene/Projections.js b/Source/Scene/Projections.js deleted file mode 100644 index a92c288888b3..000000000000 --- a/Source/Scene/Projections.js +++ /dev/null @@ -1,108 +0,0 @@ -/*global define*/ -define([ - '../Core/DeveloperError', - '../Core/Enumeration' - ], function( - DeveloperError, - Enumeration) { - "use strict"; - - /** - * DOC_TBA - * - * @exports Projections - */ - var Projections = { - /** - * DOC_TBA - * - * @type {Enumeration} - * @constant - * @default 0 - */ - WGS84 : new Enumeration(0, 'WGS84', { - toWgs84 : function(extent, image) { - return image; - } - }), - - /** - * DOC_TBA - * - * @type {Enumeration} - * @constant - * @default 1 - */ - MERCATOR : new Enumeration(1, 'MERCATOR', { - toWgs84 : function(extent, image) { - if (typeof extent === 'undefined' || typeof extent.north === 'undefined' || typeof extent.south === 'undefined') { - throw new DeveloperError('extent, extent.north and extent.south are required.'); - } - - if (typeof image === 'undefined') { - throw new DeveloperError('image is required.'); - } - - var width = parseInt(image.width, 10); - var height = parseInt(image.height, 10); - var wRowBytes = width * 4; // Always 4 bytes per pixel. - - // draw image to canvas and get the pixels - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - var context = canvas.getContext('2d'); - context.drawImage(image, 0, 0); - var fromPixels = context.getImageData(0, 0, width, height).data; - - // create array of pixels - var newImageData = context.createImageData(width, height); - var toPixels = newImageData.data; - - // WGS84 parameters - var deltaWLat = (extent.north - extent.south) / height; - var currentWLat = extent.north - (0.5 * deltaWLat); - - // mercator parameters - var sinTheta = Math.sin(extent.south); - var minMLat = 0.5 * Math.log((1 + sinTheta) / (1 - sinTheta)); - sinTheta = Math.sin(extent.north); - var maxMLat = 0.5 * Math.log((1 + sinTheta) / (1 - sinTheta)); - var invMLatDim = 1.0 / (maxMLat - minMLat); - - // first row - var heightMinusOne = height - 1; - var i = 0; - for (; i < wRowBytes; ++i) { - toPixels[i] = fromPixels[i]; - } - - // interior rows - var end, mLat, mRow; - var j = 1; - for (; j < heightMinusOne; ++j, currentWLat -= deltaWLat) { - sinTheta = Math.sin(currentWLat); - mLat = 0.5 * Math.log((1.0 + sinTheta) / (1.0 - sinTheta)); - mRow = Math.floor(heightMinusOne - ((heightMinusOne * (mLat - minMLat) * invMLatDim))); - end = i + wRowBytes; - for ( var k = 0; i < end; ++i, ++k) { - toPixels[i] = fromPixels[mRow * wRowBytes + k]; - } - } - - // last row - end = i + wRowBytes; - for (j = 0; i < end; ++i, ++j) { - toPixels[i] = fromPixels[i]; - } - - // paint new image to canvas - context.putImageData(newImageData, 0, 0); - - return canvas; - } - }) - }; - - return Projections; -}); diff --git a/Source/Scene/RectangularPyramidSensorVolume.js b/Source/Scene/RectangularPyramidSensorVolume.js index 93be4e7bd79d..0b99be2ff376 100644 --- a/Source/Scene/RectangularPyramidSensorVolume.js +++ b/Source/Scene/RectangularPyramidSensorVolume.js @@ -43,9 +43,10 @@ define([ this.show = defaultValue(options.show, true); /** - * When true, a polyline is shown where the sensor outline intersections the central body. The default is true. + * When true, a polyline is shown where the sensor outline intersections the central body. * * @type {Boolean} + * * @default true * * @see RectangularPyramidSensorVolume#intersectionColor @@ -57,9 +58,6 @@ define([ * Determines if a sensor intersecting the ellipsoid is drawn through the ellipsoid and potentially out * to the other side, or if the part of the sensor intersecting the ellipsoid stops at the ellipsoid. *

- *

- * The default is false, meaning the sensor will not go through the ellipsoid. - *

* * @type {Boolean} * @default false diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index ca85e4b42116..daff89476255 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -486,7 +486,7 @@ define([ for (var i = 0; i < numFrustums; ++i) { clearDepthStencil.execute(context, passState); - var index = numFrustums - i - 1.0; + var index = numFrustums - i - 1; var frustumCommands = frustumCommandsList[index]; frustum.near = frustumCommands.near; frustum.far = frustumCommands.far; diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js index 834331e11ea5..389beb7f1f9f 100644 --- a/Source/Scene/SkyAtmosphere.js +++ b/Source/Scene/SkyAtmosphere.js @@ -1,9 +1,9 @@ /*global define*/ define([ '../Core/defaultValue', - '../Core/CubeMapEllipsoidTessellator', + '../Core/EllipsoidGeometry', '../Core/destroyObject', - '../Core/MeshFilters', + '../Core/GeometryPipeline', '../Core/PrimitiveType', '../Core/Ellipsoid', '../Renderer/BufferUsage', @@ -15,9 +15,9 @@ define([ '../Shaders/SkyAtmosphereFS' ], function( defaultValue, - CubeMapEllipsoidTessellator, + EllipsoidGeometry, destroyObject, - MeshFilters, + GeometryPipeline, PrimitiveType, Ellipsoid, BufferUsage, @@ -52,9 +52,6 @@ define([ /** * Determines if the atmosphere is shown. - *

- * The default is true. - *

* * @type {Boolean} * @default true @@ -134,10 +131,13 @@ define([ var command = this._command; if (typeof command.vertexArray === 'undefined') { - var mesh = CubeMapEllipsoidTessellator.compute(Ellipsoid.fromCartesian3(this._ellipsoid.getRadii().multiplyByScalar(1.025)), 60); - command.vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh), + var geometry = new EllipsoidGeometry({ + radii : this._ellipsoid.getRadii().multiplyByScalar(1.025), + numberOfPartitions : 60 + }); + command.vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry), bufferUsage : BufferUsage.STATIC_DRAW }); command.primitiveType = PrimitiveType.TRIANGLES; diff --git a/Source/Scene/SkyBox.js b/Source/Scene/SkyBox.js index ae324ea427fe..7c7e4945cfeb 100644 --- a/Source/Scene/SkyBox.js +++ b/Source/Scene/SkyBox.js @@ -1,11 +1,12 @@ /*global define*/ define([ - '../Core/BoxTessellator', + '../Core/BoxGeometry', '../Core/Cartesian3', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Matrix4', - '../Core/MeshFilters', + '../Core/GeometryPipeline', + '../Core/VertexFormat', '../Core/PrimitiveType', '../Renderer/loadCubeMap', '../Renderer/BufferUsage', @@ -15,12 +16,13 @@ define([ '../Shaders/SkyBoxVS', '../Shaders/SkyBoxFS' ], function( - BoxTessellator, + BoxGeometry, Cartesian3, destroyObject, DeveloperError, Matrix4, - MeshFilters, + GeometryPipeline, + VertexFormat, PrimitiveType, loadCubeMap, BufferUsage, @@ -83,9 +85,6 @@ define([ /** * Determines if the sky box will be shown. - *

- * The default is true. - *

* * @type {Boolean} * @default true @@ -149,15 +148,16 @@ define([ } }; - var mesh = BoxTessellator.compute({ - dimensions : new Cartesian3(2.0, 2.0, 2.0) + var geometry = BoxGeometry.fromDimensions({ + dimensions : new Cartesian3(2.0, 2.0, 2.0), + vertexFormat : VertexFormat.POSITION_ONLY }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); command.primitiveType = PrimitiveType.TRIANGLES; command.modelMatrix = Matrix4.IDENTITY.clone(); - command.vertexArray = context.createVertexArrayFromMesh({ - mesh: mesh, + command.vertexArray = context.createVertexArrayFromGeometry({ + geometry: geometry, attributeIndices: attributeIndices, bufferUsage: BufferUsage.STATIC_DRAW }); diff --git a/Source/Scene/Sun.js b/Source/Scene/Sun.js index 111e3e32e6db..f604b7ffbc17 100644 --- a/Source/Scene/Sun.js +++ b/Source/Scene/Sun.js @@ -50,9 +50,6 @@ define([ /** * Determines if the sun will be shown. - *

- * The default is true. - *

* * @type {Boolean} * @default true diff --git a/Source/Scene/SunPostProcess.js b/Source/Scene/SunPostProcess.js index b78d4ae1f906..9af0f6ff7bdb 100644 --- a/Source/Scene/SunPostProcess.js +++ b/Source/Scene/SunPostProcess.js @@ -7,6 +7,8 @@ define([ '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/destroyObject', + '../Core/Geometry', + '../Core/GeometryAttribute', '../Core/Math', '../Core/Matrix4', '../Core/PrimitiveType', @@ -31,6 +33,8 @@ define([ ComponentDatatype, defaultValue, destroyObject, + Geometry, + GeometryAttribute, CesiumMath, Matrix4, PrimitiveType, @@ -113,9 +117,9 @@ define([ return vertexArray; } - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : [ @@ -124,9 +128,9 @@ define([ 1.0, 1.0, -1.0, 1.0 ] - }, + }), - textureCoordinates : { + textureCoordinates : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : [ @@ -135,12 +139,13 @@ define([ 1.0, 1.0, 0.0, 1.0 ] - } - } - }; + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); - vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, + vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, bufferUsage : BufferUsage.STATIC_DRAW }); diff --git a/Source/Scene/ViewportQuad.js b/Source/Scene/ViewportQuad.js index 0bdebb17f747..a72e3d8eeb6b 100644 --- a/Source/Scene/ViewportQuad.js +++ b/Source/Scene/ViewportQuad.js @@ -8,6 +8,8 @@ define([ '../Core/BoundingRectangle', '../Core/ComponentDatatype', '../Core/PrimitiveType', + '../Core/Geometry', + '../Core/GeometryAttribute', './Material', '../Renderer/BufferUsage', '../Renderer/BlendingState', @@ -24,6 +26,8 @@ define([ BoundingRectangle, ComponentDatatype, PrimitiveType, + Geometry, + GeometryAttribute, Material, BufferUsage, BlendingState, @@ -56,13 +60,10 @@ define([ /** * Determines if the viewport quad primitive will be shown. - *

- * The default is true. - *

* * @type {Boolean} * @default true - */ + */ this.show = true; if (typeof rectangle === 'undefined') { @@ -119,9 +120,9 @@ define([ return vertexArray; } - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : [ @@ -130,9 +131,9 @@ define([ 1.0, 1.0, -1.0, 1.0 ] - }, + }), - textureCoordinates : { + textureCoordinates : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, values : [ @@ -141,12 +142,13 @@ define([ 1.0, 1.0, 0.0, 1.0 ] - } - } - }; + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); - vertexArray = context.createVertexArrayFromMesh({ - mesh : mesh, + vertexArray = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, bufferUsage : BufferUsage.STATIC_DRAW }); diff --git a/Source/Scene/createTangentSpaceDebugPrimitive.js b/Source/Scene/createTangentSpaceDebugPrimitive.js new file mode 100644 index 000000000000..635cf8262586 --- /dev/null +++ b/Source/Scene/createTangentSpaceDebugPrimitive.js @@ -0,0 +1,100 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/DeveloperError', + '../Core/ColorGeometryInstanceAttribute', + '../Core/GeometryInstance', + '../Core/GeometryPipeline', + '../Core/Matrix4', + './Primitive', + './PerInstanceColorAppearance' + ], function( + defaultValue, + DeveloperError, + ColorGeometryInstanceAttribute, + GeometryInstance, + GeometryPipeline, + Matrix4, + Primitive, + PerInstanceColorAppearance) { + "use strict"; + + /** + * Creates a {@link Primitive} to visualize well-known vector vertex attributes: + * normal, binormal, and tangent. Normal + * is red; binormal is green; and tangent is blue. If an attribute is not + * present, it is not drawn. + * + * @exports createTangentSpaceDebugPrimitive + * + * @param {Geometry} options.geometry The Geometry instance with the attribute. + * @param {Number} [options.length=10000.0] The length of each line segment in meters. This can be negative to point the vector in the opposite direction. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates. + * + * @returns {Primitive} A new Primitive instance with geometry for the vectors. + * + * @exception {DeveloperError} options.geometry is required. + * @exception {DeveloperError} options.geometry.attributes.position is required. + * + * @example + * scene.getPrimitives().add(createTangentSpaceDebugPrimitive({ + * geometry : instance.geometry, + * length : 100000.0, + * modelMatrix : instance.modelMatrix + * })); + */ + function createTangentSpaceDebugPrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var instances = []; + + var geometry = options.geometry; + if (typeof geometry === 'undefined') { + throw new DeveloperError('options.geometry is required.'); + } + + var attributes = geometry.attributes; + var modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + var length = defaultValue(options.length, 10000.0); + + if (typeof attributes.normal !== 'undefined') { + instances.push(new GeometryInstance({ + geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'normal', length), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) + }, + modelMatrix : modelMatrix + })); + } + + if (typeof attributes.binormal !== 'undefined') { + instances.push(new GeometryInstance({ + geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'binormal', length), + attributes : { + color : new ColorGeometryInstanceAttribute(0.0, 1.0, 0.0, 1.0) + }, + modelMatrix : modelMatrix + })); + } + + if (typeof attributes.tangent !== 'undefined') { + instances.push(new GeometryInstance({ + geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'tangent', length), + attributes : { + color : new ColorGeometryInstanceAttribute(0.0, 0.0, 1.0, 1.0) + }, + modelMatrix : modelMatrix + })); + } + + return new Primitive({ + geometryInstances : instances, + appearance : new PerInstanceColorAppearance({ + flat : true, + translucent : false + }) + }); + } + + return createTangentSpaceDebugPrimitive; +}); diff --git a/Source/Shaders/Appearances/AllMaterialAppearanceFS.glsl b/Source/Shaders/Appearances/AllMaterialAppearanceFS.glsl new file mode 100644 index 000000000000..a8a2c2043e9f --- /dev/null +++ b/Source/Shaders/Appearances/AllMaterialAppearanceFS.glsl @@ -0,0 +1,31 @@ +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec3 v_tangentEC; +varying vec3 v_binormalEC; +varying vec2 v_st; + +void main() +{ + vec3 positionToEyeEC = -v_positionEC; + mat3 tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(v_normalEC, v_tangentEC, v_binormalEC); + + vec3 normalEC; +#ifdef FACE_FORWARD + normalEC = normalize(faceforward(v_normalEC, vec3(0.0, 0.0, 1.0), -v_normalEC)); +#else + normalEC = normalize(v_normalEC); +#endif + + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.tangentToEyeMatrix = tangentToEyeMatrix; + materialInput.positionToEyeEC = positionToEyeEC; + materialInput.st = v_st; + czm_material material = czm_getMaterial(materialInput); + +#ifdef FLAT + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#else + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); +#endif +} diff --git a/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl new file mode 100644 index 000000000000..59d74a27b25a --- /dev/null +++ b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl @@ -0,0 +1,25 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec3 normal; +attribute vec3 tangent; +attribute vec3 binormal; +attribute vec2 st; + +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec3 v_tangentEC; +varying vec3 v_binormalEC; +varying vec2 v_st; + +void main() +{ + vec4 p = czm_computePosition(); + + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates + v_normalEC = czm_normal * normal; // normal in eye coordinates + v_tangentEC = czm_normal * tangent; // tangent in eye coordinates + v_binormalEC = czm_normal * binormal; // binormal in eye coordinates + v_st = st; + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/Appearances/BasicMaterialAppearanceFS.glsl b/Source/Shaders/Appearances/BasicMaterialAppearanceFS.glsl new file mode 100644 index 000000000000..23dd4f6ff845 --- /dev/null +++ b/Source/Shaders/Appearances/BasicMaterialAppearanceFS.glsl @@ -0,0 +1,25 @@ +varying vec3 v_positionEC; +varying vec3 v_normalEC; + +void main() +{ + vec3 positionToEyeEC = -v_positionEC; + + vec3 normalEC; +#ifdef FACE_FORWARD + normalEC = normalize(faceforward(v_normalEC, vec3(0.0, 0.0, 1.0), -v_normalEC)); +#else + normalEC = normalize(v_normalEC); +#endif + + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.positionToEyeEC = positionToEyeEC; + czm_material material = czm_getMaterial(materialInput); + +#ifdef FLAT + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#else + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); +#endif +} diff --git a/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl new file mode 100644 index 000000000000..6fe9772ba516 --- /dev/null +++ b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl @@ -0,0 +1,16 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec3 normal; + +varying vec3 v_positionEC; +varying vec3 v_normalEC; + +void main() +{ + vec4 p = czm_computePosition(); + + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates + v_normalEC = czm_normal * normal; // normal in eye coordinates + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl new file mode 100644 index 000000000000..8243fe32fcac --- /dev/null +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl @@ -0,0 +1,35 @@ +varying vec3 v_positionMC; +varying vec3 v_positionEC; +varying vec2 v_st; + +void main() +{ + czm_materialInput materialInput; + + vec3 normalEC = czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0)); +#ifdef FACE_FORWARD + normalEC = normalize(faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC)); +#else + normalEC = normalize(normalEC); +#endif + + materialInput.s = v_st.s; + materialInput.st = v_st; + materialInput.str = vec3(v_st, 0.0); + + // Convert tangent space material normal to eye space + materialInput.normalEC = normalEC; + materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC); + + // Convert view vector to world space + vec3 positionToEyeEC = -v_positionEC; + materialInput.positionToEyeEC = positionToEyeEC; + + czm_material material = czm_getMaterial(materialInput); + +#ifdef FLAT + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#else + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); +#endif +} diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl new file mode 100644 index 000000000000..61f12dcb32e2 --- /dev/null +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl @@ -0,0 +1,18 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec2 st; + +varying vec3 v_positionMC; +varying vec3 v_positionEC; +varying vec2 v_st; + +void main() +{ + vec4 p = czm_computePosition(); + + v_positionMC = position3DHigh + position3DLow; // position in model coordinates + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates + v_st = st; + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl new file mode 100644 index 000000000000..c015090f8cdd --- /dev/null +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl @@ -0,0 +1,24 @@ +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec4 v_color; + +void main() +{ + vec3 positionToEyeEC = -v_positionEC; + + vec3 normalEC; +#ifdef FACE_FORWARD + normalEC = normalize(faceforward(v_normalEC, vec3(0.0, 0.0, 1.0), -v_normalEC)); +#else + normalEC = normalize(v_normalEC); +#endif + + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.positionToEyeEC = positionToEyeEC; + czm_material material = czm_getDefaultMaterial(materialInput); + material.diffuse = v_color.rgb; + material.alpha = v_color.a; + + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); +} diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl new file mode 100644 index 000000000000..aa8de58a0ba9 --- /dev/null +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl @@ -0,0 +1,19 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec3 normal; +attribute vec4 color; + +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec4 v_color; + +void main() +{ + vec4 p = czm_computePosition(); + + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates + v_normalEC = czm_normal * normal; // normal in eye coordinates + v_color = color; + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceFS.glsl b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceFS.glsl new file mode 100644 index 000000000000..3649a0883f23 --- /dev/null +++ b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceFS.glsl @@ -0,0 +1,6 @@ +varying vec4 v_color; + +void main() +{ + gl_FragColor = v_color; +} diff --git a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl new file mode 100644 index 000000000000..44c759da268e --- /dev/null +++ b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl @@ -0,0 +1,14 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec4 color; + +varying vec4 v_color; + +void main() +{ + vec4 p = czm_computePosition(); + + v_color = color; + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/Appearances/TexturedMaterialAppearanceFS.glsl b/Source/Shaders/Appearances/TexturedMaterialAppearanceFS.glsl new file mode 100644 index 000000000000..4cb7d18d56df --- /dev/null +++ b/Source/Shaders/Appearances/TexturedMaterialAppearanceFS.glsl @@ -0,0 +1,27 @@ +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec2 v_st; + +void main() +{ + vec3 positionToEyeEC = -v_positionEC; + + vec3 normalEC; +#ifdef FACE_FORWARD + normalEC = normalize(faceforward(v_normalEC, vec3(0.0, 0.0, 1.0), -v_normalEC)); +#else + normalEC = normalize(v_normalEC); +#endif + + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.positionToEyeEC = positionToEyeEC; + materialInput.st = v_st; + czm_material material = czm_getMaterial(materialInput); + +#ifdef FLAT + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#else + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); +#endif +} diff --git a/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl new file mode 100644 index 000000000000..b606f12a0ef1 --- /dev/null +++ b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl @@ -0,0 +1,19 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute vec3 normal; +attribute vec2 st; + +varying vec3 v_positionEC; +varying vec3 v_normalEC; +varying vec2 v_st; + +void main() +{ + vec4 p = czm_computePosition(); + + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates + v_normalEC = czm_normal * normal; // normal in eye coordinates + v_st = st; + + gl_Position = czm_modelViewProjectionRelativeToEye * p; +} diff --git a/Source/Shaders/BillboardCollectionVS.glsl b/Source/Shaders/BillboardCollectionVS.glsl index 008085b83457..286056989917 100644 --- a/Source/Shaders/BillboardCollectionVS.glsl +++ b/Source/Shaders/BillboardCollectionVS.glsl @@ -37,7 +37,7 @@ void main() /////////////////////////////////////////////////////////////////////////// - vec4 p = vec4(czm_translateRelativeToEye(positionHigh, positionLow), 1.0); + vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); vec4 positionEC = czm_modelViewRelativeToEye * p; positionEC = czm_eyeOffset(positionEC, eyeOffset); positionEC.xyz *= show; diff --git a/Source/Shaders/BuiltinFunctions.glsl b/Source/Shaders/BuiltinFunctions.glsl index 7f2cd10a3803..0bff0b47c696 100644 --- a/Source/Shaders/BuiltinFunctions.glsl +++ b/Source/Shaders/BuiltinFunctions.glsl @@ -261,6 +261,32 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate) return q; } +/** + * Creates a matrix that transforms vectors from tangent space to eye space. + * + * @name czm_tangentToEyeSpaceMatrix + * @glslFunction + * + * @param {vec3} normalEC The normal vector in eye coordinates. + * @param {vec3} tangentEC The tangent vector in eye coordinates. + * @param {vec3} binormalEC The binormal vector in eye coordinates. + * + * @returns {mat3} The matrix that transforms from tangent space to eye space. + * + * @example + * mat3 tangentToEye = czm_tangentToEyeSpaceMatrix(normalEC, tangentEC, binormalEC); + * vec3 normal = tangentToEye * texture2D(normalMap, st).xyz; + */ +mat3 czm_tangentToEyeSpaceMatrix(vec3 normalEC, vec3 tangentEC, vec3 binormalEC) +{ + vec3 normal = normalize(normalEC); + vec3 tangent = normalize(tangentEC); + vec3 binormal = normalize(binormalEC); + return mat3(tangent.x, tangent.y, tangent.z, + binormal.x, binormal.y, binormal.z, + normal.x, normal.y, normal.z); +} + /////////////////////////////////////////////////////////////////////////////// /** @@ -732,12 +758,35 @@ const int czm_morphing = 3; * @name czm_columbusViewMorph * @glslFunction */ -vec4 czm_columbusViewMorph(vec3 position2D, vec3 position3D, float time) +vec4 czm_columbusViewMorph(vec4 position2D, vec4 position3D, float time) { // Just linear for now. - vec3 p = mix(position2D, position3D, time); + vec3 p = mix(position2D.xyz, position3D.xyz, time); return vec4(p, 1.0); -} +} + +/** + * Returns a position in model coordinates relative to eye taking into + * account the current scene mode: 3D, 2D, or Columbus view. + *

+ * This uses standard position attributes, position3DHigh, + * position3DLow, position2DHigh, and position2DLow, + * and should be used when writing a vertex shader for an {@link Appearance}. + *

+ * + * @name czm_computePosition + * @glslFunction + * + * @returns {vec4} The position relative to eye. + * + * @example + * vec4 p = czm_computePosition(); + * v_positionEC = (czm_modelViewRelativeToEye * p).xyz; + * gl_Position = czm_modelViewProjectionRelativeToEye * p; + * + * @see czm_translateRelativeToEye + */ +vec4 czm_computePosition(); /////////////////////////////////////////////////////////////////////////////// @@ -1046,20 +1095,21 @@ float czm_latitudeToWebMercatorFraction(float latitude, float southMercatorYLow, * * void main() * { - * vec3 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_modelViewProjectionRelativeToEye * vec4(p, 1.0); + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_modelViewProjectionRelativeToEye * p; * } * * @see czm_modelViewRelativeToEye * @see czm_modelViewProjectionRelativeToEye + * @see czm_computePosition * @see EncodedCartesian3 */ -vec3 czm_translateRelativeToEye(vec3 high, vec3 low) +vec4 czm_translateRelativeToEye(vec3 high, vec3 low) { vec3 highDifference = high - czm_encodedCameraPositionMCHigh; vec3 lowDifference = low - czm_encodedCameraPositionMCLow; - return highDifference + lowDifference; + return vec4(highDifference + lowDifference, 1.0); } /** diff --git a/Source/Shaders/CentralBodyVS.glsl b/Source/Shaders/CentralBodyVS.glsl index c8225ef440cb..5588bf21545d 100644 --- a/Source/Shaders/CentralBodyVS.glsl +++ b/Source/Shaders/CentralBodyVS.glsl @@ -74,8 +74,8 @@ vec4 getPositionMorphingMode(vec3 position3DWC) // We do not do RTC while morphing, so there is potential for jitter. // This is unlikely to be noticable, though. float yPositionFraction = get2DYPositionFraction(); - vec3 position2DWC = vec3(0.0, mix(u_tileExtent.st, u_tileExtent.pq, vec2(textureCoordinates.x, yPositionFraction))); - vec4 morphPosition = czm_columbusViewMorph(position2DWC, position3DWC, czm_morphTime); + vec4 position2DWC = vec4(0.0, mix(u_tileExtent.st, u_tileExtent.pq, vec2(textureCoordinates.x, yPositionFraction)), 1.0); + vec4 morphPosition = czm_columbusViewMorph(position2DWC, vec4(position3DWC, 1.0), czm_morphTime); return czm_modelViewProjection * morphPosition; } diff --git a/Source/Shaders/EllipsoidVS.glsl b/Source/Shaders/EllipsoidVS.glsl index 600a4969b2ba..b6821a8649b7 100644 --- a/Source/Shaders/EllipsoidVS.glsl +++ b/Source/Shaders/EllipsoidVS.glsl @@ -7,7 +7,7 @@ varying vec3 v_positionEC; void main() { // In the vertex data, the cube goes from (-1.0, -1.0, -1.0) to (1.0, 1.0, 1.0) in model coordinates. - // Scale to consider the radii. We could also do this once on the CPU when using the BoxTessellator, + // Scale to consider the radii. We could also do this once on the CPU when using the BoxGeometry, // but doing it here allows us to change the radii without rewriting the vertex data, and // allows all ellipsoids to reuse the same vertex data. vec4 p = vec4(u_radii * position, 1.0); diff --git a/Source/Shaders/PolygonFS.glsl b/Source/Shaders/PolygonFS.glsl deleted file mode 100644 index 9612c99611f3..000000000000 --- a/Source/Shaders/PolygonFS.glsl +++ /dev/null @@ -1,24 +0,0 @@ -varying vec3 v_positionMC; -varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; - -void main() -{ - czm_materialInput materialInput; - - // TODO: Real 1D distance, and better 3D coordinate - materialInput.st = v_textureCoordinates; - materialInput.str = vec3(v_textureCoordinates, 0.0); - - //Convert tangent space material normal to eye space - materialInput.normalEC = normalize(czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0))); - materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC); - - //Convert view vector to world space - vec3 positionToEyeEC = -v_positionEC; - materialInput.positionToEyeEC = positionToEyeEC; - - czm_material material = czm_getMaterial(materialInput); - - gl_FragColor = czm_phong(normalize(positionToEyeEC), material); -} diff --git a/Source/Shaders/PolygonVS.glsl b/Source/Shaders/PolygonVS.glsl deleted file mode 100644 index abf9c436555f..000000000000 --- a/Source/Shaders/PolygonVS.glsl +++ /dev/null @@ -1,37 +0,0 @@ -attribute vec3 position3DHigh; -attribute vec3 position3DLow; -attribute vec2 position2DHigh; -attribute vec2 position2DLow; -attribute vec2 textureCoordinates; - -uniform float u_height; // in meters - -varying vec3 v_positionMC; -varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; - -void main() -{ - vec4 p; - - if (czm_morphTime == 1.0) - { - p = vec4(czm_translateRelativeToEye(position3DHigh, position3DLow), 1.0); - } - else if (czm_morphTime == 0.0) - { - p = vec4(czm_translateRelativeToEye(vec3(u_height, position2DHigh), vec3(u_height, position2DLow)), 1.0); - } - else - { - p = czm_columbusViewMorph( - czm_translateRelativeToEye(vec3(u_height, position2DHigh), vec3(u_height, position2DLow)), - czm_translateRelativeToEye(position3DHigh, position3DLow), - czm_morphTime); - } - - v_positionMC = position3DHigh + position3DLow; // position in model coordinates - v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates - v_textureCoordinates = textureCoordinates; - gl_Position = czm_modelViewProjectionRelativeToEye * p; // position in clip coordinates -} diff --git a/Source/Shaders/PolylineVS.glsl b/Source/Shaders/PolylineVS.glsl index 9e18ac4de13c..3e262cb041d0 100644 --- a/Source/Shaders/PolylineVS.glsl +++ b/Source/Shaders/PolylineVS.glsl @@ -68,15 +68,15 @@ void main() vec4 p, prev, next; if (czm_morphTime == 1.0) { - p = vec4(czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz), 1.0); - prev = vec4(czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz), 1.0); - next = vec4(czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz), 1.0); + p = czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz); + prev = czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz); + next = czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz); } else if (czm_morphTime == 0.0) { - p = vec4(czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy), 1.0); - prev = vec4(czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy), 1.0); - next = vec4(czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy), 1.0); + p = czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy); + prev = czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy); + next = czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy); } else { diff --git a/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js b/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js index 4b4675471364..bcd60e2d9d13 100644 --- a/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js +++ b/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js @@ -208,4 +208,4 @@ define([ }; return BaseLayerPicker; -}); +}); \ No newline at end of file diff --git a/Source/Widgets/BaseLayerPicker/BaseLayerPickerViewModel.js b/Source/Widgets/BaseLayerPicker/BaseLayerPickerViewModel.js index 0ff338148068..bfa565d5fc5f 100644 --- a/Source/Widgets/BaseLayerPicker/BaseLayerPickerViewModel.js +++ b/Source/Widgets/BaseLayerPicker/BaseLayerPickerViewModel.js @@ -156,4 +156,4 @@ define([ }); return BaseLayerPickerViewModel; -}); +}); \ No newline at end of file diff --git a/Specs/Core/BoundingSphereSpec.js b/Specs/Core/BoundingSphereSpec.js index a489fa983268..4db94cf3d270 100644 --- a/Specs/Core/BoundingSphereSpec.js +++ b/Specs/Core/BoundingSphereSpec.js @@ -27,17 +27,19 @@ defineSuite([ /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ var positionsRadius = 1.0; - var positionsCenter = new Cartesian3(1.0, 0.0, 0.0); + var positionsCenter = new Cartesian3(10000001.0, 0.0, 0.0); + + var center = new Cartesian3(10000000.0, 0.0, 0.0); function getPositions() { return [ - new Cartesian3(1, 0, 0), - new Cartesian3(2, 0, 0), - new Cartesian3(0, 0, 0), - new Cartesian3(1, 1, 0), - new Cartesian3(1, -1, 0), - new Cartesian3(1, 0, 1), - new Cartesian3(1, 0, -1) + center.add(new Cartesian3(1, 0, 0)), + center.add(new Cartesian3(2, 0, 0)), + center.add(new Cartesian3(0, 0, 0)), + center.add(new Cartesian3(1, 1, 0)), + center.add(new Cartesian3(1, -1, 0)), + center.add(new Cartesian3(1, 0, 1)), + center.add(new Cartesian3(1, 0, -1)) ]; } @@ -105,6 +107,10 @@ defineSuite([ expect(sphere.radius).toEqual(expectedRadius); }); + it('static clone clones undefined', function() { + expect(BoundingSphere.clone(undefined)).toBe(undefined); + }); + it('equals', function() { var sphere = new BoundingSphere(new Cartesian3(1.0, 2.0, 3.0), 4.0); expect(sphere.equals(new BoundingSphere(new Cartesian3(1.0, 2.0, 3.0), 4.0))).toEqual(true); @@ -285,6 +291,15 @@ defineSuite([ expect(BoundingSphere.fromExtent3D(extent, ellipsoid)).toEqual(expected); }); + it('fromExtent3D with height', function() { + var extent = new Extent(0.1, -0.3, 0.2, -0.4); + var height = 100000.0; + var ellipsoid = Ellipsoid.WGS84; + var points = extent.subsample(ellipsoid, height); + var expected = BoundingSphere.fromPoints(points); + expect(BoundingSphere.fromExtent3D(extent, ellipsoid, height)).toEqual(expected); + }); + it('fromCornerPoints', function() { var sphere = BoundingSphere.fromCornerPoints(new Cartesian3(-1.0, -0.0, 0.0), new Cartesian3(1.0, 0.0, 0.0)); expect(sphere).toEqual(new BoundingSphere(Cartesian3.ZERO, 1.0)); @@ -309,6 +324,27 @@ defineSuite([ }).toThrow(); }); + it('fromEllipsoid', function() { + var ellipsoid = Ellipsoid.WGS84; + var sphere = BoundingSphere.fromEllipsoid(ellipsoid); + expect(sphere.center).toEqual(Cartesian3.ZERO); + expect(sphere.radius).toEqual(ellipsoid.getMaximumRadius()); + }); + + it('fromEllipsoid with a result parameter', function() { + var ellipsoid = Ellipsoid.WGS84; + var sphere = new BoundingSphere(new Cartesian3(1.0, 2.0, 3.0), 4.0); + var result = BoundingSphere.fromEllipsoid(ellipsoid, sphere); + expect(result).toBe(sphere); + expect(result).toEqual(new BoundingSphere(Cartesian3.ZERO, ellipsoid.getMaximumRadius())); + }); + + it('fromEllipsoid throws without ellipsoid', function() { + expect(function() { + BoundingSphere.fromEllipsoid(); + }).toThrow(); + }); + it('sphere on the positive side of a plane', function() { var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5); var normal = Cartesian3.UNIT_X.negate(); @@ -370,6 +406,54 @@ defineSuite([ expect(bs.getPlaneDistances(position, direction)).toEqual(expected); }); + it('projectTo2D', function() { + var positions = getPositions(); + var projection = new GeographicProjection(); + + var positions2D = []; + for (var i = 0; i < positions.length; ++i) { + var position = positions[i]; + var cartographic = projection.getEllipsoid().cartesianToCartographic(position); + positions2D.push(projection.project(cartographic)); + } + + var boundingSphere3D = BoundingSphere.fromPoints(positions); + var boundingSphere2D = boundingSphere3D.projectTo2D(projection); + var actualSphere = BoundingSphere.fromPoints(positions2D); + actualSphere.center = new Cartesian3(actualSphere.center.z, actualSphere.center.x, actualSphere.center.y); + + expect(boundingSphere2D.center).toEqualEpsilon(actualSphere.center, CesiumMath.EPSILON6); + expect(boundingSphere2D.radius).toBeGreaterThan(actualSphere.radius); + }); + + it('projectTo2D with result parameter', function() { + var positions = getPositions(); + var projection = new GeographicProjection(); + var sphere = new BoundingSphere(); + + var positions2D = []; + for (var i = 0; i < positions.length; ++i) { + var position = positions[i]; + var cartographic = projection.getEllipsoid().cartesianToCartographic(position); + positions2D.push(projection.project(cartographic)); + } + + var boundingSphere3D = BoundingSphere.fromPoints(positions); + var boundingSphere2D = boundingSphere3D.projectTo2D(projection, sphere); + var actualSphere = BoundingSphere.fromPoints(positions2D); + actualSphere.center = new Cartesian3(actualSphere.center.z, actualSphere.center.x, actualSphere.center.y); + + expect(boundingSphere2D).toBe(sphere); + expect(boundingSphere2D.center).toEqualEpsilon(actualSphere.center, CesiumMath.EPSILON6); + expect(boundingSphere2D.radius).toBeGreaterThan(actualSphere.radius); + }); + + it('static projectTo2D throws without sphere', function() { + expect(function() { + BoundingSphere.projectTo2D(); + }).toThrow(); + }); + it('static clone returns undefined with no parameter', function() { expect(typeof BoundingSphere.clone()).toEqual('undefined'); }); diff --git a/Specs/Core/BoxGeometrySpec.js b/Specs/Core/BoxGeometrySpec.js new file mode 100644 index 000000000000..ac371e996373 --- /dev/null +++ b/Specs/Core/BoxGeometrySpec.js @@ -0,0 +1,84 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/BoxGeometry', + 'Core/VertexFormat', + 'Core/Cartesian3' + ], function( + BoxGeometry, + VertexFormat, + Cartesian3) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws without minimum corner', function() { + expect(function() { + return new BoxGeometry({ + maximumCorner : new Cartesian3() + }); + }).toThrow(); + }); + + it('constructor throws without maximum corner', function() { + expect(function() { + return new BoxGeometry({ + minimumCorner : new Cartesian3() + }); + }).toThrow(); + }); + + it('constructor creates optimized number of positions for VertexFormat.POSITIONS_ONLY', function() { + var m = new BoxGeometry({ + minimumCorner : new Cartesian3(-1, -2, -3), + maximumCorner : new Cartesian3(1, 2, 3), + vertexFormat : VertexFormat.POSITION_ONLY + }); + + expect(m.attributes.position.values.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(12 * 3); + }); + + it('constructor computes all vertex attributes', function() { + var minimumCorner = new Cartesian3(0, 0, 0); + var maximumCorner = new Cartesian3(1, 1, 1); + var m = new BoxGeometry({ + minimumCorner : minimumCorner, + maximumCorner : maximumCorner, + vertexFormat : VertexFormat.ALL + }); + + expect(m.attributes.position.values.length).toEqual(6 * 4 * 3); + expect(m.attributes.normal.values.length).toEqual(6 * 4 * 3); + expect(m.attributes.tangent.values.length).toEqual(6 * 4 * 3); + expect(m.attributes.binormal.values.length).toEqual(6 * 4 * 3); + expect(m.attributes.st.values.length).toEqual(6 * 4 * 2); + + expect(m.indices.length).toEqual(12 * 3); + + expect(m.boundingSphere.center).toEqual(Cartesian3.ZERO); + expect(m.boundingSphere.radius).toEqual(maximumCorner.magnitude() * 0.5); + }); + + it('fromDimensions throws without dimensions', function() { + expect(function() { + return BoxGeometry.fromDimensions(); + }).toThrow(); + }); + + it('fromDimensions throws with negative dimensions', function() { + expect(function() { + return BoxGeometry.fromDimensions({ + dimensions : new Cartesian3(1, 2, -1) + }); + }).toThrow(); + }); + + it('fromDimensions', function() { + var m = BoxGeometry.fromDimensions({ + dimensions : new Cartesian3(1, 2, 3), + vertexFormat : VertexFormat.POSITION_ONLY + }); + + expect(m.attributes.position.values.length).toEqual(8 * 3); + expect(m.indices.length).toEqual(12 * 3); + }); +}); \ No newline at end of file diff --git a/Specs/Core/BoxTessellatorSpec.js b/Specs/Core/BoxTessellatorSpec.js deleted file mode 100644 index 65abd664f2fe..000000000000 --- a/Specs/Core/BoxTessellatorSpec.js +++ /dev/null @@ -1,36 +0,0 @@ -/*global defineSuite*/ -defineSuite([ - 'Core/BoxTessellator', - 'Core/Cartesian3' - ], function( - BoxTessellator, - Cartesian3) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('compute0', function() { - expect(function() { - return BoxTessellator.compute({ - dimensions : new Cartesian3(1, 2, -1) - }); - }).toThrow(); - }); - - it('compute1', function() { - var m = BoxTessellator.compute({ - dimensions : new Cartesian3(1, 2, 3) - }); - - expect(m.attributes.position.values.length).toEqual(8 * 3); - expect(m.indexLists[0].values.length).toEqual(12 * 3); - }); - - it('compute2', function() { - expect(function() { - return BoxTessellator.compute({ - minimumCorner : new Cartesian3(0, 0, 0), - maximumCorner : new Cartesian3(1, 1, 1) - }); - }).not.toThrow(); - }); -}); \ No newline at end of file diff --git a/Specs/Core/CircleGeometrySpec.js b/Specs/Core/CircleGeometrySpec.js new file mode 100644 index 000000000000..bf9c9166b7ba --- /dev/null +++ b/Specs/Core/CircleGeometrySpec.js @@ -0,0 +1,84 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/CircleGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/VertexFormat' + ], function( + CircleGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without a center', function() { + expect(function() { + return new CircleGeometry({ + radius : 1.0 + }); + }).toThrow(); + }); + + it('throws without a radius', function() { + expect(function() { + return new CircleGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()) + }); + }).toThrow(); + }); + + it('throws with a negative radius', function() { + expect(function() { + return new CircleGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + radius : -1.0 + }); + }).toThrow(); + }); + + it('throws with a negative granularity', function() { + expect(function() { + return new CircleGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + radius : 1.0, + granularity : -1.0 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 24); + expect(m.indices.length).toEqual(3 * 34); + expect(m.boundingSphere.radius).toEqual(1); + }); + + it('compute all vertex attributes', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new CircleGeometry({ + vertexFormat : VertexFormat.ALL, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + radius : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 24); + expect(m.attributes.st.values.length).toEqual(2 * 24); + expect(m.attributes.normal.values.length).toEqual(3 * 24); + expect(m.attributes.tangent.values.length).toEqual(3 * 24); + expect(m.attributes.binormal.values.length).toEqual(3 * 24); + expect(m.indices.length).toEqual(3 * 34); + }); +}); diff --git a/Specs/Core/ColorGeometryInstanceAttributeSpec.js b/Specs/Core/ColorGeometryInstanceAttributeSpec.js new file mode 100644 index 000000000000..ca38ce763e4f --- /dev/null +++ b/Specs/Core/ColorGeometryInstanceAttributeSpec.js @@ -0,0 +1,51 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/ColorGeometryInstanceAttribute', + 'Core/Color', + 'Core/ComponentDatatype' + ], function( + ColorGeometryInstanceAttribute, + Color, + ComponentDatatype) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var attribute = new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 0.5); + expect(attribute.componentDatatype).toEqual(ComponentDatatype.UNSIGNED_BYTE); + expect(attribute.componentsPerAttribute).toEqual(4); + expect(attribute.normalize).toEqual(true); + + var value = new Uint8Array(new Color(1.0, 1.0, 0.0, 0.5).toBytes()); + expect(attribute.value).toEqual(value); + }); + + it('fromColor', function() { + var color = Color.AQUA; + var attribute = ColorGeometryInstanceAttribute.fromColor(color); + expect(attribute.componentDatatype).toEqual(ComponentDatatype.UNSIGNED_BYTE); + expect(attribute.componentsPerAttribute).toEqual(4); + expect(attribute.normalize).toEqual(true); + + var value = new Uint8Array(color.toBytes()); + expect(attribute.value).toEqual(value); + }); + + it('fromColor throws without color', function() { + expect(function() { + ColorGeometryInstanceAttribute.fromColor(); + }).toThrow(); + }); + + it('toValue', function() { + var color = Color.AQUA; + expect(ColorGeometryInstanceAttribute.toValue(color)).toEqual(new Uint8Array(color.toBytes())); + }); + + it('toValue throws without a color', function() { + expect(function() { + ColorGeometryInstanceAttribute.toValue(); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/CubeMapEllipsoidTessellatorSpec.js b/Specs/Core/CubeMapEllipsoidTessellatorSpec.js deleted file mode 100644 index d0a9cf2161c6..000000000000 --- a/Specs/Core/CubeMapEllipsoidTessellatorSpec.js +++ /dev/null @@ -1,43 +0,0 @@ -/*global defineSuite*/ -defineSuite([ - 'Core/CubeMapEllipsoidTessellator', - 'Core/Cartesian3', - 'Core/Ellipsoid', - 'Core/Math' - ], function( - CubeMapEllipsoidTessellator, - Cartesian3, - Ellipsoid, - CesiumMath) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('compute0', function() { - expect(function() { - return CubeMapEllipsoidTessellator.compute(Ellipsoid.UNIT_SPHERE, -1); - }).toThrow(); - }); - - it('compute1', function() { - var m = CubeMapEllipsoidTessellator.compute(Ellipsoid.UNIT_SPHERE, 1); - - expect(m.attributes.position.values.length).toEqual(3 * 8); - expect(m.indexLists[0].values.length).toEqual(12 * 3); - }); - - it('compute2', function() { - var m = CubeMapEllipsoidTessellator.compute(Ellipsoid.UNIT_SPHERE, 2); - - expect(m.attributes.position.values.length).toEqual(3 * (8 + 6 + 12)); - expect(m.indexLists[0].values.length).toEqual(2 * 3 * 4 * 6); - }); - - it('compute3', function() { - var m = CubeMapEllipsoidTessellator.compute(Ellipsoid.UNIT_SPHERE, 3); - - var position = m.attributes.position.values; - for ( var i = 0; i < position.length; i += 3) { - expect(1.0).toEqualEpsilon(new Cartesian3(position[i], position[i + 1], position[i + 2]).magnitude(), CesiumMath.EPSILON10); - } - }); -}); \ No newline at end of file diff --git a/Specs/Core/EllipseGeometrySpec.js b/Specs/Core/EllipseGeometrySpec.js new file mode 100644 index 000000000000..13ac2d333fd6 --- /dev/null +++ b/Specs/Core/EllipseGeometrySpec.js @@ -0,0 +1,109 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/EllipseGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/VertexFormat' + ], function( + EllipseGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without a center', function() { + expect(function() { + return new EllipseGeometry({ + semiMajorAxis : 1.0, + semiMinorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws without a semiMajorAxis', function() { + expect(function() { + return new EllipseGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMinorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws without a semiMinorAxis', function() { + expect(function() { + return new EllipseGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0 + }); + }).toThrow(); + }); + + it('throws with a negative axis', function() { + expect(function() { + return new EllipseGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : -1.0 + }); + }).toThrow(); + }); + + it('throws with a negative granularity', function() { + expect(function() { + return new EllipseGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : 1.0, + granularity : -1.0 + }); + }).toThrow(); + }); + + it('throws when semiMajorAxis is less than the semiMajorAxis', function() { + expect(function() { + return new EllipseGeometry({ + center : Ellipsoid.WGS84.cartographicToCartesian(new Cartographic()), + semiMajorAxis : 1.0, + semiMinorAxis : 2.0 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new EllipseGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + semiMajorAxis : 1.0, + semiMinorAxis : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 24); + expect(m.indices.length).toEqual(3 * 34); + expect(m.boundingSphere.radius).toEqual(1); + }); + + it('compute all vertex attributes', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = new EllipseGeometry({ + vertexFormat : VertexFormat.ALL, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(new Cartographic()), + granularity : 0.75, + semiMajorAxis : 1.0, + semiMinorAxis : 1.0 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 24); + expect(m.attributes.st.values.length).toEqual(2 * 24); + expect(m.attributes.normal.values.length).toEqual(3 * 24); + expect(m.attributes.tangent.values.length).toEqual(3 * 24); + expect(m.attributes.binormal.values.length).toEqual(3 * 24); + expect(m.indices.length).toEqual(3 * 34); + }); +}); diff --git a/Specs/Core/EllipsoidGeometrySpec.js b/Specs/Core/EllipsoidGeometrySpec.js new file mode 100644 index 000000000000..40434b69c0d8 --- /dev/null +++ b/Specs/Core/EllipsoidGeometrySpec.js @@ -0,0 +1,73 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/EllipsoidGeometry', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + EllipsoidGeometry, + Cartesian3, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws with invalid numberOfPartitions', function() { + expect(function() { + return new EllipsoidGeometry({ + numberOfPartitions : -1 + }); + }).toThrow(); + }); + + it('computes positions', function() { + var m = new EllipsoidGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + numberOfPartitions : 1 + }); + + expect(m.attributes.position.values.length).toEqual(3 * 8); + expect(m.indices.length).toEqual(12 * 3); + expect(m.boundingSphere.radius).toEqual(1); + }); + + it('compute all vertex attributes', function() { + var m = new EllipsoidGeometry({ + vertexFormat : VertexFormat.ALL, + numberOfPartitions : 2 + }); + + expect(m.attributes.position.values.length).toEqual(3 * (8 + 6 + 12)); + expect(m.attributes.st.values.length).toEqual(2 * (8 + 6 + 12)); + expect(m.attributes.normal.values.length).toEqual(3 * (8 + 6 + 12)); + expect(m.attributes.tangent.values.length).toEqual(3 * (8 + 6 + 12)); + expect(m.attributes.binormal.values.length).toEqual(3 * (8 + 6 + 12)); + expect(m.indices.length).toEqual(2 * 3 * 4 * 6); + }); + + it('computes attributes for a unit sphere', function() { + var m = new EllipsoidGeometry({ + vertexFormat : VertexFormat.ALL, + numberOfPartitions : 3 + }); + + var positions = m.attributes.position.values; + var normals = m.attributes.normal.values; + var tangents = m.attributes.tangent.values; + var binormals = m.attributes.binormal.values; + + for ( var i = 0; i < positions.length; i += 3) { + var position = Cartesian3.fromArray(positions, i); + var normal = Cartesian3.fromArray(normals, i); + var tangent = Cartesian3.fromArray(tangents, i); + var binormal = Cartesian3.fromArray(binormals, i); + + expect(position.magnitude()).toEqualEpsilon(1.0, CesiumMath.EPSILON10); + expect(normal).toEqualEpsilon(position.normalize(), CesiumMath.EPSILON7); + expect(Cartesian3.dot(Cartesian3.UNIT_Z, tangent)).not.toBeLessThan(0.0); + expect(binormal).toEqualEpsilon(Cartesian3.cross(normal, tangent), CesiumMath.EPSILON7); + } + }); +}); \ No newline at end of file diff --git a/Specs/Core/ExtentGeometrySpec.js b/Specs/Core/ExtentGeometrySpec.js new file mode 100644 index 000000000000..86a0abc83495 --- /dev/null +++ b/Specs/Core/ExtentGeometrySpec.js @@ -0,0 +1,119 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/ExtentGeometry', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/Extent', + 'Core/GeographicProjection', + 'Core/Math', + 'Core/Matrix2', + 'Core/VertexFormat' + ], function( + ExtentGeometry, + Cartesian3, + Ellipsoid, + Extent, + GeographicProjection, + CesiumMath, + Matrix2, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('computes positions', function() { + var extent = new Extent(-2.0, -1.0, 0.0, 1.0); + var m = new ExtentGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + extent : extent, + granularity : 1.0 + }); + var positions = m.attributes.position.values; + var length = positions.length; + + expect(positions.length).toEqual(9 * 3); + expect(m.indices.length).toEqual(8 * 3); + + var expectedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); + var expectedSECorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getSoutheast()); + expect(new Cartesian3(positions[0], positions[1], positions[2])).toEqual(expectedNWCorner); + expect(new Cartesian3(positions[length-3], positions[length-2], positions[length-1])).toEqual(expectedSECorner); + }); + + it('computes all attributes', function() { + var m = new ExtentGeometry({ + vertexFormat : VertexFormat.ALL, + extent : new Extent(-2.0, -1.0, 0.0, 1.0), + granularity : 1.0 + }); + expect(m.attributes.position.values.length).toEqual(9 * 3); + expect(m.attributes.st.values.length).toEqual(9 * 2); + expect(m.attributes.normal.values.length).toEqual(9 * 3); + expect(m.attributes.tangent.values.length).toEqual(9 * 3); + expect(m.attributes.binormal.values.length).toEqual(9 * 3); + expect(m.indices.length).toEqual(8 * 3); + }); + + it('compute positions with rotation', function() { + var extent = new Extent(-1, -1, 1, 1); + var angle = CesiumMath.PI_OVER_TWO; + var m = new ExtentGeometry({ + vertexFormat : VertexFormat.POSITIONS_ONLY, + extent: extent, + rotation: angle, + granularity : 1.0 + }); + var positions = m.attributes.position.values; + var length = positions.length; + + expect(length).toEqual(9 * 3); + expect(m.indices.length).toEqual(8 * 3); + + var unrotatedSECorner = extent.getSoutheast(); + var projection = new GeographicProjection(); + var projectedSECorner = projection.project(unrotatedSECorner); + var rotation = Matrix2.fromRotation(angle); + var rotatedSECornerCartographic = projection.unproject(rotation.multiplyByVector(projectedSECorner)); + var rotatedSECorner = Ellipsoid.WGS84.cartographicToCartesian(rotatedSECornerCartographic); + var actual = new Cartesian3(positions[length-3], positions[length-2], positions[length-1]); + expect(actual).toEqualEpsilon(rotatedSECorner, CesiumMath.EPSILON6); + }); + + it('compute vertices with PI rotation', function() { + var extent = new Extent(-1, -1, 1, 1); + var m = new ExtentGeometry({ + extent: extent, + rotation: CesiumMath.PI, + granularity : 1.0 + }); + var positions = m.attributes.position.values; + var length = positions.length; + + expect(length).toEqual(9 * 3); + expect(m.indices.length).toEqual(8 * 3); + + var unrotatedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); + var unrotatedSECorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getSoutheast()); + + var actual = new Cartesian3(positions[0], positions[1], positions[2]); + expect(actual).toEqualEpsilon(unrotatedSECorner, CesiumMath.EPSILON8); + + actual = new Cartesian3(positions[length-3], positions[length-2], positions[length-1]); + expect(actual).toEqualEpsilon(unrotatedNWCorner, CesiumMath.EPSILON8); + }); + + it('throws without extent', function() { + expect(function() { + return new ExtentGeometry({}); + }).toThrow(); + }); + + it('throws if rotated extent is invalid', function() { + expect(function() { + return new ExtentGeometry({ + extent: new Extent(-CesiumMath.PI_OVER_TWO, 1, CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO), + rotation: CesiumMath.PI_OVER_TWO + }); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/ExtentSpec.js b/Specs/Core/ExtentSpec.js index ed6c4fc1430f..8c2d55718420 100644 --- a/Specs/Core/ExtentSpec.js +++ b/Specs/Core/ExtentSpec.js @@ -36,6 +36,34 @@ defineSuite([ expect(extent.north).toEqual(north); }); + it('fromDegrees produces expected values.', function() { + var west = -10.0; + var south = -20.0; + var east = 10.0; + var north = 20.0; + + var extent = Extent.fromDegrees(west, south, east, north); + expect(extent.west).toEqual(CesiumMath.toRadians(west)); + expect(extent.south).toEqual(CesiumMath.toRadians(south)); + expect(extent.east).toEqual(CesiumMath.toRadians(east)); + expect(extent.north).toEqual(CesiumMath.toRadians(north)); + }); + + it('fromDegrees works with a result parameter.', function() { + var west = -10.0; + var south = -20.0; + var east = 10.0; + var north = 20.0; + + var result = new Extent(); + var extent = Extent.fromDegrees(west, south, east, north, result); + expect(result).toBe(extent); + expect(extent.west).toEqual(CesiumMath.toRadians(west)); + expect(extent.south).toEqual(CesiumMath.toRadians(south)); + expect(extent.east).toEqual(CesiumMath.toRadians(east)); + expect(extent.north).toEqual(CesiumMath.toRadians(north)); + }); + it('fromCartographicArray produces expected values.', function() { var minLon = new Cartographic(-0.1, 0.3, 0.0); var minLat = new Cartographic(0.0, -0.2, 0.0); @@ -87,6 +115,10 @@ defineSuite([ expect(returnedResult).toBe(extent); }); + it('clone works without extent', function() { + expect(Extent.clone()).not.toBeDefined(); + }); + it('Equals works in all cases', function() { var extent = new Extent(0.1, 0.2, 0.3, 0.4); expect(extent.equals(new Extent(0.1, 0.2, 0.3, 0.4))).toEqual(true); @@ -97,6 +129,16 @@ defineSuite([ expect(extent.equals(undefined)).toEqual(false); }); + it('Static equals works in all cases', function() { + var extent = new Extent(0.1, 0.2, 0.3, 0.4); + expect(Extent.equals(extent, new Extent(0.1, 0.2, 0.3, 0.4))).toEqual(true); + expect(Extent.equals(extent, new Extent(0.5, 0.2, 0.3, 0.4))).toEqual(false); + expect(Extent.equals(extent, new Extent(0.1, 0.5, 0.3, 0.4))).toEqual(false); + expect(Extent.equals(extent, new Extent(0.1, 0.2, 0.5, 0.4))).toEqual(false); + expect(Extent.equals(extent, new Extent(0.1, 0.2, 0.3, 0.5))).toEqual(false); + expect(Extent.equals(extent, undefined)).toEqual(false); + }); + it('Equals epsilon works in all cases', function() { var extent = new Extent(0.1, 0.2, 0.3, 0.4); expect(extent.equalsEpsilon(new Extent(0.1, 0.2, 0.3, 0.4), 0.0)).toEqual(true); @@ -341,7 +383,7 @@ defineSuite([ var extent = new Extent(west, south, east, north); var cartesian0 = new Cartesian3(); var results = [cartesian0]; - var returnedResult = extent.subsample(Ellipsoid.WGS84, results); + var returnedResult = extent.subsample(Ellipsoid.WGS84, 0.0, results); expect(results).toBe(returnedResult); expect(results[0]).toBe(cartesian0); expect(returnedResult).toEqual([Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()), @@ -385,6 +427,30 @@ defineSuite([ expect(cartographic5.longitude).toEqualEpsilon(east, CesiumMath.EPSILON16); }); + it('subsample works at a height above the ellipsoid', function() { + var west = 0.1; + var south = -0.3; + var east = 0.2; + var north = -0.4; + var extent = new Extent(west, south, east, north); + var height = 100000.0; + var returnedResult = extent.subsample(Ellipsoid.WGS84, height); + + var nw = extent.getNorthwest(); + nw.height = height; + var ne = extent.getNortheast(); + ne.height = height; + var se = extent.getSoutheast(); + se.height = height; + var sw = extent.getSouthwest(); + sw.height = height; + + expect(returnedResult).toEqual([Ellipsoid.WGS84.cartographicToCartesian(nw), + Ellipsoid.WGS84.cartographicToCartesian(ne), + Ellipsoid.WGS84.cartographicToCartesian(se), + Ellipsoid.WGS84.cartographicToCartesian(sw)]); + }); + it('equalsEpsilon throws with no epsilon', function() { var extent = new Extent(west, south, east, north); var other = new Extent(); diff --git a/Specs/Core/ExtentTessellatorSpec.js b/Specs/Core/ExtentTessellatorSpec.js deleted file mode 100644 index 1fe1d40e2c49..000000000000 --- a/Specs/Core/ExtentTessellatorSpec.js +++ /dev/null @@ -1,219 +0,0 @@ -/*global defineSuite*/ -defineSuite([ - 'Core/ExtentTessellator', - 'Core/Extent', - 'Core/Ellipsoid', - 'Core/Cartesian3', - 'Core/Math' - ], function( - ExtentTessellator, - Extent, - Ellipsoid, - Cartesian3, - CesiumMath) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('compute 0', function() { - var m = ExtentTessellator.compute({ - extent : new Extent(-2.0, -1.0, 0.0, 1.0), - granularity : 1.0 - }); - expect(m.attributes.position.values.length).toEqual(9 * 3); - expect(typeof m.attributes.textureCoordinates === 'undefined').toEqual(true); - expect(m.indexLists[0].values.length).toEqual(8 * 3); - }); - - it('compute 1', function() { - var m = ExtentTessellator.compute({ - extent : new Extent(-2.0, -1.0, 0.0, 1.0), - granularity : 1.0, - generateTextureCoordinates : true - }); - expect(m.attributes.position.values.length).toEqual(9 * 3); - expect(m.attributes.textureCoordinates.values.length).toEqual(9 * 2); - expect(m.indexLists[0].values.length).toEqual(8 * 3); - }); - - it('compute returns undefined if rotation makes extent invalid', function() { - expect(typeof ExtentTessellator.compute({ - extent : new Extent(-CesiumMath.PI, -1.0, 0.0, 1.0), - rotation: CesiumMath.PI_OVER_TWO, - granularity : 1.0, - generateTextureCoordinates : true - }) === 'undefined').toEqual(true); - }); - - it('computeBuffers 0', function() { - var buffers = ExtentTessellator.computeBuffers({ - extent : new Extent(-2.0, -1.0, 0.0, 1.0), - granularity : 1.0 - }); - - expect(buffers.positions.length).toEqual(9 * 3); - expect(buffers.indices.length).toEqual(8 * 3); - }); - - it('computeBuffers 1', function() { - var buffers = ExtentTessellator.computeBuffers({ - extent : new Extent(-2.0, -1.0, 0.0, 1.0), - granularity : 1.0, - generateTextureCoordinates : true - }); - - expect(buffers.positions.length).toEqual(9 * 3); - expect(buffers.textureCoordinates.length).toEqual(9 * 2); - expect(buffers.indices.length).toEqual(8 * 3); - }); - - it('computeBuffers 2', function() { - var buffers = ExtentTessellator.computeBuffers({ - extent : new Extent(-2.0, -1.0, 0.0, 1.0), - granularity : 1.0, - generateTextureCoordinates : true, - interleaveTextureCoordinates : true - }); - - expect(buffers.vertices.length).toEqual(9 * 3 + 9 * 2); - expect(buffers.indices.length).toEqual(8 * 3); - }); - - it('compute vertices', function() { - var extent = new Extent(-CesiumMath.PI, -CesiumMath.PI_OVER_TWO, CesiumMath.PI, CesiumMath.PI_OVER_TWO); - var description = { - extent: extent, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - var length = description.vertices.length; - var expectedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); - var expectedSECorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getSoutheast()); - expect(new Cartesian3(description.vertices[0], description.vertices[1], description.vertices[2])).toEqual(expectedNWCorner); - expect(new Cartesian3(description.vertices[length-3], description.vertices[length-2], description.vertices[length-1])).toEqual(expectedSECorner); - }); - - it('compute vertices with rotation', function() { - var extent = new Extent(-1, -1, 1, 1); - var description = { - extent: extent, - rotation: CesiumMath.PI_OVER_TWO, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - var length = description.vertices.length; - expect(length).toEqual(9 * 3); - expect(description.indices.length).toEqual(8 * 3); - var unrotatedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); - var unrotatedSECorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getSoutheast()); - expect(new Cartesian3(description.vertices[0], description.vertices[1], description.vertices[2])).not.toEqual(unrotatedNWCorner); - expect(new Cartesian3(description.vertices[length-3], description.vertices[length-2], description.vertices[length-1])).not.toEqual(unrotatedSECorner); - }); - - it('compute vertices with PI rotation', function() { - var extent = new Extent(-1, -1, 1, 1); - var description = { - extent: extent, - rotation: CesiumMath.PI, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - var length = description.vertices.length; - expect(length).toEqual(9 * 3); - expect(description.indices.length).toEqual(8 * 3); - var unrotatedNWCorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getNorthwest()); - var unrotatedSECorner = Ellipsoid.WGS84.cartographicToCartesian(extent.getSoutheast()); - expect(new Cartesian3(description.vertices[0], description.vertices[1], description.vertices[2])).toEqualEpsilon(unrotatedSECorner, CesiumMath.EPSILON8); - expect(new Cartesian3(description.vertices[length-3], description.vertices[length-2], description.vertices[length-1])).toEqualEpsilon(unrotatedNWCorner, CesiumMath.EPSILON8); - }); - - it('compute vertices has empty indices and vertices if rotated extent crosses north pole (NE Corner)', function() { - var extent = new Extent(-CesiumMath.PI_OVER_TWO, 1, CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); - var description = { - extent: extent, - rotation: CesiumMath.PI_OVER_TWO, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - expect(description.vertices.length).toEqual(0); - expect(description.indices.length).toEqual(0); - }); - - it('compute vertices has empty indices and vertices if rotated extent crosses south pole (NW Corner)', function() { - var extent = new Extent(-CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO, -1); - var description = { - extent : new Extent(-CesiumMath.PI_OVER_TWO, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO, -1), - rotation: CesiumMath.PI_OVER_TWO, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - expect(description.vertices.length).toEqual(0); - expect(description.indices.length).toEqual(0); - }); - - it('compute vertices has empty indices and vertices if rotated extent crosses IDL (SW Corner)', function() { - var extent = new Extent(-CesiumMath.PI, 0, -3, 0.3); - var description = { - extent: extent, - rotation: -CesiumMath.PI_OVER_TWO, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - expect(description.vertices.length).toEqual(0); - expect(description.indices.length).toEqual(0); - }); - - it('compute vertices has empty indices and vertices if rotated extent crosses IDL (SE Corner)', function() { - var extent = new Extent(3, 0, CesiumMath.PI, 0.3); - var description = { - extent: extent, - rotation: 0.1, - width: Math.ceil(extent.east - extent.west) + 1, - height: Math.ceil(extent.north - extent.south) + 1, - radiiSquared: Ellipsoid.WGS84.getRadiiSquared(), - relativeToCenter: Cartesian3.ZERO, - surfaceHeight: 0, - vertices: [], - indices: [] - }; - ExtentTessellator.computeVertices(description); - expect(description.vertices.length).toEqual(0); - expect(description.indices.length).toEqual(0); - }); - -}); diff --git a/Specs/Core/GeometryAttributeSpec.js b/Specs/Core/GeometryAttributeSpec.js new file mode 100644 index 000000000000..f1d8d108da8d --- /dev/null +++ b/Specs/Core/GeometryAttributeSpec.js @@ -0,0 +1,82 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GeometryAttribute', + 'Core/ComponentDatatype' + ], function( + GeometryAttribute, + ComponentDatatype) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var color = new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true, + values : new Uint8Array([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255 + ]) + }); + + expect(color.componentDatatype).toEqual(ComponentDatatype.UNSIGNED_BYTE); + expect(color.componentsPerAttribute).toEqual(4); + expect(color.normalize).toEqual(true); + expect(color.values).toEqual([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255 + ]); + }); + + it('constructor throws without componentDatatype', function() { + expect(function() { + return new GeometryAttribute({ + componentsPerAttribute : 4, + values : new Uint8Array([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255 + ]) + }); + }).toThrow(); + }); + + it('constructor throws without componentsPerAttribute', function() { + expect(function() { + return new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + values : new Uint8Array([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255 + ]) + }); + }).toThrow(); + }); + + it('constructor throws when componentsPerAttribute is less than 1 or greater than 4', function() { + expect(function() { + return new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 7, + values : new Uint8Array([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255 + ]) + }); + }).toThrow(); + }); + + it('constructor throws without values', function() { + expect(function() { + return new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4 + }); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/GeometryInstanceAttributeSpec.js b/Specs/Core/GeometryInstanceAttributeSpec.js new file mode 100644 index 000000000000..230286618ed1 --- /dev/null +++ b/Specs/Core/GeometryInstanceAttributeSpec.js @@ -0,0 +1,62 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GeometryInstanceAttribute', + 'Core/ComponentDatatype' + ], function( + GeometryInstanceAttribute, + ComponentDatatype) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var color = new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true, + value : new Uint8Array([255, 255, 0, 255]) + }); + + expect(color.componentDatatype).toEqual(ComponentDatatype.UNSIGNED_BYTE); + expect(color.componentsPerAttribute).toEqual(4); + expect(color.normalize).toEqual(true); + expect(color.value).toEqual([255, 255, 0, 255]); + }); + + it('constructor throws without componentDatatype', function() { + expect(function() { + return new GeometryInstanceAttribute({ + componentsPerAttribute : 4, + value : new Uint8Array([255, 255, 0, 255]) + }); + }).toThrow(); + }); + + it('constructor throws without componentsPerAttribute', function() { + expect(function() { + return new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + value : new Uint8Array([255, 255, 0, 255]) + }); + }).toThrow(); + }); + + it('constructor throws when componentsPerAttribute is less than 1 or greater than 4', function() { + expect(function() { + return new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 7, + value : new Uint8Array([255, 255, 0, 255]) + }); + }).toThrow(); + }); + + it('constructor throws without values', function() { + expect(function() { + return new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4 + }); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/GeometryInstanceSpec.js b/Specs/Core/GeometryInstanceSpec.js new file mode 100644 index 000000000000..152e5f1e89b1 --- /dev/null +++ b/Specs/Core/GeometryInstanceSpec.js @@ -0,0 +1,70 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GeometryInstance', + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/GeometryInstanceAttribute', + 'Core/ComponentDatatype', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/PrimitiveType', + 'Core/Matrix4' + ], function( + GeometryInstance, + Geometry, + GeometryAttribute, + GeometryInstanceAttribute, + ComponentDatatype, + BoundingSphere, + Cartesian3, + PrimitiveType, + Matrix4) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0) + }); + var modelMatrix = Matrix4.multiplyByTranslation(Matrix4.IDENTITY, new Cartesian3(0.0, 0.0, 9000000.0)); + var attributes = { + color : new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true, + value : new Uint8Array([255, 255, 0, 255]) + }) + }; + var instance = new GeometryInstance({ + geometry : geometry, + modelMatrix : modelMatrix, + id : 'geometry', + attributes : attributes + }); + + expect(instance.geometry).toBe(geometry); + expect(instance.modelMatrix).toEqual(modelMatrix); + expect(instance.id).toEqual('geometry'); + expect(attributes).toBe(attributes); + }); + + it('constructor throws without geometry', function() { + expect(function() { + return new GeometryInstance(); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/GeometryPipelineSpec.js b/Specs/Core/GeometryPipelineSpec.js new file mode 100644 index 000000000000..83ae56dfaaf4 --- /dev/null +++ b/Specs/Core/GeometryPipelineSpec.js @@ -0,0 +1,2090 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GeometryPipeline', + 'Core/PrimitiveType', + 'Core/ComponentDatatype', + 'Core/EllipsoidGeometry', + 'Core/Ellipsoid', + 'Core/Cartesian3', + 'Core/EncodedCartesian3', + 'Core/Matrix4', + 'Core/Tipsify', + 'Core/GeographicProjection', + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/GeometryInstance', + 'Core/VertexFormat', + 'Core/Math', + 'Core/BoundingSphere' + ], function( + GeometryPipeline, + PrimitiveType, + ComponentDatatype, + EllipsoidGeometry, + Ellipsoid, + Cartesian3, + EncodedCartesian3, + Matrix4, + Tipsify, + GeographicProjection, + Geometry, + GeometryAttribute, + GeometryInstance, + VertexFormat, + CesiumMath, + BoundingSphere) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('converts triangles to wireframe in place', function() { + var geometry = GeometryPipeline.toWireframe(new Geometry({ + attributes : {}, + indices : [0, 1, 2, 3, 4, 5], + primitiveType : PrimitiveType.TRIANGLES + })); + + expect(geometry.primitiveType).toEqual(PrimitiveType.LINES); + + var v = geometry.indices; + expect(v.length).toEqual(12); + + expect(v[0]).toEqual(0); + expect(v[1]).toEqual(1); + expect(v[2]).toEqual(1); + expect(v[3]).toEqual(2); + expect(v[4]).toEqual(2); + expect(v[5]).toEqual(0); + + expect(v[6]).toEqual(3); + expect(v[7]).toEqual(4); + expect(v[8]).toEqual(4); + expect(v[9]).toEqual(5); + expect(v[10]).toEqual(5); + expect(v[11]).toEqual(3); + }); + + it('converts a triangle fan to wireframe in place', function() { + var geometry = GeometryPipeline.toWireframe(new Geometry({ + attributes : {}, + indices : [0, 1, 2, 3], + primitiveType : PrimitiveType.TRIANGLE_FAN + })); + + expect(geometry.primitiveType).toEqual(PrimitiveType.LINES); + + var v = geometry.indices; + expect(v.length).toEqual(12); + + expect(v[0]).toEqual(0); + expect(v[1]).toEqual(1); + expect(v[2]).toEqual(1); + expect(v[3]).toEqual(2); + expect(v[4]).toEqual(2); + expect(v[5]).toEqual(0); + + expect(v[6]).toEqual(0); + expect(v[7]).toEqual(2); + expect(v[8]).toEqual(2); + expect(v[9]).toEqual(3); + expect(v[10]).toEqual(3); + expect(v[11]).toEqual(0); + }); + + it('converts a triangle strip to wireframe in place', function() { + var geometry = GeometryPipeline.toWireframe(new Geometry({ + attributes : {}, + indices : [0, 1, 2, 3], + primitiveType : PrimitiveType.TRIANGLE_STRIP + })); + + expect(geometry.primitiveType).toEqual(PrimitiveType.LINES); + + var v = geometry.indices; + expect(v.length).toEqual(12); + + expect(v[0]).toEqual(0); + expect(v[1]).toEqual(1); + expect(v[2]).toEqual(1); + expect(v[3]).toEqual(2); + expect(v[4]).toEqual(2); + expect(v[5]).toEqual(0); + + expect(v[6]).toEqual(2); + expect(v[7]).toEqual(3); + expect(v[8]).toEqual(3); + expect(v[9]).toEqual(1); + expect(v[10]).toEqual(1); + expect(v[11]).toEqual(2); + }); + + it('toWireframe throws without a geometry', function() { + expect(function() { + GeometryPipeline.toWireframe(undefined); + }).toThrow(); + }); + + it('toWireframe throws when primitiveType is not a triangle type', function() { + expect(function() { + GeometryPipeline.toWireframe(new Geometry({ + attributes : {}, + indices : [], + primitiveType : PrimitiveType.POINTS + })); + }).toThrow(); + }); + + it('createLineSegmentsForVectors', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0] + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0] + }) + }, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0) + }); + var lines = GeometryPipeline.createLineSegmentsForVectors(geometry, 'normal', 1.0); + var linePositions = [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0]; + + expect(lines.attributes).toBeDefined(); + expect(lines.attributes.position).toBeDefined(); + expect(lines.attributes.position.values).toEqual(linePositions); + expect(lines.primitiveType).toEqual(PrimitiveType.LINES); + expect(lines.boundingSphere.center).toEqual(geometry.boundingSphere.center); + expect(lines.boundingSphere.radius).toEqual(geometry.boundingSphere.radius + 1.0); + }); + + it('createLineSegmentsForVectors throws without geometry', function() { + expect(function() { + GeometryPipeline.createLineSegmentsForVectors(); + }).toThrow(); + }); + + it('createLineSegmentsForVectors throws without geometry.attributes.position', function() { + expect(function() { + GeometryPipeline.createLineSegmentsForVectors(); + }).toThrow(); + }); + + it('createLineSegmentsForVectors throws when geometry.attributes does not have an attributeName property', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.createLineSegmentsForVectors(geometry, 'binormal'); + }).toThrow(); + }); + + it('creates attribute indices', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [] + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [] + }), + color : new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + values : [] + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + var indices = GeometryPipeline.createAttributeIndices(geometry); + + var validIndices = [0, 1, 2]; + expect(validIndices).toContain(indices.position); + expect(validIndices).toContain(indices.normal); + expect(validIndices).toContain(indices.color); + expect(indices.position).not.toEqual(indices.normal); + expect(indices.position).not.toEqual(indices.color); + }); + + it('createAttributeIndices throws without a geometry', function() { + expect(function() { + GeometryPipeline.createAttributeIndices(undefined); + }).toThrow(); + }); + + it('reorderForPreVertexCache reorders all indices and attributes for the pre vertex cache', function() { + var geometry = new Geometry({ + attributes : { + weight : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] + }), + positions : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0] + }) + }, + indices : [5, 3, 2, 0, 1, 4, 4, 1, 3, 2, 5, 0], + primitiveType : PrimitiveType.TRIANGLES + }); + + GeometryPipeline.reorderForPreVertexCache(geometry); + + expect(geometry.indices[0]).toEqual(0); + expect(geometry.indices[1]).toEqual(1); + expect(geometry.indices[2]).toEqual(2); + expect(geometry.indices[3]).toEqual(3); + expect(geometry.indices[4]).toEqual(4); + expect(geometry.indices[5]).toEqual(5); + expect(geometry.indices[6]).toEqual(5); + expect(geometry.indices[7]).toEqual(4); + expect(geometry.indices[8]).toEqual(1); + expect(geometry.indices[9]).toEqual(2); + expect(geometry.indices[10]).toEqual(0); + expect(geometry.indices[11]).toEqual(3); + + expect(geometry.attributes.weight.values[0]).toEqual(5.0); + expect(geometry.attributes.weight.values[1]).toEqual(3.0); + expect(geometry.attributes.weight.values[2]).toEqual(2.0); + expect(geometry.attributes.weight.values[3]).toEqual(0.0); + expect(geometry.attributes.weight.values[4]).toEqual(1.0); + expect(geometry.attributes.weight.values[5]).toEqual(4.0); + + expect(geometry.attributes.positions.values[0]).toEqual(15); + expect(geometry.attributes.positions.values[1]).toEqual(16); + expect(geometry.attributes.positions.values[2]).toEqual(17); + expect(geometry.attributes.positions.values[3]).toEqual(9); + expect(geometry.attributes.positions.values[4]).toEqual(10); + expect(geometry.attributes.positions.values[5]).toEqual(11); + expect(geometry.attributes.positions.values[6]).toEqual(6); + expect(geometry.attributes.positions.values[7]).toEqual(7); + expect(geometry.attributes.positions.values[8]).toEqual(8); + expect(geometry.attributes.positions.values[9]).toEqual(0); + expect(geometry.attributes.positions.values[10]).toEqual(1); + expect(geometry.attributes.positions.values[11]).toEqual(2); + expect(geometry.attributes.positions.values[12]).toEqual(3); + expect(geometry.attributes.positions.values[13]).toEqual(4); + expect(geometry.attributes.positions.values[14]).toEqual(5); + expect(geometry.attributes.positions.values[15]).toEqual(12); + expect(geometry.attributes.positions.values[16]).toEqual(13); + expect(geometry.attributes.positions.values[17]).toEqual(14); + }); + + it('reorderForPreVertexCache throws without a geometry', function() { + expect(function() { + GeometryPipeline.reorderForPreVertexCache(undefined); + }).toThrow(); + }); + + it('reorderForPreVertexCache throws when attributes have a different number of attributes', function() { + expect(function() { + var geometry = new Geometry({ + attributes : { + attribute1 : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [0, 1, 2] + }), + attribute2 : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0, 1, 2, 3, 4, 5] + }) + } + }); + + geometry = GeometryPipeline.reorderForPreVertexCache(geometry); + }).toThrow(); + }); + + it('reorderForPostVertexCache reorders indices for the post vertex cache', function() { + var geometry = new EllipsoidGeometry(); + var acmrBefore = Tipsify.calculateACMR({ + indices : geometry.indices, + cacheSize : 24 + }); + expect(acmrBefore).toBeGreaterThan(1.0); + geometry = GeometryPipeline.reorderForPostVertexCache(geometry); + var acmrAfter = Tipsify.calculateACMR({ + indices : geometry.indices, + cacheSize : 24 + }); + expect(acmrAfter).toBeLessThan(0.7); + }); + + it('reorderForPostVertexCache throws without a geometry', function() { + expect(function() { + GeometryPipeline.reorderForPostVertexCache(undefined); + }).toThrow(); + }); + + it('fitToUnsignedShortIndices does not change geometry', function() { + var geometry = new Geometry({ + attributes : { + time : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [10.0] + }), + heat : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [1.0] + }) + }, + indices : [0, 0, 0], + primitiveType : PrimitiveType.TRIANGLES + }); + + var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + + expect(geometries.length).toEqual(1); + expect(geometries[0]).toBe(geometry); + }); + + it('fitToUnsignedShortIndices creates one geometry', function() { + var sixtyFourK = CesiumMath.SIXTY_FOUR_KILOBYTES; + var times = []; + for ( var i = 0; i < sixtyFourK + 1; ++i) { + times.push(i); + } + + var geometry = new Geometry({ + attributes : { + time : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : times + }) + }, + indices : [0, 0, 0, sixtyFourK, sixtyFourK, sixtyFourK, 0, sixtyFourK, 0], + primitiveType : PrimitiveType.TRIANGLES + }); + + var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + + expect(geometries.length).toEqual(1); + expect(geometries[0].attributes.time.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(geometries[0].attributes.time.componentsPerAttribute).toEqual(1); + expect(geometries[0].attributes.time.values).toEqual([0, sixtyFourK]); + + expect(geometries[0].primitiveType).toEqual(PrimitiveType.TRIANGLES); + expect(geometries[0].indices).toEqual([0, 0, 0, 1, 1, 1, 0, 1, 0]); + }); + + it('fitToUnsignedShortIndices creates two triangle geometries', function() { + var sixtyFourK = CesiumMath.SIXTY_FOUR_KILOBYTES; + + var positions = []; + for ( var i = 0; i < sixtyFourK + 1; ++i) { + positions.push(i, i, i); + } + + var indices = []; + for ( var j = sixtyFourK; j > 1; j -= 3) { + indices.push(j, j - 1, j - 2); + } + indices.push(0, 1, 2); + + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : positions + }) + }, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES + }); + + var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + + expect(geometries.length).toEqual(2); + + expect(geometries[0].attributes.position.values.length).toEqual(positions.length - 6); // Two vertices are not copied (0, 1) + expect(geometries[0].indices.length).toEqual(indices.length - 3); // One triangle is not copied (0, 1, 2) + + expect(geometries[1].attributes.position.values.length).toEqual(9); + expect(geometries[1].indices.length).toEqual(3); + }); + + it('fitToUnsignedShortIndices creates two line geometries', function() { + var sixtyFourK = CesiumMath.SIXTY_FOUR_KILOBYTES; + + var positions = []; + for ( var i = 0; i < sixtyFourK + 2; ++i) { + positions.push(i, i, i); + } + + var indices = []; + for ( var j = sixtyFourK; j > 1; j -= 2) { + indices.push(j, j - 1); + } + indices.push(0, 1); + + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : positions + }) + }, + indices : indices, + primitiveType : PrimitiveType.LINES + }); + + var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + + expect(geometries.length).toEqual(2); + + expect(geometries[0].attributes.position.values.length).toEqual(positions.length - 6); // Two vertices are not copied (0, 1) + expect(geometries[0].indices.length).toEqual(indices.length - 2); // One line is not copied (0, 1) + + expect(geometries[1].attributes.position.values.length).toEqual(6); + expect(geometries[1].indices.length).toEqual(2); + }); + + it('fitToUnsignedShortIndices creates two point geometries', function() { + var sixtyFourK = CesiumMath.SIXTY_FOUR_KILOBYTES; + + var positions = []; + var indices = []; + for ( var i = 0; i < sixtyFourK + 1; ++i) { + positions.push(i, i, i); + indices.push(i); + } + + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : positions + }) + }, + indices : indices, + primitiveType : PrimitiveType.POINTS + }); + + var geometries = GeometryPipeline.fitToUnsignedShortIndices(geometry); + + expect(geometries.length).toEqual(2); + + expect(geometries[0].attributes.position.values.length).toEqual(positions.length - 3); // One vertex is not copied + expect(geometries[0].indices.length).toEqual(indices.length - 1); // One point is not copied + + expect(geometries[1].attributes.position.values.length).toEqual(3); + expect(geometries[1].indices.length).toEqual(1); + }); + + it('fitToUnsignedShortIndices throws without a geometry', function() { + expect(function() { + GeometryPipeline.fitToUnsignedShortIndices(undefined); + }).toThrow(); + }); + + it('fitToUnsignedShortIndices throws without triangles, lines, or points', function() { + var geometry = new Geometry({ + attributes : { + time : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [10.0, 11.0, 12.0] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + return GeometryPipeline.fitToUnsignedShortIndices(geometry); + }).toThrow(); + }); + + it('fitToUnsignedShortIndices throws with different numbers of attributes', function() { + var geometry = new Geometry({ + attributes : { + time : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [10.0] + }), + heat : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : [1.0, 2.0] + }) + }, + indices : [0, 0, 0], + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + return GeometryPipeline.fitToUnsignedShortIndices(geometry); + }).toThrow(); + }); + + it('projectTo2D', function() { + var p1 = new Cartesian3(100000, 200000, 300000); + var p2 = new Cartesian3(400000, 500000, 600000); + + var geometry = {}; + geometry.attributes = {}; + geometry.attributes.position = { + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [p1.x, p1.y, p1.z, p2.x, p2.y, p2.z] + }; + + geometry = GeometryPipeline.projectTo2D(geometry); + + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(); + var projectedP1 = projection.project(ellipsoid.cartesianToCartographic(p1)); + var projectedP2 = projection.project(ellipsoid.cartesianToCartographic(p2)); + + expect(geometry.attributes.position2D.values[0]).toEqual(projectedP1.x); + expect(geometry.attributes.position2D.values[1]).toEqual(projectedP1.y); + expect(geometry.attributes.position2D.values[2]).toEqual(projectedP1.z); + expect(geometry.attributes.position2D.values[3]).toEqual(projectedP2.x); + expect(geometry.attributes.position2D.values[4]).toEqual(projectedP2.y); + expect(geometry.attributes.position2D.values[5]).toEqual(projectedP2.z); + + expect(geometry.attributes.position3D.values[0]).toEqual(p1.x); + expect(geometry.attributes.position3D.values[1]).toEqual(p1.y); + expect(geometry.attributes.position3D.values[2]).toEqual(p1.z); + expect(geometry.attributes.position3D.values[3]).toEqual(p2.x); + expect(geometry.attributes.position3D.values[4]).toEqual(p2.y); + expect(geometry.attributes.position3D.values[5]).toEqual(p2.z); + }); + + it('projectTo2D throws without a geometry', function() { + expect(function() { + GeometryPipeline.projectTo2D(undefined); + }).toThrow(); + }); + + it('encodeAttribute encodes positions', function() { + var c = new Cartesian3(-10000000.0, 0.0, 10000000.0); + var encoded = EncodedCartesian3.fromCartesian(c); + + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : [c.x, c.y, c.z] + }) + }, + primitiveType : PrimitiveType.POINTS + }); + geometry = GeometryPipeline.encodeAttribute(geometry, 'position', 'positionHigh', 'positionLow'); + + expect(geometry.attributes.positionHigh).toBeDefined(); + expect(geometry.attributes.positionHigh.values[0]).toEqual(encoded.high.x); + expect(geometry.attributes.positionHigh.values[1]).toEqual(encoded.high.y); + expect(geometry.attributes.positionHigh.values[2]).toEqual(encoded.high.z); + expect(geometry.attributes.positionLow).toBeDefined(); + expect(geometry.attributes.positionLow.values[0]).toEqual(encoded.low.x); + expect(geometry.attributes.positionLow.values[1]).toEqual(encoded.low.y); + expect(geometry.attributes.positionLow.values[2]).toEqual(encoded.low.z); + expect(geometry.attributes.position).not.toBeDefined(); + }); + + it('encodeAttribute throws without a geometry', function() { + expect(function() { + GeometryPipeline.encodeAttribute(undefined); + }).toThrow(); + }); + + it('encodeAttribute throws without attributeName', function() { + expect(function() { + GeometryPipeline.encodeAttribute(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + })); + }).toThrow(); + }); + + it('encodeAttribute throws without attributeHighName', function() { + expect(function() { + GeometryPipeline.encodeAttribute(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }), 'position'); + }).toThrow(); + }); + + it('encodeAttribute throws without attributeLowName', function() { + expect(function() { + GeometryPipeline.encodeAttribute(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }), 'position', 'positionHigh'); + }).toThrow(); + }); + + it('encodeAttribute throws without attribute', function() { + expect(function() { + GeometryPipeline.encodeAttribute(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }), 'normal'); + }).toThrow(); + }); + + it('encodeAttribute throws without ComponentDatatype.DOUBLE', function() { + expect(function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_SHORT, + componentsPerAttribute : 1, + values : [0.0] + }) + } + }); + GeometryPipeline.encodeAttribute(geometry); + }).toThrow(); + }); + + it('transformToWorldCoordinates', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ] + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0) + }), + modelMatrix : new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0) + }); + + var transformed = GeometryPipeline.transformToWorldCoordinates(instance); + var transformedPositions = [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]; + var transformedNormals = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]; + + expect(transformed.geometry.attributes.position.values).toEqual(transformedPositions); + expect(transformed.geometry.attributes.normal.values).toEqual(transformedNormals); + expect(transformed.geometry.boundingSphere).toEqual(new BoundingSphere(new Cartesian3(0.0, 0.5, 0.5), 1.0)); + expect(transformed.modelMatrix).toEqual(Matrix4.IDENTITY); + }); + + it('transformToWorldCoordinates does nothing when already in world coordinates', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ] + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0) + }), + modelMatrix : Matrix4.IDENTITY + }); + + var transformed = GeometryPipeline.transformToWorldCoordinates(instance); + var transformedPositions = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]; + var transformedNormals = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0]; + + expect(transformed.geometry.attributes.position.values).toEqual(transformedPositions); + expect(transformed.geometry.attributes.normal.values).toEqual(transformedNormals); + expect(transformed.geometry.boundingSphere).toEqual(new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0)); + expect(transformed.modelMatrix).toEqual(Matrix4.IDENTITY); + }); + + it('transformToWorldCoordinates throws without an instance', function() { + expect(function() { + GeometryPipeline.transformToWorldCoordinates(); + }).toThrow(); + }); + + it('combine combines one geometry', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([0.0, 0.0, 0.0]) + }) + }, + primitiveType : PrimitiveType.POINTS + }) + }); + + var combined = GeometryPipeline.combine([instance]); + expect(combined).toEqual(instance.geometry); + }); + + it('combine combines several geometries without indicess', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }) + }); + var anotherInstance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [1.0, 1.0, 1.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }) + }); + + var combined = GeometryPipeline.combine([instance, anotherInstance]); + expect(combined).toEqual(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([ + 0.0, 0.0, 0.0, + 1.0, 1.0, 1.0 + ]) + }) + }, + primitiveType : PrimitiveType.POINTS + })); + }); + + it('combine combines several geometries with indicess', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 0.0, + 1.0, 1.0, 1.0, + 2.0, 2.0, 2.0 + ] + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 0.0, + 1.0, 1.0, 1.0, + 2.0, 2.0, 2.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES + }) + }); + var anotherInstance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 3.0, 3.0, 3.0, + 4.0, 4.0, 4.0, + 5.0, 5.0, 5.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES + }) + }); + + var combined = GeometryPipeline.combine([instance, anotherInstance]); + expect(combined).toEqual(new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([ + 0.0, 0.0, 0.0, + 1.0, 1.0, 1.0, + 2.0, 2.0, 2.0, + 3.0, 3.0, 3.0, + 4.0, 4.0, 4.0, + 5.0, 5.0, 5.0 + ]) + }) + }, + indices : new Uint16Array([0, 1, 2, 3, 4, 5]), + primitiveType : PrimitiveType.TRIANGLES + })); + }); + + it('combine combines bounding spheres', function() { + var instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0) + }) + }); + var anotherInstance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [ + 1.0, 0.0, 0.0, + 2.0, 0.0, 0.0, + 1.0, 1.0, 0.0 + ] + }) + }, + indices : [0, 1, 2], + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(new Cartesian3(1.5, 0.5, 0.0), 1.0) + }) + }); + + var combined = GeometryPipeline.combine([instance, anotherInstance]); + var expected = BoundingSphere.union(instance.geometry.boundingSphere, anotherInstance.geometry.boundingSphere); + expect(combined.boundingSphere).toEqual(expected); + }); + + it('combine throws without instances', function() { + expect(function() { + GeometryPipeline.combine(); + }).toThrow(); + }); + + it('combine throws when instances.length is zero', function() { + expect(function() { + GeometryPipeline.combine([]); + }).toThrow(); + }); + + it('combine throws when instances.modelMatrix do not match', function() { + var instance0 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }), + modelMatrix : Matrix4.fromScale(new Cartesian3(1.0, 1.0, 1.0)) + }); + + var instance1 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }), + modelMatrix : Matrix4.fromScale(new Cartesian3(2.0, 2.0, 2.0)) + }); + + expect(function() { + GeometryPipeline.combine([instance0, instance1]); + }).toThrow(); + }); + + it('combine throws when instance geometries do not all have or not have an indices', function() { + var instance0 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + indices : [0], + primitiveType : PrimitiveType.POINTS + }) + }); + + var instance1 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }) + }); + + expect(function() { + GeometryPipeline.combine([instance0, instance1]); + }).toThrow(); + }); + + it('combine throws when instance geometries do not all have the same primitive type', function() { + var instance0 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.POINTS + }) + }); + + var instance1 = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : [0.0, 0.0, 0.0, 1.0, 0.0, 0.0] + }) + }, + primitiveType : PrimitiveType.LINES + }) + }); + + expect(function() { + GeometryPipeline.combine([instance0, instance1]); + }).toThrow(); + }); + + it('computeNormal throws when geometry is undefined', function() { + expect(function() { + GeometryPipeline.computeNormal(); + }).toThrow(); + }); + + it('computeNormal throws when geometry.attributes.position is undefined', function() { + var geometry = new Geometry({ + attributes: {}, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.computeNormal(geometry); + }).toThrow(); + }); + + it('computeNormal throws when geometry.indices is undefined', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.computeNormal(geometry); + }).toThrow(); + }); + + it('computeNormal throws when geometry.indices.length is not a multiple of 3', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.computeNormal(geometry); + }).toThrow(); + }); + + it('computeNormal throws when primitive type is not triangle', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.computeNormal(geometry); + }).toThrow(); + }); + + + it('computeNormal computes normal for one triangle', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLES + }); + + geometry = GeometryPipeline.computeNormal(geometry); + + expect(geometry.attributes.normal.values.length).toEqual(3*3); + expect(geometry.attributes.normal.values).toEqual([0, 0, 1, 0, 0, 1, 0, 0, 1]); + }); + + it('computeNormal computes normal for two triangles', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 1, 1, 1, 1, 2, 0, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2, 1, 3, 2], + primitiveType: PrimitiveType.TRIANGLES + }); + + geometry = GeometryPipeline.computeNormal(geometry); + + var normals = geometry.attributes.normal.values; + expect(normals.length).toEqual(4*3); + + var a = new Cartesian3(-1, 0, 1).normalize(); + + expect(Cartesian3.fromArray(normals, 0)).toEqualEpsilon(a, CesiumMath.EPSILON7); + expect(Cartesian3.fromArray(normals, 3)).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON7); + expect(Cartesian3.fromArray(normals, 6)).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON7); + + a = new Cartesian3(1, 0, 1).normalize(); + expect(Cartesian3.fromArray(normals, 9)).toEqualEpsilon(a, CesiumMath.EPSILON7); + }); + + it('computeNormal computes normal for six triangles', function() { + var geometry = new Geometry ({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2, 3, 0, 2, 4, 0, 3, 4, 5, 0, 5, 6, 0, 6, 1, 0], + primitiveType: PrimitiveType.TRIANGLES + }); + + geometry = GeometryPipeline.computeNormal(geometry); + + var normals = geometry.attributes.normal.values; + expect(normals.length).toEqual(7*3); + + var a = new Cartesian3(-1, -1, -1).normalize(); + expect(Cartesian3.fromArray(normals, 0)).toEqualEpsilon(a, CesiumMath.EPSILON7); + + a = new Cartesian3(0, -1, -1).normalize(); + expect(Cartesian3.fromArray(normals, 3)).toEqualEpsilon(a, CesiumMath.EPSILON7); + + expect(Cartesian3.fromArray(normals, 6)).toEqualEpsilon(Cartesian3.UNIT_Y.negate(), CesiumMath.EPSILON7); + + a = new Cartesian3(-1, -1, 0).normalize(); + expect(Cartesian3.fromArray(normals, 9)).toEqualEpsilon(a, CesiumMath.EPSILON7); + + expect(Cartesian3.fromArray(normals, 12)).toEqualEpsilon(Cartesian3.UNIT_X.negate(), CesiumMath.EPSILON7); + + a = new Cartesian3(-1, 0, -1).normalize(); + expect(Cartesian3.fromArray(normals, 15)).toEqualEpsilon(a, CesiumMath.EPSILON7); + + expect(Cartesian3.fromArray(normals, 18)).toEqualEpsilon(Cartesian3.UNIT_Z.negate(), CesiumMath.EPSILON7); + }); + + it('computeBinormalAndTangent throws when geometry is undefined', function() { + expect(function() { + GeometryPipeline.computeBinormalAndTangent(); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when position is undefined', function() { + var geometry = new Geometry({ + attributes: { + normal: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when normal is undefined', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when st is undefined', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + normal: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when geometry.indices is undefined', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }), + normal: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + primitiveType : PrimitiveType.POINTS + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when indices is not a multiple of 3', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + normal: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2, 3, 4], + primitiveType: PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent throws when primitive type is not triangle', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + normal: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.computeBinormalAndTangent(geometry); + }).toThrow(); + }); + + it('computeBinormalAndTangent computes tangent and binormal for one triangle', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 0, 0, 1, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 0, 0, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2], + primitiveType: PrimitiveType.TRIANGLES + }); + + geometry = GeometryPipeline.computeNormal(geometry); + geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + + expect(geometry.attributes.tangent.values).toEqual([1, 0, 0, 1, 0, 0, 1, 0, 0]); + expect(geometry.attributes.binormal.values).toEqual([0, 1, 0, 0, 1, 0, 0, 1, 0]); + }); + + it('computeBinormalAndTangent computes tangent and binormal for two triangles', function() { + var geometry = new Geometry({ + attributes: { + position: new GeometryAttribute({ + values: [0, 0, 0, 1, 0, 1, 1, 1, 1, 2, 0, 0], + componentsPerAttribute: 3, + componentDatatype : ComponentDatatype.FLOAT + }), + st: new GeometryAttribute({ + values: [0, 0, 1, 0, 1, 1, 0, 1], + componentsPerAttribute: 2, + componentDatatype : ComponentDatatype.FLOAT + }) + }, + indices : [0, 1, 2, 1, 3, 2], + primitiveType: PrimitiveType.TRIANGLES + }); + + geometry = GeometryPipeline.computeNormal(geometry); + geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + expect(geometry.attributes.tangent.values).toEqualEpsilon([0.7071067811865475, 0, 0.7071067811865475, + 0, 1, 0, + 0, 1, 0, + -0.5773502691896258, 0.5773502691896258, 0.5773502691896258], CesiumMath.EPSILON7); + expect(geometry.attributes.binormal.values).toEqualEpsilon([0, 1, 0, + -1, 0, 0, + -1, 0, 0, + -0.4082482904638631, -0.8164965809277261, 0.4082482904638631], CesiumMath.EPSILON7); + }); + + it ('computeBinormalAndTangent computes tangent and binormal for an EllipsoidGeometry', function() { + var numberOfPartitions = 10; + var geometry = new EllipsoidGeometry({ + vertexFormat : new VertexFormat({ + position : true, + normal : true, + st : true + }), + numberOfPartitions : numberOfPartitions + }); + geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + var actualTangents = geometry.attributes.tangent.values; + var actualBinormals = geometry.attributes.binormal.values; + + var expectedGeometry = new EllipsoidGeometry({ + vertexFormat: VertexFormat.ALL, + numberOfPartitions : numberOfPartitions + }); + var expectedTangents = expectedGeometry.attributes.tangent.values; + var expectedBinormals = expectedGeometry.attributes.binormal.values; + + expect(actualTangents.length).toEqual(expectedTangents.length); + expect(actualBinormals.length).toEqual(expectedBinormals.length); + + for (var i = 300; i < 500; i += 3) { + var actual = Cartesian3.fromArray(actualTangents, i); + var expected = Cartesian3.fromArray(expectedTangents, i); + expect(actual).toEqualEpsilon(expected, CesiumMath.EPSILON1); + + actual = Cartesian3.fromArray(actualBinormals, i); + expected = Cartesian3.fromArray(expectedBinormals, i); + expect(actual).toEqualEpsilon(expected, CesiumMath.EPSILON1); + } + }); + + it('wrapLongitude provides indices for an un-indexed triangle list', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2, 3, 4, 5]); + }); + + it('wrapLongitude returns unchanged geometry if indices are already defined for an un-indexed triangle list', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES, + indices : new Uint16Array([0, 1, 2, 3, 4, 5]) + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2, 3, 4, 5]); + }); + + it('wrapLongitude throws when primitive type is TRIANGLES and number of vertices is less than 3', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude throws when primitive type is TRIANGLES and number of vertices is not a multiple of 3', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude creates indexed triangles for a triangle fan', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0, 7.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLE_FAN + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.primitiveType).toEqual(PrimitiveType.TRIANGLES); + expect(geometry.indices).toEqual([1, 0, 2, 2, 0, 3]); + }); + + it('wrapLongitude throws when primitive type is TRIANGLE_FAN and number of vertices is less than 3', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLE_FAN + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude creates indexd triangles for triangle strips', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLE_STRIP + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.primitiveType).toEqual(PrimitiveType.TRIANGLES); + expect(geometry.indices).toEqual([0, 1, 2, 0, 2, 3, 3, 2, 4, 3, 4, 5]); + }); + + it('wrapLongitude throws when the primitive type is TRIANGLE_STRIP and number of vertices is less than 3', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLE_STRIP + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude creates indexed lines', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0, 7.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.LINES + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2, 3]); + }); + + it('wrapLongitude returns lines unchanged if indices are provided', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0, 7.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.LINES, + indices : new Uint16Array([0, 1, 2, 3]) + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2, 3]); + }); + + it('wrapLongitude throws when primitive type is LINES and number of vertices is less than 2', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.LINES + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude throws when primitive type is LINES and number of vertices is not a multiple 2', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + }) + }, + primitiveType : PrimitiveType.LINES + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude creates indexed lines from line strip', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0, 7.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.LINE_STRIP + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.primitiveType).toEqual(PrimitiveType.LINES); + expect(geometry.indices).toEqual([0, 1, 1, 2, 2, 3]); + }); + + it('wrapLongitude throws when primitive type is LINE_STRIP and number of vertices is less than 2', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.LINE_STRIP + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude creates indexed lines from line loops', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0, 7.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.LINE_LOOP + }); + + GeometryPipeline.wrapLongitude(geometry); + expect(geometry.primitiveType).toEqual(PrimitiveType.LINES); + expect(geometry.indices).toEqual([0, 1, 1, 2, 2, 3, 3, 0]); + }); + + it('wrapLongitude throws when the primitive type is LINE_LOOP and number of vertices is less than 2', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([0.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.LINE_LOOP + }); + + expect(function() { + GeometryPipeline.wrapLongitude(geometry); + }).toThrow(); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p0 behind', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, -1.0, 0.0, -1.0, 1.0, 2.0, -1.0, 2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 3, 4, 1, 2, 6, 1, 6, 5]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, -1.0, 0.0, -1.0, 1.0, 2.0, -1.0, 2.0, 2.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p1 behind', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 1.0, 2.0, -1.0, -1.0, 0.0, -1.0, 2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([1, 3, 4, 2, 0, 6, 2, 6, 5]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, 1.0, 2.0, -1.0, -1.0, 0.0, -1.0, 2.0, 2.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p2 behind', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 1.0, 2.0, -1.0, 2.0, 2.0, -1.0, -1.0, 0.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([2, 3, 4, 0, 1, 6, 0, 6, 5]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, 1.0, 2.0, -1.0, 2.0, 2.0, -1.0, -1.0, 0.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p0 ahead', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 2.0, 0.0, -1.0, -1.0, 0.0, -1.0, -1.0, 0.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([1, 2, 4, 1, 4, 3, 0, 5, 6]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, 2.0, 0.0, -1.0, -1.0, 0.0, -1.0, -1.0, 0.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p1 ahead', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, -1.0, 0.0, -1.0, 2.0, 0.0, -1.0, -1.0, 0.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([2, 0, 4, 2, 4, 3, 1, 5, 6]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, -1.0, 0.0, -1.0, 2.0, 0.0, -1.0, -1.0, 0.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude subdivides triangle crossing the international date line, p2 ahead', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 2.0, 0.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 4, 0, 4, 3, 2, 5, 6]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 3 * 3)).toEqual([-1.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 2.0, 0.0]); + expect(positions.length).toEqual(7 * 3); + }); + + it('wrapLongitude returns offset triangle that touches the international date line', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 0.0, 1.0, -1.0, CesiumMath.EPSILON14, 2.0, -2.0, 2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([-1.0, CesiumMath.EPSILON11, 1.0, -1.0, CesiumMath.EPSILON11, 2.0, -2.0, 2.0, 2.0]); + expect(positions.length).toEqual(3 * 3); + }); + + it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, behind', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, -1.0, 1.0, -1.0, -2.0, 1.0, -1.0, -2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([-1.0, -1.0, 1.0, -1.0, -2.0, 1.0, -1.0, -2.0, 2.0]); + expect(positions.length).toEqual(3 * 3); + }); + + it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, ahead', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 1.0, 1.0, -1.0, 2.0, 1.0, -1.0, 2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([-1.0, 1.0, 1.0, -1.0, 2.0, 1.0, -1.0, 2.0, 2.0]); + expect(positions.length).toEqual(3 * 3); + }); + + it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, positive x', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1, 2]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1, 2]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 2.0]); + expect(positions.length).toEqual(3 * 3); + }); + + it('wrapLongitude computes all attributes for a triangle crossing the international date line', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-2.0, -1.0, 0.0, -3.0, 1.0, 0.0, -1.0, 1.0, 0.0]) + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0]) + }), + tangent : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0]) + }), + binormal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0]) + }), + st : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : new Float32Array([0.0, 0.0, 1.0, 0.0, 0.5, 0.5]) + }) + }, + indices : new Uint16Array([1, 2, 0]), + primitiveType : PrimitiveType.TRIANGLES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 3, 4, 1, 2, 6, 1, 6, 5]); + + var positions = geometry.attributes.position.values; + var normals = geometry.attributes.normal.values; + var binormals = geometry.attributes.binormal.values; + var tangents = geometry.attributes.tangent.values; + var texCoords = geometry.attributes.st.values; + + expect(positions.length).toEqual(7 * 3); + expect(normals.length).toEqual(7 * 3); + expect(binormals.length).toEqual(7 * 3); + expect(tangents.length).toEqual(7 * 3); + expect(texCoords.length).toEqual(7 * 2); + + for (var i = 0; i < positions.length; i += 3) { + expect(Cartesian3.fromArray(normals, i)).toEqual(Cartesian3.UNIT_Z); + expect(Cartesian3.fromArray(binormals, i)).toEqual(Cartesian3.UNIT_Y.negate()); + expect(Cartesian3.fromArray(tangents, i)).toEqual(Cartesian3.UNIT_X.negate()); + } + }); + + it('wrapLongitude subdivides line crossing the international date line', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, -1.0, 0.0, -1.0, 1.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1]), + primitiveType : PrimitiveType.LINES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 2, 3, 1]); + + var positions = geometry.attributes.position.values; + expect(positions.subarray(0, 2 * 3)).toEqual([-1.0, -1.0, 0.0, -1.0, 1.0, 2.0]); + expect(positions.length).toEqual(4 * 3); + }); + + it('wrapLongitude returns offset line that touches the international date line', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([-1.0, 0.0, 0.0, -1.0, 1.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1]), + primitiveType : PrimitiveType.LINES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([-1.0, CesiumMath.EPSILON6, 0.0, -1.0, 1.0, 2.0]); + expect(positions.length).toEqual(2 * 3); + }); + + it('wrapLongitude returns the same points if the line doesn\'t cross the international date line', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([1.0, 1.0, 0.0, 1.0, 1.0, 2.0]) + }) + }, + indices : new Uint16Array([0, 1]), + primitiveType : PrimitiveType.LINES + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).toEqual([0, 1]); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([1.0, 1.0, 0.0, 1.0, 1.0, 2.0]); + expect(positions.length).toEqual(2 * 3); + }); + + it('wrapLongitude does nothing for points', function() { + var geometry = new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([1.0, 1.0, 0.0, 1.0, 1.0, 2.0]) + }) + }, + primitiveType : PrimitiveType.POINTS + }); + geometry = GeometryPipeline.wrapLongitude(geometry); + expect(geometry.indices).not.toBeDefined(); + + var positions = geometry.attributes.position.values; + expect(positions).toEqual([1.0, 1.0, 0.0, 1.0, 1.0, 2.0]); + expect(positions.length).toEqual(2 * 3); + }); + + it('wrapLongitude throws when geometry is undefined', function() { + expect(function() { + return GeometryPipeline.wrapLongitude(); + }).toThrow(); + }); +}); diff --git a/Specs/Core/GeometrySpec.js b/Specs/Core/GeometrySpec.js new file mode 100644 index 000000000000..ce41c563620f --- /dev/null +++ b/Specs/Core/GeometrySpec.js @@ -0,0 +1,138 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/ComponentDatatype', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/PrimitiveType' + ], function( + Geometry, + GeometryAttribute, + ComponentDatatype, + BoundingSphere, + Cartesian3, + PrimitiveType) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var attributes = { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ]) + }) + }; + var indices = new Uint16Array([0, 1, 2]); + var boundingSphere = new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0); + + var geometry = new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : boundingSphere + }); + + expect(geometry.attributes).toBe(attributes); + expect(geometry.indices).toBe(indices); + expect(geometry.primitiveType).toEqual(PrimitiveType.TRIANGLES); + expect(geometry.boundingSphere).toBe(boundingSphere); + }); + + it('constructor thows without primitiveType', function() { + expect(function() { + return new Geometry({ + attributes : {} + }); + }).toThrow(); + }); + + it('constructor throws without attributes', function() { + expect(function() { + return new Geometry({ + primitiveType : PrimitiveType.TRIANGLES + }); + }).toThrow(); + }); + + it('computeNumberOfVertices', function() { + var attributes = { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ]) + }), + st : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : new Float32Array([ + 0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0 + ]) + }) + }; + var indices = new Uint16Array([0, 1, 2]); + var boundingSphere = new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0); + + var geometry = new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : boundingSphere + }); + + expect(Geometry.computeNumberOfVertices(geometry)).toEqual(3); + }); + + it('computeNumberOfVertices throws when attributes have different number of vertices', function() { + var attributes = { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ]) + }), + st : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : new Float32Array([ + 0.0, 0.0, + 1.0, 0.0 + ]) + }) + }; + var indices = new Uint16Array([0, 1, 2]); + var boundingSphere = new BoundingSphere(new Cartesian3(0.5, 0.5, 0.0), 1.0); + + var geometry = new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : boundingSphere + }); + + expect(function() { + Geometry.computeNumberOfVertices(geometry); + }).toThrow(); + }); + + it('computeNumberOfVertices throws without geometry', function() { + expect(function() { + Geometry.computeNumberOfVertices(); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/MeshFiltersSpec.js b/Specs/Core/MeshFiltersSpec.js deleted file mode 100644 index d2b99cebc219..000000000000 --- a/Specs/Core/MeshFiltersSpec.js +++ /dev/null @@ -1,495 +0,0 @@ -/*global defineSuite*/ -defineSuite([ - 'Core/MeshFilters', - 'Core/PrimitiveType', - 'Core/ComponentDatatype', - 'Core/CubeMapEllipsoidTessellator', - 'Core/Ellipsoid', - 'Core/Cartesian3', - 'Core/EncodedCartesian3', - 'Core/Tipsify', - 'Core/GeographicProjection' - ], function( - MeshFilters, - PrimitiveType, - ComponentDatatype, - CubeMapEllipsoidTessellator, - Ellipsoid, - Cartesian3, - EncodedCartesian3, - Tipsify, - GeographicProjection) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('converts triangles to wireframe in place', function() { - var mesh = MeshFilters.toWireframeInPlace({ - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : [0, 1, 2, 3, 4, 5] - }] - }); - - expect(mesh.indexLists[0].primitiveType).toEqual(PrimitiveType.LINES); - - var v = mesh.indexLists[0].values; - expect(v.length).toEqual(12); - - expect(v[0]).toEqual(0); - expect(v[1]).toEqual(1); - expect(v[2]).toEqual(1); - expect(v[3]).toEqual(2); - expect(v[4]).toEqual(2); - expect(v[5]).toEqual(0); - - expect(v[6]).toEqual(3); - expect(v[7]).toEqual(4); - expect(v[8]).toEqual(4); - expect(v[9]).toEqual(5); - expect(v[10]).toEqual(5); - expect(v[11]).toEqual(3); - }); - - it('converts a triangle fan to wireframe in place', function() { - var mesh = MeshFilters.toWireframeInPlace({ - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLE_FAN, - values : [0, 1, 2, 3] - }] - }); - - expect(mesh.indexLists[0].primitiveType).toEqual(PrimitiveType.LINES); - - var v = mesh.indexLists[0].values; - expect(v.length).toEqual(12); - - expect(v[0]).toEqual(0); - expect(v[1]).toEqual(1); - expect(v[2]).toEqual(1); - expect(v[3]).toEqual(2); - expect(v[4]).toEqual(2); - expect(v[5]).toEqual(0); - - expect(v[6]).toEqual(0); - expect(v[7]).toEqual(2); - expect(v[8]).toEqual(2); - expect(v[9]).toEqual(3); - expect(v[10]).toEqual(3); - expect(v[11]).toEqual(0); - }); - - it('converts a triangle strip to wireframe in place', function() { - var mesh = MeshFilters.toWireframeInPlace({ - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLE_STRIP, - values : [0, 1, 2, 3] - }] - }); - - expect(mesh.indexLists[0].primitiveType).toEqual(PrimitiveType.LINES); - - var v = mesh.indexLists[0].values; - expect(v.length).toEqual(12); - - expect(v[0]).toEqual(0); - expect(v[1]).toEqual(1); - expect(v[2]).toEqual(1); - expect(v[3]).toEqual(2); - expect(v[4]).toEqual(2); - expect(v[5]).toEqual(0); - - expect(v[6]).toEqual(2); - expect(v[7]).toEqual(3); - expect(v[8]).toEqual(3); - expect(v[9]).toEqual(1); - expect(v[10]).toEqual(1); - expect(v[11]).toEqual(2); - }); - - it('creates attribute indices', function() { - var mesh = { - attributes : { - position : {}, - normal : {}, - color : {} - } - }; - - var indices = MeshFilters.createAttributeIndices(mesh); - - var validIndices = [0, 1, 2]; - expect(validIndices).toContain(indices.position); - expect(validIndices).toContain(indices.normal); - expect(validIndices).toContain(indices.color); - expect(indices.position).not.toEqual(indices.normal); - expect(indices.position).not.toEqual(indices.color); - }); - - it('maps attribute indices to different names', function() { - var indices = { - positions : 0, - normals : 1, - colors : 2 - }; - - var mappedIndices = MeshFilters.mapAttributeIndices(indices, { - positions : 'position', - normals : 'normal', - colors : 'color' - }); - - expect(mappedIndices.position).toEqual(indices.positions); - expect(mappedIndices.normal).toEqual(indices.normals); - expect(mappedIndices.color).toEqual(indices.colors); - }); - - it('throws an exception when mesh properties have a different number of attributes', function() { - expect(function() { - var mesh = {}; - mesh.attributes = {}; - - mesh.attributes.attribute1 = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [0, 1, 2] - }; - - mesh.attributes.attribute2 = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : [0, 1, 2, 3, 4, 5] - }; - mesh = MeshFilters.reorderForPreVertexCache(mesh); - }).toThrow(); - }); - - it('can reorder all indices and attributes for the pre vertex cahce', function() { - var mesh = {}; - mesh.attributes = {}; - mesh.indexLists = []; - - mesh.indexLists.push({ - primitiveType : PrimitiveType.TRIANGLES, - values : [5, 3, 2, 0, 1, 4] - }); - - mesh.indexLists.push({ - primitiveType : PrimitiveType.TRIANGLES, - values : [4, 1, 3, 2, 5, 0] - }); - - mesh.attributes.vertexNames = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : ['v0', 'v1', 'v2', 'v3', 'v4', 'v5'] - }; - - mesh.attributes.positions = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] - }; - MeshFilters.reorderForPreVertexCache(mesh); - - expect(mesh.indexLists[0].values[0]).toEqual(0); - expect(mesh.indexLists[0].values[1]).toEqual(1); - expect(mesh.indexLists[0].values[2]).toEqual(2); - expect(mesh.indexLists[0].values[3]).toEqual(3); - expect(mesh.indexLists[0].values[4]).toEqual(4); - expect(mesh.indexLists[0].values[5]).toEqual(5); - - expect(mesh.indexLists[1].values[0]).toEqual(5); - expect(mesh.indexLists[1].values[1]).toEqual(4); - expect(mesh.indexLists[1].values[2]).toEqual(1); - expect(mesh.indexLists[1].values[3]).toEqual(2); - expect(mesh.indexLists[1].values[4]).toEqual(0); - expect(mesh.indexLists[1].values[5]).toEqual(3); - - expect(mesh.attributes.vertexNames.values[0]).toEqual('v5'); - expect(mesh.attributes.vertexNames.values[1]).toEqual('v3'); - expect(mesh.attributes.vertexNames.values[2]).toEqual('v2'); - expect(mesh.attributes.vertexNames.values[3]).toEqual('v0'); - expect(mesh.attributes.vertexNames.values[4]).toEqual('v1'); - expect(mesh.attributes.vertexNames.values[5]).toEqual('v4'); - - expect(mesh.attributes.positions.values[0]).toEqual(15); - expect(mesh.attributes.positions.values[1]).toEqual(16); - expect(mesh.attributes.positions.values[2]).toEqual(17); - expect(mesh.attributes.positions.values[3]).toEqual(9); - expect(mesh.attributes.positions.values[4]).toEqual(10); - expect(mesh.attributes.positions.values[5]).toEqual(11); - expect(mesh.attributes.positions.values[6]).toEqual(6); - expect(mesh.attributes.positions.values[7]).toEqual(7); - expect(mesh.attributes.positions.values[8]).toEqual(8); - expect(mesh.attributes.positions.values[9]).toEqual(0); - expect(mesh.attributes.positions.values[10]).toEqual(1); - expect(mesh.attributes.positions.values[11]).toEqual(2); - expect(mesh.attributes.positions.values[12]).toEqual(3); - expect(mesh.attributes.positions.values[13]).toEqual(4); - expect(mesh.attributes.positions.values[14]).toEqual(5); - expect(mesh.attributes.positions.values[15]).toEqual(12); - expect(mesh.attributes.positions.values[16]).toEqual(13); - expect(mesh.attributes.positions.values[17]).toEqual(14); - }); - - it('can reorder indices for the post vertex cache', function() { - var mesh = CubeMapEllipsoidTessellator.compute(new Ellipsoid(10.0, 10.0, 10.0), 100); - var indices = mesh.indexLists[0].values; - var numIndices = indices.length; - var maximumIndex = 0; - for ( var i = 0; i < numIndices; i++) { - if (indices[i] > maximumIndex) { - maximumIndex = indices[i]; - } - } - var ACMRbefore = Tipsify.calculateACMR({indices : indices, - maximumIndex : maximumIndex, - cacheSize : 24}); - expect(ACMRbefore).toBeGreaterThan(1.00); - mesh = MeshFilters.reorderForPostVertexCache(mesh); - indices = mesh.indexLists[0].values; - var ACMRafter = Tipsify.calculateACMR({indices : indices, - maximumIndex : maximumIndex, - cacheSize : 24}); - expect(ACMRafter).toBeLessThan(0.70); - }); - - it('fitToUnsignedShortIndices does not change mesh', function() { - var mesh = { - attributes : { - time : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [10.0] - }, - heat : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [1.0] - } - }, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : [0, 0, 0] - }] - }; - - var meshes = MeshFilters.fitToUnsignedShortIndices(mesh); - - expect(meshes.length).toEqual(1); - expect(meshes[0]).toBe(mesh); - }); - - it('fitToUnsignedShortIndices creates one mesh', function() { - var sixtyFourK = 64 * 1024; - var times = []; - for ( var i = 0; i < sixtyFourK + 1; ++i) { - times.push(i); - } - - var mesh = { - attributes : { - time : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : times - } - }, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : [0, 0, 0, sixtyFourK, sixtyFourK, sixtyFourK, 0, sixtyFourK, 0] - }] - }; - - var meshes = MeshFilters.fitToUnsignedShortIndices(mesh); - - expect(meshes.length).toEqual(1); - expect(meshes[0].attributes.time.componentDatatype).toEqual(ComponentDatatype.FLOAT); - expect(meshes[0].attributes.time.componentsPerAttribute).toEqual(1); - expect(meshes[0].attributes.time.values).toEqual([0, sixtyFourK]); - - expect(meshes[0].indexLists[0].primitiveType).toEqual(PrimitiveType.TRIANGLES); - expect(meshes[0].indexLists[0].values).toEqual([0, 0, 0, 1, 1, 1, 0, 1, 0]); - }); - - it('fitToUnsignedShortIndices creates two meshes', function() { - var sixtyFourK = 64 * 1024; - - var positions = []; - for ( var i = 0; i < sixtyFourK + 1; ++i) { - positions.push(i, i, i); - } - - var indices = []; - for ( var j = sixtyFourK; j > 1; j -= 3) { - indices.push(j, j - 1, j - 2); - } - indices.push(0, 1, 2); - - var mesh = { - attributes : { - position : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : positions - } - }, - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : indices - }] - }; - - var meshes = MeshFilters.fitToUnsignedShortIndices(mesh); - - expect(meshes.length).toEqual(2); - - expect(meshes[0].attributes.position.values.length).toEqual(positions.length - 6); // Two vertices are not copied (0, 1) - expect(meshes[0].indexLists[0].values.length).toEqual(indices.length - 3); // One triangle is not copied (0, 1, 2) - - expect(meshes[1].attributes.position.values.length).toEqual(9); - expect(meshes[1].indexLists[0].values.length).toEqual(3); - }); - - it('fitToUnsignedShortIndices throws without triangles', function() { - var mesh = { - attributes : { - time : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [10.0] - } - }, - - indexLists : [{ - primitiveType : PrimitiveType.POINTS, - values : [0] - }] - }; - - expect(function() { - return MeshFilters.fitToUnsignedShortIndices(mesh); - }).toThrow(); - }); - - it('fitToUnsignedShortIndices throws with different numbers of attributes', function() { - var mesh = { - attributes : { - time : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [10.0] - }, - - heat : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : [1.0, 2.0] - } - }, - - indexLists : [{ - primitiveType : PrimitiveType.TRIANGLES, - values : [0, 0, 0] - }] - }; - - expect(function() { - return MeshFilters.fitToUnsignedShortIndices(mesh); - }).toThrow(); - }); - - it('projectTo2D', function() { - var p1 = new Cartesian3(100000, 200000, 300000); - var p2 = new Cartesian3(400000, 500000, 600000); - - var mesh = {}; - mesh.attributes = {}; - mesh.attributes.position = { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : [p1.x, p1.y, p1.z, p2.x, p2.y, p2.z] - }; - - mesh = MeshFilters.projectTo2D(mesh); - - var ellipsoid = Ellipsoid.WGS84; - var projection = new GeographicProjection(); - var projectedP1 = projection.project(ellipsoid.cartesianToCartographic(p1)); - var projectedP2 = projection.project(ellipsoid.cartesianToCartographic(p2)); - - expect(mesh.attributes.position2D.values[0]).toEqual(projectedP1.x); - expect(mesh.attributes.position2D.values[1]).toEqual(projectedP1.y); - expect(mesh.attributes.position2D.values[2]).toEqual(projectedP2.x); - expect(mesh.attributes.position2D.values[3]).toEqual(projectedP2.y); - - expect(mesh.attributes.position3D.values[0]).toEqual(p1.x); - expect(mesh.attributes.position3D.values[1]).toEqual(p1.y); - expect(mesh.attributes.position3D.values[2]).toEqual(p1.z); - expect(mesh.attributes.position3D.values[3]).toEqual(p2.x); - expect(mesh.attributes.position3D.values[4]).toEqual(p2.y); - expect(mesh.attributes.position3D.values[5]).toEqual(p2.z); - }); - - it('MeshFilters.encodeAttribute encodes positions', function() { - var c = new Cartesian3(-10000000.0, 0.0, 10000000.0); - var encoded = EncodedCartesian3.fromCartesian(c); - - var mesh = { - attributes : { - position : { - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : [c.x, c.y, c.z] - } - } - }; - mesh = MeshFilters.encodeAttribute(mesh); - - expect(mesh.attributes.positionHigh).toBeDefined(); - expect(mesh.attributes.positionHigh.values[0]).toEqual(encoded.high.x); - expect(mesh.attributes.positionHigh.values[1]).toEqual(encoded.high.y); - expect(mesh.attributes.positionHigh.values[2]).toEqual(encoded.high.z); - expect(mesh.attributes.positionLow).toBeDefined(); - expect(mesh.attributes.positionLow.values[0]).toEqual(encoded.low.x); - expect(mesh.attributes.positionLow.values[1]).toEqual(encoded.low.y); - expect(mesh.attributes.positionLow.values[2]).toEqual(encoded.low.z); - expect(mesh.attributes.position).not.toBeDefined(); - }); - - it('MeshFilters.encodeAttribute throws without a mesh', function() { - expect(function() { - MeshFilters.encodeAttribute(undefined); - }).toThrow(); - }); - - it('MeshFilters.encodeAttribute throws with mesh without attributes property', function() { - expect(function() { - MeshFilters.encodeAttribute({}); - }).toThrow(); - }); - - it('MeshFilters.encodeAttribute throws without attribute', function() { - expect(function() { - var mesh = { - attributes : { - } - }; - MeshFilters.encodeAttribute(mesh); - }).toThrow(); - }); - - it('MeshFilters.encodeAttribute throws without ComponentDatatype.FLOAT', function() { - expect(function() { - var mesh = { - attributes : { - componentDatatype : ComponentDatatype.UNSIGNED_SHORT, - componentsPerAttribute : 1, - values : [0.0] - } - }; - MeshFilters.encodeAttribute(mesh); - }).toThrow(); - }); - -}); \ No newline at end of file diff --git a/Specs/Core/PlaneTessellatorSpec.js b/Specs/Core/PlaneTessellatorSpec.js deleted file mode 100644 index 695f66df824f..000000000000 --- a/Specs/Core/PlaneTessellatorSpec.js +++ /dev/null @@ -1,63 +0,0 @@ -/*global defineSuite*/ -defineSuite(['Core/PlaneTessellator'], function(PlaneTessellator) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('compute with default arguments', function() { - var m = PlaneTessellator.compute(); - - expect(m.indexLists[0].values.length).toEqual(2 * 3); // 2 triangles - }); - - it('compute with arguments', function() { - var m = PlaneTessellator.compute({ - resolution : { - x : 4, - y : 3 - }, - onInterpolation : function(time) { - } - }); - - expect(m.indexLists[0].values.length).toEqual(12 * 3); // 8 triangles - }); - - it('compute with onInterpolation callback', function() { - var callbacks = []; - - PlaneTessellator.compute({ - resolution : { - x : 2, - y : 2 - }, - onInterpolation : function(time) { - callbacks.push(time); - } - }); - - expect(callbacks).toEqual([{ - x : 0.0, - y : 0.0 - }, { - x : 1.0, - y : 0.0 - }, { - x : 0.0, - y : 1.0 - }, { - x : 1.0, - y : 1.0 - }]); - }); - - it('throws if resolution is less than 1', function() { - expect(function() { - PlaneTessellator.compute({ - resolution: { - x : 0.0, - y : 0.0 - } - }); - }).toThrow(); - }); -}); \ No newline at end of file diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js new file mode 100644 index 000000000000..1700875bf85d --- /dev/null +++ b/Specs/Core/PolygonGeometrySpec.js @@ -0,0 +1,197 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolygonGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + PolygonGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('throws without hierarchy', function() { + expect(function() { + return new PolygonGeometry(); + }).toThrow(); + }); + + it('throws without positions', function() { + expect(function() { + return PolygonGeometry.fromPositions(); + }).toThrow(); + }); + + it('throws with less than three positions', function() { + expect(function() { + return PolygonGeometry.fromPositions({ positions : [new Cartesian3()] }); + }).toThrow(); + }); + + it('throws with polygon hierarchy with less than three positions', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + new Cartographic() + ]) + }; + + expect(function() { + return new PolygonGeometry({ polygonHierarchy : hierarchy }); + }).toThrow(); + }); + + it('throws due to duplicate positions', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + + expect(function() { + return PolygonGeometry.fromPositions({ + positions : [ + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)), + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)) + ], + ellipsoid : ellipsoid + }); + }).toThrow(); + }); + + it('throws due to duplicate hierarchy positions', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var hierarchy = { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(1.0, 1.0, 0.0), + Cartographic.fromDegrees(1.0, 1.0, 0.0), + Cartographic.fromDegrees(1.0, 1.0, 0.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 0.0, 0.0), + Cartographic.fromDegrees(0.0, 0.0, 0.0), + Cartographic.fromDegrees(0.0, 0.0, 0.0) + ]) + }] + }; + + expect(function() { + return new PolygonGeometry({ + polygonHierarchy : hierarchy, + ellipsoid : ellipsoid + }); + }).toThrow(); + }); + + it('computes positions', function() { + var p = PolygonGeometry.fromPositions({ + vertexformat : VertexFormat.POSITION_ONLY, + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, 50.0, 0.0), + Cartographic.fromDegrees(-50.0, 50.0, 0.0) + ]), + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 11); + expect(p.indices.length).toEqual(3 * 14); + }); + + it('computes all attributes', function() { + var p = PolygonGeometry.fromPositions({ + vertexFormat : VertexFormat.ALL, + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, -50.0, 0.0), + Cartographic.fromDegrees(50.0, 50.0, 0.0), + Cartographic.fromDegrees(-50.0, 50.0, 0.0) + ]), + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 11); + expect(p.attributes.st.values.length).toEqual(2 * 11); + expect(p.attributes.normal.values.length).toEqual(3 * 11); + expect(p.attributes.tangent.values.length).toEqual(3 * 11); + expect(p.attributes.binormal.values.length).toEqual(3 * 11); + expect(p.indices.length).toEqual(3 * 14); + }); + + it('creates a polygon from hierarchy', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-124.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 35.0, 0.0), + Cartographic.fromDegrees(-110.0, 40.0, 0.0), + Cartographic.fromDegrees(-124.0, 40.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-122.0, 36.0, 0.0), + Cartographic.fromDegrees(-122.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 39.0, 0.0), + Cartographic.fromDegrees(-112.0, 36.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-120.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 36.5, 0.0), + Cartographic.fromDegrees(-114.0, 38.5, 0.0), + Cartographic.fromDegrees(-120.0, 38.5, 0.0) + ]) + }] + }] + }; + + var p = new PolygonGeometry({ + vertexformat : VertexFormat.POSITION_ONLY, + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 14); + expect(p.indices.length).toEqual(3 * 10); + }); + + it('creates a polygon from clockwise hierarchy', function() { + var hierarchy = { + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-124.0, 35.0, 0.0), + Cartographic.fromDegrees(-124.0, 40.0, 0.0), + Cartographic.fromDegrees(-110.0, 40.0, 0.0), + Cartographic.fromDegrees(-110.0, 35.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-122.0, 36.0, 0.0), + Cartographic.fromDegrees(-112.0, 36.0, 0.0), + Cartographic.fromDegrees(-112.0, 39.0, 0.0), + Cartographic.fromDegrees(-122.0, 39.0, 0.0) + ]), + holes : [{ + positions : Ellipsoid.WGS84.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-120.0, 36.5, 0.0), + Cartographic.fromDegrees(-120.0, 38.5, 0.0), + Cartographic.fromDegrees(-114.0, 38.5, 0.0), + Cartographic.fromDegrees(-114.0, 36.5, 0.0) + ]) + }] + }] + }; + + var p = new PolygonGeometry({ + vertexformat : VertexFormat.POSITION_ONLY, + polygonHierarchy : hierarchy, + granularity : CesiumMath.PI_OVER_THREE + }); + + expect(p.attributes.position.values.length).toEqual(3 * 14); + expect(p.indices.length).toEqual(3 * 10); + }); + +}, 'WebGL'); diff --git a/Specs/Core/PolygonPipelineSpec.js b/Specs/Core/PolygonPipelineSpec.js index a8f975cca16b..c40aafd4bc8d 100644 --- a/Specs/Core/PolygonPipelineSpec.js +++ b/Specs/Core/PolygonPipelineSpec.js @@ -18,8 +18,8 @@ defineSuite([ "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - it('cleanUp removes duplicate points', function() { - var positions = PolygonPipeline.cleanUp([ + it('removeDuplicates removes duplicate points', function() { + var positions = PolygonPipeline.removeDuplicates([ new Cartesian3(1.0, 1.0, 1.0), new Cartesian3(2.0, 2.0, 2.0), new Cartesian3(2.0, 2.0, 2.0), @@ -33,8 +33,8 @@ defineSuite([ ]); }); - it('cleanUp removes duplicate first and last points', function() { - var positions = PolygonPipeline.cleanUp([ + it('removeDuplicates removes duplicate first and last points', function() { + var positions = PolygonPipeline.removeDuplicates([ new Cartesian3(1.0, 1.0, 1.0), new Cartesian3(2.0, 2.0, 2.0), new Cartesian3(3.0, 3.0, 3.0), @@ -48,15 +48,15 @@ defineSuite([ ]); }); - it('cleanUp throws without positions', function() { + it('removeDuplicates throws without positions', function() { expect(function() { - PolygonPipeline.cleanUp(); + PolygonPipeline.removeDuplicates(); }).toThrow(); }); - it('cleanUp throws without three positions', function() { + it('removeDuplicates throws without three positions', function() { expect(function() { - PolygonPipeline.cleanUp([Cartesian3.ZERO, Cartesian3.ZERO]); + PolygonPipeline.removeDuplicates([Cartesian3.ZERO, Cartesian3.ZERO]); }).toThrow(); }); @@ -175,142 +175,6 @@ defineSuite([ /////////////////////////////////////////////////////////////////////// - it('wrapLongitude subdivides triangle it crosses the international date line, p0 behind', function() { - var positions = [new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, 1.0, 2.0), new Cartesian3(-1.0, 2.0, 2.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 3, 4, 1, 2, 6, 1, 6, 5]); - expect(positions[0].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, 1.0, 2.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, 2.0, 2.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude subdivides triangle it crosses the international date line, p1 behind', function() { - var positions = [new Cartesian3(-1.0, 1.0, 2.0), new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, 2.0, 2.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([1, 3, 4, 2, 0, 6, 2, 6, 5]); - expect(positions[0].equals(new Cartesian3(-1.0, 1.0, 2.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, 2.0, 2.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude subdivides triangle it crosses the international date line, p2 behind', function() { - var positions = [new Cartesian3(-1.0, 1.0, 2.0), new Cartesian3(-1.0, 2.0, 2.0), new Cartesian3(-1.0, -1.0, 0.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([2, 3, 4, 0, 1, 6, 0, 6, 5]); - expect(positions[0].equals(new Cartesian3(-1.0, 1.0, 2.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, 2.0, 2.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude subdivides triangle it crosses the international date line, p0 ahead', function() { - var positions = [new Cartesian3(-1.0, 2.0, 0.0), new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, -1.0, 0.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([1, 2, 4, 1, 4, 3, 0, 5, 6]); - expect(positions[0].equals(new Cartesian3(-1.0, 2.0, 0.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude subdivides triangle it crosses the international date line, p1 ahead', function() { - var positions = [new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, 2.0, 0.0), new Cartesian3(-1.0, -1.0, 0.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([2, 0, 4, 2, 4, 3, 1, 5, 6]); - expect(positions[0].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, 2.0, 0.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude subdivides triangle it crosses the international date line, p2 ahead', function() { - var positions = [new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, -1.0, 0.0), new Cartesian3(-1.0, 2.0, 0.0)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 1, 4, 0, 4, 3, 2, 5, 6]); - expect(positions[0].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1.0, -1.0, 0.0))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1.0, 2.0, 0.0))).toEqual(true); - expect(positions.length).toEqual(7); - }); - - it('wrapLongitude returns offsets triangle that touches the international date line', function() { - var positions = [new Cartesian3(-1, 0, 1), new Cartesian3(-1, CesiumMath.EPSILON14, 2), new Cartesian3(-2, 2, 2)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 1, 2]); - expect(positions[0].equals(new Cartesian3(-1, CesiumMath.EPSILON11, 1))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1, CesiumMath.EPSILON11, 2))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-2, 2, 2))).toEqual(true); - expect(positions.length).toEqual(3); - }); - - it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, behind', function() { - var positions = [new Cartesian3(-1, -1, 1), new Cartesian3(-1, -2, 1), new Cartesian3(-1, -2, 2)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 1, 2]); - expect(positions[0].equals(new Cartesian3(-1, -1, 1))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1, -2, 1))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1, -2, 2))).toEqual(true); - expect(positions.length).toEqual(3); - }); - - it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, ahead', function() { - var positions = [new Cartesian3(-1, 1, 1), new Cartesian3(-1, 2, 1), new Cartesian3(-1, 2, 2)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 1, 2]); - expect(positions[0].equals(new Cartesian3(-1, 1, 1))).toEqual(true); - expect(positions[1].equals(new Cartesian3(-1, 2, 1))).toEqual(true); - expect(positions[2].equals(new Cartesian3(-1, 2, 2))).toEqual(true); - expect(positions.length).toEqual(3); - }); - - it('wrapLongitude returns the same points if the triangle doesn\'t cross the international date line, positive x', function() { - var positions = [new Cartesian3(1, 1, 1), new Cartesian3(1, 2, 1), new Cartesian3(1, 2, 2)]; - var indices = [0, 1, 2]; - indices = PolygonPipeline.wrapLongitude(positions, indices); - expect(indices).toEqual([0, 1, 2]); - expect(positions[0].equals(new Cartesian3(1, 1, 1))).toEqual(true); - expect(positions[1].equals(new Cartesian3(1, 2, 1))).toEqual(true); - expect(positions[2].equals(new Cartesian3(1, 2, 2))).toEqual(true); - expect(positions.length).toEqual(3); - }); - - it('wrapLongitude throws with count indices not multiple of three', function() { - expect(function() { - PolygonPipeline.wrapLongitude([Cartesian3.ZERO, Cartesian3.ZERO], [0, 0, 0, 0]); - }).toThrow(); - }); - - it('wrapLongitude throws with < 3 indices', function() { - expect(function() { - PolygonPipeline.wrapLongitude([Cartesian3.ZERO, Cartesian3.ZERO], []); - }).toThrow(); - }); - - it('wrapLongitude throws without positions', function() { - expect(function() { - PolygonPipeline.wrapLongitude(); - }).toThrow(); - }); - - it('wrapLongitude throws without indices', function() { - expect(function() { - PolygonPipeline.wrapLongitude([Cartesian3.ZERO, Cartesian3.ZERO]); - }).toThrow(); - }); - - /////////////////////////////////////////////////////////////////////// - it('computeSubdivision throws without positions', function() { expect(function() { PolygonPipeline.computeSubdivision(); @@ -360,9 +224,9 @@ defineSuite([ expect(subdivision.attributes.position.values[7]).toEqual(0.0); expect(subdivision.attributes.position.values[8]).toEqual(0.0); - expect(subdivision.indexLists[0].values[0]).toEqual(0); - expect(subdivision.indexLists[0].values[1]).toEqual(1); - expect(subdivision.indexLists[0].values[2]).toEqual(2); + expect(subdivision.indices[0]).toEqual(0); + expect(subdivision.indices[1]).toEqual(1); + expect(subdivision.indices[2]).toEqual(2); }); it('eliminateHoles throws an exception without an outerRing', function() { diff --git a/Specs/Core/PolylinePipelineSpec.js b/Specs/Core/PolylinePipelineSpec.js index 7e45362b702b..73d4d56a212a 100644 --- a/Specs/Core/PolylinePipelineSpec.js +++ b/Specs/Core/PolylinePipelineSpec.js @@ -49,4 +49,33 @@ defineSuite([ expect(segments.lengths[0]).toEqual(2); expect(segments.lengths[1]).toEqual(2); }); + + it('removeDuplicates to return one positions', function() { + var positions = [Cartesian3.ZERO]; + var nonDuplicatePositions = PolylinePipeline.removeDuplicates(positions); + expect(nonDuplicatePositions).not.toBe(positions); + expect(nonDuplicatePositions).toEqual(positions); + }); + + it('removeDuplicates to remove duplicates', function() { + var positions = [ + new Cartesian3(1.0, 1.0, 1.0), + new Cartesian3(1.0, 1.0, 1.0), + new Cartesian3(2.0, 2.0, 2.0), + new Cartesian3(3.0, 3.0, 3.0), + new Cartesian3(3.0, 3.0, 3.0)]; + var expectedPositions = [ + new Cartesian3(1.0, 1.0, 1.0), + new Cartesian3(2.0, 2.0, 2.0), + new Cartesian3(3.0, 3.0, 3.0)]; + var nonDuplicatePositions = PolylinePipeline.removeDuplicates(positions); + expect(nonDuplicatePositions).not.toBe(expectedPositions); + expect(nonDuplicatePositions).toEqual(expectedPositions); + }); + + it('removeDuplicates throws without positions', function() { + expect(function() { + PolylinePipeline.removeDuplicates(); + }).toThrow(); + }); }); \ No newline at end of file diff --git a/Specs/Core/ShowGeometryInstanceAttributeSpec.js b/Specs/Core/ShowGeometryInstanceAttributeSpec.js new file mode 100644 index 000000000000..89f9e813a19a --- /dev/null +++ b/Specs/Core/ShowGeometryInstanceAttributeSpec.js @@ -0,0 +1,30 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/ShowGeometryInstanceAttribute', + 'Core/ComponentDatatype' + ], function( + ShowGeometryInstanceAttribute, + ComponentDatatype) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var attribute = new ShowGeometryInstanceAttribute(false); + expect(attribute.componentDatatype).toEqual(ComponentDatatype.UNSIGNED_BYTE); + expect(attribute.componentsPerAttribute).toEqual(1); + expect(attribute.normalize).toEqual(true); + + expect(attribute.value).toEqual(new Uint8Array([false])); + }); + + it('toValue', function() { + expect(ShowGeometryInstanceAttribute.toValue(true)).toEqual(new Uint8Array([true])); + }); + + it('toValue throws without a color', function() { + expect(function() { + ShowGeometryInstanceAttribute.toValue(); + }).toThrow(); + }); + +}); diff --git a/Specs/Core/SimplePolylineGeometrySpec.js b/Specs/Core/SimplePolylineGeometrySpec.js new file mode 100644 index 000000000000..a59d84d8a423 --- /dev/null +++ b/Specs/Core/SimplePolylineGeometrySpec.js @@ -0,0 +1,40 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/SimplePolylineGeometry', + 'Core/Cartesian3', + 'Core/BoundingSphere', + 'Core/PrimitiveType' + ], function( + SimplePolylineGeometry, + Cartesian3, + BoundingSphere, + PrimitiveType) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor throws with no positions', function() { + expect(function() { + return new SimplePolylineGeometry(); + }).toThrow(); + }); + + it('constructor throws with less than two positions', function() { + expect(function() { + return new SimplePolylineGeometry({ + positions : [Cartesian3.ZERO] + }); + }).toThrow(); + }); + + it('constructor computes all vertex attributes', function() { + var positions = [new Cartesian3(), new Cartesian3(1.0, 0.0, 0.0), new Cartesian3(2.0, 0.0, 0.0)]; + var line = new SimplePolylineGeometry({ + positions : positions + }); + + expect(line.attributes.position.values).toEqual([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0]); + expect(line.indices).toEqual([0, 1, 1, 2]); + expect(line.primitiveType).toEqual(PrimitiveType.LINES); + expect(line.boundingSphere).toEqual(BoundingSphere.fromPoints(positions)); + }); +}); \ No newline at end of file diff --git a/Specs/Core/WallGeometrySpec.js b/Specs/Core/WallGeometrySpec.js new file mode 100644 index 000000000000..08b8e16788af --- /dev/null +++ b/Specs/Core/WallGeometrySpec.js @@ -0,0 +1,156 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/WallGeometry', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + WallGeometry, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var ellipsoid = Ellipsoid.WGS84; + + it('throws with no positions', function() { + expect(function() { + return new WallGeometry(); + }).toThrow(); + }); + + it('throws when positions and minimumHeights length do not match', function() { + expect(function() { + return new WallGeometry({ + positions : new Array(2), + minimumHeights : new Array(3) + }); + }).toThrow(); + }); + + it('throws when positions and maximumHeights length do not match', function() { + expect(function() { + return new WallGeometry({ + positions : new Array(2), + maximumHeights : new Array(3) + }); + }).toThrow(); + }); + + it('creates positions relative to ellipsoid', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var w = new WallGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : ellipsoid.cartographicArrayToCartesianArray(coords) + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(2 * 3); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(0.0, CesiumMath.EPSILON9); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(1000.0, CesiumMath.EPSILON9); + }); + + it('creates positions with minimum and maximum heights', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var w = new WallGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : ellipsoid.cartographicArrayToCartesianArray(coords), + minimumHeights : [1000.0, 2000.0], + maximumHeights : [3000.0, 4000.0] + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(2 * 3); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(1000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(3000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 6)); + expect(cartographic.height).toEqualEpsilon(2000.0, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 9)); + expect(cartographic.height).toEqualEpsilon(4000.0, CesiumMath.EPSILON8); + }); + + it('creates all attributes', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0), + Cartographic.fromDegrees(51.0, 18.0, 1000.0) + ]; + + var w = new WallGeometry({ + vertexFormat : VertexFormat.ALL, + positions : ellipsoid.cartographicArrayToCartesianArray(coords) + }); + + expect(w.attributes.position.values.length).toEqual(4 * 2 * 3); + expect(w.attributes.normal.values.length).toEqual(4 * 2 * 3); + expect(w.attributes.tangent.values.length).toEqual(4 * 2 * 3); + expect(w.attributes.binormal.values.length).toEqual(4 * 2 * 3); + expect(w.attributes.st.values.length).toEqual(4 * 2 * 2); + expect(w.indices.length).toEqual((4 * 2 - 2) * 3); + }); + + it('fromConstantHeights throws without positions', function() { + expect(function() { + return WallGeometry.fromConstantHeights(); + }).toThrow(); + }); + + it('creates positions with constant minimum and maximum heights', function() { + var coords = [ + Cartographic.fromDegrees(49.0, 18.0, 1000.0), + Cartographic.fromDegrees(50.0, 18.0, 1000.0) + ]; + + var min = 1000.0; + var max = 2000.0; + + var w = WallGeometry.fromConstantHeights({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : ellipsoid.cartographicArrayToCartesianArray(coords), + minimumHeight : min, + maximumHeight : max + }); + + var positions = w.attributes.position.values; + expect(positions.length).toEqual(2 * 2 * 3); + expect(w.indices.length).toEqual(2 * 3); + + var cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 0)); + expect(cartographic.height).toEqualEpsilon(min, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 3)); + expect(cartographic.height).toEqualEpsilon(max, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 6)); + expect(cartographic.height).toEqualEpsilon(min, CesiumMath.EPSILON8); + + cartographic = ellipsoid.cartesianToCartographic(Cartesian3.fromArray(positions, 9)); + expect(cartographic.height).toEqualEpsilon(max, CesiumMath.EPSILON8); + }); +}); + diff --git a/Specs/Core/barycentricCoordinatesSpec.js b/Specs/Core/barycentricCoordinatesSpec.js new file mode 100644 index 000000000000..f4977ed6ac07 --- /dev/null +++ b/Specs/Core/barycentricCoordinatesSpec.js @@ -0,0 +1,76 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/barycentricCoordinates', + 'Core/Cartesian3', + 'Core/Math' + ], function( + barycentricCoordinates, + Cartesian3, + CesiumMath) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var p0 = new Cartesian3(-1.0, 0.0, 0.0); + var p1 = new Cartesian3( 1.0, 0.0, 0.0); + var p2 = new Cartesian3( 0.0, 1.0, 1.0); + + it('evaluates to p0', function() { + var point = Cartesian3.clone(p0); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(Cartesian3.UNIT_X); + }); + + it('evaluates to p1', function() { + var point = Cartesian3.clone(p1); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(Cartesian3.UNIT_Y); + }); + + it('evaluates to p2', function() { + var point = Cartesian3.clone(p2); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(Cartesian3.UNIT_Z); + }); + + it('evaluates on the p0-p1 edge', function() { + var point = p1.add(p0).multiplyByScalar(0.5); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(new Cartesian3(0.5, 0.5, 0.0)); + }); + + it('evaluates on the p0-p2 edge', function() { + var point = p2.add(p0).multiplyByScalar(0.5); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(new Cartesian3(0.5, 0.0, 0.5)); + }); + + it('evaluates on the p1-p2 edge', function() { + var point = p2.add(p1).multiplyByScalar(0.5); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqual(new Cartesian3(0.0, 0.5, 0.5)); + }); + + it('evaluates on the interior', function() { + var scalar = 1.0 / 3.0; + var point = p0.add(p1).add(p2).multiplyByScalar(scalar); + expect(barycentricCoordinates(point, p0, p1, p2)).toEqualEpsilon(new Cartesian3(scalar, scalar, scalar), CesiumMath.EPSILON14); + }); + + it('throws without point', function() { + expect(function() { + barycentricCoordinates(); + }).toThrow(); + }); + + it('throws without p0', function() { + expect(function() { + barycentricCoordinates(new Cartesian3()); + }).toThrow(); + }); + + it('throws without p1', function() { + expect(function() { + barycentricCoordinates(new Cartesian3(), new Cartesian3()); + }).toThrow(); + }); + + it('throws without p2', function() { + expect(function() { + barycentricCoordinates(new Cartesian3(), new Cartesian3(), new Cartesian3()); + }).toThrow(); + }); +}); \ No newline at end of file diff --git a/Specs/Core/pointInsideTriangle2DSpec.js b/Specs/Core/pointInsideTriangle2DSpec.js deleted file mode 100644 index a1ed0823ec4e..000000000000 --- a/Specs/Core/pointInsideTriangle2DSpec.js +++ /dev/null @@ -1,58 +0,0 @@ -/*global defineSuite*/ -defineSuite([ - 'Core/pointInsideTriangle2D', - 'Core/Cartesian2' - ], function( - pointInsideTriangle2D, - Cartesian2) { - "use strict"; - /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - - it('pointInsideTriangle2D has point inside', function() { - expect(pointInsideTriangle2D(new Cartesian2(0.25, 0.25), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(true); - }); - - it('pointInsideTriangle2D has point outside', function() { - expect(pointInsideTriangle2D(new Cartesian2(1.0, 1.0), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); - }); - - it('pointInsideTriangle2D has point outside (2)', function() { - expect(pointInsideTriangle2D(new Cartesian2(0.5, -0.5), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); - }); - - it('pointInsideTriangle2D has point outside (3)', function() { - expect(pointInsideTriangle2D(new Cartesian2(-0.5, 0.5), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); - }); - - it('pointInsideTriangle2D has point on corner', function() { - expect(pointInsideTriangle2D(Cartesian2.ZERO, Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); - }); - - it('pointInsideTriangle2D has point inside on edge', function() { - expect(pointInsideTriangle2D(new Cartesian2(0.5, 0.0), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); - }); - - it('throws without point', function() { - expect(function() { - pointInsideTriangle2D(); - }).toThrow(); - }); - - it('throws without p0', function() { - expect(function() { - pointInsideTriangle2D(new Cartesian2()); - }).toThrow(); - }); - - it('throws without p1', function() { - expect(function() { - pointInsideTriangle2D(new Cartesian2(), new Cartesian2()); - }).toThrow(); - }); - - it('throws without p2', function() { - expect(function() { - pointInsideTriangle2D(new Cartesian2(), new Cartesian2(), new Cartesian2()); - }).toThrow(); - }); -}); \ No newline at end of file diff --git a/Specs/Core/pointInsideTriangleSpec.js b/Specs/Core/pointInsideTriangleSpec.js new file mode 100644 index 000000000000..ec2c97dcc39d --- /dev/null +++ b/Specs/Core/pointInsideTriangleSpec.js @@ -0,0 +1,58 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/pointInsideTriangle', + 'Core/Cartesian2' + ], function( + pointInsideTriangle, + Cartesian2) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('pointInsideTriangle has point inside', function() { + expect(pointInsideTriangle(new Cartesian2(0.25, 0.25), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(true); + }); + + it('pointInsideTriangle has point outside', function() { + expect(pointInsideTriangle(new Cartesian2(1.0, 1.0), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); + }); + + it('pointInsideTriangle has point outside (2)', function() { + expect(pointInsideTriangle(new Cartesian2(0.5, -0.5), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); + }); + + it('pointInsideTriangle has point outside (3)', function() { + expect(pointInsideTriangle(new Cartesian2(-0.5, 0.5), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); + }); + + it('pointInsideTriangle has point on corner', function() { + expect(pointInsideTriangle(Cartesian2.ZERO, Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); + }); + + it('pointInsideTriangle has point inside on edge', function() { + expect(pointInsideTriangle(new Cartesian2(0.5, 0.0), Cartesian2.ZERO, new Cartesian2(1.0, 0.0), new Cartesian2(0.0, 1.0))).toEqual(false); + }); + + it('throws without point', function() { + expect(function() { + pointInsideTriangle(); + }).toThrow(); + }); + + it('throws without p0', function() { + expect(function() { + pointInsideTriangle(new Cartesian2()); + }).toThrow(); + }); + + it('throws without p1', function() { + expect(function() { + pointInsideTriangle(new Cartesian2(), new Cartesian2()); + }).toThrow(); + }); + + it('throws without p2', function() { + expect(function() { + pointInsideTriangle(new Cartesian2(), new Cartesian2(), new Cartesian2()); + }).toThrow(); + }); +}); \ No newline at end of file diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index 8709e55fa46b..54f7ba633ad6 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -144,6 +144,19 @@ defineSuite([ verifyDraw(fs); }); + it('has czm_tangentToEyeSpaceMatrix', function() { + var fs = + 'void main() { ' + + ' vec3 tangent = vec3(1.0, 0.0, 0.0); ' + + ' vec3 binormal = vec3(0.0, 1.0, 0.0); ' + + ' vec3 normal = vec3(0.0, 0.0, 1.0); ' + + ' mat3 expected = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); ' + + ' mat3 actual = czm_tangentToEyeSpaceMatrix(normal, tangent, binormal); ' + + ' gl_FragColor = vec4(actual == expected); ' + + '}'; + verifyDraw(fs); + }); + it('has czm_translateRelativeToEye', function() { var camera = createCamera(context, new Cartesian3(1.0, 2.0, 3.0)); context.getUniformState().update(createFrameState(camera)); @@ -164,8 +177,8 @@ defineSuite([ 'uniform vec3 u_high;' + 'uniform vec3 u_low;' + 'void main() { ' + - ' vec3 p = czm_translateRelativeToEye(u_high, u_low);' + - ' gl_FragColor = vec4(p == vec3(5.0, 3.0, 1.0)); ' + + ' vec4 p = czm_translateRelativeToEye(u_high, u_low);' + + ' gl_FragColor = vec4(p == vec4(5.0, 3.0, 1.0, 1.0)); ' + '}'; verifyDraw(fs, uniformMap); diff --git a/Specs/Renderer/ContextSpec.js b/Specs/Renderer/ContextSpec.js index 953463fb3e0d..ecbebf683ce2 100644 --- a/Specs/Renderer/ContextSpec.js +++ b/Specs/Renderer/ContextSpec.js @@ -2,12 +2,16 @@ defineSuite([ 'Renderer/Context', 'Core/Color', + 'Core/IndexDatatype', + 'Renderer/BufferUsage', 'Specs/createContext', 'Specs/destroyContext', 'Specs/renderFragment' ], function( Context, Color, + IndexDatatype, + BufferUsage, createContext, destroyContext, renderFragment) { @@ -170,6 +174,18 @@ defineSuite([ } }); + it('gets the element index uint extension', function() { + if (context.getElementIndexUint()) { + var buffer = context.createIndexBuffer(6, BufferUsage.STREAM_DRAW, IndexDatatype.UNSIGNED_INT); + expect(buffer).toBeDefined(); + buffer.destroy(); + } else { + expect(function() { + context.createIndexBuffer(6, BufferUsage.STREAM_DRAW, IndexDatatype.UNSIGNED_INT); + }).toThrow(); + } + }); + it('gets the depth texture extension', function() { expect(context.getDepthTexture()).toBeDefined(); }); diff --git a/Specs/Renderer/VertexArrayFactorySpec.js b/Specs/Renderer/VertexArrayFactorySpec.js index 2f6bc84a293a..c98a6122dbec 100644 --- a/Specs/Renderer/VertexArrayFactorySpec.js +++ b/Specs/Renderer/VertexArrayFactorySpec.js @@ -3,7 +3,9 @@ defineSuite([ 'Specs/createContext', 'Specs/destroyContext', 'Core/ComponentDatatype', - 'Core/MeshFilters', + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/GeometryPipeline', 'Core/PrimitiveType', 'Core/IndexDatatype', 'Renderer/BufferUsage', @@ -13,7 +15,9 @@ defineSuite([ createContext, destroyContext, ComponentDatatype, - MeshFilters, + Geometry, + GeometryAttribute, + GeometryPipeline, PrimitiveType, IndexDatatype, BufferUsage, @@ -40,13 +44,13 @@ defineSuite([ }); it('creates with no arguments', function() { - va = context.createVertexArrayFromMesh(); + va = context.createVertexArrayFromGeometry(); expect(va.getNumberOfAttributes()).toEqual(0); expect(va.getIndexBuffer()).not.toBeDefined(); }); - it('creates with no mesh', function() { - va = context.createVertexArrayFromMesh({ + it('creates with no geometry', function() { + va = context.createVertexArrayFromGeometry({ vertexLayout : VertexLayout.INTERLEAVED }); expect(va.getNumberOfAttributes()).toEqual(0); @@ -54,25 +58,26 @@ defineSuite([ }); it('creates a single-attribute vertex (non-interleaved)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 1.0, 1.0, 1.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh) + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry) }); expect(va.getNumberOfAttributes()).toEqual(1); expect(va.getIndexBuffer()).not.toBeDefined(); - var position = mesh.attributes.position; + var position = geometry.attributes.position; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); @@ -83,19 +88,20 @@ defineSuite([ }); it('creates a single-attribute vertex (interleaved)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 1.0, 1.0, 1.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh), + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry), vertexLayout : VertexLayout.INTERLEAVED, bufferUsage : BufferUsage.STATIC_DRAW }); @@ -103,7 +109,7 @@ defineSuite([ expect(va.getNumberOfAttributes()).toEqual(1); expect(va.getIndexBuffer()).not.toBeDefined(); - var position = mesh.attributes.position; + var position = geometry.attributes.position; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); @@ -114,37 +120,38 @@ defineSuite([ }); it('creates a homogeneous multiple-attribute vertex (non-interleaved)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + customPosition : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] - }, - normal : { + }), + customNormal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 1.0, 1.0, 3.0, 3.0, 3.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh) + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry) }); expect(va.getNumberOfAttributes()).toEqual(2); expect(va.getIndexBuffer()).not.toBeDefined(); - var position = mesh.attributes.position; + var position = geometry.attributes.customPosition; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(0); // Tightly packed - var normal = mesh.attributes.position; + var normal = geometry.attributes.customNormal; expect(va.getAttribute(1).index).toEqual(1); expect(va.getAttribute(1).componentDatatype).toEqual(normal.componentDatatype); expect(va.getAttribute(1).componentsPerAttribute).toEqual(normal.componentsPerAttribute); @@ -155,32 +162,33 @@ defineSuite([ }); it('creates a homogeneous multiple-attribute vertex (interleaved)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + customPosition : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] - }, - normal : { + }), + customNormal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 1.0, 1.0, 3.0, 3.0, 3.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh), + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry), vertexLayout : VertexLayout.INTERLEAVED }); expect(va.getNumberOfAttributes()).toEqual(2); expect(va.getIndexBuffer()).not.toBeDefined(); - var position = mesh.attributes.position; - var normal = mesh.attributes.position; + var position = geometry.attributes.customPosition; + var normal = geometry.attributes.customNormal; var expectedStride = position.componentDatatype.sizeInBytes * position.componentsPerAttribute + normal.componentDatatype.sizeInBytes * normal.componentsPerAttribute; expect(va.getAttribute(0).index).toEqual(0); @@ -199,32 +207,33 @@ defineSuite([ }); it('creates a heterogeneous multiple-attribute vertex (interleaved)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] - }, - colors : { + }), + colors : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, values : [1, 1, 1, 1, 2, 2, 2, 2] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, - attributeIndices : MeshFilters.createAttributeIndices(mesh), + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, + attributeIndices : GeometryPipeline.createAttributeIndices(geometry), vertexLayout : VertexLayout.INTERLEAVED }); expect(va.getNumberOfAttributes()).toEqual(2); expect(va.getIndexBuffer()).not.toBeDefined(); - var position = mesh.attributes.position; - var colors = mesh.attributes.colors; + var position = geometry.attributes.position; + var colors = geometry.attributes.colors; var expectedStride = position.componentDatatype.sizeInBytes * position.componentsPerAttribute + colors.componentDatatype.sizeInBytes * colors.componentsPerAttribute; expect(va.getAttribute(0).index).toEqual(0); @@ -243,29 +252,30 @@ defineSuite([ }); it('sorts interleaved attributes from large to small components', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - bytes : { + bytes : new GeometryAttribute({ componentDatatype : ComponentDatatype.BYTE, componentsPerAttribute : 1, values : [0] - }, - shorts : { + }), + shorts : new GeometryAttribute({ componentDatatype : ComponentDatatype.SHORT, componentsPerAttribute : 1, values : [1] - }, - floats : { + }), + floats : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [2] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, vertexLayout : VertexLayout.INTERLEAVED }); @@ -307,25 +317,26 @@ defineSuite([ }); it('sorts interleaved attributes from large to small components (2)', function() { - // TODO: Color should be normalized - var mesh = { + var geometry = new Geometry({ attributes : { - color : { + color : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, + normalize : true, values : [255, 0, 0, 255, 0, 255, 0, 255] - }, - position : { + }), + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, vertexLayout : VertexLayout.INTERLEAVED }); @@ -372,34 +383,35 @@ defineSuite([ }); it('sorts interleaved attributes from large to small components (3)', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - unsignedByteAttribute : { + unsignedByteAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 2, values : [1, 2] - }, - unsignedShortAttribute : { + }), + unsignedShortAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_SHORT, componentsPerAttribute : 1, values : [3] - }, - byteAttribute : { + }), + byteAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.BYTE, componentsPerAttribute : 1, values : [4] - }, - shortAttribute : { + }), + shortAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.SHORT, componentsPerAttribute : 1, values : [5] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, vertexLayout : VertexLayout.INTERLEAVED }); @@ -438,36 +450,36 @@ defineSuite([ }); it('creates a custom interleaved vertex', function() { - // TODO: Color should be normalized - - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - }, - color : { + }), + color : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 3, + normalize : true, values : [255, 0, 0, 0, 255, 0] - }, - normal : { + }), + normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 0.0, 0.0, 0.0, 1.0, 0.0] - }, - temperature : { + }), + temperature : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_SHORT, componentsPerAttribute : 1, values : [75, 100] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); - var va = context.createVertexArrayFromMesh({ - mesh : mesh, + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + var va = context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : attributeIndices, vertexLayout : VertexLayout.INTERLEAVED }); @@ -540,85 +552,68 @@ defineSuite([ }); it('creates an index buffer', function() { - var mesh = { - indexLists : [{ - primitiveType : PrimitiveType.POINTS, - values : [0] - }] - }; - - var va = context.createVertexArrayFromMesh({ - mesh : mesh + var geometry = new Geometry({ + attributes : {}, + indices : [0], + primitiveType : PrimitiveType.POINTS + }); + + var va = context.createVertexArrayFromGeometry({ + geometry : geometry }); expect(va.getNumberOfAttributes()).toEqual(0); expect(va.getIndexBuffer()).toBeDefined(); expect(va.getIndexBuffer().getUsage()).toEqual(BufferUsage.DYNAMIC_DRAW); // Default expect(va.getIndexBuffer().getIndexDatatype()).toEqual(IndexDatatype.UNSIGNED_SHORT); - expect(va.getIndexBuffer().getNumberOfIndices()).toEqual(mesh.indexLists[0].values.length); - }); - - it('throws with multiple index lists', function() { - var mesh = { - indexLists : [{ - primitiveType : PrimitiveType.POINTS, - values : [0] - }, { - primitiveType : PrimitiveType.POINTS, - values : [1] - }] - }; - - expect(function() { - return context.createVertexArrayFromMesh({ - mesh : mesh - }); - }).toThrow(); + expect(va.getIndexBuffer().getNumberOfIndices()).toEqual(geometry.indices.length); }); it('throws with different number of interleaved attributes', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [0.0] - }, - normal : { + }), + normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [1.0, 2.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); expect(function() { - return context.createVertexArrayFromMesh({ - mesh : mesh, + return context.createVertexArrayFromGeometry({ + geometry : geometry, vertexLayout : VertexLayout.INTERLEAVED }); }).toThrow(); }); it('throws with duplicate indices', function() { - var mesh = { + var geometry = new Geometry({ attributes : { - position : { + position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [0.0] - }, - normal : { + }), + normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [1.0] - } - } - }; + }) + }, + primitiveType : PrimitiveType.POINTS + }); expect(function() { - return context.createVertexArrayFromMesh({ - mesh : mesh, + return context.createVertexArrayFromGeometry({ + geometry : geometry, attributeIndices : { position : 0, normal : 0 diff --git a/Specs/Scene/AppearanceSpec.js b/Specs/Scene/AppearanceSpec.js new file mode 100644 index 000000000000..0e696bedee6e --- /dev/null +++ b/Specs/Scene/AppearanceSpec.js @@ -0,0 +1,91 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/Appearance', + 'Scene/Material', + 'Renderer/BlendingState', + 'Renderer/CullFace' + ], function( + Appearance, + Material, + BlendingState, + CullFace) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('constructor', function() { + var material = Material.fromType(undefined, 'Color'); + var vs = + 'attribute vec3 position3DHigh;\n' + + 'attribute vec3 position3DLow;\n' + + 'attribute vec4 color;\n' + + 'varying vec4 v_color;\n' + + 'void main() {\n' + + ' gl_Position = czm_modelViewProjectionRelativeToEye * czm_computePosition();\n' + + ' v_color = color;\n' + + '}\n'; + var fs = + 'varying vec4 v_color;\n' + + 'void main() {\n' + + ' gl_FragColor = v_color;\n' + + '}\n'; + var renderState = { + depthTest : { + enabled : true + } + }; + var appearance = new Appearance({ + material : material, + vertexShaderSource : vs, + fragmentShaderSource : fs, + renderState : renderState + }); + + expect(appearance.material).toBe(material); + expect(appearance.vertexShaderSource).toBe(vs); + expect(appearance.fragmentShaderSource).toBe(fs); + expect(appearance.renderState).toBe(renderState); + }); + + it('getFragmentShaderSource', function() { + var fs = + 'varying vec4 v_color;\n' + + 'void main() {\n' + + ' gl_FragColor = v_color;\n' + + '}\n'; + var appearance = new Appearance({ + fragmentShaderSource : fs + }); + + expect(appearance.getFragmentShaderSource().indexOf(fs)).toBeGreaterThan(-1); + }); + + it('getFragmentShaderSource with material', function() { + var material = Material.fromType(undefined, 'Color'); + var fs = + 'varying vec4 v_color;\n' + + 'void main() {\n' + + ' gl_FragColor = v_color;\n' + + '}\n'; + var appearance = new Appearance({ + material : material, + fragmentShaderSource : fs + }); + + var fragmentSource = appearance.getFragmentShaderSource(); + expect(fragmentSource.indexOf(material.shaderSource)).toBeGreaterThan(-1); + expect(fragmentSource.indexOf(fs)).toBeGreaterThan(-1); + }); + + it('getDefaultRenderState', function() { + var renderState = Appearance.getDefaultRenderState(true, true); + + expect(renderState.depthTest).toBeDefined(); + expect(renderState.depthTest.enabled).toEqual(true); + expect(renderState.depthMask).toEqual(false); + expect(renderState.blending).toEqual(BlendingState.ALPHA_BLEND); + expect(renderState.cull).toBeDefined(); + expect(renderState.cull.enabled).toEqual(true); + expect(renderState.cull.face).toEqual(CullFace.BACK); + }); + +}); diff --git a/Specs/Scene/DebugAppearanceSpec.js b/Specs/Scene/DebugAppearanceSpec.js new file mode 100644 index 000000000000..faa968cc28fb --- /dev/null +++ b/Specs/Scene/DebugAppearanceSpec.js @@ -0,0 +1,340 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/DebugAppearance', + 'Scene/Appearance', + 'Scene/Primitive', + 'Core/ExtentGeometry', + 'Core/Extent', + 'Core/GeometryInstance', + 'Core/GeometryInstanceAttribute', + 'Core/ComponentDatatype', + 'Core/VertexFormat', + 'Renderer/ClearCommand', + 'Specs/render', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], function( + DebugAppearance, + Appearance, + Primitive, + ExtentGeometry, + Extent, + GeometryInstance, + GeometryInstanceAttribute, + ComponentDatatype, + VertexFormat, + ClearCommand, + render, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var frameState; + var extentInstance; + + beforeAll(function() { + context = createContext(); + frameState = createFrameState(); + + var extent = Extent.fromDegrees(-80.0, 20.0, -70.0, 40.0); + extentInstance = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : VertexFormat.ALL, + extent : extent + }) + }); + + frameState.camera.controller.viewExtent(extent); + var us = context.getUniformState(); + us.update(frameState); + }); + + afterAll(function() { + destroyContext(context); + }); + + it('constructor throws without attributeName', function() { + expect(function() { + return new DebugAppearance(); + }).toThrow(); + }); + + it('default construct with normal, binormal, or tangent attribute name', function() { + var a = new DebugAppearance({ + attributeName : 'normal' + }); + + expect(a.vertexShaderSource).toBeDefined(); + expect(a.vertexShaderSource.indexOf('normal')).toBeGreaterThan(-1); + expect(a.vertexShaderSource.indexOf('v_normal')).toBeGreaterThan(-1); + + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.fragmentShaderSource.indexOf('v_normal')).toBeGreaterThan(-1); + + expect(a.material).not.toBeDefined(); + expect(a.attributeName).toEqual('normal'); + expect(a.glslDatatype).toEqual('vec3'); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(false, false)); + }); + + it('default construct with st attribute name', function() { + var a = new DebugAppearance({ + attributeName : 'st' + }); + + expect(a.vertexShaderSource).toBeDefined(); + expect(a.vertexShaderSource.indexOf('st')).toBeGreaterThan(-1); + expect(a.vertexShaderSource.indexOf('v_st')).toBeGreaterThan(-1); + + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.fragmentShaderSource.indexOf('v_st')).toBeGreaterThan(-1); + + expect(a.material).not.toBeDefined(); + expect(a.attributeName).toEqual('st'); + expect(a.glslDatatype).toEqual('vec2'); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(false, false)); + }); + + it('debug appearance with float attribute name', function() { + var a = new DebugAppearance({ + attributeName : 'rotation', + glslDatatype : 'float' + }); + + expect(a.vertexShaderSource).toBeDefined(); + expect(a.vertexShaderSource.indexOf('rotation')).toBeGreaterThan(-1); + expect(a.vertexShaderSource.indexOf('v_rotation')).toBeGreaterThan(-1); + + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.fragmentShaderSource.indexOf('v_rotation')).toBeGreaterThan(-1); + + expect(a.material).not.toBeDefined(); + expect(a.attributeName).toEqual('rotation'); + expect(a.glslDatatype).toEqual('float'); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(false, false)); + }); + + it('debug appearance with vec3 attribute name', function() { + var a = new DebugAppearance({ + attributeName : 'str', + glslDatatype : 'vec3' + }); + + expect(a.vertexShaderSource).toBeDefined(); + expect(a.vertexShaderSource.indexOf('str')).toBeGreaterThan(-1); + expect(a.vertexShaderSource.indexOf('v_str')).toBeGreaterThan(-1); + + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.fragmentShaderSource.indexOf('v_str')).toBeGreaterThan(-1); + + expect(a.material).not.toBeDefined(); + expect(a.attributeName).toEqual('str'); + expect(a.glslDatatype).toEqual('vec3'); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(false, false)); + }); + + it('debug appearance with vec4 attribute name', function() { + var a = new DebugAppearance({ + attributeName : 'quaternion', + glslDatatype : 'vec4' + }); + + expect(a.vertexShaderSource).toBeDefined(); + expect(a.vertexShaderSource.indexOf('quaternion')).toBeGreaterThan(-1); + expect(a.vertexShaderSource.indexOf('v_quaternion')).toBeGreaterThan(-1); + + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.fragmentShaderSource.indexOf('v_quaternion')).toBeGreaterThan(-1); + + expect(a.material).not.toBeDefined(); + expect(a.attributeName).toEqual('quaternion'); + expect(a.glslDatatype).toEqual('vec4'); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(false, false)); + }); + + it('debug appearance throws with invalid glsl datatype', function() { + expect(function() { + return new DebugAppearance({ + attributeName : 'invalid_datatype', + glslDatatype : 'invalid' + }); + }).toThrow(); + }); + + it('renders normal', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'normal' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders binormal', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'binormal' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders tangent', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'tangent' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders st', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'st' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders float', function() { + extentInstance.attributes = { + debug : new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + value : [1.0] + }) + }; + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'debug', + glslDatatype : 'float' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders vec2', function() { + extentInstance.attributes = { + debug : new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + value : [1.0, 2.0] + }) + }; + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'debug', + glslDatatype : 'vec2' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders vec3', function() { + extentInstance.attributes = { + debug : new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + value : [1.0, 2.0, 3.0] + }) + }; + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'debug', + glslDatatype : 'vec3' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('renders vec4', function() { + extentInstance.attributes = { + debug : new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + value : [1.0, 2.0, 3.0, 4.0] + }) + }; + var primitive = new Primitive({ + geometryInstances : extentInstance, + appearance : new DebugAppearance({ + attributeName : 'debug', + glslDatatype : 'vec4' + }) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + +}); diff --git a/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js b/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js new file mode 100644 index 000000000000..0fe4ad7fb27d --- /dev/null +++ b/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js @@ -0,0 +1,92 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/EllipsoidSurfaceAppearance', + 'Scene/Appearance', + 'Scene/Material', + 'Scene/Primitive', + 'Core/ExtentGeometry', + 'Core/Extent', + 'Core/GeometryInstance', + 'Core/ColorGeometryInstanceAttribute', + 'Renderer/ClearCommand', + 'Specs/render', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], function( + EllipsoidSurfaceAppearance, + Appearance, + Material, + Primitive, + ExtentGeometry, + Extent, + GeometryInstance, + ColorGeometryInstanceAttribute, + ClearCommand, + render, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var frameState; + var primitive; + + beforeAll(function() { + context = createContext(); + frameState = createFrameState(); + + var extent = Extent.fromDegrees(-80.0, 20.0, -70.0, 40.0); + primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new ExtentGeometry({ + extent : extent + }), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }) + }); + + frameState.camera.controller.viewExtent(extent); + var us = context.getUniformState(); + us.update(frameState); + }); + + afterAll(function() { + primitive = primitive && primitive.destroy(); + destroyContext(context); + }); + + it('constructor', function() { + var a = new EllipsoidSurfaceAppearance(); + + expect(a.material).toBeDefined(); + expect(a.material.type).toEqual(Material.ColorType); + expect(a.vertexShaderSource).toBeDefined(); + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(true, true)); + expect(a.vertexFormat).toEqual(EllipsoidSurfaceAppearance.VERTEX_FORMAT); + expect(a.flat).toEqual(false); + expect(a.faceForward).toEqual(false); + expect(a.translucent).toEqual(true); + expect(a.aboveGround).toEqual(false); + }); + + it('renders', function() { + primitive.appearance = new EllipsoidSurfaceAppearance(); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + +}); diff --git a/Specs/Scene/ExtentPrimitiveSpec.js b/Specs/Scene/ExtentPrimitiveSpec.js new file mode 100644 index 000000000000..f1c8b4a0030c --- /dev/null +++ b/Specs/Scene/ExtentPrimitiveSpec.js @@ -0,0 +1,226 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/ExtentPrimitive', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createCamera', + 'Specs/createFrameState', + 'Specs/frameState', + 'Specs/pick', + 'Specs/render', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Extent', + 'Core/Math', + 'Renderer/ClearCommand', + 'Scene/SceneMode' + ], function( + ExtentPrimitive, + createContext, + destroyContext, + createCamera, + createFrameState, + frameState, + pick, + render, + BoundingSphere, + Cartesian3, + Cartographic, + Ellipsoid, + Extent, + CesiumMath, + ClearCommand, + SceneMode) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var extent; + var us; + + beforeAll(function() { + context = createContext(); + }); + + afterAll(function() { + destroyContext(context); + }); + + beforeEach(function() { + extent = new ExtentPrimitive(); + + us = context.getUniformState(); + us.update(createFrameState(createCamera(context, new Cartesian3(1.02, 0.0, 0.0), Cartesian3.ZERO, Cartesian3.UNIT_Z))); + }); + + afterEach(function() { + extent = extent && extent.destroy(); + us = undefined; + }); + + function createExtent() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + + var e = new ExtentPrimitive({ + ellipsoid : ellipsoid, + granularity : CesiumMath.toRadians(20.0), + extent : Extent.fromDegrees(-50.0, -50.0, 50.0, 50.0) + }); + + return e; + } + + it('gets default show', function() { + expect(extent.show).toEqual(true); + }); + + it('gets the default color', function() { + expect(extent.material.uniforms.color).toEqual({ + red : 1.0, + green : 1.0, + blue : 0.0, + alpha : 0.5 + }); + }); + + it('has a default ellipsoid', function() { + expect(extent.ellipsoid).toEqual(Ellipsoid.WGS84); + }); + + it('gets the default granularity', function() { + expect(extent.granularity).toEqual(CesiumMath.RADIANS_PER_DEGREE); + }); + + it('renders', function() { + extent = createExtent(); + extent.material.uniforms.color = { + red : 1.0, + green : 0.0, + blue : 0.0, + alpha : 1.0 + }; + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, extent); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + + it('does not render when show is false', function() { + extent = createExtent(); + extent.material.uniforms.color = { + red : 1.0, + green : 0.0, + blue : 0.0, + alpha : 1.0 + }; + extent.show = false; + + expect(render(context, frameState, extent)).toEqual(0); + }); + + it('does not render without extent', function() { + extent = new ExtentPrimitive(); + extent.ellipsoid = Ellipsoid.UNIT_SPHERE; + extent.granularity = CesiumMath.toRadians(20.0); + expect(render(context, frameState, extent)).toEqual(0); + }); + + it('is picked', function() { + extent = createExtent(); + + var pickedObject = pick(context, frameState, extent, 0, 0); + expect(pickedObject).toEqual(extent); + }); + + it('is not picked (show === false)', function() { + extent = createExtent(); + extent.show = false; + + var pickedObject = pick(context, frameState, extent, 0, 0); + expect(pickedObject).not.toBeDefined(); + }); + + it('is not picked (alpha === 0.0)', function() { + extent = createExtent(); + extent.material.uniforms.color.alpha = 0.0; + + var pickedObject = pick(context, frameState, extent, 0, 0); + expect(pickedObject).not.toBeDefined(); + }); + + it('test 3D bounding sphere', function() { + extent = createExtent(); + var commandList = []; + extent.update(context, frameState, commandList); + var boundingVolume = commandList[0].colorList[0].boundingVolume; + expect(boundingVolume).toEqual(BoundingSphere.fromExtent3D(extent.extent, Ellipsoid.UNIT_SPHERE)); + }); + + it('test Columbus view bounding sphere', function() { + extent = createExtent(); + + var mode = frameState.mode; + frameState.mode = SceneMode.COLUMBUS_VIEW; + var commandList = []; + extent.update(context, frameState, commandList); + var boundingVolume = commandList[0].colorList[0].boundingVolume; + frameState.mode = mode; + + var b3D = BoundingSphere.fromExtent3D(extent.extent, Ellipsoid.UNIT_SPHERE); + expect(boundingVolume).toEqual(BoundingSphere.projectTo2D(b3D, frameState.scene2D.projection)); + }); + + it('test 2D bounding sphere', function() { + extent = createExtent(); + + var mode = frameState.mode; + frameState.mode = SceneMode.SCENE2D; + var commandList = []; + extent.update(context, frameState, commandList); + var boundingVolume = commandList[0].colorList[0].boundingVolume; + frameState.mode = mode; + + var b3D = BoundingSphere.fromExtent3D(extent.extent, Ellipsoid.UNIT_SPHERE); + var b2D = BoundingSphere.projectTo2D(b3D, frameState.scene2D.projection); + b2D.center.x = 0.0; + expect(boundingVolume).toEqual(b2D); + }); + + it('isDestroyed', function() { + var e = new ExtentPrimitive(); + expect(e.isDestroyed()).toEqual(false); + e.destroy(); + expect(e.isDestroyed()).toEqual(true); + }); + + it('throws when updated/rendered without a ellipsoid', function() { + extent = createExtent(); + extent.ellipsoid = undefined; + + expect(function() { + extent.update(context, frameState); + }).toThrow(); + }); + + it('throws when updated/rendered without an invalid granularity', function() { + extent = createExtent(); + extent.granularity = -1.0; + + expect(function() { + extent.update(context, frameState); + }).toThrow(); + }); + + it('throws when rendered without a material', function() { + extent = createExtent(); + extent.material = undefined; + + expect(function() { + render(context, frameState, extent); + }).toThrow(); + }); +}, 'WebGL'); diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js new file mode 100644 index 000000000000..8e55ddfe7610 --- /dev/null +++ b/Specs/Scene/GeometryRenderingSpec.js @@ -0,0 +1,784 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/BoxGeometry', + 'Core/CircleGeometry', + 'Core/EllipseGeometry', + 'Core/EllipsoidGeometry', + 'Core/ExtentGeometry', + 'Core/PolygonGeometry', + 'Core/SimplePolylineGeometry', + 'Core/WallGeometry', + 'Core/defaultValue', + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/GeometryInstance', + 'Core/ColorGeometryInstanceAttribute', + 'Core/GeometryInstanceAttribute', + 'Core/ComponentDatatype', + 'Core/Cartesian3', + 'Core/Matrix4', + 'Core/Extent', + 'Core/Ellipsoid', + 'Core/PrimitiveType', + 'Core/Transforms', + 'Core/Cartographic', + 'Core/BoundingSphere', + 'Core/Math', + 'Renderer/ClearCommand', + 'Scene/PerInstanceColorAppearance', + 'Scene/Primitive', + 'Scene/SceneMode', + 'Scene/OrthographicFrustum', + 'Specs/render', + 'Specs/pick', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], 'Scene/GeometryRendering', function( + BoxGeometry, + CircleGeometry, + EllipseGeometry, + EllipsoidGeometry, + ExtentGeometry, + PolygonGeometry, + SimplePolylineGeometry, + WallGeometry, + defaultValue, + Geometry, + GeometryAttribute, + GeometryInstance, + ColorGeometryInstanceAttribute, + GeometryInstanceAttribute, + ComponentDatatype, + Cartesian3, + Matrix4, + Extent, + Ellipsoid, + PrimitiveType, + Transforms, + Cartographic, + BoundingSphere, + CesiumMath, + ClearCommand, + PerInstanceColorAppearance, + Primitive, + SceneMode, + OrthographicFrustum, + render, + pick, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var ellipsoid; + + beforeAll(function() { + context = createContext(); + ellipsoid = Ellipsoid.WGS84; + }); + + afterAll(function() { + destroyContext(context); + }); + + function viewSphere3D(camera, sphere, modelMatrix) { + sphere = BoundingSphere.transform(sphere, modelMatrix); + var center = sphere.center.clone(); + var radius = sphere.radius; + + var direction = ellipsoid.geodeticSurfaceNormal(center, camera.direction); + Cartesian3.negate(direction, direction); + Cartesian3.normalize(direction, direction); + var right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, camera.right); + Cartesian3.normalize(right, right); + Cartesian3.cross(right, direction, camera.up); + + var scalar = center.magnitude() + radius; + Cartesian3.normalize(center, center); + Cartesian3.multiplyByScalar(center, scalar, camera.position); + } + + function render3D(instance, afterView, boundingSphere) { + var primitive = new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + flat : true + }) + }); + + var frameState = createFrameState(); + + var sphere = defaultValue(instance.geometry.boundingSphere, boundingSphere); + viewSphere3D(frameState.camera, sphere, instance.modelMatrix); + + if (typeof afterView === 'function') { + afterView(frameState); + } + + context.getUniformState().update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + } + + function viewSphereCV(camera, sphere, modelMatrix) { + sphere = BoundingSphere.transform(sphere, modelMatrix); + sphere = BoundingSphere.projectTo2D(sphere); + var center = sphere.center.clone(); + var radius = sphere.radius * 0.5; + + Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); + Cartesian3.negate(camera.direction, camera.direction); + Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); + Cartesian3.clone(Cartesian3.UNIT_X, camera.right); + + camera.position.x = center.y; + camera.position.y = center.z; + camera.position.z = center.x + radius; + } + + function renderCV(instance, afterView, boundingSphere) { + var primitive = new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + flat : true + }) + }); + + var frameState = createFrameState(); + frameState.mode = SceneMode.COLUMBUS_VIEW; + frameState.morphTime = frameState.mode.morphTime; + frameState.camera.transform = new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + frameState.camera.controller.update(frameState.mode, frameState.scene2D); + + var sphere = defaultValue(instance.geometry.boundingSphere, boundingSphere); + viewSphereCV(frameState.camera, sphere, instance.modelMatrix); + + if (typeof afterView === 'function') { + afterView(frameState); + } + + context.getUniformState().update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + } + + function viewSphere2D(camera, sphere, modelMatrix) { + sphere = BoundingSphere.transform(sphere, modelMatrix); + sphere = BoundingSphere.projectTo2D(sphere); + var center = sphere.center.clone(); + var radius = sphere.radius; + + Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); + Cartesian3.negate(camera.direction, camera.direction); + Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); + Cartesian3.clone(Cartesian3.UNIT_X, camera.right); + + camera.position.x = center.y; + camera.position.y = center.z; + + var frustum = camera.frustum; + var ratio = camera.frustum.right / camera.frustum.top; + frustum.right = radius * 0.25; + frustum.top = frustum.right / ratio; + frustum.left = -frustum.right; + frustum.bottom = -frustum.top; + } + + function render2D(instance, boundingSphere) { + var primitive = new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + flat : true + }) + }); + + var frameState = createFrameState(); + frameState.mode = SceneMode.SCENE2D; + frameState.morphTime = frameState.mode.morphTime; + frameState.camera.transform = new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + var frustum = new OrthographicFrustum(); + frustum.right = ellipsoid.getMaximumRadius() * Math.PI; + frustum.left = -frustum.right; + frustum.top = frustum.right; + frustum.bottom = -frustum.top; + frameState.camera.frustum = frustum; + frameState.camera.controller.update(frameState.mode, frameState.scene2D); + + var sphere = defaultValue(instance.geometry.boundingSphere, boundingSphere); + viewSphere2D(frameState.camera, sphere, instance.modelMatrix); + context.getUniformState().update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + } + + function pickGeometry(instance, afterView, boundingSphere) { + var primitive = new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + flat : true + }) + }); + + var frameState = createFrameState(); + + var sphere = defaultValue(instance.geometry.boundingSphere, boundingSphere); + viewSphere3D(frameState.camera, sphere, instance.modelMatrix); + + if (typeof afterView === 'function') { + afterView(frameState); + } + + context.getUniformState().update(frameState); + + expect(pick(context, frameState, primitive)).toEqual(instance.id); + + primitive = primitive && primitive.destroy(); + } + + describe('BoxGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + dimensions : new Cartesian3(1000000.0, 1000000.0, 2000000.0) + }), + modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883))), new Cartesian3(0.0, 0.0, 3000000.0)), + id : 'box', + 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); + }); + }, 'WebGL'); + + describe('CircleGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new CircleGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20)), + radius : 1000000.0 + }), + id : 'circle', + 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); + }); + }, 'WebGL'); + + describe('EllipseGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new EllipseGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20)), + semiMinorAxis : 1000000.0, + semiMajorAxis : 1000000.0 + }), + id : 'ellipse', + 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('rotated', function() { + var rotated = new GeometryInstance({ + geometry : new EllipseGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20)), + semiMinorAxis : 1000000.0, + semiMajorAxis : 1000000.0, + bearing : CesiumMath.PI_OVER_FOUR + }), + id : 'ellipse', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(rotated); + }); + + it('at height', function() { + var atHeight = new GeometryInstance({ + geometry : new EllipseGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + center : ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20)), + semiMinorAxis : 1000000.0, + semiMajorAxis : 1000000.0, + height : 1000000.0 + }), + id : 'ellipse', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(atHeight); + }); + }, 'WebGL'); + + describe('EllipsoidGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new EllipsoidGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + radii : new Cartesian3(1000000.0, 1000000.0, 500000.0) + }), + modelMatrix : Matrix4.multiplyByTranslation(Transforms.eastNorthUpToFixedFrame( + ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-100, 20))), new Cartesian3(0.0, 0.0, 1000000.0)), + id : 'ellipsoid', + 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); + }); + }, 'WebGL'); + + describe('ExtentGeometry', function() { + var instance; + var extent; + beforeAll(function() { + extent = Extent.fromDegrees(0, 0, 1, 1); + instance = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + extent : extent + }), + id : 'extent', + 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('rotated', function() { + var rotated = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + extent : extent, + rotation : CesiumMath.PI_OVER_FOUR + }), + id : 'extent', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(rotated); + }); + + it('at height', function() { + var atHeight = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + extent : extent, + height : 100000.0 + }), + id : 'extent', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(atHeight); + }); + }, 'WebGL'); + + describe('PolygonGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 45.0), + Cartographic.fromDegrees(10.0, 45.0), + Cartographic.fromDegrees(10.0, 55.0), + Cartographic.fromDegrees(0.0, 55.0) + ]) + }), + id : 'polygon', + 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('at height', function() { + var atHeight = new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 45.0), + Cartographic.fromDegrees(10.0, 45.0), + Cartographic.fromDegrees(10.0, 55.0), + Cartographic.fromDegrees(0.0, 55.0) + ]), + height : 3000000.0 + }), + id : 'polygon', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(atHeight); + }); + + it('hierarchy', function() { + var hierarchy = new GeometryInstance({ + geometry : new PolygonGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + polygonHierarchy : { + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-109.0, 30.0), + Cartographic.fromDegrees(-95.0, 30.0), + Cartographic.fromDegrees(-95.0, 40.0), + Cartographic.fromDegrees(-109.0, 40.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-107.0, 31.0), + Cartographic.fromDegrees(-107.0, 39.0), + Cartographic.fromDegrees(-97.0, 39.0), + Cartographic.fromDegrees(-97.0, 31.0) + ]), + holes : [{ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(-106.5, 31.5), + Cartographic.fromDegrees(-97.5, 31.5), + Cartographic.fromDegrees(-97.5, 38.5), + Cartographic.fromDegrees(-106.5, 38.5) + ]) + }] + }] + } + }), + id : 'polygon', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + render3D(hierarchy); + }); + }, 'WebGL'); + + describe('WallGeometry', function() { + var instance; + var afterViewCV; + var afterView3D; + beforeAll(function() { + var height = 100000.0; + + instance = new GeometryInstance({ + geometry : new WallGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 0.0, height), + Cartographic.fromDegrees(0.01, 0.0, height) + ]) + }), + id : 'wall', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + + afterView3D = function(frameState) { + var transform = Transforms.eastNorthUpToFixedFrame(instance.geometry.boundingSphere.center); + frameState.camera.controller.rotateDown(-CesiumMath.PI_OVER_TWO, transform); + frameState.camera.controller.zoomIn(instance.geometry.boundingSphere.radius * 0.99); + }; + + afterViewCV = function(frameState) { + var translation = frameState.camera.position.clone(); + translation.z = 0.0; + var transform = Matrix4.fromTranslation(translation); + frameState.camera.controller.rotateDown(-CesiumMath.PI_OVER_TWO, transform); + frameState.camera.controller.zoomIn(instance.geometry.boundingSphere.radius * 1.85); + }; + }); + + it('3D', function() { + render3D(instance, afterView3D); + }); + + it('Columbus view', function() { + renderCV(instance, afterViewCV); + }); + + // walls do not render in 2D + + it('pick', function() { + pickGeometry(instance, afterView3D); + }); + }, 'WebGL'); + + describe('SimplePolylineGeometry', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new SimplePolylineGeometry({ + positions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, 0.0), + Cartographic.fromDegrees(5.0, 0.0) + ]) + }), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 1.0, 1.0) + }, + id : 'simple polyline' + }); + }); + + it('3D', function() { + render3D(instance); + }); + + it('Columbus view', function() { + renderCV(instance); + }); + + it('2D', function() { + render2D(instance); + }); + + it('pick', function() { + pickGeometry(instance); + }); + }); + + describe('Custom geometry', function() { + describe('with indices', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 7000000.0, 0.0, 0.0, + 7000000.0, 1000000.0, 0.0, + 7000000.0, 0.0, 1000000.0, + 7000000.0, 1000000.0, 1000000.0 + ]) + }) + }, + indices : new Uint16Array([0, 1, 2, 2, 1, 3]), + primitiveType : PrimitiveType.TRIANGLES + }), + id : 'customWithIndices', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 1.0, 1.0) + } + }); + instance.geometry.boundingSphere = BoundingSphere.fromVertices(instance.geometry.attributes.position.values); + }); + + it('3D', function() { + render3D(instance); + }); + + it('Columbus view', function() { + renderCV(instance); + }); + + it('2D', function() { + render2D(instance); + }); + + it('pick', function() { + pickGeometry(instance); + }); + }); + + describe('without indices', function() { + var instance; + beforeAll(function() { + instance = new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array([ + 7000000.0, 0.0, 0.0, + 7000000.0, 1000000.0, 0.0, + 7000000.0, 0.0, 1000000.0, + 7000000.0, 0.0, 1000000.0, + 7000000.0, 1000000.0, 0.0, + 7000000.0, 1000000.0, 1000000.0 + ]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }), + id : 'customWithIndices', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 1.0, 1.0) + } + }); + instance.geometry.boundingSphere = BoundingSphere.fromVertices(instance.geometry.attributes.position.values); + }); + + it('3D', function() { + render3D(instance); + }); + + it('Columbus view', function() { + renderCV(instance); + }); + + it('2D', function() { + render2D(instance); + }); + + it('pick', function() { + pickGeometry(instance); + }); + }); + }); + +}); diff --git a/Specs/Scene/MaterialAppearanceSpec.js b/Specs/Scene/MaterialAppearanceSpec.js new file mode 100644 index 000000000000..b7781a13bc8d --- /dev/null +++ b/Specs/Scene/MaterialAppearanceSpec.js @@ -0,0 +1,129 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/MaterialAppearance', + 'Scene/Appearance', + 'Scene/Material', + 'Scene/Primitive', + 'Core/ExtentGeometry', + 'Core/Extent', + 'Core/GeometryInstance', + 'Core/ColorGeometryInstanceAttribute', + 'Renderer/ClearCommand', + 'Specs/render', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], function( + MaterialAppearance, + Appearance, + Material, + Primitive, + ExtentGeometry, + Extent, + GeometryInstance, + ColorGeometryInstanceAttribute, + ClearCommand, + render, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var frameState; + var primitive; + + beforeAll(function() { + context = createContext(); + frameState = createFrameState(); + + var extent = Extent.fromDegrees(-80.0, 20.0, -70.0, 40.0); + primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : MaterialAppearance.MaterialSupport.ALL.vertexFormat, + extent : extent + }), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }) + }); + + frameState.camera.controller.viewExtent(extent); + var us = context.getUniformState(); + us.update(frameState); + }); + + afterAll(function() { + primitive = primitive && primitive.destroy(); + destroyContext(context); + }); + + it('constructor', function() { + var a = new MaterialAppearance(); + + expect(a.materialSupport).toEqual(MaterialAppearance.MaterialSupport.TEXTURED); + expect(a.material).toBeDefined(); + expect(a.material.type).toEqual(Material.ColorType); + expect(a.vertexShaderSource).toEqual(MaterialAppearance.MaterialSupport.TEXTURED.vertexShaderSource); + expect(a.fragmentShaderSource).toEqual(MaterialAppearance.MaterialSupport.TEXTURED.fragmentShaderSource); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(true, false)); + expect(a.vertexFormat).toEqual(MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat); + expect(a.flat).toEqual(false); + expect(a.faceForward).toEqual(false); + expect(a.translucent).toEqual(true); + expect(a.closed).toEqual(false); + }); + + it('renders basic', function() { + primitive.appearance = new MaterialAppearance({ + materialSupport : MaterialAppearance.MaterialSupport.BASIC, + translucent : false, + closed : true, + material : Material.fromType(context, Material.DotType) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + + it('renders textured', function() { + primitive.appearance = new MaterialAppearance({ + materialSupport : MaterialAppearance.MaterialSupport.TEXTURED, + translucent : false, + closed : true, + material : Material.fromType(context, Material.ImageType) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + + it('renders all', function() { + primitive.appearance = new MaterialAppearance({ + materialSupport : MaterialAppearance.MaterialSupport.ALL, + translucent : false, + closed : true, + material : Material.fromType(context, Material.NormalMapType) + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + +}); diff --git a/Specs/Scene/MultifrustumSpec.js b/Specs/Scene/MultifrustumSpec.js index 4e3419eba98a..767608946599 100644 --- a/Specs/Scene/MultifrustumSpec.js +++ b/Specs/Scene/MultifrustumSpec.js @@ -4,14 +4,14 @@ defineSuite([ 'Specs/destroyScene', 'Core/destroyObject', 'Core/BoundingSphere', - 'Core/BoxTessellator', + 'Core/BoxGeometry', 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Color', 'Core/defaultValue', 'Core/Math', 'Core/Matrix4', - 'Core/MeshFilters', + 'Core/GeometryPipeline', 'Core/PrimitiveType', 'Renderer/BlendingState', 'Renderer/BufferUsage', @@ -25,14 +25,14 @@ defineSuite([ destroyScene, destroyObject, BoundingSphere, - BoxTessellator, + BoxGeometry, Cartesian2, Cartesian3, Color, defaultValue, CesiumMath, Matrix4, - MeshFilters, + GeometryPipeline, PrimitiveType, BlendingState, BufferUsage, @@ -222,13 +222,13 @@ defineSuite([ var dimensions = new Cartesian3(500000.0, 500000.0, 500000.0); var maximumCorner = dimensions.multiplyByScalar(0.5); var minimumCorner = maximumCorner.negate(); - var mesh = BoxTessellator.compute({ + var geometry = new BoxGeometry({ minimumCorner: minimumCorner, maximumCorner: maximumCorner }); - var attributeIndices = MeshFilters.createAttributeIndices(mesh); - this._va = context.createVertexArrayFromMesh({ - mesh: mesh, + var attributeIndices = GeometryPipeline.createAttributeIndices(geometry); + this._va = context.createVertexArrayFromGeometry({ + geometry: geometry, attributeIndices: attributeIndices, bufferUsage: BufferUsage.STATIC_DRAW }); diff --git a/Specs/Scene/PerInstanceColorAppearanceSpec.js b/Specs/Scene/PerInstanceColorAppearanceSpec.js new file mode 100644 index 000000000000..71e1050a0e66 --- /dev/null +++ b/Specs/Scene/PerInstanceColorAppearanceSpec.js @@ -0,0 +1,106 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/PerInstanceColorAppearance', + 'Scene/Appearance', + 'Scene/Material', + 'Scene/Primitive', + 'Core/ExtentGeometry', + 'Core/Extent', + 'Core/GeometryInstance', + 'Core/ColorGeometryInstanceAttribute', + 'Renderer/ClearCommand', + 'Specs/render', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], function( + PerInstanceColorAppearance, + Appearance, + Material, + Primitive, + ExtentGeometry, + Extent, + GeometryInstance, + ColorGeometryInstanceAttribute, + ClearCommand, + render, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var frameState; + var primitive; + + beforeAll(function() { + context = createContext(); + frameState = createFrameState(); + + var extent = Extent.fromDegrees(-80.0, 20.0, -70.0, 40.0); + primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, + extent : extent + }), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }) + }); + + frameState.camera.controller.viewExtent(extent); + var us = context.getUniformState(); + us.update(frameState); + }); + + afterAll(function() { + primitive = primitive && primitive.destroy(); + destroyContext(context); + }); + + it('constructor', function() { + var a = new PerInstanceColorAppearance(); + + expect(a.material).not.toBeDefined(); + expect(a.vertexShaderSource).toBeDefined(); + expect(a.fragmentShaderSource).toBeDefined(); + expect(a.renderState).toEqual(Appearance.getDefaultRenderState(true, false)); + expect(a.vertexFormat).toEqual(PerInstanceColorAppearance.VERTEX_FORMAT); + expect(a.flat).toEqual(false); + expect(a.faceForward).toEqual(false); + expect(a.translucent).toEqual(true); + expect(a.closed).toEqual(false); + }); + + it('renders', function() { + primitive.appearance = new PerInstanceColorAppearance(); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + + it('renders flat', function() { + primitive.appearance = new PerInstanceColorAppearance({ + flat : true, + translucent : false, + closed : true + }); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + }); + +}); diff --git a/Specs/Scene/PolygonSpec.js b/Specs/Scene/PolygonSpec.js index 01e1021d1145..c2746ada23de 100644 --- a/Specs/Scene/PolygonSpec.js +++ b/Specs/Scene/PolygonSpec.js @@ -8,16 +8,11 @@ defineSuite([ 'Specs/frameState', 'Specs/pick', 'Specs/render', - 'Core/BoundingRectangle', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Cartographic', 'Core/Ellipsoid', - 'Core/Extent', - 'Core/Matrix4', 'Core/Math', - 'Core/JulianDate', - 'Renderer/BufferUsage', 'Renderer/ClearCommand', 'Scene/SceneMode' ], function( @@ -29,16 +24,11 @@ defineSuite([ frameState, pick, render, - BoundingRectangle, BoundingSphere, Cartesian3, Cartographic, Ellipsoid, - Extent, - Matrix4, CesiumMath, - JulianDate, - BufferUsage, ClearCommand, SceneMode) { "use strict"; @@ -173,23 +163,12 @@ defineSuite([ new Cartographic() ]) }; + polygon.configureFromPolygonHierarchy(hierarchy); expect(function() { - polygon.configureFromPolygonHierarchy(hierarchy); + render(context, frameState, polygon); }).toThrow(); }); - it('configures extent', function() { - var extent = new Extent( - 0.0, - 0.0, - CesiumMath.toRadians(10.0), - CesiumMath.toRadians(10.0) - ); - - polygon.configureExtent(extent); - expect(polygon.getPositions()).not.toBeDefined(); - }); - it('gets the default color', function() { expect(polygon.material.uniforms.color).toEqual({ red : 1.0, @@ -199,16 +178,12 @@ defineSuite([ }); }); - it('gets default buffer usage', function() { - expect(polygon.bufferUsage).toEqual(BufferUsage.STATIC_DRAW); - }); - it('has a default ellipsoid', function() { expect(polygon.ellipsoid).toEqual(Ellipsoid.WGS84); }); it('gets the default granularity', function() { - expect(polygon.granularity).toEqual(CesiumMath.toRadians(1.0)); + expect(polygon.granularity).toEqual(CesiumMath.RADIANS_PER_DEGREE); }); it('renders', function() { @@ -228,32 +203,6 @@ defineSuite([ expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); }); - it('renders extent', function() { - // This test fails in Chrome if a breakpoint is set inside this function. Strange. - - var ellipsoid = Ellipsoid.UNIT_SPHERE; - polygon.ellipsoid = ellipsoid; - polygon.granularity = CesiumMath.toRadians(20.0); - polygon.configureExtent(new Extent( - 0.0, - 0.0, - CesiumMath.toRadians(10.0), - CesiumMath.toRadians(10.0) - )); - polygon.material.uniforms.color = { - red : 1.0, - green : 0.0, - blue : 0.0, - alpha : 1.0 - }; - - ClearCommand.ALL.execute(context); - expect(context.readPixels()).toEqual([0, 0, 0, 0]); - - render(context, frameState, polygon); - expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); - }); - it('does not render when show is false', function() { polygon = createPolygon(); polygon.material.uniforms.color = { @@ -274,7 +223,7 @@ defineSuite([ expect(render(context, frameState, polygon)).toEqual(0); }); - it('does not render without positions due to duplicates', function() { + it('throws without positions due to duplicates', function() { var ellipsoid = Ellipsoid.UNIT_SPHERE; polygon = new Polygon(); @@ -285,10 +234,12 @@ defineSuite([ ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(0.0, 0.0, 0.0)) ]); - expect(render(context, frameState, polygon)).toEqual(0); + expect(function() { + render(context, frameState, polygon); + }).toThrow(); }); - it('does not render without hierarchy positions due to duplicates', function() { + it('throws without hierarchy positions due to duplicates', function() { var ellipsoid = Ellipsoid.UNIT_SPHERE; var hierarchy = { positions : ellipsoid.cartographicArrayToCartesianArray([ @@ -309,22 +260,9 @@ defineSuite([ polygon.ellipsoid = ellipsoid; polygon.configureFromPolygonHierarchy(hierarchy); - expect(render(context, frameState, polygon)).toEqual(0); - }); - - it('does not render with empty extent', function() { - var extent = new Extent( - 0.0, - 0.0, - 0.0, - 0.0 - ); - - polygon = new Polygon(); - polygon.ellipsoid = Ellipsoid.UNIT_SPHERE; - polygon.configureExtent(extent); - - expect(render(context, frameState, polygon)).toEqual(0); + expect(function () { + render(context, frameState, polygon); + }).toThrow(); }); it('is picked', function() { @@ -355,7 +293,7 @@ defineSuite([ var commandList = []; polygon.update(context, frameState, commandList); var boundingVolume = commandList[0].colorList[0].boundingVolume; - expect(boundingVolume).toEqual(BoundingSphere.fromPoints(polygon._positions)); + expect(boundingVolume).toEqual(BoundingSphere.fromPoints(polygon.getPositions())); }); function test2DBoundingSphereFromPositions(testMode) { @@ -380,18 +318,13 @@ defineSuite([ var boundingVolume = commandList[0].colorList[0].boundingVolume; frameState.mode = mode; - var projectedPositions = []; - for (var i = 0; i < positions.length; ++i) { - projectedPositions.push(projection.project(positions[i])); - } - - var sphere = BoundingSphere.fromPoints(projectedPositions); - sphere.center = new Cartesian3(0.0, sphere.center.x, sphere.center.y); - expect(boundingVolume.center).toEqualEpsilon(sphere.center, CesiumMath.EPSILON9); - expect(boundingVolume.radius).toEqualEpsilon(sphere.radius, CesiumMath.EPSILON9); + var sphere = BoundingSphere.projectTo2D(BoundingSphere.fromPoints(polygon.getPositions())); + sphere.center.x = (testMode === SceneMode.SCENE2D) ? 0.0 : sphere.center.x; + expect(boundingVolume.center).toEqualEpsilon(sphere.center, CesiumMath.EPSILON2); + expect(boundingVolume.radius).toEqualEpsilon(sphere.radius, CesiumMath.EPSILON2); } - it('test 2D bounding sphere from positions', function() { + it('test Columbus view bounding sphere from positions', function() { test2DBoundingSphereFromPositions(SceneMode.COLUMBUS_VIEW); }); @@ -399,57 +332,6 @@ defineSuite([ test2DBoundingSphereFromPositions(SceneMode.SCENE2D); }); - it('test 3D bounding sphere from extent', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var extent = new Extent( - 0.0, - 0.0, - CesiumMath.toRadians(10.0), - CesiumMath.toRadians(10.0)); - - var polygon = new Polygon(); - polygon.ellipsoid = ellipsoid; - polygon.configureExtent(extent); - - var commandList = []; - polygon.update(context, frameState, commandList); - var boundingVolume = commandList[0].colorList[0].boundingVolume; - expect(boundingVolume).toEqual(BoundingSphere.fromExtent3D(extent, ellipsoid)); - }); - - function test2DBoundingSphereFromExtent(testMode) { - var projection = frameState.scene2D.projection; - var ellipsoid = projection.getEllipsoid(); - var extent = new Extent( - 0.0, - 0.0, - CesiumMath.toRadians(10.0), - CesiumMath.toRadians(10.0)); - - var polygon = new Polygon(); - polygon.ellipsoid = ellipsoid; - polygon.configureExtent(extent); - - var mode = frameState.mode; - frameState.mode = testMode; - var commandList = []; - polygon.update(context, frameState, commandList); - var boundingVolume = commandList[0].colorList[0].boundingVolume; - frameState.mode = mode; - - var sphere = BoundingSphere.fromExtent2D(extent, projection); - sphere.center = new Cartesian3(0.0, sphere.center.x, sphere.center.y); - expect(boundingVolume).toEqualEpsilon(sphere, CesiumMath.EPSILON9); - } - - it('test 2D bounding sphere from extent', function() { - test2DBoundingSphereFromExtent(SceneMode.COLUMBUS_VIEW); - }); - - it('test 2D bounding sphere from extent', function() { - test2DBoundingSphereFromExtent(SceneMode.SCENE2D); - }); - it('isDestroyed', function() { var p = new Polygon(); expect(p.isDestroyed()).toEqual(false); diff --git a/Specs/Scene/PolylineCollectionSpec.js b/Specs/Scene/PolylineCollectionSpec.js index 381e70b3fba2..aa2f1891f14a 100644 --- a/Specs/Scene/PolylineCollectionSpec.js +++ b/Specs/Scene/PolylineCollectionSpec.js @@ -430,7 +430,7 @@ defineSuite([ it('renders 64K vertices of same polyline', function() { var positions = []; - for ( var i = 0; i < (64 * 1024) / 2; ++i) { + for ( var i = 0; i < CesiumMath.SIXTY_FOUR_KILOBYTES / 2; ++i) { positions.push({ x : 0, y : -1, @@ -455,7 +455,7 @@ defineSuite([ it('creates two vertex arrays and renders', function() { var positions = []; - for ( var i = 0; i < (64 * 1024) / 2; ++i) { + for ( var i = 0; i < CesiumMath.SIXTY_FOUR_KILOBYTES / 2; ++i) { positions.push({ x : 0, y : -1, @@ -498,7 +498,7 @@ defineSuite([ it('renders more than 64K vertices of same polyline', function() { var positions = []; - for ( var i = 0; i < 64 * 1024; ++i) { + for ( var i = 0; i < CesiumMath.SIXTY_FOUR_KILOBYTES; ++i) { positions.push({ x : 0, y : -1, @@ -757,7 +757,7 @@ defineSuite([ it('renders more than 64K vertices of different polylines', function() { var positions = []; - for ( var i = 0; i < 64 * 1024; ++i) { + for ( var i = 0; i < CesiumMath.SIXTY_FOUR_KILOBYTES; ++i) { positions.push({ x : -1, y : -1, @@ -797,7 +797,7 @@ defineSuite([ it('renders more than 64K vertices of different polylines of different widths', function() { var positions = []; - for ( var i = 0; i < 64 * 1024 - 2; ++i) { + for ( var i = 0; i < CesiumMath.SIXTY_FOUR_KILOBYTES - 2; ++i) { positions.push({ x : -1, y : -1, diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js new file mode 100644 index 000000000000..eb3bc352d6f5 --- /dev/null +++ b/Specs/Scene/PrimitiveSpec.js @@ -0,0 +1,595 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/Primitive', + 'Core/ExtentGeometry', + 'Core/Geometry', + 'Core/GeometryAttribute', + 'Core/GeometryInstance', + 'Core/ColorGeometryInstanceAttribute', + 'Core/ShowGeometryInstanceAttribute', + 'Core/GeometryInstanceAttribute', + 'Core/ComponentDatatype', + 'Core/Cartesian3', + 'Core/Matrix4', + 'Core/Extent', + 'Core/Ellipsoid', + 'Core/PrimitiveType', + 'Renderer/ClearCommand', + 'Scene/MaterialAppearance', + 'Scene/PerInstanceColorAppearance', + 'Scene/SceneMode', + 'Scene/OrthographicFrustum', + 'Specs/render', + 'Specs/pick', + 'Specs/createCanvas', + 'Specs/destroyCanvas', + 'Specs/createContext', + 'Specs/destroyContext', + 'Specs/createFrameState' + ], function( + Primitive, + ExtentGeometry, + Geometry, + GeometryAttribute, + GeometryInstance, + ColorGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GeometryInstanceAttribute, + ComponentDatatype, + Cartesian3, + Matrix4, + Extent, + Ellipsoid, + PrimitiveType, + ClearCommand, + MaterialAppearance, + PerInstanceColorAppearance, + SceneMode, + OrthographicFrustum, + render, + pick, + createCanvas, + destroyCanvas, + createContext, + destroyContext, + createFrameState) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var context; + var frameState; + var us; + + var ellipsoid; + + var extent1; + var extent2; + + var extentInstance1; + var extentInstance2; + + beforeAll(function() { + context = createContext(); + frameState = createFrameState(); + + us = context.getUniformState(); + us.update(frameState); + + ellipsoid = Ellipsoid.WGS84; + }); + + afterAll(function() { + destroyContext(context); + }); + + beforeEach(function() { + extent1 = Extent.fromDegrees(-80.0, 20.0, -70.0, 30.0); + extent2 = Extent.fromDegrees(70.0, 20.0, 80.0, 30.0); + + var translation = ellipsoid.cartographicToCartesian(extent1.getCenter()).normalize().multiplyByScalar(2.0); + extentInstance1 = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, + ellipsoid : ellipsoid, + extent : extent1 + }), + modelMatrix : Matrix4.fromTranslation(translation), + id : 'extent1', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), + show : new ShowGeometryInstanceAttribute(true) + } + }); + + translation = ellipsoid.cartographicToCartesian(extent2.getCenter()).normalize().multiplyByScalar(3.0); + extentInstance2 = new GeometryInstance({ + geometry : new ExtentGeometry({ + vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, + ellipsoid : ellipsoid, + extent : extent2 + }), + modelMatrix : Matrix4.fromTranslation(translation), + id : 'extent2', + attributes : { + color : new ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 1.0), + show : new ShowGeometryInstanceAttribute(true) + } + }); + }); + + it('default constructs', function() { + var primitive = new Primitive(); + expect(primitive.geometryInstances).not.toBeDefined(); + expect(primitive.appearance).not.toBeDefined(); + expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY); + expect(primitive.show).toEqual(true); + }); + + it('releases geometry instances when releaseGeometryInstances is true', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + releaseGeometryInstances : true + }); + + expect(primitive.geometryInstances).toBeDefined(); + primitive.update(context, frameState, []); + expect(primitive.geometryInstances).not.toBeDefined(); + + primitive = primitive && primitive.destroy(); + }); + + it('does not release geometry instances when releaseGeometryInstances is false', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + releaseGeometryInstances : false + }); + + expect(primitive.geometryInstances).toBeDefined(); + primitive.update(context, frameState, []); + expect(primitive.geometryInstances).toBeDefined(); + + primitive = primitive && primitive.destroy(); + }); + + it('does not render when show is false', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + var commands = []; + primitive.update(context, frameState, commands); + expect(commands.length).toBeGreaterThan(0); + + commands.length = 0; + primitive.show = false; + primitive.update(context, frameState, commands); + expect(commands.length).toEqual(0); + + primitive = primitive && primitive.destroy(); + }); + + it('does not render other than for the color or pick pass', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.passes.color = false; + frameState.passes.pick = false; + + var commands = []; + primitive.update(context, frameState, commands); + expect(commands.length).toEqual(0); + + frameState.passes.color = true; + frameState.passes.pick = true; + + primitive = primitive && primitive.destroy(); + }); + + it('does not render when allow3DOnly is true and the scene mode is SCENE2D', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.mode = SceneMode.SCENE2D; + + var commands = []; + primitive.update(context, frameState, commands); + expect(commands.length).toEqual(0); + + frameState.mode = SceneMode.SCENE3D; + primitive = primitive && primitive.destroy(); + }); + + it('does not render when allow3DOnly is true and the scene mode is COLUMBUS_VIEW', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.mode = SceneMode.COLUMBUS_VIEW; + + var commands = []; + primitive.update(context, frameState, commands); + expect(commands.length).toEqual(0); + + frameState.mode = SceneMode.SCENE3D; + primitive = primitive && primitive.destroy(); + }); + + it('renders in Columbus view when allow3DOnly is false', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : false + }); + + frameState.mode = SceneMode.COLUMBUS_VIEW; + frameState.morphTime = frameState.mode.morphTime; + frameState.camera.transform = new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + frameState.camera.controller.update(frameState.mode, frameState.scene2D); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState.camera.controller.viewExtent(extent2); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState = createFrameState(); // reset frame state + primitive = primitive && primitive.destroy(); + }); + + it('renders in 2D when allow3DOnly is false', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : false + }); + + frameState.mode = SceneMode.SCENE2D; + frameState.morphTime = frameState.mode.morphTime; + frameState.camera.transform = new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + var frustum = new OrthographicFrustum(); + frustum.right = Ellipsoid.WGS84.getMaximumRadius() * Math.PI; + frustum.left = -frustum.right; + frustum.top = frustum.right; + frustum.bottom = -frustum.top; + frameState.camera.frustum = frustum; + frameState.camera.controller.update(frameState.mode, frameState.scene2D); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState.camera.controller.viewExtent(extent2); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState = createFrameState(); // reset frame state + primitive = primitive && primitive.destroy(); + }); + + it('transforms to world coordinates', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState.camera.controller.viewExtent(extent2); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY); + + primitive = primitive && primitive.destroy(); + }); + + it('does not transform to world coordinates', function() { + extentInstance2.modelMatrix = Matrix4.clone(extentInstance1.modelMatrix); + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + frameState.camera.controller.viewExtent(extent2); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + expect(primitive.modelMatrix).not.toEqual(Matrix4.IDENTITY); + + primitive = primitive && primitive.destroy(); + }); + + it('get common per instance attributes', function() { + extentInstance2.attributes.not_used = new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + value : [0.5] + }); + + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + primitive.update(context, frameState, []); + + var attributes = primitive.getGeometryInstanceAttributes('extent1'); + expect(attributes.color).toBeDefined(); + expect(attributes.show).toBeDefined(); + + attributes = primitive.getGeometryInstanceAttributes('extent2'); + expect(attributes.color).toBeDefined(); + expect(attributes.show).toBeDefined(); + expect(attributes.not_used).not.toBeDefined(); + + primitive = primitive && primitive.destroy(); + }); + + it('modify color instance attribute', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + var pixels = context.readPixels(); + expect(pixels).not.toEqual([0, 0, 0, 0]); + + var attributes = primitive.getGeometryInstanceAttributes('extent1'); + expect(attributes.color).toBeDefined(); + attributes.color = [255, 255, 255, 255]; + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + var newPixels = context.readPixels(); + expect(newPixels).not.toEqual([0, 0, 0, 0]); + expect(newPixels).not.toEqual(pixels); + + primitive = primitive && primitive.destroy(); + }); + + it('modify show instance attribute', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).not.toEqual([0, 0, 0, 0]); + + var attributes = primitive.getGeometryInstanceAttributes('extent1'); + expect(attributes.show).toBeDefined(); + attributes.show = [0]; + + ClearCommand.ALL.execute(context); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + render(context, frameState, primitive); + expect(context.readPixels()).toEqual([0, 0, 0, 0]); + + primitive = primitive && primitive.destroy(); + }); + + it('picking', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + frameState.camera.controller.viewExtent(extent1); + us.update(frameState); + + expect(pick(context, frameState, primitive)).toEqual('extent1'); + + frameState.camera.controller.viewExtent(extent2); + us.update(frameState); + + expect(pick(context, frameState, primitive)).toEqual('extent2'); + + primitive = primitive && primitive.destroy(); + }); + + it('update throws when geometry primitive types are different', function() { + var primitive = new Primitive({ + geometryInstances : [ + new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([1.0, 2.0, 3.0, 4.0]) + }) + }, + primitiveType : PrimitiveType.LINES + }) + }), + new GeometryInstance({ + geometry : new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : new Float32Array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + }) + }, + primitiveType : PrimitiveType.TRIANGLES + }) + }) + ], + appearance : new PerInstanceColorAppearance() + }); + + expect(function() { + primitive.update(context, frameState, []); + }).toThrow(); + }); + + it('shader validation', function() { + var primitive = new Primitive({ + geometryInstances : [extentInstance1, extentInstance2], + allow3DOnly : true, + appearance : new MaterialAppearance({ + materialSupport : MaterialAppearance.MaterialSupport.ALL + }) + }); + + expect(function() { + primitive.update(context, frameState, []); + }).toThrow(); + }); + + it('setting per instance attribute throws when value is undefined', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + primitive.update(context, frameState, []); + var attributes = primitive.getGeometryInstanceAttributes('extent1'); + + expect(function() { + attributes.color = undefined; + }).toThrow(); + + primitive = primitive && primitive.destroy(); + }); + + it('getGeometryInstanceAttributes throws without id', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + primitive.update(context, frameState, []); + + expect(function() { + primitive.getGeometryInstanceAttributes(); + }).toThrow(); + + primitive = primitive && primitive.destroy(); + }); + + it('getGeometryInstanceAttributes throws if update was not called', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + expect(function() { + primitive.getGeometryInstanceAttributes('extent1'); + }).toThrow(); + + primitive = primitive && primitive.destroy(); + }); + + it('getGeometryInstanceAttributes returns undefined if id does not exist', function() { + var primitive = new Primitive({ + geometryInstances : extentInstance1, + appearance : new PerInstanceColorAppearance(), + allow3DOnly : true + }); + + primitive.update(context, frameState, []); + + expect(primitive.getGeometryInstanceAttributes('unknown')).not.toBeDefined(); + + primitive = primitive && primitive.destroy(); + }); + + it('isDestroyed', function() { + var p = new Primitive(); + expect(p.isDestroyed()).toEqual(false); + p.destroy(); + expect(p.isDestroyed()).toEqual(true); + }); + +}); diff --git a/Specs/Scene/createTangentSpaceDebugPrimitiveSpec.js b/Specs/Scene/createTangentSpaceDebugPrimitiveSpec.js new file mode 100644 index 000000000000..d07b428ebb03 --- /dev/null +++ b/Specs/Scene/createTangentSpaceDebugPrimitiveSpec.js @@ -0,0 +1,63 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/createTangentSpaceDebugPrimitive', + 'Core/EllipsoidGeometry', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/Matrix4', + 'Core/VertexFormat', + 'Core/PrimitiveType' + ], function( + createTangentSpaceDebugPrimitive, + EllipsoidGeometry, + Cartesian3, + Ellipsoid, + Matrix4, + VertexFormat, + PrimitiveType) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + + it('computes all attributes', function() { + var geometry = new EllipsoidGeometry({ + vertexFormat : VertexFormat.ALL, + radii : new Cartesian3(500000.0, 500000.0, 1000000.0) + }); + var modelMatrix = Matrix4.multiplyByTranslation(Matrix4.IDENTITY, new Cartesian3(0.0, 0.0, 11000000.0)); + + var primitive = createTangentSpaceDebugPrimitive({ + geometry : geometry, + modelMatrix : modelMatrix, + length : 1000.0 + }); + + expect(primitive.geometryInstances).toBeDefined(); + expect(primitive.appearance).toBeDefined(); + + var instances = primitive.geometryInstances; + expect(instances.length).toEqual(3); + + expect(instances[0].modelMatrix).toEqual(modelMatrix); + expect(instances[1].modelMatrix).toEqual(modelMatrix); + expect(instances[2].modelMatrix).toEqual(modelMatrix); + + expect(instances[0].attributes).toBeDefined(); + expect(instances[0].attributes.color).toBeDefined(); + expect(instances[1].attributes).toBeDefined(); + expect(instances[1].attributes.color).toBeDefined(); + expect(instances[2].attributes).toBeDefined(); + expect(instances[2].attributes.color).toBeDefined(); + + expect(instances[0].geometry.primitiveType).toEqual(PrimitiveType.LINES); + expect(instances[1].geometry.primitiveType).toEqual(PrimitiveType.LINES); + expect(instances[2].geometry.primitiveType).toEqual(PrimitiveType.LINES); + }); + + it('throws without geometry', function() { + expect(function() { + createTangentSpaceDebugPrimitive(); + }).toThrow(); + }); + +});