diff --git a/Apps/Sandcastle/gallery/CZML Path.html b/Apps/Sandcastle/gallery/CZML Path.html index 3ec59c233939..763d1dba9aae 100644 --- a/Apps/Sandcastle/gallery/CZML Path.html +++ b/Apps/Sandcastle/gallery/CZML Path.html @@ -51,13 +51,7 @@ "outlineColor" : { "rgba" : [0, 255, 255, 255] }, - "outlineWidth" : 5, - "polylineGlow" : { - "color" : { - "rgba" : [255, 255, 0, 255] - }, - "glowPower" : 3 - } + "outlineWidth" : 5 } }, "width" : 8, @@ -67,7 +61,10 @@ }, "billboard" : { "image" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAfCAYAAACVgY94AAAACXBIWXMAAC4jAAAuIwF4pT92AAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAA7VJREFUeNrEl2uIlWUQx39nXUu0m2uQbZYrbabdLKMs/VBkmHQjioqFIhBS+hKEQpQRgVAf2u5RQkGBRUllRH4I2e5ZUBJlEZVt5i0tTfHStrZ6fn35L70d9n7Obg88vOedmWfmf2bmmZkXlRrtq9V16mZ1iVqqhd5agXvQf1c5zw/V8dXqrqO6dQKwBrgdWApsCb0VqAc2AnOrMVANwIsD4BLgTOBPYB2wHJgEzAG+ANqAu4ZsZYiuX5QwfqI2hvaNulA9J7zLQn8o76vUuuHOwXHqSzH4aIF+TWjnBkSH+nCBf716SP1KPWO4AJ6ltgfIjRW8p9U/1KPz/ry6RT2mIDNF3Zjz19Ya4G1R/J16dgWvQd2pPlXhMdVZPUTgxfCW1wJgXUJpQlvfg8zs8K8r0Caom9QHetG7NGfa1ElDBThRXRtFd/Qh16puKIS3e7+clBjdy7kL1b3q4fzJQQGck5z6Nb97kxujblWf64HXov7Vl/E4YXWccP9AAd6dAx+ox/WTArNzY1t64B0f8K0DyLXuUvRGZfcpCo1VX4tg6wB76WMB0dALf526foAX8cqUot2pGP8B2Kz+krBeNYjS8636dh/8Beo2deoA9TWp76pd6g0q9cDNwKvAD8A84EfglLRBe2g+JWAfcEF68bPABOCoAl/gIPA5MA64FVgGnNhP292W3r0SeB1YVlJXAjcBP8XwyQUj9AKwAzg2+/fQSsBhoJxBAaALaIzenZGnD911wA7gEDAD2FFSpwOzgDHZ5T7+ZSlGd2d6AXgi5+qAn+O5U0PbBVwKtAD3AHuB8f3YGBUdncCGoQ4LE9XtGRqK9LnduVPRIu2BPqwD65IYbS7Qpql7Ql9YoJcy9bwzkgPrfOCj5G33+h54E/g0PAr5thq4ApgyEgNrc27aWwVaPTA1QJ4BjgTGFvhteV40EgPrgvTP7qlmZqFnl9WD+b2posN83E/NrEkOjlI/U1fkfUYa/pe5IE3qZPW8jFOqiyN7p3pAPX04c7AxYSoDDcAjKT2LgLXA6IR2M3Bviv59wDTgQGTPH84Qd8+HXfHcoUws2zM0HMjuUPep+xP2PWpnwtw0GJsldbBpewQwE/gbeDyt7H1gcW53O7AC+A3Yn6+/W+Ld9SnWA15DAVhc8xK2TuA9YHrCuhV4EngFuBx4YagG6qv8cF+T52kB2Zy+e1I8taUacNV+uBdXO7ABmJwJpwx8XQvF9TUCWM64tiQhbq/oMv+7BwFWpQzNT8vbVQul/wwAGzzdmXU1xuUAAAAASUVORK5CYII=", - "scale" : 1.5 + "scale" : 1.5, + "eyeOffset": { + "cartesian": [ 0.0, 0.0, -10.0 ] + } }, "position" : { "epoch" : "2012-08-04T10:00:00Z", diff --git a/Apps/Sandcastle/gallery/Cardboard.html b/Apps/Sandcastle/gallery/Cardboard.html index cb0d07dfd594..3c0c199e3201 100644 --- a/Apps/Sandcastle/gallery/Cardboard.html +++ b/Apps/Sandcastle/gallery/Cardboard.html @@ -35,7 +35,7 @@ viewer.scene.globe.enableLighting = true; viewer.terrainProvider = new Cesium.CesiumTerrainProvider({ - url : '//assets.agi.com/stk-terrain/world', + url : 'https://assets.agi.com/stk-terrain/world', requestVertexNormals : true }); diff --git a/Apps/Sandcastle/gallery/Clustering.html b/Apps/Sandcastle/gallery/Clustering.html new file mode 100644 index 000000000000..ee5608cf3959 --- /dev/null +++ b/Apps/Sandcastle/gallery/Clustering.html @@ -0,0 +1,172 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + +
Pixel Range + + +
Minimum Cluster Size + + +
Enabled
Custom Styling
+
+ + + diff --git a/Apps/Sandcastle/gallery/Clustering.jpg b/Apps/Sandcastle/gallery/Clustering.jpg new file mode 100644 index 000000000000..9c5a1ab68bbc Binary files /dev/null and b/Apps/Sandcastle/gallery/Clustering.jpg differ diff --git a/Apps/Sandcastle/gallery/Custom DataSource.html b/Apps/Sandcastle/gallery/Custom DataSource.html index ec30ffded215..d705d4016939 100644 --- a/Apps/Sandcastle/gallery/Custom DataSource.html +++ b/Apps/Sandcastle/gallery/Custom DataSource.html @@ -53,6 +53,7 @@ this._seriesNames = []; this._seriesToDisplay = undefined; this._heightScale = 10000000; + this._entityCluster = new Cesium.EntityCluster(); } Object.defineProperties(WebGLGlobeDataSource.prototype, { @@ -196,6 +197,22 @@ set : function(value) { this._entityCollection = value; } + }, + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * @memberof WebGLGlobeDataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : function() { + return this._entityCluster; + }, + set : function(value) { + if (!Cesium.defined(value)) { + throw new Cesium.DeveloperError('value must be defined.'); + } + this._entityCluster = value; + } } }); diff --git a/Apps/Sandcastle/gallery/Distance Display Conditions.html b/Apps/Sandcastle/gallery/Distance Display Conditions.html new file mode 100644 index 000000000000..988e9eb62a3c --- /dev/null +++ b/Apps/Sandcastle/gallery/Distance Display Conditions.html @@ -0,0 +1,103 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Distance Display Conditions.jpg b/Apps/Sandcastle/gallery/Distance Display Conditions.jpg new file mode 100644 index 000000000000..0e9d7551f88b Binary files /dev/null and b/Apps/Sandcastle/gallery/Distance Display Conditions.jpg differ diff --git a/Apps/Sandcastle/gallery/Shadows.html b/Apps/Sandcastle/gallery/Shadows.html index af0faaac9463..0f524d53ee76 100644 --- a/Apps/Sandcastle/gallery/Shadows.html +++ b/Apps/Sandcastle/gallery/Shadows.html @@ -37,7 +37,7 @@ }); viewer.terrainProvider = new Cesium.CesiumTerrainProvider({ - url : '//assets.agi.com/stk-terrain/world', + url : 'https://assets.agi.com/stk-terrain/world', requestWaterMask : true, requestVertexNormals : true }); diff --git a/Apps/Sandcastle/gallery/development/Display Conditions.html b/Apps/Sandcastle/gallery/development/Display Conditions.html new file mode 100644 index 000000000000..a4c304c7cb2f --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Display Conditions.html @@ -0,0 +1,108 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/Labels.html b/Apps/Sandcastle/gallery/development/Labels.html index f865fccc0f1a..f419ca8d1a92 100644 --- a/Apps/Sandcastle/gallery/development/Labels.html +++ b/Apps/Sandcastle/gallery/development/Labels.html @@ -181,4 +181,4 @@ } - + \ No newline at end of file diff --git a/Apps/Sandcastle/gallery/development/Multiple Shadows.html b/Apps/Sandcastle/gallery/development/Multiple Shadows.html index 3ef885c19f1d..3794a9b205d0 100644 --- a/Apps/Sandcastle/gallery/development/Multiple Shadows.html +++ b/Apps/Sandcastle/gallery/development/Multiple Shadows.html @@ -44,7 +44,7 @@ timeline : false }); viewer.terrainProvider = new Cesium.CesiumTerrainProvider({ - url : '//assets.agi.com/stk-terrain/world', + url : 'https://assets.agi.com/stk-terrain/world', requestWaterMask : true, requestVertexNormals : true }); diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index 2bd2f4c3a484..91d9640a257d 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -482,7 +482,7 @@ var spotLightCamera = new Cesium.Camera(scene); var cesiumTerrainProvider = new Cesium.CesiumTerrainProvider({ - url : '//assets.agi.com/stk-terrain/world', + url : 'https://assets.agi.com/stk-terrain/world', requestWaterMask : true, requestVertexNormals : true }); diff --git a/CHANGES.md b/CHANGES.md index 11419cea0918..986d4aa5f8dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,30 @@ Change Log ========== +### 1.26 - 2016-10-03 + +* Deprecated + * The `scene` parameter for creating `BillboardVisualizer`, `LabelVisualizer`, and `PointVisualizer` has been deprecated and will be removed in 1.28. Instead, pass an instance of `EntityCluster`. +* Breaking changes + * Vertex texture fetch is now required to be supported to render polylines. Maximum vertex texture image units must be greater than zero. + * Removed `castShadows` and `receiveShadows` properties from `Model`, `Primitive`, and `Globe`. Instead, use `shadows` with the `ShadowMode` enum, e.g. `model.shadows = ShadowMode.ENABLED`. + * `Viewer.terrainShadows` now uses the `ShadowMode` enum instead of a Boolean, e.g. `viewer.terrainShadows = ShadowMode.RECEIVE_ONLY`. +* Added support for clustering `Billboard`, `Label` and `Point` entities. [#4240](https://github.com/AnalyticalGraphicsInc/cesium/pull/4240) +* Added `DistanceDisplayCondition`s to all primitives to determine the range interval from the camera for when it will be visible. +* Removed the default gamma correction for Bing Maps aerial imagery, because it is no longer an improvement to current versions of the tiles. To restore the previous look, set the `defaultGamma` property of your `BingMapsImageryProvider` instance to 1.3. +* Fixed a bug that could lead to incorrect terrain heights when using `HeightmapTerrainData` with an encoding in which actual heights were equal to the minimum representable height. +* Fixed a bug in `AttributeCompression.compressTextureCoordinates` and `decompressTextureCoordinates` that could cause a small inaccuracy in the encoded texture coordinates. +* Fixed a bug where viewing a model with transparent geometry would cause a crash. [#4378](https://github.com/AnalyticalGraphicsInc/cesium/issues/4378) +* Added `TrustedServer` collection that controls which servers should have `withCredential` set to `true` on XHR Requests. +* Fixed billboard rotation when sized in meters. [#3979](https://github.com/AnalyticalGraphicsInc/cesium/issues/3979) +* Added `backgroundColor` and `borderWidth` properties to `writeTextToCanvas`. +* Fixed timeline touch events. [#4305](https://github.com/AnalyticalGraphicsInc/cesium/pull/4305) +* Fixed a bug that was incorrectly clamping Latitudes in KML (s) to the range -PI..PI. Now correctly clamps to -PI/2..PI/2. +* Added `CesiumMath.clampToLatitudeRange`. A convenience function to clamp a passed radian angle to valid Latitudes. +* Added `DebugCameraPrimitive` to visualize the view frustum of a camera. + ### 1.25 - 2016-09-01 + * Breaking changes * The number and order of arguments passed to `KmlDataSource` `unsupportedNodeEvent` listeners have changed to allow better handling of unsupported KML Features. * Changed billboards and labels that are clamped to terrain to have the `verticalOrigin` set to `CENTER` by default instead of `BOTTOM`. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d322dd58bd41..2408f5478bf0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -62,6 +62,8 @@ * [Dave Whipps](https://github.com/dwhipps) * [Geoscan](https://www.geoscan.aero) * [Andrey Orlov](https://github.com/AndreyOrlov) +* [The Imagineers](https://www.theimagineers.com/) + * [Heerco Grond](https://github.com/HeercoGrond) ## [Individual CLA](http://www.agi.com/licenses/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) diff --git a/LICENSE.md b/LICENSE.md index 21d694470418..4fe332029945 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -270,6 +270,24 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +### kdbush + +https://github.com/mourner/kdbush + +> Copyright (c) 2016, Vladimir Agafonkin +> +>Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. +> +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + Tests ===== diff --git a/README.md b/README.md index a038b06112cc..981de97c4833 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ We appreciate attribution by including the Cesium logo and link in your app. ### Featured Demos ###

-  +          -  +     

@@ -82,7 +82,6 @@ We appreciate attribution by including the Cesium logo and link in your app.       -        @@ -132,6 +131,5 @@ We appreciate attribution by including the Cesium logo and link in your app.       - 

diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 6236c4d7365c..8354bcacc8e4 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -60,7 +60,7 @@ define([ * proxy : new Cesium.DefaultProxy('/terrain/') * }); * viewer.terrainProvider = terrainProvider; - * + * * @see TerrainProvider */ function ArcGisImageServerTerrainProvider(options) { @@ -91,7 +91,9 @@ define([ elementsPerHeight : 3, stride : 4, elementMultiplier : 256.0, - isBigEndian : true + isBigEndian : true, + lowestEncodedHeight : 0, + highestEncodedHeight : 256 * 256 * 256 - 1 }; this._errorEvent = new Event(); diff --git a/Source/Core/AttributeCompression.js b/Source/Core/AttributeCompression.js index f4a41c5da943..d5e6973d29cf 100644 --- a/Source/Core/AttributeCompression.js +++ b/Source/Core/AttributeCompression.js @@ -75,9 +75,9 @@ define([ * @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding. * @param {Cartesian2} result The 2 byte oct-encoded unit length vector. * @returns {Cartesian2} The 2 byte oct-encoded unit length vector. - * + * * @exception {DeveloperError} vector must be normalized. - * + * * @see AttributeCompression.octEncodeInRange * @see AttributeCompression.octDecode */ @@ -124,14 +124,14 @@ define([ /** * Decodes a unit-length vector in 2 byte 'oct' encoding to a normalized 3-component vector. - * + * * @param {Number} x The x component of the oct-encoded unit length vector. * @param {Number} y The y component of the oct-encoded unit length vector. * @param {Cartesian3} result The decoded and normalized vector. * @returns {Cartesian3} The decoded and normalized vector. - * + * * @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and 255. - * + * * @see AttributeCompression.octDecodeInRange */ AttributeCompression.octDecode = function(x, y, result) { @@ -268,7 +268,7 @@ define([ /** * Pack texture coordinates into a single float. The texture coordinates will only preserve 12 bits of precision. * - * @param {Cartesian2} textureCoordinates The texture coordinates to compress + * @param {Cartesian2} textureCoordinates The texture coordinates to compress. Both coordinates must be in the range 0.0-1.0. * @returns {Number} The packed texture coordinates. * */ @@ -279,8 +279,9 @@ define([ } //>>includeEnd('debug'); - var x = textureCoordinates.x === 1.0 ? 4095.0 : (textureCoordinates.x * 4096.0) | 0; - var y = textureCoordinates.y === 1.0 ? 4095.0 : (textureCoordinates.y * 4096.0) | 0; + // Move x and y to the range 0-4095; + var x = (textureCoordinates.x * 4095.0) | 0; + var y = (textureCoordinates.y * 4095.0) | 0; return 4096.0 * x + y; }; @@ -303,8 +304,9 @@ define([ //>>includeEnd('debug'); var temp = compressed / 4096.0; - result.x = Math.floor(temp) / 4096.0; - result.y = temp - Math.floor(temp); + var xZeroTo4095 = Math.floor(temp); + result.x = xZeroTo4095 / 4095.0; + result.y = (compressed - xZeroTo4095 * 4096) / 4095; return result; }; diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index f06c0055868d..b7196fa1ca6f 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -75,7 +75,7 @@ define([ * url : 'https://assets.agi.com/stk-terrain/world', * requestVertexNormals : true * }); - * + * * // Terrain geometry near the surface of the globe is difficult to view when using NaturalEarthII imagery, * // unless the TerrainProvider provides additional lighting information to shade the terrain (as shown above). * var imageryProvider = Cesium.createTileMapServiceImageryProvider({ @@ -91,7 +91,7 @@ define([ * * // The globe must enable lighting to make use of the terrain's vertex normals * viewer.scene.globe.enableLighting = true; - * + * * @see TerrainProvider */ function CesiumTerrainProvider(options) { @@ -182,7 +182,9 @@ define([ elementsPerHeight : 1, stride : 1, elementMultiplier : 256.0, - isBigEndian : false + isBigEndian : false, + lowestEncodedHeight : 0, + highestEncodedHeight : 256 * 256 - 1 }; that._hasWaterMask = true; that._requestWaterMask = true; diff --git a/Source/Core/DistanceDisplayCondition.js b/Source/Core/DistanceDisplayCondition.js new file mode 100644 index 000000000000..aacaa73fab11 --- /dev/null +++ b/Source/Core/DistanceDisplayCondition.js @@ -0,0 +1,123 @@ +/*global define*/ +define([ + './Cartesian3', + './defaultValue', + './defined', + './defineProperties' + ], function( + Cartesian3, + defaultValue, + defined, + defineProperties) { + 'use strict'; + + /** + * Determines visibility based on the distance to the camera. + * + * @alias DistanceDisplayCondition + * @constructor + * + * @param {Number} [near=0.0] The smallest distance in the interval where the object is visible. + * @param {Number} [far=Number.MAX_VALUE] The largest distance in the interval where the object is visible. + * + * @example + * // Make a billboard that is only visible when the distance to the camera is between 10 and 20 meters. + * billboard.distanceDisplayCondition = new DistanceDisplayCondition(10.0 20.0); + */ + function DistanceDisplayCondition(near, far) { + near = defaultValue(near, 0.0); + this._near = near; + + far = defaultValue(far, Number.MAX_VALUE); + this._far = far; + } + + defineProperties(DistanceDisplayCondition.prototype, { + /** + * The smallest distance in the interval where the object is visible. + * @memberof DistanceDisplayCondition.prototype + * @type {Number} + * @default 0.0 + */ + near : { + get : function() { + return this._near; + }, + set : function(value) { + this._near = value; + } + }, + /** + * The largest distance in the interval where the object is visible. + * @memberof DistanceDisplayCondition.prototype + * @type {Number} + * @default Number.MAX_VALUE + */ + far : { + get : function() { + return this._far; + }, + set : function(value) { + this._far = value; + } + } + }); + + /** + * Determines if two distance display conditions are equal. + * + * @param {DistanceDisplayCondition} left A distance display condition. + * @param {DistanceDisplayCondition} right Another distance display condition. + * @return {Boolean} Whether the two distance display conditions are equal. + */ + DistanceDisplayCondition.equals = function(left, right) { + return left === right || + (defined(left) && + defined(right) && + left.near === right.near && + left.far === right.far); + }; + + /** + * Duplicates a distance display condition instance. + * + * @param {DistanceDisplayCondition} [value] The distance display condition to duplicate. + * @param {DistanceDisplayCondition} [result] The result onto which to store the result. + * @return {DistanceDisplayCondition} The duplicated instance. + */ + DistanceDisplayCondition.clone = function(value, result) { + if (!defined(value)) { + return undefined; + } + + if (!defined(result)) { + result = new DistanceDisplayCondition(); + } + + result.near = value.near; + result.far = value.far; + return result; + }; + + /** + * Duplicates this instance. + * + * @param {DistanceDisplayCondition} [result] The result onto which to store the result. + * @return {DistanceDisplayCondition} The duplicated instance. + */ + DistanceDisplayCondition.prototype.clone = function(result) { + return DistanceDisplayCondition.clone(this, result); + }; + + /** + * Determines if this distance display condition is equal to another. + * + * @param {DistanceDisplayCondition} other Another distance display condition. + * @return {Boolean} Whether this distance display condition is equal to the other. + */ + DistanceDisplayCondition.prototype.equals = function(other) { + return DistanceDisplayCondition.equals(this, other); + }; + + return DistanceDisplayCondition; +}); \ No newline at end of file diff --git a/Source/Core/DistanceDisplayConditionGeometryInstanceAttribute.js b/Source/Core/DistanceDisplayConditionGeometryInstanceAttribute.js new file mode 100644 index 000000000000..9d039fa3298f --- /dev/null +++ b/Source/Core/DistanceDisplayConditionGeometryInstanceAttribute.js @@ -0,0 +1,174 @@ +/*global define*/ +define([ + './ComponentDatatype', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError' + ], function( + ComponentDatatype, + defaultValue, + defined, + defineProperties, + DeveloperError) { + 'use strict'; + + /** + * Value and type information for per-instance geometry attribute that determines if the geometry instance has a distance display condition. + * + * @alias DistanceDisplayConditionGeometryInstanceAttribute + * @constructor + * + * @param {Number} [near=0.0] The near distance. + * @param {Number} [far=Number.MAX_VALUE] The far distance. + * + * @exception {DeveloperError} far must be greater than near. + * + * @example + * var instance = new Cesium.GeometryInstance({ + * geometry : new Cesium.BoxGeometry({ + * vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL, + * minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0), + * maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0) + * }), + * modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( + * Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()), + * id : 'box', + * attributes : { + * show : new Cesium.DistanceDisplayConditionGeometryInstanceAttribute(100.0, 10000.0) + * } + * }); + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + */ + function DistanceDisplayConditionGeometryInstanceAttribute(near, far) { + near = defaultValue(near, 0.0); + far = defaultValue(far, Number.MAX_VALUE); + + //>>includeStart('debug', pragmas.debug); + if (far <= near) { + throw new DeveloperError('far distance must be greater than near distance.'); + } + //>>includeEnd('debug'); + + /** + * The values for the attributes stored in a typed array. + * + * @type Float32Array + * + * @default [0.0, 0.0, Number.MAX_VALUE] + */ + this.value = new Float32Array([near, far]); + } + + defineProperties(DistanceDisplayConditionGeometryInstanceAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link DistanceDisplayConditionGeometryInstanceAttribute#value}. + * + * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link DistanceDisplayConditionGeometryInstanceAttribute#value}. + * + * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 3 + */ + componentsPerAttribute : { + get : function() { + return 2; + } + }, + + /** + * 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. + * + * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + /** + * Creates a new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance given the provided an enabled flag and {@link DistanceDisplayCondition}. + * + * @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition. + * @returns {DistanceDisplayConditionGeometryInstanceAttribute} The new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance. + * + * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near + * + * @example + * var instance = new Cesium.GeometryInstance({ + * geometry : geometry, + * attributes : { + * color : Cesium.DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition), + * } + * }); + */ + DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition = function(distanceDisplayCondition) { + //>>includeStart('debug', pragmas.debug); + if (!defined(distanceDisplayCondition)) { + throw new DeveloperError('distanceDisplayCondition is required.'); + } + if (distanceDisplayCondition.far <= distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far distance must be greater than distanceDisplayCondition.near distance.'); + } + //>>includeEnd('debug'); + + return new DistanceDisplayConditionGeometryInstanceAttribute(distanceDisplayCondition.near, distanceDisplayCondition.far); + }; + + /** + * Converts a distance display condition to a typed array that can be used to assign a distance display condition attribute. + * + * @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition value. + * @param {Float32Array} [result] The array to store the result in, if undefined a new instance will be created. + * @returns {Float32Array} The modified result parameter or a new instance if result was undefined. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + */ + DistanceDisplayConditionGeometryInstanceAttribute.toValue = function(distanceDisplayCondition, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(distanceDisplayCondition)) { + throw new DeveloperError('distanceDisplayCondition is required.'); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + return new Float32Array([distanceDisplayCondition.near, distanceDisplayCondition.far]); + } + result[0] = distanceDisplayCondition.near; + result[1] = distanceDisplayCondition.far; + return result; + }; + + return DistanceDisplayConditionGeometryInstanceAttribute; +}); diff --git a/Source/Core/Geometry.js b/Source/Core/Geometry.js index 55dd903af05c..c10078c250ef 100644 --- a/Source/Core/Geometry.js +++ b/Source/Core/Geometry.js @@ -158,7 +158,7 @@ define([ /** * @private */ - this.boundingSphereCV = undefined; + this.boundingSphereCV = options.boundingSphereCV; } /** diff --git a/Source/Core/GeometryInstanceAttribute.js b/Source/Core/GeometryInstanceAttribute.js index 539d3a929d3b..ff8a578e120d 100644 --- a/Source/Core/GeometryInstanceAttribute.js +++ b/Source/Core/GeometryInstanceAttribute.js @@ -44,6 +44,7 @@ define([ * * @see ColorGeometryInstanceAttribute * @see ShowGeometryInstanceAttribute + * @see DistanceDisplayConditionGeometryInstanceAttribute */ function GeometryInstanceAttribute(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); diff --git a/Source/Core/GeometryPipeline.js b/Source/Core/GeometryPipeline.js index ad9079d35a72..95b1027baef9 100644 --- a/Source/Core/GeometryPipeline.js +++ b/Source/Core/GeometryPipeline.js @@ -1033,7 +1033,7 @@ define([ var instance = instances[i]; if (defined(instance.geometry)) { instanceGeometry.push(instance); - } else { + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { instanceSplitGeometry.push(instance); } } diff --git a/Source/Core/HeightmapTerrainData.js b/Source/Core/HeightmapTerrainData.js index 567727abe11f..1dad3a8a2632 100644 --- a/Source/Core/HeightmapTerrainData.js +++ b/Source/Core/HeightmapTerrainData.js @@ -73,6 +73,14 @@ define([ * @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the * stride property is greater than 1. If this property is false, the first element is the * low-order element. If it is true, the first element is the high-order element. + * @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower + * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height + * buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is + * not specified, no minimum value is enforced. + * @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher + * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height + * buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger + * than 65535. If this parameter is not specified, no maximum value is enforced. * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance; * otherwise, false. * @@ -89,7 +97,7 @@ define([ * childTileMask : childTileMask, * waterMask : waterMask * }); - * + * * @see TerrainData * @see QuantizedMeshTerrainData */ @@ -350,6 +358,12 @@ define([ for (var i = 0; i < width; ++i) { var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1)); var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration); + + // Use conditionals here instead of Math.min and Math.max so that an undefined + // lowestEncodedHeight or highestEncodedHeight has no effect. + heightSample = heightSample < structure.lowestEncodedHeight ? structure.lowestEncodedHeight : heightSample; + heightSample = heightSample > structure.highestEncodedHeight ? structure.highestEncodedHeight : heightSample; + setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, j * width + i, heightSample); } } @@ -448,6 +462,7 @@ define([ } function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration) { + // returns a height encoded according to the structure's heightScale and heightOffset. var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west); var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south); diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index fbb28c43ebb7..fc478f62a2b8 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -101,6 +101,14 @@ define([ * `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256` * This is assuming that the isBigEndian property is false. If it is true, the order of the * elements is reversed. + * @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower + * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height + * buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is + * not specified, no minimum value is enforced. + * @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher + * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height + * buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger + * than 65535. If this parameter is not specified, no maximum value is enforced. * @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the * stride property is greater than 1. If this property is false, the first element is the * low-order element. If it is true, the first element is the high-order element. diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 7ca053d20e57..7c9952c522b5 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -469,6 +469,27 @@ define([ return simplified; }; + /** + * Convenience function that clamps a latitude value, in radians, to the range [-Math.PI/2, Math.PI/2). + * Useful for sanitizing data before use in objects requiring correct range. + * + * @param {Number} angle The latitude value, in radians, to clamp to the range [-Math.PI/2, Math.PI/2). + * @returns {Number} The latitude value clamped to the range [-Math.PI/2, Math.PI/2). + * + * @example + * // Clamp 108 degrees latitude to 90 degrees latitude + * var latitude = Cesium.Math.clampToLatitudeRange(Cesium.Math.toRadians(108.0)); + */ + CesiumMath.clampToLatitudeRange = function(angle) { + //>>includeStart('debug', pragmas.debug); + if (!defined(angle)) { + throw new DeveloperError('angle is required.'); + } + //>>includeEnd('debug'); + + return CesiumMath.clamp(angle, -1*CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); + }; + /** * Produces an angle in the range -Pi <= angle <= Pi which is equivalent to the provided angle. * diff --git a/Source/Core/TrustedServers.js b/Source/Core/TrustedServers.js new file mode 100644 index 000000000000..cc538ad2241c --- /dev/null +++ b/Source/Core/TrustedServers.js @@ -0,0 +1,156 @@ +/*global define*/ +define([ + './defined', + './defineProperties', + './DeveloperError', + '../ThirdParty/Uri' +], function( + defined, + defineProperties, + DeveloperError, + Uri) { + 'use strict'; + + /** + * A singleton that contains all of the servers that are trusted. Credentials will be sent with + * any requests to these servers. + * + * @exports TrustedServers + * + * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} + */ + var TrustedServers = {}; + var _servers = {}; + + /** + * Adds a trusted server to the registry + * + * @param {String} host The host to be added. + * @param {Number} port The port used to access the host. + * + * @example + * // Add a trusted server + * TrustedServers.add('my.server.com', 80); + */ + TrustedServers.add = function(host, port) { + //>>includeStart('debug', pragmas.debug); + if (!defined(host)) { + throw new DeveloperError('host is required.'); + } + if (!defined(port) || port <= 0) { + throw new DeveloperError('port is required to be greater than 0.'); + } + //>>includeEnd('debug'); + + var authority = host.toLowerCase() + ':' + port; + if (!defined(_servers[authority])) { + _servers[authority] = true; + } + }; + + /** + * Removes a trusted server from the registry + * + * @param {String} host The host to be removed. + * @param {Number} port The port used to access the host. + * + * @example + * // Remove a trusted server + * TrustedServers.remove('my.server.com', 80); + */ + TrustedServers.remove = function(host, port) { + //>>includeStart('debug', pragmas.debug); + if (!defined(host)) { + throw new DeveloperError('host is required.'); + } + if (!defined(port) || port <= 0) { + throw new DeveloperError('port is required to be greater than 0.'); + } + //>>includeEnd('debug'); + + var authority = host.toLowerCase() + ':' + port; + if (defined(_servers[authority])) { + delete _servers[authority]; + } + }; + + function getAuthority(url) { + var uri = new Uri(url); + uri.normalize(); + + // Removes username:password@ so we just have host[:port] + var authority = uri.getAuthority(); + if (!defined(authority)) { + return undefined; // Relative URL + } + + if (authority.indexOf('@') !== -1) { + var parts = authority.split('@'); + authority = parts[1]; + } + + // If the port is missing add one based on the scheme + if (authority.indexOf(':') === -1) { + var scheme = uri.getScheme(); + if (!defined(scheme)) { + scheme = window.location.protocol; + scheme = scheme.substring(0, scheme.length-1); + } + if (scheme === 'http') { + authority += ':80'; + } else if (scheme === 'https') { + authority += ':443'; + } else { + return undefined; + } + } + + return authority; + } + + /** + * Tests whether a server is trusted or not. The server must have been added with the port if it is included in the url. + * + * @param {String} url The url to be tested against the trusted list + * + * @returns {boolean} Returns true if url is trusted, false otherwise. + * + * @example + * // Add server + * TrustedServers.add('my.server.com', 81); + * + * // Check if server is trusted + * if (TrustedServers.contains('https://my.server.com:81/path/to/file.png')) { + * // my.server.com:81 is trusted + * } + * if (TrustedServers.contains('https://my.server.com/path/to/file.png')) { + * // my.server.com isn't trusted + * } + */ + TrustedServers.contains = function(url) { + //>>includeStart('debug', pragmas.debug); + if (!defined(url)) { + throw new DeveloperError('url is required.'); + } + //>>includeEnd('debug'); + var authority = getAuthority(url); + if (defined(authority) && defined(_servers[authority])) { + return true; + } + + return false; + }; + + /** + * Clears the registry + * + * @example + * // Remove a trusted server + * TrustedServers.clear(); + */ + TrustedServers.clear = function() { + _servers = {}; + }; + + return TrustedServers; +}); diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index edfcbfe5de5b..f7b27be75a37 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -64,7 +64,7 @@ define([ * url : 'https://www.vr-theworld.com/vr-theworld/tiles1.0.0/73/' * }); * viewer.terrainProvider = terrainProvider; - * + * * @see TerrainProvider */ function VRTheWorldTerrainProvider(options) { @@ -90,7 +90,9 @@ define([ elementsPerHeight : 3, stride : 4, elementMultiplier : 256.0, - isBigEndian : true + isBigEndian : true, + lowestEncodedHeight : 0, + highestEncodedHeight : 256 * 256 * 256 - 1 }; var credit = options.credit; diff --git a/Source/Core/loadImage.js b/Source/Core/loadImage.js index 3178cf5f393f..861e29343feb 100644 --- a/Source/Core/loadImage.js +++ b/Source/Core/loadImage.js @@ -1,12 +1,14 @@ /*global define*/ define([ '../ThirdParty/when', + './TrustedServers', './defaultValue', './defined', './DeveloperError', './isCrossOriginUrl' ], function( when, + TrustedServers, defaultValue, defined, DeveloperError, @@ -84,7 +86,11 @@ define([ }; if (crossOrigin) { - image.crossOrigin = ''; + if (TrustedServers.contains(url)) { + image.crossOrigin = 'use-credentials'; + } else { + image.crossOrigin = ''; + } } image.src = url; diff --git a/Source/Core/loadWithXhr.js b/Source/Core/loadWithXhr.js index fe2d42c85846..209f0b7a7388 100644 --- a/Source/Core/loadWithXhr.js +++ b/Source/Core/loadWithXhr.js @@ -1,6 +1,7 @@ /*global define*/ define([ '../ThirdParty/when', + './TrustedServers', './defaultValue', './defined', './DeveloperError', @@ -8,6 +9,7 @@ define([ './RuntimeError' ], function( when, + TrustedServers, defaultValue, defined, DeveloperError, @@ -132,6 +134,10 @@ define([ var xhr = new XMLHttpRequest(); + if (TrustedServers.contains(url)) { + xhr.withCredentials = true; + } + if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) { xhr.overrideMimeType(overrideMimeType); } diff --git a/Source/Core/writeTextToCanvas.js b/Source/Core/writeTextToCanvas.js index 8669aa6d0e32..f032aad58d95 100644 --- a/Source/Core/writeTextToCanvas.js +++ b/Source/Core/writeTextToCanvas.js @@ -27,7 +27,9 @@ define([ * @param {Boolean} [options.stroke=false] Whether to stroke the text. * @param {Color} [options.fillColor=Color.WHITE] The fill color. * @param {Color} [options.strokeColor=Color.BLACK] The stroke color. - * @param {Color} [options.strokeWidth=1] The stroke width. + * @param {Number} [options.strokeWidth=1] The stroke width. + * @param {Color} [options.backgroundColor=Color.TRANSPARENT] The background color of the canvas. + * @param {Number} [options.padding=0] The pixel size of the padding to add around the text. * @returns {Canvas} A new canvas with the given text drawn into it. The dimensions object * from measureText will also be added to the returned canvas. If text is * blank, returns undefined. @@ -47,6 +49,9 @@ define([ var stroke = defaultValue(options.stroke, false); var fill = defaultValue(options.fill, true); var strokeWidth = defaultValue(options.strokeWidth, 1); + var backgroundColor = defaultValue(options.backgroundColor, Color.TRANSPARENT); + var padding = defaultValue(options.padding, 0); + var doublePadding = padding * 2.0; var canvas = document.createElement('canvas'); canvas.width = 1; @@ -88,9 +93,9 @@ define([ document.body.removeChild(canvas); canvas.style.visibility = ''; - var baseline = dimensions.height - dimensions.ascent; - canvas.width = dimensions.computedWidth; - canvas.height = dimensions.height; + var baseline = dimensions.height - dimensions.ascent + padding; + canvas.width = dimensions.computedWidth + doublePadding; + canvas.height = dimensions.height + doublePadding; var y = canvas.height - baseline; // Properties must be explicitly set again after changing width and height @@ -99,16 +104,22 @@ define([ context2D.lineWidth = strokeWidth; context2D[imageSmoothingEnabledName] = false; + // Draw background + if (backgroundColor !== Color.TRANSPARENT) { + context2D.fillStyle = backgroundColor.toCssColorString(); + context2D.fillRect(0, 0, canvas.width, canvas.height); + } + if (stroke) { var strokeColor = defaultValue(options.strokeColor, Color.BLACK); context2D.strokeStyle = strokeColor.toCssColorString(); - context2D.strokeText(text, 0, y); + context2D.strokeText(text, padding, y); } if (fill) { var fillColor = defaultValue(options.fillColor, Color.WHITE); context2D.fillStyle = fillColor.toCssColorString(); - context2D.fillText(text, 0, y); + context2D.fillText(text, padding, y); } return canvas; diff --git a/Source/DataSources/BillboardGraphics.js b/Source/DataSources/BillboardGraphics.js index 9613c1ffb9b9..628768b00007 100644 --- a/Source/DataSources/BillboardGraphics.js +++ b/Source/DataSources/BillboardGraphics.js @@ -46,6 +46,7 @@ define([ * @param {Property} [options.imageSubRegion] A Property specifying a {@link BoundingRectangle} that defines a sub-region of the image to use for the billboard, rather than the entire image, measured in pixels from the bottom-left. * @param {Property} [options.sizeInMeters] A boolean Property specifying whether this billboard's size should be measured in meters. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this billboard will be displayed. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo} */ @@ -86,6 +87,8 @@ define([ this._pixelOffsetScaleByDistanceSubscription = undefined; this._sizeInMeters = undefined; this._sizeInMetersSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -306,7 +309,14 @@ define([ * @type {Property} * @default false */ - sizeInMeters : createPropertyDescriptor('sizeInMeters') + sizeInMeters : createPropertyDescriptor('sizeInMeters'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this billboard will be displayed. + * @memberof BillboardGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -337,6 +347,7 @@ define([ result.translucencyByDistance = this._translucencyByDistance; result.pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance; result.sizeInMeters = this._sizeInMeters; + result.distanceDisplayCondition = this._distanceDisplayCondition; return result; }; @@ -371,6 +382,7 @@ define([ this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance); this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance); this.sizeInMeters = defaultValue(this._sizeInMeters, source.sizeInMeters); + this.distanceDisplayCondition = defaultValue(this._distanceDisplayCondition, source.distanceDisplayCondition); }; return BillboardGraphics; diff --git a/Source/DataSources/BillboardVisualizer.js b/Source/DataSources/BillboardVisualizer.js index 51b252e79970..e66d5cd10b8d 100644 --- a/Source/DataSources/BillboardVisualizer.js +++ b/Source/DataSources/BillboardVisualizer.js @@ -7,14 +7,16 @@ define([ '../Core/Color', '../Core/defaultValue', '../Core/defined', + '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/NearFarScalar', - '../Scene/BillboardCollection', '../Scene/HeightReference', '../Scene/HorizontalOrigin', '../Scene/VerticalOrigin', './BoundingSphereState', + './EntityCluster', './Property' ], function( AssociativeArray, @@ -24,14 +26,16 @@ define([ Color, defaultValue, defined, + deprecationWarning, destroyObject, DeveloperError, + DistanceDisplayCondition, NearFarScalar, - BillboardCollection, HeightReference, HorizontalOrigin, VerticalOrigin, BoundingSphereState, + EntityCluster, Property) { 'use strict'; @@ -54,6 +58,7 @@ define([ var translucencyByDistance = new NearFarScalar(); var pixelOffsetScaleByDistance = new NearFarScalar(); var boundingRectangle = new BoundingRectangle(); + var distanceDisplayCondition = new DistanceDisplayCondition(); function EntityData(entity) { this.entity = entity; @@ -66,24 +71,29 @@ define([ * @alias BillboardVisualizer * @constructor * - * @param {Scene} scene The scene the primitives will be rendered in. + * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities. * @param {EntityCollection} entityCollection The entityCollection to visualize. */ - function BillboardVisualizer(scene, entityCollection) { + function BillboardVisualizer(entityCluster, entityCollection) { //>>includeStart('debug', pragmas.debug); - if (!defined(scene)) { - throw new DeveloperError('scene is required.'); + if (!defined(entityCluster)) { + throw new DeveloperError('entityCluster is required.'); } if (!defined(entityCollection)) { throw new DeveloperError('entityCollection is required.'); } //>>includeEnd('debug'); + if (!defined(entityCluster.minimumClusterSize)) { + deprecationWarning('BillboardVisualizer scene constructor parameter', 'The scene is no longer a parameter the BillboardVisualizer. An EntityCluster is required.'); + entityCluster = new EntityCluster({ + enabled : false + }); + } + entityCollection.collectionChanged.addEventListener(BillboardVisualizer.prototype._onCollectionChanged, this); - this._scene = scene; - this._unusedIndexes = []; - this._billboardCollection = undefined; + this._cluster = entityCluster; this._entityCollection = entityCollection; this._items = new AssociativeArray(); this._onCollectionChanged(entityCollection, entityCollection.values, [], []); @@ -104,7 +114,8 @@ define([ //>>includeEnd('debug'); var items = this._items.values; - var unusedIndexes = this._unusedIndexes; + var cluster = this._cluster; + for (var i = 0, len = items.length; i < len; i++) { var item = items[i]; var entity = item.entity; @@ -121,26 +132,16 @@ define([ if (!show) { //don't bother creating or updating anything else - returnBillboard(item, unusedIndexes); + cluster.removeBillboard(entity); continue; } - if (!defined(billboard)) { - var billboardCollection = this._billboardCollection; - if (!defined(billboardCollection)) { - billboardCollection = this._scene.primitives.add(new BillboardCollection({ - scene : this._scene - })); - this._billboardCollection = billboardCollection; - } - - var length = unusedIndexes.length; - if (length > 0) { - billboard = billboardCollection.get(unusedIndexes.pop()); - } else { - billboard = billboardCollection.add(); - } + if (!Property.isConstant(entity._position)) { + cluster._clusterDirty = true; + } + if (!defined(billboard)) { + billboard = cluster.getBillboard(entity); billboard.id = entity; billboard.image = undefined; item.billboard = billboard; @@ -167,6 +168,7 @@ define([ billboard.translucencyByDistance = Property.getValueOrUndefined(billboardGraphics._translucencyByDistance, time, translucencyByDistance); billboard.pixelOffsetScaleByDistance = Property.getValueOrUndefined(billboardGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance); billboard.sizeInMeters = Property.getValueOrDefault(billboardGraphics._sizeInMeters, defaultSizeInMeters); + billboard.distanceDisplayCondition = Property.getValueOrUndefined(billboardGraphics._distanceDisplayCondition, time, distanceDisplayCondition); var subRegion = Property.getValueOrUndefined(billboardGraphics._imageSubRegion, time, boundingRectangle); if (defined(subRegion)) { @@ -229,17 +231,14 @@ define([ */ BillboardVisualizer.prototype.destroy = function() { this._entityCollection.collectionChanged.removeEventListener(BillboardVisualizer.prototype._onCollectionChanged, this); - if (defined(this._billboardCollection)) { - this._scene.primitives.remove(this._billboardCollection); - } return destroyObject(this); }; BillboardVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { var i; var entity; - var unusedIndexes = this._unusedIndexes; var items = this._items; + var cluster = this._cluster; for (i = added.length - 1; i > -1; i--) { entity = added[i]; @@ -255,31 +254,17 @@ define([ items.set(entity.id, new EntityData(entity)); } } else { - returnBillboard(items.get(entity.id), unusedIndexes); + cluster.removeBillboard(entity); items.remove(entity.id); } } for (i = removed.length - 1; i > -1; i--) { entity = removed[i]; - returnBillboard(items.get(entity.id), unusedIndexes); + cluster.removeBillboard(entity); items.remove(entity.id); } }; - function returnBillboard(item, unusedIndexes) { - if (defined(item)) { - var billboard = item.billboard; - if (defined(billboard)) { - item.textureValue = undefined; - item.billboard = undefined; - billboard.id = undefined; - billboard.show = false; - billboard.image = undefined; - unusedIndexes.push(billboard._index); - } - } - } - return BillboardVisualizer; }); diff --git a/Source/DataSources/BoxGeometryUpdater.js b/Source/DataSources/BoxGeometryUpdater.js index 63d3a9e8e5a1..cb9f7086e9dc 100644 --- a/Source/DataSources/BoxGeometryUpdater.js +++ b/Source/DataSources/BoxGeometryUpdater.js @@ -9,6 +9,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -32,6 +34,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -53,6 +57,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -94,6 +99,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'box', entity.box, undefined); } @@ -234,6 +240,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof BoxGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -322,6 +340,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -330,11 +350,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -368,6 +390,7 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, @@ -375,7 +398,8 @@ define([ modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -454,6 +478,7 @@ define([ this._showOutlineProperty = defaultValue(box.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(box.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(box.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(box.distanceDisplayCondition, defaultDistanceDisplayCondition); var outlineWidth = box.outlineWidth; @@ -541,6 +566,10 @@ define([ var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); + if (Property.getValueOrDefault(box.fill, time, true)) { var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); this._material = material; @@ -556,7 +585,10 @@ define([ geometryInstances : new GeometryInstance({ id : entity, geometry : BoxGeometry.fromDimensions(options), - modelMatrix : modelMatrix + modelMatrix : modelMatrix, + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + } }), appearance : appearance, asynchronous : false, @@ -577,7 +609,8 @@ define([ geometry : BoxOutlineGeometry.fromDimensions(options), modelMatrix : modelMatrix, attributes : { - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : new PerInstanceColorAppearance({ diff --git a/Source/DataSources/BoxGraphics.js b/Source/DataSources/BoxGraphics.js index a9e4be26f6eb..50e3e78560d7 100644 --- a/Source/DataSources/BoxGraphics.js +++ b/Source/DataSources/BoxGraphics.js @@ -32,6 +32,7 @@ define([ * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline. * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the box casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this box will be displayed. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} */ @@ -52,6 +53,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -132,7 +135,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this box will be displayed. + * @memberof BoxGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -153,6 +163,7 @@ define([ result.outlineColor = this.outlineColor; result.outlineWidth = this.outlineWidth; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -177,6 +188,7 @@ define([ this.outlineColor = defaultValue(this.outlineColor, source.outlineColor); this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return BoxGraphics; diff --git a/Source/DataSources/CorridorGeometryUpdater.js b/Source/DataSources/CorridorGeometryUpdater.js index 29f9374f73e7..391b121e5bd3 100644 --- a/Source/DataSources/CorridorGeometryUpdater.js +++ b/Source/DataSources/CorridorGeometryUpdater.js @@ -9,6 +9,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -34,6 +36,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -57,6 +61,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -104,6 +109,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._onTerrain = false; this._options = new GeometryOptions(entity); @@ -246,6 +252,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayCondition; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -348,6 +366,7 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time)); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -356,11 +375,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayCondition, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayCondition }; } @@ -399,7 +420,8 @@ define([ geometry : new CorridorOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time)) } }); }; @@ -478,6 +500,7 @@ define([ this._showOutlineProperty = defaultValue(corridor.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(corridor.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(corridor.shadows, defaultShadows); + this._distanceDisplayCondition = defaultValue(corridor.distanceDisplayCondition, defaultDistanceDisplayCondition); var height = corridor.height; var extrudedHeight = corridor.extrudedHeight; @@ -599,6 +622,8 @@ define([ options.cornerType = Property.getValueOrUndefined(corridor.cornerType, time); var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + var distanceDisplayCondition = this._geometryUpdater.distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (!defined(corridor.fill) || corridor.fill.getValue(time)) { var fillMaterialProperty = geometryUpdater.fillMaterialProperty; @@ -616,7 +641,8 @@ define([ id : entity, geometry : new CorridorGeometry(options), attributes: { - color: ColorGeometryInstanceAttribute.fromColor(currentColor) + color: ColorGeometryInstanceAttribute.fromColor(currentColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), asynchronous : false, @@ -633,7 +659,10 @@ define([ this._primitive = primitives.add(new Primitive({ geometryInstances : new GeometryInstance({ id : entity, - geometry : new CorridorGeometry(options) + geometry : new CorridorGeometry(options), + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + } }), appearance : appearance, asynchronous : false, @@ -654,7 +683,8 @@ define([ id : entity, geometry : new CorridorOutlineGeometry(options), attributes : { - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : new PerInstanceColorAppearance({ diff --git a/Source/DataSources/CorridorGraphics.js b/Source/DataSources/CorridorGraphics.js index 98161c48e343..4b74c8359fa6 100644 --- a/Source/DataSources/CorridorGraphics.js +++ b/Source/DataSources/CorridorGraphics.js @@ -39,6 +39,7 @@ define([ * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the distance between each latitude and longitude. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the corridor casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this corridor will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo} @@ -70,6 +71,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -190,7 +193,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this corridor will be displayed. + * @memberof CorridorGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -216,6 +226,7 @@ define([ result.outlineWidth = this.outlineWidth; result.cornerType = this.cornerType; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -245,6 +256,7 @@ define([ this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.cornerType = defaultValue(this.cornerType, source.cornerType); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return CorridorGraphics; diff --git a/Source/DataSources/CustomDataSource.js b/Source/DataSources/CustomDataSource.js index 82b1fbf82ff5..da9be5220fa3 100644 --- a/Source/DataSources/CustomDataSource.js +++ b/Source/DataSources/CustomDataSource.js @@ -1,13 +1,19 @@ /*global define*/ define([ + '../Core/defined', '../Core/defineProperties', + '../Core/DeveloperError', '../Core/Event', './DataSource', + './EntityCluster', './EntityCollection' ], function( + defined, defineProperties, + DeveloperError, Event, DataSource, + EntityCluster, EntityCollection) { 'use strict'; @@ -39,6 +45,7 @@ define([ this._isLoading = false; this._loading = new Event(); this._entityCollection = new EntityCollection(this); + this._entityCluster = new EntityCluster(); } defineProperties(CustomDataSource.prototype, { @@ -139,6 +146,26 @@ define([ set : function(value) { this._entityCollection.show = value; } + }, + + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof CustomDataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : function() { + return this._entityCluster; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value must be defined.'); + } + //>>includeEnd('debug'); + this._entityCluster = value; + } } }); diff --git a/Source/DataSources/CylinderGeometryUpdater.js b/Source/DataSources/CylinderGeometryUpdater.js index 86881bb9eec7..6556de373db0 100644 --- a/Source/DataSources/CylinderGeometryUpdater.js +++ b/Source/DataSources/CylinderGeometryUpdater.js @@ -10,6 +10,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -34,6 +36,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -55,6 +59,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); @@ -101,6 +106,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'cylinder', entity.cylinder, undefined); } @@ -241,6 +247,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof CylinderGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -329,6 +347,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -337,11 +357,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -375,6 +397,7 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, @@ -382,7 +405,8 @@ define([ modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -464,6 +488,7 @@ define([ this._showOutlineProperty = defaultValue(cylinder.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(cylinder.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(cylinder.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(cylinder.distanceDisplayCondition, defaultDistanceDisplayCondition); var slices = cylinder.slices; var outlineWidth = cylinder.outlineWidth; @@ -566,6 +591,10 @@ define([ options.numberOfVerticalLines = Property.getValueOrUndefined(cylinder.numberOfVerticalLines, time); var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (Property.getValueOrDefault(cylinder.fill, time, true)) { var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); @@ -582,7 +611,10 @@ define([ geometryInstances : new GeometryInstance({ id : entity, geometry : new CylinderGeometry(options), - modelMatrix : modelMatrix + modelMatrix : modelMatrix, + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + } }), appearance : appearance, asynchronous : false, @@ -603,7 +635,8 @@ define([ geometry : new CylinderOutlineGeometry(options), modelMatrix : modelMatrix, attributes : { - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : new PerInstanceColorAppearance({ diff --git a/Source/DataSources/CylinderGraphics.js b/Source/DataSources/CylinderGraphics.js index d2f4196519d9..e1a7c01cbe44 100644 --- a/Source/DataSources/CylinderGraphics.js +++ b/Source/DataSources/CylinderGraphics.js @@ -37,6 +37,7 @@ define([ * @param {Property} [options.numberOfVerticalLines=16] A numeric Property specifying the number of vertical lines to draw along the perimeter for the outline. * @param {Property} [options.slices=128] The number of edges around the perimeter of the cylinder. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the cylinder casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this cylinder will be displayed. */ function CylinderGraphics(options) { this._length = undefined; @@ -63,6 +64,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -174,7 +177,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this cylinder will be displayed. + * @memberof CylinderGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -199,6 +209,7 @@ define([ result.outlineColor = this.outlineColor; result.outlineWidth = this.outlineWidth; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -227,6 +238,7 @@ define([ this.outlineColor = defaultValue(this.outlineColor, source.outlineColor); this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return CylinderGraphics; diff --git a/Source/DataSources/CzmlDataSource.js b/Source/DataSources/CzmlDataSource.js index 706049c9fc25..46563103ff83 100644 --- a/Source/DataSources/CzmlDataSource.js +++ b/Source/DataSources/CzmlDataSource.js @@ -55,6 +55,7 @@ define([ './DataSourceClock', './EllipseGraphics', './EllipsoidGraphics', + './EntityCluster', './EntityCollection', './GridMaterialProperty', './ImageMaterialProperty', @@ -138,6 +139,7 @@ define([ DataSourceClock, EllipseGraphics, EllipsoidGraphics, + EntityCluster, EntityCollection, GridMaterialProperty, ImageMaterialProperty, @@ -1409,8 +1411,6 @@ define([ processPacketData(Number, model, 'maximumScale', modelData.maximumScale, interval, sourceUri, entityCollection); processPacketData(Boolean, model, 'incrementallyLoadTextures', modelData.incrementallyLoadTextures, interval, sourceUri, entityCollection); processPacketData(Boolean, model, 'runAnimations', modelData.runAnimations, interval, sourceUri, entityCollection); - processPacketData(Boolean, model, 'castShadows', modelData.castShadows, interval, sourceUri, entityCollection); - processPacketData(Boolean, model, 'receiveShadows', modelData.receiveShadows, interval, sourceUri, entityCollection); processPacketData(ShadowMode, model, 'shadows', modelData.shadows, interval, sourceUri, entityCollection); processPacketData(HeightReference, model, 'heightReference', modelData.heightReference, interval, sourceUri, entityCollection); @@ -1834,6 +1834,7 @@ define([ this._documentPacket = new DocumentPacket(); this._version = undefined; this._entityCollection = new EntityCollection(this); + this._entityCluster = new EntityCluster(); } /** @@ -1933,6 +1934,26 @@ define([ set : function(value) { this._entityCollection.show = value; } + }, + + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof CzmlDataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : function() { + return this._entityCluster; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value must be defined.'); + } + //>>includeEnd('debug'); + this._entityCluster = value; + } } }); diff --git a/Source/DataSources/DataSource.js b/Source/DataSources/DataSource.js index ed1c01fa3f00..a2afc6f7eb64 100644 --- a/Source/DataSources/DataSource.js +++ b/Source/DataSources/DataSource.js @@ -85,6 +85,16 @@ define([ */ show : { get : DeveloperError.throwInstantiationError + }, + + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof DataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : DeveloperError.throwInstantiationError } }); diff --git a/Source/DataSources/DataSourceDisplay.js b/Source/DataSources/DataSourceDisplay.js index c5ae516e6ac1..115ba375e7be 100644 --- a/Source/DataSources/DataSourceDisplay.js +++ b/Source/DataSources/DataSourceDisplay.js @@ -16,6 +16,7 @@ define([ './CylinderGeometryUpdater', './EllipseGeometryUpdater', './EllipsoidGeometryUpdater', + './EntityCluster', './GeometryVisualizer', './LabelVisualizer', './ModelVisualizer', @@ -43,6 +44,7 @@ define([ CylinderGeometryUpdater, EllipseGeometryUpdater, EllipsoidGeometryUpdater, + EntityCluster, GeometryVisualizer, LabelVisualizer, ModelVisualizer, @@ -111,9 +113,9 @@ define([ * @member * @type {DataSourceDisplay~VisualizersCallback} */ - DataSourceDisplay.defaultVisualizersCallback = function(scene, dataSource) { + DataSourceDisplay.defaultVisualizersCallback = function(scene, entityCluster, dataSource) { var entities = dataSource.entities; - return [new BillboardVisualizer(scene, entities), + return [new BillboardVisualizer(entityCluster, entities), new GeometryVisualizer(BoxGeometryUpdater, scene, entities), new GeometryVisualizer(CylinderGeometryUpdater, scene, entities), new GeometryVisualizer(CorridorGeometryUpdater, scene, entities), @@ -124,9 +126,9 @@ define([ new GeometryVisualizer(PolylineVolumeGeometryUpdater, scene, entities), new GeometryVisualizer(RectangleGeometryUpdater, scene, entities), new GeometryVisualizer(WallGeometryUpdater, scene, entities), - new LabelVisualizer(scene, entities), + new LabelVisualizer(entityCluster, entities), new ModelVisualizer(scene, entities), - new PointVisualizer(scene, entities), + new PointVisualizer(entityCluster, entities), new PathVisualizer(scene, entities)]; }; @@ -357,17 +359,45 @@ define([ }; DataSourceDisplay.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) { - var visualizers = this._visualizersCallback(this._scene, dataSource); + var scene = this._scene; + + var entityCluster = dataSource.clustering; + entityCluster._initialize(scene); + + scene.primitives.add(entityCluster); + + var visualizers = this._visualizersCallback(scene, entityCluster, dataSource); dataSource._visualizers = visualizers; + + var length = visualizers.length; + for (var i = 0; i < length; ++i) { + var visualizer = visualizers[i]; + var cluster = visualizer._cluster; + + if (defined(cluster) && !defined(cluster._scene)) { + cluster._initialize(scene); + scene.primitives.add(cluster); + } + } }; DataSourceDisplay.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) { + var scene = this._scene; + var entityCluster = dataSource.clustering; + scene.primitives.remove(entityCluster); + var visualizers = dataSource._visualizers; var length = visualizers.length; for (var i = 0; i < length; i++) { + var cluster = visualizers[i]._cluster; + if (cluster !== entityCluster) { + scene.primitives.remove(cluster); + } + visualizers[i].destroy(); - dataSource._visualizers = undefined; } + + dataSource._visualizers = undefined; }; /** diff --git a/Source/DataSources/EllipseGeometryUpdater.js b/Source/DataSources/EllipseGeometryUpdater.js index a9e13c630672..4808d717ef4d 100644 --- a/Source/DataSources/EllipseGeometryUpdater.js +++ b/Source/DataSources/EllipseGeometryUpdater.js @@ -7,6 +7,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/EllipseGeometry', '../Core/EllipseOutlineGeometry', '../Core/Event', @@ -32,6 +34,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, EllipseGeometry, EllipseOutlineGeometry, Event, @@ -57,6 +61,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -107,6 +112,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._onTerrain = false; this._options = new GeometryOptions(entity); @@ -249,6 +255,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -351,6 +369,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -359,11 +379,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -396,13 +418,15 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, geometry : new EllipseOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -483,6 +507,7 @@ define([ this._showOutlineProperty = defaultValue(ellipse.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(ellipse.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(ellipse.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(ellipse.distanceDisplayCondition, defaultDistanceDisplayCondition); var rotation = ellipse.rotation; var height = ellipse.height; @@ -617,6 +642,10 @@ define([ var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); + if (Property.getValueOrDefault(ellipse.fill, time, true)) { var fillMaterialProperty = geometryUpdater.fillMaterialProperty; var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); @@ -633,7 +662,8 @@ define([ id : entity, geometry : new EllipseGeometry(options), attributes: { - color: ColorGeometryInstanceAttribute.fromColor(currentColor) + color: ColorGeometryInstanceAttribute.fromColor(currentColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), asynchronous : false, @@ -652,6 +682,9 @@ define([ id : entity, geometry : new EllipseGeometry(options) }), + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + }, appearance : appearance, asynchronous : false, shadows : shadows @@ -671,7 +704,8 @@ define([ id : entity, geometry : new EllipseOutlineGeometry(options), attributes : { - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : new PerInstanceColorAppearance({ diff --git a/Source/DataSources/EllipseGraphics.js b/Source/DataSources/EllipseGraphics.js index 39e64d17671d..a8f33b913c11 100644 --- a/Source/DataSources/EllipseGraphics.js +++ b/Source/DataSources/EllipseGraphics.js @@ -42,6 +42,7 @@ define([ * @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the ellipse texture counter-clockwise from north. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the ellipse. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipse casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipse will be displayed. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Circles and Ellipses.html|Cesium Sandcastle Circles and Ellipses Demo} */ @@ -76,6 +77,8 @@ define([ this._numberOfVerticalLinesSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -212,7 +215,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipse will be displayed. + * @memberof EllipseGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -240,6 +250,7 @@ define([ result.outlineWidth = this.outlineWidth; result.numberOfVerticalLines = this.numberOfVerticalLines; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -271,6 +282,7 @@ define([ this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.numberOfVerticalLines = defaultValue(this.numberOfVerticalLines, source.numberOfVerticalLines); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return EllipseGraphics; diff --git a/Source/DataSources/EllipsoidGeometryUpdater.js b/Source/DataSources/EllipsoidGeometryUpdater.js index 04662eb305ff..89646db743d1 100644 --- a/Source/DataSources/EllipsoidGeometryUpdater.js +++ b/Source/DataSources/EllipsoidGeometryUpdater.js @@ -8,6 +8,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/EllipsoidGeometry', '../Core/EllipsoidOutlineGeometry', '../Core/Event', @@ -34,6 +36,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, EllipsoidGeometry, EllipsoidOutlineGeometry, Event, @@ -59,6 +63,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var radiiScratch = new Cartesian3(); var scratchColor = new Color(); @@ -106,6 +111,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'ellipsoid', entity.ellipsoid, undefined); } @@ -246,6 +252,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof EllipsoidGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -334,6 +352,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -342,11 +362,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -381,6 +403,7 @@ define([ var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, @@ -388,7 +411,8 @@ define([ modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -468,6 +492,7 @@ define([ this._showOutlineProperty = defaultValue(ellipsoid.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(ellipsoid.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(ellipsoid.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(ellipsoid.distanceDisplayCondition, defaultDistanceDisplayCondition); this._fillEnabled = fillEnabled; this._outlineEnabled = outlineEnabled; @@ -598,6 +623,10 @@ define([ var options = this._options; var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); //We only rebuild the primitive if something other than the radii has changed //For the radii, we use unit sphere and then deform it with a scale matrix. @@ -632,7 +661,8 @@ define([ geometry : new EllipsoidGeometry(options), modelMatrix : !in3D ? modelMatrix : undefined, attributes : { - show : new ShowGeometryInstanceAttribute(showFill) + show : new ShowGeometryInstanceAttribute(showFill), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : appearance, @@ -649,7 +679,8 @@ define([ modelMatrix : !in3D ? modelMatrix : undefined, attributes : { show : new ShowGeometryInstanceAttribute(showOutline), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute } }), appearance : new PerInstanceColorAppearance({ @@ -666,6 +697,7 @@ define([ this._lastShow = showFill; this._lastOutlineShow = showOutline; this._lastOutlineColor = Color.clone(outlineColor, this._lastOutlineColor); + this._lastDistanceDisplayCondition = distanceDisplayCondition; } else if (this._primitive.ready) { //Update attributes only. var primitive = this._primitive; @@ -703,6 +735,12 @@ define([ outlineAttributes.color = ColorGeometryInstanceAttribute.toValue(outlineColor, outlineAttributes.color); Color.clone(outlineColor, this._lastOutlineColor); } + + if (!DistanceDisplayCondition.equals(distanceDisplayCondition, this._lastDistanceDisplayCondition)) { + attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + outlineAttributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, outlineAttributes.distanceDisplayCondition); + DistanceDisplayCondition.clone(distanceDisplayCondition, this._lastDistanceDisplayCondition); + } } if (in3D) { diff --git a/Source/DataSources/EllipsoidGraphics.js b/Source/DataSources/EllipsoidGraphics.js index efa7d92247cb..ccf1ce9ee63d 100644 --- a/Source/DataSources/EllipsoidGraphics.js +++ b/Source/DataSources/EllipsoidGraphics.js @@ -35,6 +35,7 @@ define([ * @param {Property} [options.stackPartitions=64] A Property specifying the number of stacks. * @param {Property} [options.slicePartitions=64] A Property specifying the number of radial slices. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipsoid casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipsoid will be displayed. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Spheres%20and%20Ellipsoids.html|Cesium Sandcastle Spheres and Ellipsoids Demo} */ @@ -60,6 +61,9 @@ define([ this._outlineWidth = undefined; this._outlineWidthSubscription = undefined; this._shadows = undefined; + this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -165,7 +169,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipsoid will be displayed. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -189,6 +200,7 @@ define([ result.slicePartitions = this.slicePartitions; result.subdivisions = this.subdivisions; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -217,6 +229,7 @@ define([ this.slicePartitions = defaultValue(this.slicePartitions, source.slicePartitions); this.subdivisions = defaultValue(this.subdivisions, source.subdivisions); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return EllipsoidGraphics; diff --git a/Source/DataSources/EntityCluster.js b/Source/DataSources/EntityCluster.js new file mode 100644 index 000000000000..a9a105e6966e --- /dev/null +++ b/Source/DataSources/EntityCluster.js @@ -0,0 +1,808 @@ +/*global define*/ +define([ + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/EllipsoidalOccluder', + '../Core/Event', + '../Core/Matrix4', + '../Scene/Billboard', + '../Scene/BillboardCollection', + '../Scene/HeightReference', + '../Scene/HorizontalOrigin', + '../Scene/Label', + '../Scene/LabelCollection', + '../Scene/LabelStyle', + '../Scene/PointPrimitive', + '../Scene/PointPrimitiveCollection', + '../Scene/SceneTransforms', + '../Scene/VerticalOrigin', + '../ThirdParty/kdbush', + './Entity', + './Property' +], function( + BoundingRectangle, + Cartesian2, + Cartesian3, + Color, + defaultValue, + defined, + defineProperties, + destroyObject, + EllipsoidalOccluder, + Event, + Matrix4, + Billboard, + BillboardCollection, + HeightReference, + HorizontalOrigin, + Label, + LabelCollection, + LabelStyle, + PointPrimitive, + PointPrimitiveCollection, + SceneTransforms, + VerticalOrigin, + kdbush, + Entity, + Property) { + 'use strict'; + + /** + * Defines how screen space objects (billboards, points, labels) are clustered. + * + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.enabled=false] Whether or not to enable clustering. + * @param {Number} [options.pixelRange=80] The pixel range to extend the screen space bounding box. + * @param {Number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered. + * + * @alias EntityCluster + * @constructor + * + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo} + */ + function EntityCluster(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + this._enabled = defaultValue(options.enabled, false); + this._pixelRange = defaultValue(options.pixelRange, 80); + this._minimumClusterSize = defaultValue(options.minimumClusterSize, 2); + + this._labelCollection = undefined; + this._billboardCollection = undefined; + this._pointCollection = undefined; + + this._clusterBillboardCollection = undefined; + this._clusterLabelCollection = undefined; + this._clusterPointCollection = undefined; + + this._unusedLabelIndices = []; + this._unusedBillboardIndices = []; + this._unusedPointIndices = []; + + this._previousClusters = []; + this._previousHeight = undefined; + + this._enabledDirty = false; + this._pixelRangeDirty = false; + this._minimumClusterSizeDirty = false; + this._clusterDirty = false; + + this._cluster = undefined; + this._removeEventListener = undefined; + + this._clusterEvent = new Event(); + } + + function getX(point) { + return point.coord.x; + } + + function getY(point) { + return point.coord.y; + } + + function expandBoundingBox(bbox, pixelRange) { + bbox.x -= pixelRange; + bbox.y -= pixelRange; + bbox.width += pixelRange * 2.0; + bbox.height += pixelRange * 2.0; + } + + var labelBoundingBoxScratch = new BoundingRectangle(); + + function getBoundingBox(item, coord, pixelRange, entityCluster, result) { + if (defined(item._labelCollection)) { + result = Label.getScreenSpaceBoundingBox(item, coord, result); + } else if (defined(item._billboardCollection)) { + result = Billboard.getScreenSpaceBoundingBox(item, coord, result); + } else if (defined(item._pointPrimitiveCollection)) { + result = PointPrimitive.getScreenSpaceBoundingBox(item, coord, result); + } + + expandBoundingBox(result, pixelRange); + + if (!defined(item._labelCollection) && defined(item.id) && defined(item.id._label) && defined(entityCluster._labelCollection)) { + var labelIndex = item.id._labelIndex; + var label = entityCluster._labelCollection.get(labelIndex); + var labelBBox = Label.getScreenSpaceBoundingBox(label, coord, labelBoundingBoxScratch); + expandBoundingBox(labelBBox, pixelRange); + result = BoundingRectangle.union(result, labelBBox, result); + } + + return result; + } + + function addNonClusteredItem(item, entityCluster) { + item.clusterShow = true; + + if (!defined(item._labelCollection) && defined(item.id) && defined(item.id._label)) { + var labelIndex = item.id._labelIndex; + var label = entityCluster._labelCollection.get(labelIndex); + label.clusterShow = true; + } + } + + function addCluster(position, numPoints, ids, entityCluster) { + var cluster = { + billboard : entityCluster._clusterBillboardCollection.add(), + label : entityCluster._clusterLabelCollection.add(), + point : entityCluster._clusterPointCollection.add() + }; + + cluster.billboard.show = false; + cluster.point.show = false; + cluster.label.show = true; + cluster.label.text = numPoints.toLocaleString(); + cluster.billboard.position = cluster.label.position = cluster.point.position = position; + + entityCluster._clusterEvent.raiseEvent(ids, cluster); + } + + function getScreenSpacePositions(collection, points, scene, occluder) { + if (!defined(collection)) { + return; + } + + var length = collection.length; + for (var i = 0; i < length; ++i) { + var item = collection.get(i); + item.clusterShow = false; + + if (!item.show || !occluder.isPointVisible(item.position)) { + continue; + } + + if (defined(item._labelCollection) && (defined(item.id._billboard) || defined(item.id._point))) { + continue; + } + + var coord = item.computeScreenSpacePosition(scene); + if (!defined(coord)) { + continue; + } + + points.push({ + index : i, + collection : collection, + clustered : false, + coord : coord + }); + } + } + + var pointBoundinRectangleScratch = new BoundingRectangle(); + var totalBoundingRectangleScratch = new BoundingRectangle(); + var neighborBoundingRectangleScratch = new BoundingRectangle(); + + function createDeclutterCallback(entityCluster) { + return function(amount) { + if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) { + return; + } + + var scene = entityCluster._scene; + + var labelCollection = entityCluster._labelCollection; + var billboardCollection = entityCluster._billboardCollection; + var pointCollection = entityCluster._pointCollection; + + if (!defined(labelCollection) && !defined(billboardCollection) && !defined(pointCollection)) { + return; + } + + var clusteredLabelCollection = entityCluster._clusterLabelCollection; + var clusteredBillboardCollection = entityCluster._clusterBillboardCollection; + var clusteredPointCollection = entityCluster._clusterPointCollection; + + if (defined(clusteredLabelCollection)) { + clusteredLabelCollection.removeAll(); + } else { + clusteredLabelCollection = entityCluster._clusterLabelCollection = new LabelCollection({ + scene : scene + }); + } + + if (defined(clusteredBillboardCollection)) { + clusteredBillboardCollection.removeAll(); + } else { + clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new BillboardCollection({ + scene : scene + }); + } + + if (defined(clusteredPointCollection)) { + clusteredPointCollection.removeAll(); + } else { + clusteredPointCollection = entityCluster._clusterPointCollection = new PointPrimitiveCollection(); + } + + var pixelRange = entityCluster._pixelRange; + var minimumClusterSize = entityCluster._minimumClusterSize; + + var clusters = entityCluster._previousClusters; + var newClusters = []; + + var previousHeight = entityCluster._previousHeight; + var currentHeight = scene.camera.positionCartographic.height; + + var ellipsoid = scene.mapProjection.ellipsoid; + var cameraPosition = scene.camera.positionWC; + var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition); + + var points = []; + getScreenSpacePositions(labelCollection, points, scene, occluder); + getScreenSpacePositions(billboardCollection, points, scene, occluder); + getScreenSpacePositions(pointCollection, points, scene, occluder); + + var i; + var j; + var length; + var bbox; + var neighbors; + var neighborLength; + var neighborIndex; + var neighborPoint; + var ids; + var numPoints; + + var collection; + var collectionIndex; + + var index = kdbush(points, getX, getY, 64, Int32Array); + + if (currentHeight < previousHeight) { + length = clusters.length; + for (i = 0; i < length; ++i) { + var cluster = clusters[i]; + + if (!occluder.isPointVisible(cluster.position)) { + continue; + } + + var coord = Billboard._computeScreenSpacePosition(Matrix4.IDENTITY, cluster.position, Cartesian3.ZERO, Cartesian2.ZERO, scene); + if (!defined(coord)) { + continue; + } + + var factor = 1.0 - currentHeight / previousHeight; + var width = cluster.width = cluster.width * factor; + var height = cluster.height = cluster.height * factor; + + width = Math.max(width, cluster.minimumWidth); + height = Math.max(height, cluster.minimumHeight); + + var minX = coord.x - width * 0.5; + var minY = coord.y - height * 0.5; + var maxX = coord.x + width; + var maxY = coord.y + height; + + neighbors = index.range(minX, minY, maxX, maxY); + neighborLength = neighbors.length; + numPoints = 0; + ids = []; + + for (j = 0; j < neighborLength; ++j) { + neighborIndex = neighbors[j]; + neighborPoint = points[neighborIndex]; + if (!neighborPoint.clustered) { + ++numPoints; + + collection = neighborPoint.collection; + collectionIndex = neighborPoint.index; + ids.push(collection.get(collectionIndex).id); + } + } + + if (numPoints >= minimumClusterSize) { + addCluster(cluster.position, numPoints, ids, entityCluster); + newClusters.push(cluster); + + for (j = 0; j < neighborLength; ++j) { + points[neighbors[j]].clustered = true; + } + } + } + } + + length = points.length; + for (i = 0; i < length; ++i) { + var point = points[i]; + if (point.clustered) { + continue; + } + + point.clustered = true; + + collection = point.collection; + collectionIndex = point.index; + + var item = collection.get(collectionIndex); + bbox = getBoundingBox(item, point.coord, pixelRange, entityCluster, pointBoundinRectangleScratch); + var totalBBox = BoundingRectangle.clone(bbox, totalBoundingRectangleScratch); + + neighbors = index.range(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height); + neighborLength = neighbors.length; + + var clusterPosition = Cartesian3.clone(item.position); + numPoints = 1; + ids = [item.id]; + + for (j = 0; j < neighborLength; ++j) { + neighborIndex = neighbors[j]; + neighborPoint = points[neighborIndex]; + if (!neighborPoint.clustered) { + var neighborItem = neighborPoint.collection.get(neighborPoint.index); + var neighborBBox = getBoundingBox(neighborItem, neighborPoint.coord, pixelRange, entityCluster, neighborBoundingRectangleScratch); + + Cartesian3.add(neighborItem.position, clusterPosition, clusterPosition); + + BoundingRectangle.union(totalBBox, neighborBBox, totalBBox); + ++numPoints; + + ids.push(neighborItem.id); + } + } + + if (numPoints >= minimumClusterSize) { + var position = Cartesian3.multiplyByScalar(clusterPosition, 1.0 / numPoints, clusterPosition); + addCluster(position, numPoints, ids, entityCluster); + newClusters.push({ + position : position, + width : totalBBox.width, + height : totalBBox.height, + minimumWidth : bbox.width, + minimumHeight : bbox.height + }); + + for (j = 0; j < neighborLength; ++j) { + points[neighbors[j]].clustered = true; + } + } else { + addNonClusteredItem(item, entityCluster); + } + } + + if (clusteredLabelCollection.length === 0) { + clusteredLabelCollection.destroy(); + entityCluster._clusterLabelCollection = undefined; + } + + if (clusteredBillboardCollection.length === 0) { + clusteredBillboardCollection.destroy(); + entityCluster._clusterBillboardCollection = undefined; + } + + if (clusteredPointCollection.length === 0) { + clusteredPointCollection.destroy(); + entityCluster._clusterPointCollection = undefined; + } + + entityCluster._previousClusters = newClusters; + entityCluster._previousHeight = currentHeight; + }; + } + + EntityCluster.prototype._initialize = function(scene) { + this._scene = scene; + + var cluster = createDeclutterCallback(this); + this._cluster = cluster; + this._removeEventListener = scene.camera.changed.addEventListener(cluster); + }; + + defineProperties(EntityCluster.prototype, { + /** + * Gets or sets whether clustering is enabled. + * @memberof EntityCluster.prototype + * @type {Boolean} + */ + enabled : { + get : function() { + return this._enabled; + }, + set : function(value) { + this._enabledDirty = value !== this._enabled; + this._enabled = value; + } + }, + /** + * Gets or sets the pixel range to extend the screen space bounding box. + * @memberof EntityCluster.prototype + * @type {Number} + */ + pixelRange : { + get : function() { + return this._pixelRange; + }, + set : function(value) { + this._pixelRangeDirty = value !== this._pixelRange; + this._pixelRange = value; + } + }, + /** + * Gets or sets the minimum number of screen space objects that can be clustered. + * @memberof EntityCluster.prototype + * @type {Number} + */ + minimumClusterSize : { + get : function() { + return this._minimumClusterSize; + }, + set : function(value) { + this._minimumClusterSizeDirty = value !== this._minimumClusterSize; + this._minimumClusterSize = value; + } + }, + /** + * Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link EntityCluster~newClusterCallback}. + * @memberof EntityCluster.prototype + * @type {Event} + */ + clusterEvent : { + get : function() { + return this._clusterEvent; + } + } + }); + + /** + * Returns a new {@link Label}. + * @param {Entity} entity The entity that will use the returned {@link Label} for visualization. + * @returns {Label} The label that will be used to visualize an entity. + * + * @private + */ + EntityCluster.prototype.getLabel = function(entity) { + var labelCollection = this._labelCollection; + if (defined(labelCollection) && defined(entity._labelIndex)) { + return labelCollection.get(entity._labelIndex); + } + + if (!defined(labelCollection)) { + labelCollection = this._labelCollection = new LabelCollection({ + scene : this._scene + }); + } + + var index; + var label; + + var unusedIndices = this._unusedLabelIndices; + if (unusedIndices.length > 0) { + index = unusedIndices.pop(); + label = labelCollection.get(index); + } else { + label = labelCollection.add(); + index = labelCollection.length - 1; + } + + entity._labelIndex = index; + + this._clusterDirty = true; + + return label; + }; + + /** + * Removes the {@link Label} associated with an entity so it can be reused by another entity. + * @param {Entity} entity The entity that will uses the returned {@link Label} for visualization. + * + * @private + */ + EntityCluster.prototype.removeLabel = function(entity) { + if (!defined(this._labelCollection) || !defined(entity._labelIndex)) { + return; + } + + var index = entity._labelIndex; + entity._labelIndex = undefined; + + var label = this._labelCollection.get(index); + label.show = false; + label.id = undefined; + + this._unusedLabelIndices.push(index); + + this._clusterDirty = true; + }; + + /** + * Returns a new {@link Billboard}. + * @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization. + * @returns {Billboard} The label that will be used to visualize an entity. + * + * @private + */ + EntityCluster.prototype.getBillboard = function(entity) { + var billboardCollection = this._billboardCollection; + if (defined(billboardCollection) && defined(entity._billboardIndex)) { + return billboardCollection.get(entity._billboardIndex); + } + + if (!defined(billboardCollection)) { + billboardCollection = this._billboardCollection = new BillboardCollection({ + scene : this._scene + }); + } + + var index; + var billboard; + + var unusedIndices = this._unusedBillboardIndices; + if (unusedIndices.length > 0) { + index = unusedIndices.pop(); + billboard = billboardCollection.get(index); + } else { + billboard = billboardCollection.add(); + index = billboardCollection.length - 1; + } + + entity._billboardIndex = index; + + this._clusterDirty = true; + + return billboard; + }; + + /** + * Removes the {@link Billboard} associated with an entity so it can be reused by another entity. + * @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization. + * + * @private + */ + EntityCluster.prototype.removeBillboard = function(entity) { + if (!defined(this._billboardCollection) || !defined(entity._billboardIndex)) { + return; + } + + var index = entity._billboardIndex; + entity._billboardIndex = undefined; + + var billboard = this._billboardCollection.get(index); + billboard.id = undefined; + billboard.show = false; + billboard.image = undefined; + + this._unusedBillboardIndices.push(index); + + this._clusterDirty = true; + }; + + /** + * Returns a new {@link Point}. + * @param {Entity} entity The entity that will use the returned {@link Point} for visualization. + * @returns {Point} The label that will be used to visualize an entity. + * + * @private + */ + EntityCluster.prototype.getPoint = function(entity) { + var pointCollection = this._pointCollection; + if (defined(pointCollection) && defined(entity._pointIndex)) { + return pointCollection.get(entity._pointIndex); + } + + if (!defined(pointCollection)) { + pointCollection = this._pointCollection = new PointPrimitiveCollection(); + } + + var index; + var point; + + var unusedIndices = this._unusedPointIndices; + if (unusedIndices.length > 0) { + index = unusedIndices.pop(); + point = pointCollection.get(index); + } else { + point = pointCollection.add(); + index = pointCollection.length - 1; + } + + entity._pointIndex = index; + + this._clusterDirty = true; + + return point; + }; + + /** + * Removes the {@link Point} associated with an entity so it can be reused by another entity. + * @param {Entity} entity The entity that will uses the returned {@link Point} for visualization. + * + * @private + */ + EntityCluster.prototype.removePoint = function(entity) { + if (!defined(this._pointCollection) || !defined(entity._pointIndex)) { + return; + } + + var index = entity._pointIndex; + entity._pointIndex = undefined; + + var point = this._pointCollection.get(index); + point.show = false; + point.id = undefined; + + this._unusedPointIndices.push(index); + + this._clusterDirty = true; + }; + + function disableCollectionClustering(collection) { + if (!defined(collection)) { + return; + } + + var length = collection.length; + for (var i = 0; i < length; ++i) { + collection.get(i).clusterShow = true; + } + } + + function updateEnable(entityCluster) { + if (entityCluster.enabled) { + return; + } + + if (defined(entityCluster._clusterLabelCollection)) { + entityCluster._clusterLabelCollection.destroy(); + } + if (defined(entityCluster._clusterBillboardCollection)) { + entityCluster._clusterBillboardCollection.destroy(); + } + if (defined(entityCluster._clusterPointCollection)) { + entityCluster._clusterPointCollection.destroy(); + } + + entityCluster._clusterLabelCollection = undefined; + entityCluster._clusterBillboardCollection = undefined; + entityCluster._clusterPointCollection = undefined; + + disableCollectionClustering(entityCluster._labelCollection); + disableCollectionClustering(entityCluster._billboardCollection); + disableCollectionClustering(entityCluster._pointCollection); + } + + /** + * Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise, + * queues the draw commands for billboards/points/labels created for entities. + * @private + */ + EntityCluster.prototype.update = function(frameState) { + // If clustering is enabled before the label collection is updated, + // the glyphs haven't been created so the screen space bounding boxes + // are incorrect. + if (defined(this._labelCollection) && this._labelCollection.length > 0 && this._labelCollection.get(0)._glyphs.length === 0) { + var commandList = frameState.commandList; + frameState.commandList = []; + this._labelCollection.update(frameState); + frameState.commandList = commandList; + } + + if (this._enabledDirty) { + this._enabledDirty = false; + updateEnable(this); + this._clusterDirty = true; + } + + if (this._pixelRangeDirty || this._minimumClusterSizeDirty) { + this._pixelRangeDirty = false; + this._minimumClusterSizeDirty = false; + this._clusterDirty = true; + } + + if (this._clusterDirty) { + this._clusterDirty = false; + this._cluster(); + } + + if (defined(this._clusterLabelCollection)) { + this._clusterLabelCollection.update(frameState); + } + if (defined(this._clusterBillboardCollection)) { + this._clusterBillboardCollection.update(frameState); + } + if (defined(this._clusterPointCollection)) { + this._clusterPointCollection.update(frameState); + } + + if (defined(this._labelCollection)) { + this._labelCollection.update(frameState); + } + if (defined(this._billboardCollection)) { + this._billboardCollection.update(frameState); + } + if (defined(this._pointCollection)) { + this._pointCollection.update(frameState); + } + }; + + /** + * 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. + *

+ * Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed + * from a data source collection and added to another. + *

+ * + * @returns {undefined} + */ + EntityCluster.prototype.destroy = function() { + this._labelCollection = this._labelCollection && this._labelCollection.destroy(); + this._billboardCollection = this._billboardCollection && this._billboardCollection.destroy(); + this._pointCollection = this._pointCollection && this._pointCollection.destroy(); + + this._clusterLabelCollection = this._clusterLabelCollection && this._clusterLabelCollection.destroy(); + this._clusterBillboardCollection = this._clusterBillboardCollection && this._clusterBillboardCollection.destroy(); + this._clusterPointCollection = this._clusterPointCollection && this._clusterPointCollection.destroy(); + + if (defined(this._removeEventListener)) { + this._removeEventListener(); + this._removeEventListener = undefined; + } + + this._labelCollection = undefined; + this._billboardCollection = undefined; + this._pointCollection = undefined; + + this._clusterBillboardCollection = undefined; + this._clusterLabelCollection = undefined; + this._clusterPointCollection = undefined; + + this._unusedLabelIndices = []; + this._unusedBillboardIndices = []; + this._unusedPointIndices = []; + + this._previousClusters = []; + this._previousHeight = undefined; + + this._enabledDirty = false; + this._pixelRangeDirty = false; + this._minimumClusterSizeDirty = false; + + return undefined; + }; + + /** + * A event listener function used to style clusters. + * @callback EntityCluster~newClusterCallback + * + * @param {Entity[]} clusteredEntities An array of the entities contained in the cluster. + * @param {Object} cluster An object containing billboard, label, and point properties. The values are the same as + * billboard, label and point entities, but must be the values of the ConstantProperty. + * + * @example + * // The default cluster values. + * dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) { + * cluster.label.show = true; + * cluster.label.text = entities.length.toLocaleString(); + * }); + */ + + return EntityCluster; +}); \ No newline at end of file diff --git a/Source/DataSources/GeoJsonDataSource.js b/Source/DataSources/GeoJsonDataSource.js index d14dc04d9410..a06d5ac60e21 100644 --- a/Source/DataSources/GeoJsonDataSource.js +++ b/Source/DataSources/GeoJsonDataSource.js @@ -24,6 +24,7 @@ define([ './ConstantProperty', './CorridorGraphics', './DataSource', + './EntityCluster', './EntityCollection', './PolygonGraphics', './PolylineGraphics' @@ -52,6 +53,7 @@ define([ ConstantProperty, CorridorGraphics, DataSource, + EntityCluster, EntityCollection, PolygonGraphics, PolylineGraphics) { @@ -512,6 +514,7 @@ define([ this._entityCollection = new EntityCollection(this); this._promises = []; this._pinBuilder = new PinBuilder(); + this._entityCluster = new EntityCluster(); } /** @@ -764,6 +767,26 @@ define([ set : function(value) { this._entityCollection.show = value; } + }, + + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof GeoJsonDataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : function() { + return this._entityCluster; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value must be defined.'); + } + //>>includeEnd('debug'); + this._entityCluster = value; + } } }); diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index 88dec7dc5868..f5eeabb5454e 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -46,6 +46,7 @@ define([ './DataSource', './DataSourceClock', './Entity', + './EntityCluster', './EntityCollection', './LabelGraphics', './PathGraphics', @@ -105,6 +106,7 @@ define([ DataSource, DataSourceClock, Entity, + EntityCluster, EntityCollection, LabelGraphics, PathGraphics, @@ -1664,13 +1666,13 @@ define([ west = CesiumMath.negativePiToPi(CesiumMath.toRadians(west)); } if (defined(south)) { - south = CesiumMath.negativePiToPi(CesiumMath.toRadians(south)); + south = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(south)); } if (defined(east)) { east = CesiumMath.negativePiToPi(CesiumMath.toRadians(east)); } if (defined(north)) { - north = CesiumMath.negativePiToPi(CesiumMath.toRadians(north)); + north = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(north)); } geometry.coordinates = new Rectangle(west, south, east, north); @@ -2229,6 +2231,7 @@ define([ this._pinBuilder = new PinBuilder(); this._promises = []; this._networkLinks = new AssociativeArray(); + this._entityCluster = new EntityCluster(); this._canvas = canvas; this._camera = camera; @@ -2365,6 +2368,26 @@ define([ set : function(value) { this._entityCollection.show = value; } + }, + + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof KmlDataSource.prototype + * @type {EntityCluster} + */ + clustering : { + get : function() { + return this._entityCluster; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value must be defined.'); + } + //>>includeEnd('debug'); + this._entityCluster = value; + } } }); diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 6cc1ca46b1a6..f16fcb5977a9 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -43,6 +43,7 @@ define([ * @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera. * @param {Property} [options.pixelOffsetScaleByDistance] A {@link NearFarScalar} Property used to set pixelOffset based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -77,6 +78,8 @@ define([ this._translucencyByDistanceSubscription = undefined; this._pixelOffsetScaleByDistance = undefined; this._pixelOffsetScaleByDistanceSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -249,8 +252,14 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance') + pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'), + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this label will be displayed. + * @memberof LabelGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -278,6 +287,7 @@ define([ result.pixelOffset = this.pixelOffset; result.translucencyByDistance = this.translucencyByDistance; result.pixelOffsetScaleByDistance = this.pixelOffsetScaleByDistance; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -309,6 +319,7 @@ define([ this.pixelOffset = defaultValue(this.pixelOffset, source.pixelOffset); this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance); this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 34183d2b2c94..09386c105bb4 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -6,15 +6,17 @@ define([ '../Core/Color', '../Core/defaultValue', '../Core/defined', + '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/NearFarScalar', '../Scene/HeightReference', '../Scene/HorizontalOrigin', - '../Scene/LabelCollection', '../Scene/LabelStyle', '../Scene/VerticalOrigin', './BoundingSphereState', + './EntityCluster', './Property' ], function( AssociativeArray, @@ -23,15 +25,17 @@ define([ Color, defaultValue, defined, + deprecationWarning, destroyObject, DeveloperError, + DistanceDisplayCondition, NearFarScalar, HeightReference, HorizontalOrigin, - LabelCollection, LabelStyle, VerticalOrigin, BoundingSphereState, + EntityCluster, Property) { 'use strict'; @@ -54,6 +58,7 @@ define([ var pixelOffset = new Cartesian2(); var translucencyByDistance = new NearFarScalar(); var pixelOffsetScaleByDistance = new NearFarScalar(); + var distanceDisplayCondition = new DistanceDisplayCondition(); function EntityData(entity) { this.entity = entity; @@ -67,24 +72,29 @@ define([ * @alias LabelVisualizer * @constructor * - * @param {Scene} scene The scene the primitives will be rendered in. + * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities. * @param {EntityCollection} entityCollection The entityCollection to visualize. */ - function LabelVisualizer(scene, entityCollection) { + function LabelVisualizer(entityCluster, entityCollection) { //>>includeStart('debug', pragmas.debug); - if (!defined(scene)) { - throw new DeveloperError('scene is required.'); + if (!defined(entityCluster)) { + throw new DeveloperError('entityCluster is required.'); } if (!defined(entityCollection)) { throw new DeveloperError('entityCollection is required.'); } //>>includeEnd('debug'); + if (!defined(entityCluster.minimumClusterSize)) { + deprecationWarning('BillboardVisualizer scene constructor parameter', 'The scene is no longer a parameter the BillboardVisualizer. An EntityCluster is required.'); + entityCluster = new EntityCluster({ + enabled : false + }); + } + entityCollection.collectionChanged.addEventListener(LabelVisualizer.prototype._onCollectionChanged, this); - this._scene = scene; - this._unusedIndexes = []; - this._labelCollection = undefined; + this._cluster = entityCluster; this._entityCollection = entityCollection; this._items = new AssociativeArray(); @@ -106,7 +116,8 @@ define([ //>>includeEnd('debug'); var items = this._items.values; - var unusedIndexes = this._unusedIndexes; + var cluster = this._cluster; + for (var i = 0, len = items.length; i < len; i++) { var item = items[i]; var entity = item.entity; @@ -123,28 +134,16 @@ define([ if (!show) { //don't bother creating or updating anything else - returnLabel(item, unusedIndexes); + cluster.removeLabel(entity); continue; } - if (!defined(label)) { - var labelCollection = this._labelCollection; - if (!defined(labelCollection)) { - labelCollection = this._scene.primitives.add(new LabelCollection({ - scene : this._scene - })); - this._labelCollection = labelCollection; - } + if (!Property.isConstant(entity._position)) { + cluster._clusterDirty = true; + } - var length = unusedIndexes.length; - if (length > 0) { - var index = unusedIndexes.pop(); - item.index = index; - label = labelCollection.get(index); - } else { - label = labelCollection.add(); - item.index = labelCollection.length - 1; - } + if (!defined(label)) { + label = cluster.getLabel(entity); label.id = entity; item.label = label; } @@ -165,6 +164,7 @@ define([ label.verticalOrigin = Property.getValueOrDefault(labelGraphics._verticalOrigin, time, defaultVerticalOrigin); label.translucencyByDistance = Property.getValueOrUndefined(labelGraphics._translucencyByDistance, time, translucencyByDistance); label.pixelOffsetScaleByDistance = Property.getValueOrUndefined(labelGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance); + label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); } return true; }; @@ -215,17 +215,14 @@ define([ */ LabelVisualizer.prototype.destroy = function() { this._entityCollection.collectionChanged.removeEventListener(LabelVisualizer.prototype._onCollectionChanged, this); - if (defined(this._labelCollection)) { - this._scene.primitives.remove(this._labelCollection); - } return destroyObject(this); }; LabelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { var i; var entity; - var unusedIndexes = this._unusedIndexes; var items = this._items; + var cluster = this._cluster; for (i = added.length - 1; i > -1; i--) { entity = added[i]; @@ -241,30 +238,17 @@ define([ items.set(entity.id, new EntityData(entity)); } } else { - returnLabel(items.get(entity.id), unusedIndexes); + cluster.removeLabel(entity); items.remove(entity.id); } } for (i = removed.length - 1; i > -1; i--) { entity = removed[i]; - returnLabel(items.get(entity.id), unusedIndexes); + cluster.removeLabel(entity); items.remove(entity.id); } }; - function returnLabel(item, unusedIndexes) { - if (defined(item)) { - var label = item.label; - if (defined(label)) { - unusedIndexes.push(item.index); - label.id = undefined; - label.show = false; - item.label = undefined; - item.index = -1; - } - } - } - return LabelVisualizer; }); diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js index e0b5ceb10794..81d48182b0cb 100644 --- a/Source/DataSources/ModelGraphics.js +++ b/Source/DataSources/ModelGraphics.js @@ -47,10 +47,9 @@ define([ * @param {Property} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. * @param {Property} [options.runAnimations=true] A boolean Property specifying if glTF animations specified in the model should be started. * @param {Property} [options.nodeTransformations] An object, where keys are names of nodes, and values are {@link TranslationRotationScale} Properties describing the transformation to apply to that node. - * @param {Property} [options.castShadows=true] Deprecated, use options.shadows instead. A boolean Property specifying whether the model casts shadows from each light source. - * @param {Property} [options.receiveShadows=true] Deprecated, use options.shadows instead. A boolean Property specifying whether the model receives shadows from shadow casters in the scene. * @param {Property} [options.shadows=ShadowMode.ENABLED] An enum Property specifying whether the model casts or receives shadows from each light source. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this model will be displayed. * * @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial} * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo} @@ -66,10 +65,6 @@ define([ this._maximumScaleSubscription = undefined; this._incrementallyLoadTextures = undefined; this._incrementallyLoadTexturesSubscription = undefined; - this._castShadows = undefined; - this._castShadowsSubscription = undefined; - this._receiveShadows = undefined; - this._receiveShadowsSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; this._uri = undefined; @@ -80,6 +75,8 @@ define([ this._nodeTransformationsSubscription = undefined; this._heightReference = undefined; this._heightReferenceSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -144,24 +141,6 @@ define([ */ incrementallyLoadTextures : createPropertyDescriptor('incrementallyLoadTextures'), - /** - * Get or sets the boolean Property specifying whether the model - * casts shadows from each light source. - * @memberof ModelGraphics.prototype - * @type {Property} - * @deprecated - */ - castShadows : createPropertyDescriptor('castShadows'), - - /** - * Get or sets the boolean Property specifying whether the model - * receives shadows from shadow casters in the scene. - * @memberof ModelGraphics.prototype - * @type {Property} - * @deprecated - */ - receiveShadows : createPropertyDescriptor('receiveShadows'), - /** * Get or sets the enum Property specifying whether the model * casts or receives shadows from each light source. @@ -200,7 +179,14 @@ define([ * @type {Property} * @default HeightReference.NONE */ - heightReference : createPropertyDescriptor('heightReference') + heightReference : createPropertyDescriptor('heightReference'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this model will be displayed. + * @memberof ModelGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -218,13 +204,12 @@ define([ result.minimumPixelSize = this.minimumPixelSize; result.maximumScale = this.maximumScale; result.incrementallyLoadTextures = this.incrementallyLoadTextures; - result.castShadows = this.castShadows; - result.receiveShadows = this.receiveShadows; result.shadows = this.shadows; result.uri = this.uri; result.runAnimations = this.runAnimations; result.nodeTransformations = this.nodeTransformations; result.heightReference = this._heightReference; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -247,12 +232,11 @@ define([ this.minimumPixelSize = defaultValue(this.minimumPixelSize, source.minimumPixelSize); this.maximumScale = defaultValue(this.maximumScale, source.maximumScale); this.incrementallyLoadTextures = defaultValue(this.incrementallyLoadTextures, source.incrementallyLoadTextures); - this.castShadows = defaultValue(this.castShadows, source.castShadows); - this.receiveShadows = defaultValue(this.receiveShadows, source.receiveShadows); this.shadows = defaultValue(this.shadows, source.shadows); this.uri = defaultValue(this.uri, source.uri); this.runAnimations = defaultValue(this.runAnimations, source.runAnimations); this.heightReference = defaultValue(this.heightReference, source.heightReference); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); var sourceNodeTransformations = source.nodeTransformations; if (defined(sourceNodeTransformations)) { diff --git a/Source/DataSources/ModelVisualizer.js b/Source/DataSources/ModelVisualizer.js index fde2fedcb7f8..110aafe889f8 100644 --- a/Source/DataSources/ModelVisualizer.js +++ b/Source/DataSources/ModelVisualizer.js @@ -131,22 +131,14 @@ define([ modelHash[entity.id] = modelData; } - var shadows = defaultShadows; - if (defined(modelGraphics._shadows)) { - shadows = Property.getValueOrDefault(modelGraphics._shadows, time, defaultShadows); - } else if (defined(modelGraphics._castShadows) || defined(modelGraphics._receiveShadows)) { - var castShadows = Property.getValueOrDefault(modelGraphics._castShadows, time, true); - var receiveShadows = Property.getValueOrDefault(modelGraphics.receiveShadows, time, true); - shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); - } - model.show = true; model.scale = Property.getValueOrDefault(modelGraphics._scale, time, defaultScale); model.minimumPixelSize = Property.getValueOrDefault(modelGraphics._minimumPixelSize, time, defaultMinimumPixelSize); model.maximumScale = Property.getValueOrUndefined(modelGraphics._maximumScale, time); model.modelMatrix = Matrix4.clone(modelMatrix, model.modelMatrix); - model.shadows = shadows; + model.shadows = Property.getValueOrDefault(modelGraphics._shadows, time, defaultShadows); model.heightReference = Property.getValueOrDefault(modelGraphics._heightReference, time, defaultHeightReference); + model.distanceDisplayCondition = Property.getValueOrUndefined(modelGraphics._distanceDisplayCondition, time); if (model.ready) { var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true); diff --git a/Source/DataSources/PathGraphics.js b/Source/DataSources/PathGraphics.js index 818a82edfa8b..b900b552c3a9 100644 --- a/Source/DataSources/PathGraphics.js +++ b/Source/DataSources/PathGraphics.js @@ -30,6 +30,7 @@ define([ * @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels. * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the path. * @param {Property} [options.resolution=60] A numeric Property specifying the maximum number of seconds to step when sampling the position. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this path will be displayed. */ function PathGraphics(options) { this._material = undefined; @@ -44,6 +45,8 @@ define([ this._leadTimeSubscription = undefined; this._trailTime = undefined; this._trailTimeSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -106,7 +109,14 @@ define([ * @memberof PathGraphics.prototype * @type {Property} */ - trailTime : createPropertyDescriptor('trailTime') + trailTime : createPropertyDescriptor('trailTime'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this path will be displayed. + * @memberof PathGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -125,6 +135,7 @@ define([ result.show = this.show; result.leadTime = this.leadTime; result.trailTime = this.trailTime; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -147,6 +158,7 @@ define([ this.show = defaultValue(this.show, source.show); this.leadTime = defaultValue(this.leadTime, source.leadTime); this.trailTime = defaultValue(this.trailTime, source.trailTime); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return PathGraphics; diff --git a/Source/DataSources/PathVisualizer.js b/Source/DataSources/PathVisualizer.js index 8c39644abf93..e80a388bb5e2 100644 --- a/Source/DataSources/PathVisualizer.js +++ b/Source/DataSources/PathVisualizer.js @@ -273,6 +273,7 @@ define([ this._referenceFrame = referenceFrame; scene.primitives.add(this._polylineCollection); } + PolylineUpdater.prototype.update = function(time) { if (this._referenceFrame === ReferenceFrame.INERTIAL) { var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch); @@ -368,6 +369,7 @@ define([ polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions.slice()); polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material); polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth); + polyline.distanceDisplayCondition = Property.getValueOrUndefined(pathGraphics._distanceDisplayCondition, time, polyline.distanceDisplayCondition); }; PolylineUpdater.prototype.removeObject = function(item) { diff --git a/Source/DataSources/PointGraphics.js b/Source/DataSources/PointGraphics.js index 55ebc5fc676e..af7633d7a8e6 100644 --- a/Source/DataSources/PointGraphics.js +++ b/Source/DataSources/PointGraphics.js @@ -29,6 +29,8 @@ define([ * @param {Property} [options.show=true] A boolean Property specifying the visibility of the point. * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to scale the point based on distance. * @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera. + * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this point will be displayed. */ function PointGraphics(options) { this._color = undefined; @@ -47,6 +49,8 @@ define([ this._translucencyByDistanceSubscription = undefined; this._heightReference = undefined; this._heightReferenceSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -131,7 +135,14 @@ define([ * @type {Property} * @default HeightReference.NONE */ - heightReference : createPropertyDescriptor('heightReference') + heightReference : createPropertyDescriptor('heightReference'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this point will be displayed. + * @memberof PointGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -152,6 +163,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.translucencyByDistance = this._translucencyByDistance; result.heightReference = this.heightReference; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -176,6 +188,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance); this.heightReference = defaultValue(this.heightReference, source.heightReference); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return PointGraphics; diff --git a/Source/DataSources/PointVisualizer.js b/Source/DataSources/PointVisualizer.js index b7bbe52fb024..81aae2987be9 100644 --- a/Source/DataSources/PointVisualizer.js +++ b/Source/DataSources/PointVisualizer.js @@ -5,13 +5,14 @@ define([ '../Core/Color', '../Core/defaultValue', '../Core/defined', + '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/NearFarScalar', - '../Scene/BillboardCollection', '../Scene/HeightReference', - '../Scene/PointPrimitiveCollection', './BoundingSphereState', + './EntityCluster', './Property' ], function( AssociativeArray, @@ -19,13 +20,14 @@ define([ Color, defaultValue, defined, + deprecationWarning, destroyObject, DeveloperError, + DistanceDisplayCondition, NearFarScalar, - BillboardCollection, HeightReference, - PointPrimitiveCollection, BoundingSphereState, + EntityCluster, Property) { 'use strict'; @@ -39,6 +41,7 @@ define([ var outlineColor = new Color(); var scaleByDistance = new NearFarScalar(); var translucencyByDistance = new NearFarScalar(); + var distanceDisplayCondition = new DistanceDisplayCondition(); function EntityData(entity) { this.entity = entity; @@ -55,27 +58,30 @@ define([ * @alias PointVisualizer * @constructor * - * @param {Scene} scene The scene the primitives will be rendered in. + * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities. * @param {EntityCollection} entityCollection The entityCollection to visualize. */ - function PointVisualizer(scene, entityCollection) { + function PointVisualizer(entityCluster, entityCollection) { //>>includeStart('debug', pragmas.debug); - if (!defined(scene)) { - throw new DeveloperError('scene is required.'); + if (!defined(entityCluster)) { + throw new DeveloperError('entityCluster is required.'); } if (!defined(entityCollection)) { throw new DeveloperError('entityCollection is required.'); } //>>includeEnd('debug'); + if (!defined(entityCluster.minimumClusterSize)) { + deprecationWarning('BillboardVisualizer scene constructor parameter', 'The scene is no longer a parameter the BillboardVisualizer. An EntityCluster is required.'); + entityCluster = new EntityCluster({ + enabled : false + }); + } + entityCollection.collectionChanged.addEventListener(PointVisualizer.prototype._onCollectionChanged, this); - this._scene = scene; - this._unusedPointIndexes = []; - this._unusedBillboardIndexes = []; + this._cluster = entityCluster; this._entityCollection = entityCollection; - this._pointPrimitiveCollection = undefined; - this._billboardCollection = undefined; this._items = new AssociativeArray(); this._onCollectionChanged(entityCollection, entityCollection.values, [], []); } @@ -94,10 +100,8 @@ define([ } //>>includeEnd('debug'); - var scene = this._scene; var items = this._items.values; - var unusedPointIndexes = this._unusedPointIndexes; - var unusedBillboardIndexes = this._unusedBillboardIndexes; + var cluster = this._cluster; for (var i = 0, len = items.length; i < len; i++) { var item = items[i]; var entity = item.entity; @@ -111,53 +115,33 @@ define([ show = defined(position); } if (!show) { - returnPrimitive(item, unusedPointIndexes, unusedBillboardIndexes); + returnPrimitive(item, entity, cluster); continue; } + if (!Property.isConstant(entity._position)) { + cluster._clusterDirty = true; + } + var needsRedraw = false; if ((heightReference !== HeightReference.NONE) && !defined(billboard)) { if (defined(pointPrimitive)) { - returnPrimitive(item, unusedPointIndexes, unusedBillboardIndexes); + returnPrimitive(item, entity, cluster); pointPrimitive = undefined; } - var billboardCollection = this._billboardCollection; - if (!defined(billboardCollection)) { - billboardCollection = new BillboardCollection({scene: scene}); - this._billboardCollection = billboardCollection; - scene.primitives.add(billboardCollection); - } - - if (unusedBillboardIndexes.length > 0) { - billboard = billboardCollection.get(unusedBillboardIndexes.pop()); - } else { - billboard = billboardCollection.add(); - } - + billboard = cluster.getBillboard(entity); billboard.id = entity; billboard.image = undefined; item.billboard = billboard; needsRedraw = true; } else if ((heightReference === HeightReference.NONE) && !defined(pointPrimitive)) { if (defined(billboard)) { - returnPrimitive(item, unusedPointIndexes, unusedBillboardIndexes); + returnPrimitive(item, entity, cluster); billboard = undefined; } - var pointPrimitiveCollection = this._pointPrimitiveCollection; - if (!defined(pointPrimitiveCollection)) { - pointPrimitiveCollection = new PointPrimitiveCollection(); - this._pointPrimitiveCollection = pointPrimitiveCollection; - scene.primitives.add(pointPrimitiveCollection); - } - - if (unusedPointIndexes.length > 0) { - pointPrimitive = pointPrimitiveCollection.get(unusedPointIndexes.pop()); - } else { - pointPrimitive = pointPrimitiveCollection.add(); - } - + pointPrimitive = cluster.getPoint(entity); pointPrimitive.id = entity; item.pointPrimitive = pointPrimitive; } @@ -171,11 +155,13 @@ define([ pointPrimitive.outlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor); pointPrimitive.outlineWidth = Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth); pointPrimitive.pixelSize = Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize); + pointPrimitive.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition); } else { // billboard billboard.show = true; billboard.position = position; billboard.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance); billboard.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance); + billboard.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition); billboard.heightReference = heightReference; var newColor = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color); @@ -271,21 +257,14 @@ define([ */ PointVisualizer.prototype.destroy = function() { this._entityCollection.collectionChanged.removeEventListener(PointVisualizer.prototype._onCollectionChanged, this); - if (defined(this._pointPrimitiveCollection)) { - this._scene.primitives.remove(this._pointPrimitiveCollection); - } - if (defined(this._billboardCollection)) { - this._scene.primitives.remove(this._billboardCollection); - } return destroyObject(this); }; PointVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { var i; var entity; - var unusedPointIndexes = this._unusedPointIndexes; - var unusedBillboardIndexes = this._unusedBillboardIndexes; var items = this._items; + var cluster = this._cluster; for (i = added.length - 1; i > -1; i--) { entity = added[i]; @@ -301,34 +280,30 @@ define([ items.set(entity.id, new EntityData(entity)); } } else { - returnPrimitive(items.get(entity.id), unusedPointIndexes, unusedBillboardIndexes); + returnPrimitive(items.get(entity.id), entity, cluster); items.remove(entity.id); } } for (i = removed.length - 1; i > -1; i--) { entity = removed[i]; - returnPrimitive(items.get(entity.id), unusedPointIndexes, unusedBillboardIndexes); + returnPrimitive(items.get(entity.id), entity, cluster); items.remove(entity.id); } }; - function returnPrimitive(item, unusedPointIndexes, unusedBillboardIndexes) { + function returnPrimitive(item, entity, cluster) { if (defined(item)) { var pointPrimitive = item.pointPrimitive; if (defined(pointPrimitive)) { item.pointPrimitive = undefined; - pointPrimitive.id = undefined; - pointPrimitive.show = false; - unusedPointIndexes.push(pointPrimitive._index); + cluster.removePoint(entity); return; } var billboard = item.billboard; if (defined(billboard)) { item.billboard = undefined; - billboard.id = undefined; - billboard.show = false; - unusedBillboardIndexes.push(billboard._index); + cluster.removeBillboard(entity); } } } diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index 6c2aafd06577..c9fbd76c0faf 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -7,6 +7,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/isArray', @@ -34,6 +36,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, isArray, @@ -61,6 +65,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -110,6 +115,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._onTerrain = false; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'polygon', entity.polygon, undefined); @@ -251,6 +257,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof PolygonGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -353,6 +371,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -361,11 +381,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -398,13 +420,15 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, geometry : new PolygonOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -486,6 +510,7 @@ define([ this._showOutlineProperty = defaultValue(polygon.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(polygon.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(polygon.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(polygon.distanceDisplayCondition, defaultDistanceDisplayCondition); var height = polygon.height; var extrudedHeight = polygon.extrudedHeight; diff --git a/Source/DataSources/PolygonGraphics.js b/Source/DataSources/PolygonGraphics.js index cdd0b39f6bfa..66e37aa5c37a 100644 --- a/Source/DataSources/PolygonGraphics.js +++ b/Source/DataSources/PolygonGraphics.js @@ -41,6 +41,7 @@ define([ * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open. * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polygon casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polygon will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo} @@ -77,6 +78,8 @@ define([ this._closeBottomSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } @@ -213,7 +216,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polygon will be displayed. + * @memberof BillboardGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -241,6 +251,7 @@ define([ result.closeTop = this.closeTop; result.closeBottom = this.closeBottom; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -272,6 +283,7 @@ define([ this.closeTop = defaultValue(this.closeTop, source.closeTop); this.closeBottom = defaultValue(this.closeBottom, source.closeBottom); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return PolygonGraphics; diff --git a/Source/DataSources/PolylineGeometryUpdater.js b/Source/DataSources/PolylineGeometryUpdater.js index 174248e04ba7..f0b600e34dc2 100644 --- a/Source/DataSources/PolylineGeometryUpdater.js +++ b/Source/DataSources/PolylineGeometryUpdater.js @@ -8,6 +8,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Ellipsoid', '../Core/Event', '../Core/GeometryInstance', @@ -33,6 +35,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Ellipsoid, Event, GeometryInstance, @@ -57,6 +61,7 @@ define([ var defaultMaterial = new ColorMaterialProperty(Color.WHITE); var defaultShow = new ConstantProperty(true); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); function GeometryOptions(entity) { this.id = entity; @@ -95,6 +100,7 @@ define([ this._showProperty = undefined; this._materialProperty = undefined; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined); } @@ -210,6 +216,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof PolylineGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -295,6 +313,8 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; @@ -304,11 +324,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -384,6 +406,7 @@ define([ this._materialProperty = material; this._showProperty = defaultValue(show, defaultShow); this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition); this._fillEnabled = true; var width = polyline.width; @@ -504,6 +527,7 @@ define([ line.positions = positions.slice(); line.material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, line.material); line.width = Property.getValueOrDefault(polyline._width, time, 1); + line.distanceDisplayCondition = Property.getValueOrUndefined(polyline._distanceDisplayCondition, time, line.distanceDisplayCondition); }; DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { diff --git a/Source/DataSources/PolylineGraphics.js b/Source/DataSources/PolylineGraphics.js index c1416aed104f..4b22a4a9602c 100644 --- a/Source/DataSources/PolylineGraphics.js +++ b/Source/DataSources/PolylineGraphics.js @@ -33,6 +33,7 @@ define([ * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo} @@ -53,6 +54,8 @@ define([ this._widthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -128,7 +131,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polyline will be displayed. + * @memberof PolylineGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -148,6 +158,7 @@ define([ result.followSurface = this.followSurface; result.granularity = this.granularity; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -171,6 +182,7 @@ define([ this.followSurface = defaultValue(this.followSurface, source.followSurface); this.granularity = defaultValue(this.granularity, source.granularity); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return PolylineGraphics; diff --git a/Source/DataSources/PolylineVolumeGeometryUpdater.js b/Source/DataSources/PolylineVolumeGeometryUpdater.js index 9252f9374c6f..0bff5eeba316 100644 --- a/Source/DataSources/PolylineVolumeGeometryUpdater.js +++ b/Source/DataSources/PolylineVolumeGeometryUpdater.js @@ -7,6 +7,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -30,6 +32,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -53,6 +57,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -97,6 +102,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'polylineVolume', entity.polylineVolume, undefined); } @@ -237,6 +243,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof PolylineVolumeGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -325,6 +343,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -333,11 +353,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, }; } @@ -370,13 +392,15 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, geometry : new PolylineVolumeOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -455,6 +479,7 @@ define([ this._showOutlineProperty = defaultValue(polylineVolume.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(polylineVolume.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(polylineVolume.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(polylineVolume.distanceDisplayCondition, defaultDistanceDisplayCondition); var granularity = polylineVolume.granularity; var outlineWidth = polylineVolume.outlineWidth; diff --git a/Source/DataSources/PolylineVolumeGraphics.js b/Source/DataSources/PolylineVolumeGraphics.js index 824a97a9b7ea..5004f3f5f131 100644 --- a/Source/DataSources/PolylineVolumeGraphics.js +++ b/Source/DataSources/PolylineVolumeGraphics.js @@ -36,6 +36,7 @@ define([ * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the volume casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this volume will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo} @@ -63,6 +64,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubsription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -167,7 +170,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this volume will be displayed. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -191,6 +201,7 @@ define([ result.outlineWidth = this.outlineWidth; result.cornerType = this.cornerType; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -218,6 +229,7 @@ define([ this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.cornerType = defaultValue(this.cornerType, source.cornerType); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return PolylineVolumeGraphics; diff --git a/Source/DataSources/RectangleGeometryUpdater.js b/Source/DataSources/RectangleGeometryUpdater.js index 27a25798c18c..24ca7ac61df3 100644 --- a/Source/DataSources/RectangleGeometryUpdater.js +++ b/Source/DataSources/RectangleGeometryUpdater.js @@ -7,6 +7,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -32,6 +34,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -57,6 +61,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -106,6 +111,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._onTerrain = false; this._options = new GeometryOptions(entity); @@ -248,6 +254,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof RectangleGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -350,6 +368,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -358,11 +378,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute }; } @@ -395,13 +417,15 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, geometry : new RectangleOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -480,6 +504,7 @@ define([ this._showOutlineProperty = defaultValue(rectangle.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(rectangle.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(rectangle.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(rectangle.distanceDisplayCondition, defaultDistanceDisplayCondition); var height = rectangle.height; var extrudedHeight = rectangle.extrudedHeight; diff --git a/Source/DataSources/RectangleGraphics.js b/Source/DataSources/RectangleGraphics.js index f0e22a2029e4..d2aa92cee27d 100644 --- a/Source/DataSources/RectangleGraphics.js +++ b/Source/DataSources/RectangleGraphics.js @@ -41,6 +41,7 @@ define([ * @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the rectangle texture counter-clockwise from north. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the rectangle. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the rectangle casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this rectangle will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo} @@ -76,6 +77,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distancedisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -213,7 +216,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this rectangle will be displayed. + * @memberof RectangleGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -241,6 +251,7 @@ define([ result.closeTop = this.closeTop; result.closeBottom = this.closeBottom; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -272,6 +283,7 @@ define([ this.closeTop = defaultValue(this.closeTop, source.closeTop); this.closeBottom = defaultValue(this.closeBottom, source.closeBottom); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return RectangleGraphics; diff --git a/Source/DataSources/WallGeometryUpdater.js b/Source/DataSources/WallGeometryUpdater.js index 27cd6ed42d6b..f42c9d84282e 100644 --- a/Source/DataSources/WallGeometryUpdater.js +++ b/Source/DataSources/WallGeometryUpdater.js @@ -7,6 +7,8 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', '../Core/Iso8601', @@ -30,6 +32,8 @@ define([ defineProperties, destroyObject, DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, Iso8601, @@ -53,6 +57,7 @@ define([ var defaultOutline = new ConstantProperty(false); var defaultOutlineColor = new ConstantProperty(Color.BLACK); var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var scratchColor = new Color(); function GeometryOptions(entity) { @@ -97,6 +102,7 @@ define([ this._outlineColorProperty = undefined; this._outlineWidth = 1.0; this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'wall', entity.wall, undefined); } @@ -237,6 +243,18 @@ define([ return this._shadowsProperty; } }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof WallGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, /** * Gets a value indicating if the geometry is time-varying. * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} @@ -327,6 +345,8 @@ define([ var color; var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); if (this._materialProperty instanceof ColorMaterialProperty) { var currentColor = Color.WHITE; if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { @@ -335,11 +355,13 @@ define([ color = ColorGeometryInstanceAttribute.fromColor(currentColor); attributes = { show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, color : color }; } else { attributes = { - show : show + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, }; } @@ -372,13 +394,15 @@ define([ var entity = this._entity; var isAvailable = entity.isAvailable(time); var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); return new GeometryInstance({ id : entity, geometry : new WallOutlineGeometry(this._options), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), - color : ColorGeometryInstanceAttribute.fromColor(outlineColor) + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) } }); }; @@ -457,6 +481,7 @@ define([ this._showOutlineProperty = defaultValue(wall.outline, defaultOutline); this._outlineColorProperty = outlineEnabled ? defaultValue(wall.outlineColor, defaultOutlineColor) : undefined; this._shadowsProperty = defaultValue(wall.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(wall.distanceDisplayCondition, defaultDistanceDisplayCondition); var minimumHeights = wall.minimumHeights; var maximumHeights = wall.maximumHeights; diff --git a/Source/DataSources/WallGraphics.js b/Source/DataSources/WallGraphics.js index f0a8e25f89b3..8887df71de0f 100644 --- a/Source/DataSources/WallGraphics.js +++ b/Source/DataSources/WallGraphics.js @@ -36,6 +36,7 @@ define([ * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the wall casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this wall will be displayed. * * @see Entity * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo} @@ -63,6 +64,8 @@ define([ this._outlineWidthSubscription = undefined; this._shadows = undefined; this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -168,7 +171,14 @@ define([ * @type {Property} * @default ShadowMode.DISABLED */ - shadows : createPropertyDescriptor('shadows') + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this wall will be displayed. + * @memberof WallGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') }); /** @@ -192,6 +202,7 @@ define([ result.outlineColor = this.outlineColor; result.outlineWidth = this.outlineWidth; result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; return result; }; @@ -219,6 +230,7 @@ define([ this.outlineColor = defaultValue(this.outlineColor, source.outlineColor); this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); }; return WallGraphics; diff --git a/Source/Scene/BatchTable.js b/Source/Scene/BatchTable.js new file mode 100644 index 000000000000..2b81160f0fac --- /dev/null +++ b/Source/Scene/BatchTable.js @@ -0,0 +1,478 @@ +/*global define*/ +define([ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/PixelFormat', + '../Core/RuntimeError', + '../Renderer/ContextLimits', + '../Renderer/PixelDatatype', + '../Renderer/Sampler', + '../Renderer/Texture', + '../Renderer/TextureMagnificationFilter', + '../Renderer/TextureMinificationFilter' + ], function( + Cartesian2, + Cartesian3, + Cartesian4, + combine, + ComponentDatatype, + defined, + defineProperties, + destroyObject, + DeveloperError, + PixelFormat, + RuntimeError, + ContextLimits, + PixelDatatype, + Sampler, + Texture, + TextureMagnificationFilter, + TextureMinificationFilter) { + 'use strict'; + + /** + * Creates a texture to look up per instance attributes for batched primitives. For example, store each primitive's pick color in the texture. + * + * @alias BatchTable + * @constructor + * @private + * + * @param {Object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name + * to retrieve the value in the vertex shader. + * @param {Number} numberOfInstances The number of instances in a batch table. + * + * @example + * // create the batch table + * var attributes = [{ + * functionName : 'getShow', + * componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * componentsPerAttribute : 1 + * }, { + * functionName : 'getPickColor', + * componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + * componentsPerAttribute : 4, + * normalize : true + * }]; + * var batchTable = new BatchTable(attributes, 5); + * + * // when creating the draw commands, update the uniform map and the vertex shader + * vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource); + * var shaderProgram = ShaderProgram.fromCache({ + * // ... + * vertexShaderSource : vertexShaderSource, + * }); + * + * drawCommand.shaderProgram = shaderProgram; + * drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap); + * + * // use the attribute function names in the shader to retrieve the instance values + * // ... + * attribute float batchId; + * + * void main() { + * // ... + * float show = getShow(batchId); + * vec3 pickColor = getPickColor(batchId); + * // ... + * } + */ + function BatchTable(attributes, numberOfInstances) { + //>>includeStart('debug', pragmas.debug); + if (!defined(attributes)) { + throw new DeveloperError('attributes is required'); + } + if (!defined(numberOfInstances)) { + throw new DeveloperError('numberOfInstances is required'); + } + //>>includeEnd('debug'); + + this._attributes = attributes; + this._numberOfInstances = numberOfInstances; + + var pixelDatatype = getDatatype(attributes); + + var numberOfAttributes = attributes.length; + var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / numberOfAttributes); + + var instancesPerWidth = Math.min(numberOfInstances, maxNumberOfInstancesPerRow); + var width = numberOfAttributes * instancesPerWidth; + var height = Math.ceil(numberOfInstances / instancesPerWidth); + + var stepX = 1.0 / width; + var centerX = stepX * 0.5; + var stepY = 1.0 / height; + var centerY = stepY * 0.5; + + this._textureDimensions = new Cartesian2(width, height); + this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY); + this._pixelDatatype = pixelDatatype; + this._texture = undefined; + + var batchLength = width * height * 4; + this._batchValues = pixelDatatype === PixelDatatype.FLOAT ? new Float32Array(batchLength) : new Uint8Array(batchLength); + this._batchValuesDirty = false; + } + + defineProperties(BatchTable.prototype, { + /** + * The attribute descriptions. + * @memberOf BatchTable.prototype + * @type {Object[]} + * @readonly + */ + attributes : { + get : function() { + return this._attributes; + } + }, + /** + * The number of instances. + * @memberOf BatchTable.prototype + * @type {Number} + * @readonly + */ + numberOfInstances : { + get : function () { + return this._numberOfInstances; + } + } + }); + + function getDatatype(attributes) { + var foundFloatDatatype = false; + var length = attributes.length; + for (var i = 0; i < length; ++i) { + if (attributes[i].componentDatatype !== ComponentDatatype.UNSIGNED_BYTE) { + foundFloatDatatype = true; + break; + } + } + return foundFloatDatatype ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE; + } + + function getAttributeType(attributes, attributeIndex) { + var componentsPerAttribute = attributes[attributeIndex].componentsPerAttribute; + if (componentsPerAttribute === 2) { + return Cartesian2; + } else if (componentsPerAttribute === 3) { + return Cartesian3; + } else if (componentsPerAttribute === 4) { + return Cartesian4; + } + return Number; + } + + var scratchGetAttributeCartesian4 = new Cartesian4(); + + /** + * Gets the value of an attribute in the table. + * + * @param {Number} instanceIndex The index of the instance. + * @param {Number} attributeIndex The index of the attribute. + * @param {undefined|Cartesian2|Cartesian3|Cartesian4} [result] The object onto which to store the result. The type is dependent on the attribute's number of components. + * @returns {Number|Cartesian2|Cartesian3|Cartesian4} The attribute value stored for the instance. + * + * @exception {DeveloperError} instanceIndex is out of range. + * @exception {DeveloperError} attributeIndex is out of range. + */ + BatchTable.prototype.getBatchedAttribute = function(instanceIndex, attributeIndex, result) { + //>>includeStart('debug', pragmas.debug); + if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) { + throw new DeveloperError('instanceIndex is out of range.'); + } + if (attributeIndex < 0 || attributeIndex >= this._attributes.length) { + throw new DeveloperError('attributeIndex is out of range'); + } + //>>includeEnd('debug'); + + var attributes = this._attributes; + var index = 4 * attributes.length * instanceIndex + 4 * attributeIndex; + var value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4); + + var attributeType = getAttributeType(attributes, attributeIndex); + if (defined(attributeType.fromCartesian4)) { + return attributeType.fromCartesian4(value, result); + } else if (defined(attributeType.clone)) { + return attributeType.clone(value, result); + } + + return value.x; + }; + + var setAttributeScratchValues = [undefined, undefined, new Cartesian2(), new Cartesian3(), new Cartesian4()]; + var setAttributeScratchCartesian4 = new Cartesian4(); + + /** + * Sets the value of an attribute in the table. + * + * @param {Number} instanceIndex The index of the instance. + * @param {Number} attributeIndex The index of the attribute. + * @param {Number|Cartesian2|Cartesian3|Cartesian4} value The value to be stored in the table. The type of value will depend on the number of components of the attribute. + * + * @exception {DeveloperError} instanceIndex is out of range. + * @exception {DeveloperError} attributeIndex is out of range. + */ + BatchTable.prototype.setBatchedAttribute = function(instanceIndex, attributeIndex, value) { + //>>includeStart('debug', pragmas.debug); + if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) { + throw new DeveloperError('instanceIndex is out of range.'); + } + if (attributeIndex < 0 || attributeIndex >= this._attributes.length) { + throw new DeveloperError('attributeIndex is out of range'); + } + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + var attributes = this._attributes; + var result = setAttributeScratchValues[attributes[attributeIndex].componentsPerAttribute]; + var currentAttribute = this.getBatchedAttribute(instanceIndex, attributeIndex, result); + var attributeType = getAttributeType(this._attributes, attributeIndex); + var entriesEqual = defined(attributeType.equals) ? attributeType.equals(currentAttribute, value) : currentAttribute === value; + if (entriesEqual) { + return; + } + + var attributeValue = setAttributeScratchCartesian4; + attributeValue.x = defined(value.x) ? value.x : value; + attributeValue.y = defined(value.y) ? value.y : 0.0; + attributeValue.z = defined(value.z) ? value.z : 0.0; + attributeValue.w = defined(value.w) ? value.w : 0.0; + + var index = 4 * attributes.length * instanceIndex + 4 * attributeIndex; + Cartesian4.pack(attributeValue, this._batchValues, index); + + this._batchValuesDirty = true; + }; + + function createTexture(batchTable, context) { + var dimensions = batchTable._textureDimensions; + batchTable._texture = new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : batchTable._pixelDatatype, + width : dimensions.x, + height : dimensions.y, + sampler : new Sampler({ + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }) + }); + } + + function updateTexture(batchTable) { + var dimensions = batchTable._textureDimensions; + batchTable._texture.copyFrom({ + width : dimensions.x, + height : dimensions.y, + arrayBufferView : batchTable._batchValues + }); + } + + /** + * Creates/updates the batch table texture. + * @param {FrameState} frameState The frame state. + * + * @exception {RuntimeError} The floating point texture extension is required but not supported. + */ + BatchTable.prototype.update = function(frameState) { + var context = frameState.context; + if (this._pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) { + // We could probably pack the floats to RGBA unsigned bytes but that would add a lot CPU and memory overhead. + throw new RuntimeError('The floating point texture extension is required but not supported.'); + } + + if (defined(this._texture) && !this._batchValuesDirty) { + return; + } + + this._batchValuesDirty = false; + + if (!defined(this._texture)) { + createTexture(this, context); + } + updateTexture(this); + }; + + /** + * Gets a function that will update a uniform map to contain values for looking up values in the batch table. + * + * @returns {BatchTable~updateUniformMapCallback} A callback for updating uniform maps. + */ + BatchTable.prototype.getUniformMapCallback = function() { + var that = this; + return function(uniformMap) { + var batchUniformMap = { + batchTexture : function() { + return that._texture; + }, + batchTextureDimensions : function() { + return that._textureDimensions; + }, + batchTextureStep : function() { + return that._textureStep; + } + }; + + return combine(uniformMap, batchUniformMap); + }; + }; + + function getGlslComputeSt(batchTable) { + var numberOfAttributes = batchTable._attributes.length; + + // GLSL batchId is zero-based: [0, numberOfInstances - 1] + if (batchTable._textureDimensions.y === 1) { + return 'uniform vec4 batchTextureStep; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = batchTextureStep.x; \n' + + ' float centerX = batchTextureStep.y; \n' + + ' float numberOfAttributes = float('+ numberOfAttributes + '); \n' + + ' return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n' + + '} \n'; + } + + return 'uniform vec4 batchTextureStep; \n' + + 'uniform vec2 batchTextureDimensions; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = batchTextureStep.x; \n' + + ' float centerX = batchTextureStep.y; \n' + + ' float stepY = batchTextureStep.z; \n' + + ' float centerY = batchTextureStep.w; \n' + + ' float numberOfAttributes = float('+ numberOfAttributes + '); \n' + + ' float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n' + + ' float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n' + + ' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' + + '} \n'; + } + + function getComponentType(componentsPerAttribute) { + if (componentsPerAttribute === 1) { + return 'float'; + } + return 'vec' + componentsPerAttribute; + } + + function getComponentSwizzle(componentsPerAttribute) { + if (componentsPerAttribute === 1) { + return '.x'; + } else if (componentsPerAttribute === 2) { + return '.xy'; + } else if (componentsPerAttribute === 3) { + return '.xyz'; + } + return ''; + } + + function getGlslAttributeFunction(batchTable, attributeIndex) { + var attributes = batchTable._attributes; + var attribute = attributes[attributeIndex]; + var componentsPerAttribute = attribute.componentsPerAttribute; + var functionName = attribute.functionName; + var functionReturnType = getComponentType(componentsPerAttribute); + var functionReturnValue = getComponentSwizzle(componentsPerAttribute); + + var glslFunction = + functionReturnType + ' ' + functionName + '(float batchId) \n' + + '{ \n' + + ' vec2 st = computeSt(batchId); \n' + + ' st.x += batchTextureStep.x * float(' + attributeIndex + '); \n' + + ' vec4 textureValue = texture2D(batchTexture, st); \n' + + ' ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n'; + + if (batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE && !attribute.normalize) { + glslFunction += 'value *= 255.0; \n'; + } else if (batchTable._pixelDatatype === PixelDatatype.FLOAT && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && attribute.normalize) { + glslFunction += 'value /= 255.0; \n'; + } + + glslFunction += + ' return value; \n' + + '} \n'; + return glslFunction; + } + + /** + * Gets a function that will update a vertex shader to contain functions for looking up values in the batch table. + * + * @returns {BatchTable~updateVertexShaderSourceCallback} A callback for updating a vertex shader source. + */ + BatchTable.prototype.getVertexShaderCallback = function() { + var batchTableShader = 'uniform sampler2D batchTexture; \n'; + batchTableShader += getGlslComputeSt(this) + '\n'; + + var attributes = this._attributes; + var length = attributes.length; + for (var i = 0; i < length; ++i) { + batchTableShader += getGlslAttributeFunction(this, i); + } + + return function(source) { + var mainIndex = source.indexOf('void main'); + var beforeMain = source.substring(0, mainIndex); + var afterMain = source.substring(mainIndex); + return beforeMain + '\n' + batchTableShader + '\n' + afterMain; + }; + }; + + /** + * 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. + * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see BatchTable#destroy + */ + BatchTable.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. + * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @see BatchTable#isDestroyed + */ + BatchTable.prototype.destroy = function() { + this._texture = this._texture && this._texture.destroy(); + return destroyObject(this); + }; + + /** + * A callback for updating uniform maps. + * @callback BatchTable~updateUniformMapCallback + * + * @param {Object} uniformMap The uniform map. + * @returns {Object} The new uniform map with properties for retrieving values from the batch table. + */ + + /** + * A callback for updating a vertex shader source. + * @callback BatchTable~updateVertexShaderSourceCallback + * + * @param {String} vertexShaderSource The vertex shader source. + * @returns {String} The new vertex shader source with the functions for retrieving batch table values injected. + */ + + return BatchTable; +}); \ No newline at end of file diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js index e875a1dd68fa..814aa5fcbd3d 100644 --- a/Source/Scene/Billboard.js +++ b/Source/Scene/Billboard.js @@ -11,6 +11,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/Matrix4', '../Core/NearFarScalar', './HeightReference', @@ -30,6 +31,7 @@ define([ defined, defineProperties, DeveloperError, + DistanceDisplayCondition, Matrix4, NearFarScalar, HeightReference, @@ -61,6 +63,7 @@ define([ * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near + * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near * * @see BillboardCollection * @see BillboardCollection#add @@ -83,6 +86,9 @@ define([ if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) { throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); } + if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); + } //>>includeEnd('debug'); this._show = defaultValue(options.show, true); @@ -104,6 +110,7 @@ define([ this._translucencyByDistance = options.translucencyByDistance; this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; this._sizeInMeters = defaultValue(options.sizeInMeters, false); + this._distanceDisplayCondition = options.distanceDisplayCondition; this._id = options.id; this._collection = defaultValue(options.collection, billboardCollection); @@ -151,6 +158,8 @@ define([ this._removeCallbackFunc = undefined; this._mode = SceneMode.SCENE3D; + this._clusterShow = true; + this._updateClamping(); } @@ -168,7 +177,8 @@ define([ var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX = 11; var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX = 12; var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = 13; - Billboard.NUMBER_OF_PROPERTIES = 14; + var DISTANCE_DISPLAY_CONDITION = Billboard.DISTANCE_DISPLAY_CONDITION = 14; + Billboard.NUMBER_OF_PROPERTIES = 15; function makeDirty(billboard, propertyChanged) { var billboardCollection = billboard._billboardCollection; @@ -714,6 +724,29 @@ define([ } }, + /** + * Gets or sets the condition specifying at what distance from the camera that this billboard will be displayed. + * @memberof Billboard.prototype + * @type {DistanceDisplayCondition} + * @default undefined + */ + distanceDisplayCondition : { + get : function() { + return this._distanceDisplayCondition; + }, + set : function(value) { + if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far <= value.near) { + throw new DeveloperError('far distance must be greater than near distance.'); + } + //>>includeEnd('debug'); + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION); + } + } + }, + /** * Gets or sets the user-defined object returned when the billboard is picked. * @memberof Billboard.prototype @@ -821,6 +854,24 @@ define([ this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition); makeDirty(this, POSITION_INDEX); } + }, + + /** + * Determines whether or not this billboard will be shown or hidden because it was clustered. + * @memberof Billboard.prototype + * @type {Boolean} + * @private + */ + clusterShow : { + get : function() { + return this._clusterShow; + }, + set : function(value) { + if (this._clusterShow !== value) { + this._clusterShow = value; + makeDirty(this, SHOW_INDEX); + } + } } }); @@ -1092,6 +1143,9 @@ define([ // World to window coordinates var positionWC = SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, positionWorld, eyeOffset, result); + if (!defined(positionWC)) { + return undefined; + } // Apply pixel offset pixelOffset = Cartesian2.clone(pixelOffset, scratchComputePixelOffset); @@ -1148,6 +1202,49 @@ define([ return windowCoordinates; }; + /** + * Gets a billboard's screen space bounding box centered around screenSpacePosition. + * @param {Billboard} billboard The billboard to get the screen space bounding box for. + * @param {Cartesian2} screenSpacePosition The screen space center of the label. + * @param {BoundingRectangle} [result] The object onto which to store the result. + * @returns {BoundingRectangle} The screen space bounding box. + * + * @private + */ + Billboard.getScreenSpaceBoundingBox = function(billboard, screenSpacePosition, result) { + var width = billboard.width; + var height = billboard.height; + + var scale = billboard.scale; + width *= scale; + height *= scale; + + var x = screenSpacePosition.x; + if (billboard.horizontalOrigin === HorizontalOrigin.RIGHT) { + x -= width; + } else if (billboard.horizontalOrigin === HorizontalOrigin.CENTER) { + x -= width * 0.5; + } + + var y = screenSpacePosition.y; + if (billboard.verticalOrigin === VerticalOrigin.TOP) { + y -= height; + } else if (billboard.verticalOrigin === VerticalOrigin.CENTER) { + y -= height * 0.5; + } + + if (!defined(result)) { + result = new BoundingRectangle(); + } + + result.x = x; + result.y = y; + result.width = width; + result.height = height; + + return result; + }; + /** * Determines if this billboard equals another billboard. Billboards are equal if all their properties * are equal. Billboards in different collections can be equal. @@ -1173,7 +1270,8 @@ define([ Cartesian3.equals(this._eyeOffset, other._eyeOffset) && NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) && NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) && - NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance); + NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) && + DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition); }; Billboard.prototype._destroy = function() { diff --git a/Source/Scene/BillboardCollection.js b/Source/Scene/BillboardCollection.js index f31b0cde817e..78836da2f652 100644 --- a/Source/Scene/BillboardCollection.js +++ b/Source/Scene/BillboardCollection.js @@ -81,6 +81,7 @@ define([ var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX; var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX; var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX; + var DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION_INDEX; var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES; var attributeLocations; @@ -93,7 +94,8 @@ define([ compressedAttribute2 : 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free eyeOffset : 5, // 4 bytes free scaleByDistance : 6, - pixelOffsetScaleByDistance : 7 + pixelOffsetScaleByDistance : 7, + distanceDisplayCondition : 8 }; var attributeLocationsInstanced = { @@ -105,7 +107,8 @@ define([ compressedAttribute2 : 5, eyeOffset : 6, // texture range in w scaleByDistance : 7, - pixelOffsetScaleByDistance : 8 + pixelOffsetScaleByDistance : 8, + distanceDisplayCondition : 9 }; /** @@ -193,6 +196,10 @@ define([ this._compiledShaderPixelOffsetScaleByDistance = false; this._compiledShaderPixelOffsetScaleByDistancePick = false; + this._shaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayConditionPick = false; + this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); this._maxSize = 0.0; @@ -276,7 +283,8 @@ define([ BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX - BufferUsage.STATIC_DRAW // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX + BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX + BufferUsage.STATIC_DRAW // DISTANCE_DISPLAY_CONDITION_INDEX ]; var that = this; @@ -689,6 +697,11 @@ define([ componentsPerAttribute : 4, componentDatatype : ComponentDatatype.FLOAT, usage : buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX] + }, { + index : attributeLocations.distanceDisplayCondition, + componentsPerAttribute : 2, + componentDatatype : ComponentDatatype.FLOAT, + usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX] }]; // Instancing requires one non-instanced attribute. @@ -790,7 +803,7 @@ define([ var horizontalOrigin = billboard.horizontalOrigin; var verticalOrigin = billboard._verticalOrigin; - var show = billboard.show; + var show = billboard.show && billboard.clusterShow; // If the color alpha is zero, do not show this billboard. This lets us avoid providing // color during the pick pass and also eliminates a discard in the fragment shader. @@ -1097,6 +1110,32 @@ define([ } } + function writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { + var i; + var writer = vafWriters[attributeLocations.distanceDisplayCondition]; + var near = 0.0; + var far = Number.MAX_VALUE; + + var distanceDisplayCondition = billboard.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + near = distanceDisplayCondition.near; + far = distanceDisplayCondition.far; + + billboardCollection._shaderDistanceDisplayCondition = true; + } + + if (billboardCollection._instanced) { + i = billboard._index; + writer(i, near, far); + } else { + i = billboard._index * 4; + writer(i + 0, near, far); + writer(i + 1, near, far); + writer(i + 2, near, far); + writer(i + 3, near, far); + } + } + function writeBillboard(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); @@ -1105,6 +1144,7 @@ define([ writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); + writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); } function recomputeActualPositions(billboardCollection, billboards, length, frameState, modelMatrix, recomputeBoundingVolume) { @@ -1300,6 +1340,10 @@ define([ writers.push(writePixelOffsetScaleByDistance); } + if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) { + writers.push(writeDistanceDisplayCondition); + } + var numWriters = writers.length; vafWriters = this._vaf.writers; @@ -1391,7 +1435,8 @@ define([ (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) || (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) || (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) || - (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance)) { + (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) || + (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition)) { vs = new ShaderSource({ sources : [BillboardCollectionVS] @@ -1414,6 +1459,9 @@ define([ if (this._shaderPixelOffsetScaleByDistance) { vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } this._sp = ShaderProgram.replaceCache({ context : context, @@ -1428,6 +1476,7 @@ define([ this._compiledShaderScaleByDistance = this._shaderScaleByDistance; this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; } va = this._vaf.va; @@ -1469,7 +1518,8 @@ define([ (this._shaderAlignedAxis !== this._compiledShaderAlignedAxisPick) || (this._shaderScaleByDistance !== this._compiledShaderScaleByDistancePick) || (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistancePick) || - (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick)) { + (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick) || + (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayConditionPick)) { vs = new ShaderSource({ defines : ['RENDER_FOR_PICK'], @@ -1494,6 +1544,9 @@ define([ if (this._shaderPixelOffsetScaleByDistance) { vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } fs = new ShaderSource({ defines : ['RENDER_FOR_PICK'], @@ -1512,6 +1565,7 @@ define([ this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance; + this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; } va = this._vaf.va; diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index cf98c7a7be1d..fb2ea3096e40 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -117,17 +117,13 @@ define([ /** * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider. - * By default, this is set to 1.3 for the "aerial" and "aerial with labels" map styles and 1.0 for - * all others. Changing this value after creating an {@link ImageryLayer} for this provider will have + * Changing this value after creating an {@link ImageryLayer} for this provider will have * no effect. Instead, set the layer's {@link ImageryLayer#gamma} property. * * @type {Number} * @default 1.0 */ this.defaultGamma = 1.0; - if (this._mapStyle === BingMapsStyle.AERIAL || this._mapStyle === BingMapsStyle.AERIAL_WITH_LABELS) { - this.defaultGamma = 1.3; - } this._tilingScheme = new WebMercatorTilingScheme({ numberOfLevelZeroTilesX : 2, diff --git a/Source/Scene/DebugAppearance.js b/Source/Scene/DebugAppearance.js index 5de436b25ac4..70047a7fd72a 100644 --- a/Source/Scene/DebugAppearance.js +++ b/Source/Scene/DebugAppearance.js @@ -26,6 +26,7 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.attributeName The name of the attribute to visualize. + * @param {Boolean} options.perInstanceAttribute Boolean that determines whether this attribute is a per-instance geometry attribute. * @param {String} [options.glslDatatype='vec3'] The GLSL datatype of the attribute. Supported datatypes are float, vec2, vec3, and vec4. * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader. * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader. @@ -44,11 +45,15 @@ define([ function DebugAppearance(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var attributeName = options.attributeName; + var perInstanceAttribute = options.perInstanceAttribute; //>>includeStart('debug', pragmas.debug); if (!defined(attributeName)) { throw new DeveloperError('options.attributeName is required.'); } + if (!defined(perInstanceAttribute)) { + throw new DeveloperError('options.perInstanceAttribute is required.'); + } //>>includeEnd('debug'); var glslDatatype = defaultValue(options.glslDatatype, 'vec3'); @@ -85,12 +90,15 @@ define([ var vs = 'attribute vec3 position3DHigh;\n' + 'attribute vec3 position3DLow;\n' + - 'attribute ' + glslDatatype + ' ' + attributeName + ';\n' + + 'attribute float batchId;\n' + + (perInstanceAttribute ? '' : 'attribute ' + glslDatatype + ' ' + attributeName + ';\n') + 'varying ' + glslDatatype + ' ' + varyingName + ';\n' + 'void main()\n' + '{\n' + 'vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' + - varyingName + ' = ' + attributeName + ';\n' + + (perInstanceAttribute ? + varyingName + ' = czm_batchTable_' + attributeName + '(batchId);\n' : + varyingName + ' = ' + attributeName + ';\n') + 'gl_Position = czm_modelViewProjectionRelativeToEye * p;\n' + '}'; var fs = diff --git a/Source/Scene/DebugCameraPrimitive.js b/Source/Scene/DebugCameraPrimitive.js new file mode 100644 index 000000000000..12df3e1ddb1b --- /dev/null +++ b/Source/Scene/DebugCameraPrimitive.js @@ -0,0 +1,247 @@ +/*global define*/ +define([ + '../Core/BoundingSphere', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/Geometry', + '../Core/GeometryAttribute', + '../Core/GeometryAttributes', + '../Core/GeometryInstance', + '../Core/Matrix4', + '../Core/PrimitiveType', + './PerInstanceColorAppearance', + './Primitive' + ], function( + BoundingSphere, + Cartesian3, + Cartesian4, + Color, + ColorGeometryInstanceAttribute, + ComponentDatatype, + defaultValue, + defined, + destroyObject, + DeveloperError, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryInstance, + Matrix4, + PrimitiveType, + PerInstanceColorAppearance, + Primitive) { + 'use strict'; + + /** + * Draws the outline of the camera's view frustum. + * + * @alias DebugCameraPrimitive + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {Camera} options.camera The camera. + * @param {Color} [options.color=Color.CYAN] The color of the debug outline. + * @param {Boolean} [options.updateOnChange=true] Whether the primitive updates when the underlying camera changes. + * @param {Boolean} [options.show=true] Determines if this primitive will be shown. + * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}. + * + * @example + * primitives.add(new Cesium.DebugCameraPrimitive({ + * camera : camera, + * color : Cesium.Color.YELLOW + * })); + */ + function DebugCameraPrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.camera)) { + throw new DeveloperError('options.camera is required.'); + } + //>>includeEnd('debug'); + + this._camera = options.camera; + this._color = defaultValue(options.color, Color.CYAN); + this._updateOnChange = defaultValue(options.updateOnChange, true); + + /** + * Determines if this primitive will be shown. + * + * @type Boolean + * @default true + */ + this.show = defaultValue(options.show, true); + + /** + * User-defined object returned when the primitive is picked. + * + * @type {Object} + * @default undefined + * + * @see Scene#pick + */ + this.id = options.id; + this._id = undefined; + + this._outlinePrimitive = undefined; + this._planesPrimitive = undefined; + } + + var frustumCornersNDC = new Array(8); + frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0); + frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0); + frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0); + frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0); + frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0); + frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0); + frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0); + frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0); + + var scratchMatrix = new Matrix4(); + var scratchFrustumCorners = new Array(8); + for (var i = 0; i < 8; ++i) { + scratchFrustumCorners[i] = new Cartesian4(); + } + + var scratchColor = new Color(); + + /** + * @private + */ + DebugCameraPrimitive.prototype.update = function(frameState) { + if (!this.show) { + return; + } + + if (this._updateOnChange) { + // Recreate the primitive every frame + this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); + this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + } + + if (!defined(this._outlinePrimitive)) { + var view = this._camera.viewMatrix; + var projection = this._camera.frustum.projectionMatrix; + var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); + var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); + + var positions = new Float64Array(8 * 3); + for (var i = 0; i < 8; ++i) { + var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]); + Matrix4.multiplyByVector(inverseViewProjection, corner, corner); + Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide + positions[i * 3] = corner.x; + positions[i * 3 + 1] = corner.y; + positions[i * 3 + 2] = corner.z; + } + + var boundingSphere = new BoundingSphere.fromVertices(positions); + + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + // Create the outline primitive + var outlineIndices = new Uint16Array([0,1,1,2,2,3,3,0,0,4,4,7,7,3,7,6,6,2,2,1,1,5,5,4,5,6]); + + this._outlinePrimitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : { + attributes : attributes, + indices : outlineIndices, + primitiveType : PrimitiveType.LINES, + boundingSphere : boundingSphere + }, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(this._color) + }, + id : this.id, + pickPrimitive : this + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + + // Create the planes primitive + var planesIndices = new Uint16Array([4,5,6,4,6,7,5,1,2,5,2,6,7,6,2,7,2,3,0,1,5,0,5,4,0,4,7,0,7,3,1,0,3,1,3,2]); + + this._planesPrimitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : { + attributes : attributes, + indices : planesIndices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : boundingSphere + }, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(Color.fromAlpha(this._color, 0.1, scratchColor)) + }, + id : this.id, + pickPrimitive : this + }), + appearance : new PerInstanceColorAppearance({ + translucent : true, + flat : true + }), + asynchronous : false + }); + } + + this._outlinePrimitive.update(frameState); + this._planesPrimitive.update(frameState); + }; + + /** + * 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. + *

+ * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see DebugCameraPrimitive#destroy + */ + DebugCameraPrimitive.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. + *

+ * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * p = p && p.destroy(); + * + * @see DebugCameraPrimitive#isDestroyed + */ + DebugCameraPrimitive.prototype.destroy = function() { + this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); + this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + return destroyObject(this); + }; + + return DebugCameraPrimitive; +}); diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index e16b87fba3bc..e84b483997b0 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -7,7 +7,6 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Ellipsoid', @@ -38,7 +37,6 @@ define([ defaultValue, defined, defineProperties, - deprecationWarning, destroyObject, DeveloperError, Ellipsoid, @@ -281,46 +279,6 @@ define([ get: function() { return this._surface.tileLoadProgressEvent; } - }, - - /** - * Determines whether the globe casts shadows from each light source. - * - * @memberof Globe.prototype - * @type {Boolean} - * @deprecated - */ - castShadows : { - get : function() { - deprecationWarning('Globe.castShadows', 'Globe.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Globe.shadows instead.'); - return ShadowMode.castShadows(this.shadows); - }, - set : function(value) { - deprecationWarning('Globe.castShadows', 'Globe.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Globe.shadows instead.'); - var castShadows = value; - var receiveShadows = ShadowMode.receiveShadows(this.shadows); - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); - } - }, - - /** - * Determines whether the globe receives shadows from shadow casters in the scene. - * - * @memberof Globe.prototype - * @type {Boolean} - * @deprecated - */ - receiveShadows : { - get : function() { - deprecationWarning('Globe.receiveShadows', 'Globe.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Globe.shadows instead.'); - return ShadowMode.receiveShadows(this.shadows); - }, - set : function(value) { - deprecationWarning('Globe.receiveShadows', 'Globe.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Globe.shadows instead.'); - var castShadows = ShadowMode.castShadows(this.shadows); - var receiveShadows = value; - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); - } } }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index b60a7d8213df..21faac629474 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -740,8 +740,11 @@ define([ var context = frameState.context; var vs = ShadowVolumeVS; - vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); + vs = primitive._primitive._batchTable.getVertexShaderCallback()(vs); vs = Primitive._appendShowToShader(primitive._primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive._primitive, vs); + vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive._primitive, vs); var fs = ShadowVolumeFS; var attributeLocations = primitive._primitive._attributeLocations; @@ -755,6 +758,9 @@ define([ }); if (primitive._primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + var pickFS = new ShaderSource({ sources : [fs], pickColorQualifier : 'varying' @@ -762,7 +768,7 @@ define([ primitive._spPick = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._spPick, - vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), + vertexShaderSource : vsPick, fragmentShaderSource : pickFS, attributeLocations : attributeLocations }); @@ -782,6 +788,7 @@ define([ colorCommands.length = length; var vaIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); for (var i = 0; i < length; i += 3) { var vertexArray = primitive._va[vaIndex++]; @@ -798,7 +805,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsStencilPreloadPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // stencil depth command @@ -813,7 +820,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsStencilDepthPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // color command @@ -828,7 +835,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsColorPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; } } @@ -840,6 +847,7 @@ define([ pickCommands.length = length; var pickIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); for (var j = 0; j < length; j += 3) { var pickOffset = pickOffsets[pickIndex++]; @@ -862,7 +870,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsStencilPreloadPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // stencil depth command @@ -879,7 +887,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsStencilDepthPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // color command @@ -896,7 +904,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsPickPass; command.shaderProgram = groundPrimitive._spPick; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; } } diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 7bf979ef1936..fbe3b1bc68a3 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1,5 +1,6 @@ /*global define*/ define([ + '../Core/BoundingRectangle', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Color', @@ -7,6 +8,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/NearFarScalar', './Billboard', './HeightReference', @@ -14,6 +16,7 @@ define([ './LabelStyle', './VerticalOrigin' ], function( + BoundingRectangle, Cartesian2, Cartesian3, Color, @@ -21,6 +24,7 @@ define([ defined, defineProperties, DeveloperError, + DistanceDisplayCondition, NearFarScalar, Billboard, HeightReference, @@ -54,6 +58,7 @@ define([ * * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near + * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near * * @see LabelCollection * @see LabelCollection#add @@ -70,6 +75,9 @@ define([ if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) { throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); } + if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); + } //>>includeEnd('debug'); this._text = defaultValue(options.text, ''); @@ -89,6 +97,7 @@ define([ this._translucencyByDistance = options.translucencyByDistance; this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); + this._distanceDisplayCondition = options.distanceDisplayCondition; this._labelCollection = labelCollection; this._glyphs = []; @@ -100,6 +109,8 @@ define([ this._removeCallbackFunc = undefined; this._mode = undefined; + this._clusterShow = true; + this._updateClamping(); } @@ -652,6 +663,36 @@ define([ } }, + /** + * Gets or sets the condition specifying at what distance from the camera that this label will be displayed. + * @memberof Label.prototype + * @type {DistanceDisplayCondition} + * @default undefined + */ + distanceDisplayCondition : { + get : function() { + return this._distanceDisplayCondition; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far <= value.near) { + throw new DeveloperError('far must be greater than near'); + } + //>>includeEnd('debug'); + if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + + var glyphs = this._glyphs; + for (var i = 0, len = glyphs.length; i < len; i++) { + var glyph = glyphs[i]; + if (defined(glyph.billboard)) { + glyph.billboard.distanceDisplayCondition = value; + } + } + } + } + }, + /** * Gets or sets the user-defined object returned when the label is picked. * @memberof Label.prototype @@ -701,6 +742,33 @@ define([ } } } + }, + + /** + * Determines whether or not this label will be shown or hidden because it was clustered. + * @memberof Label.prototype + * @type {Boolean} + * @private + */ + clusterShow : { + get : function() { + return this._clusterShow; + }, + set : function(value) { + if (this._clusterShow !== value) { + this._clusterShow = value; + + var glyphs = this._glyphs; + for (var i = 0, len = glyphs.length; i < len; i++) { + var glyph = glyphs[i]; + if (defined(glyph.billboard)) { + // Set all the private values here, because we already clamped to ground + // so we don't want to do it again for every glyph + glyph.billboard.clusterShow = value; + } + } + } + } } }); @@ -744,6 +812,62 @@ define([ return windowCoordinates; }; + /** + * Gets a label's screen space bounding box centered around screenSpacePosition. + * @param {Label} label The label to get the screen space bounding box for. + * @param {Cartesian2} screenSpacePosition The screen space center of the label. + * @param {BoundingRectangle} [result] The object onto which to store the result. + * @returns {BoundingRectangle} The screen space bounding box. + * + * @private + */ + Label.getScreenSpaceBoundingBox = function(label, screenSpacePosition, result) { + var width = 0; + var height = 0; + + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } + + width += billboard.width; + height = Math.max(height, billboard.height); + } + + var scale = label.scale; + width *= scale; + height *= scale; + + var x = screenSpacePosition.x; + if (label.horizontalOrigin === HorizontalOrigin.RIGHT) { + x -= width; + } else if (label.horizontalOrigin === HorizontalOrigin.CENTER) { + x -= width * 0.5; + } + + var y = screenSpacePosition.y; + if (label.verticalOrigin === VerticalOrigin.TOP) { + y -= height; + } else if (label.verticalOrigin === VerticalOrigin.CENTER) { + y -= height * 0.5; + } + + if (!defined(result)) { + result = new BoundingRectangle(); + } + + result.x = x; + result.y = y; + result.width = width; + result.height = height; + + return result; + }; + /** * Determines if this label equals another label. Labels are equal if all their properties * are equal. Labels in different collections can be equal. @@ -769,6 +893,7 @@ define([ Cartesian3.equals(this._eyeOffset, other._eyeOffset) && NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) && NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) && + DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition) && this._id === other._id; }; diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js index 6b28307d8c66..bbaa96c887a8 100644 --- a/Source/Scene/LabelCollection.js +++ b/Source/Scene/LabelCollection.js @@ -203,6 +203,7 @@ define([ billboard.image = id; billboard.translucencyByDistance = label._translucencyByDistance; billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance; + billboard.distanceDisplayCondition = label._distanceDisplayCondition; } } diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 5644c433eb83..30fae4afc845 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -11,9 +11,9 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/FeatureDetection', '../Core/getAbsoluteUri', '../Core/getBaseUri', @@ -71,9 +71,9 @@ define([ defaultValue, defined, defineProperties, - deprecationWarning, destroyObject, DeveloperError, + DistanceDisplayCondition, FeatureDetection, getAbsoluteUri, getBaseUri, @@ -315,13 +315,12 @@ define([ * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. - * @param {Boolean} [options.castShadows=true] Deprecated, use options.shadows instead. Determines whether the model casts shadows from each light source. - * @param {Boolean} [options.receiveShadows=true] Deprecated, use options.shadows instead. Determines whether the model receives shadows from shadow casters in the scene. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from each light source. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @param {HeightReference} [options.heightReference] Determines how the model is drawn relative to terrain. * @param {Scene} [options.scene] Must be passed in for models that use the height reference property. + * @param {DistanceDisplayCondition} [options.istanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed. * * @exception {DeveloperError} bgltf is not a valid Binary glTF file. * @exception {DeveloperError} Only glTF Binary version 1 is supported. @@ -507,10 +506,6 @@ define([ this._incrementallyLoadTextures = defaultValue(options.incrementallyLoadTextures, true); this._asynchronous = defaultValue(options.asynchronous, true); - // Deprecated options - var castShadows = defaultValue(options.castShadows, true); - var receiveShadows = defaultValue(options.receiveShadows, true); - /** * Determines whether the model casts or receives shadows from each light source. * @@ -518,7 +513,7 @@ define([ * * @default ShadowMode.ENABLED */ - this.shadows = defaultValue(options.shadows, ShadowMode.fromCastReceive(castShadows, receiveShadows)); + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); this._shadows = this.shadows; /** @@ -548,6 +543,8 @@ define([ this.debugWireframe = defaultValue(options.debugWireframe, false); this._debugWireframe = false; + this._distanceDisplayCondition = options.distanceDisplayCondition; + // Undocumented options this._precreatedAttributes = options.precreatedAttributes; this._vertexShaderLoaded = options.vertexShaderLoaded; @@ -870,46 +867,22 @@ define([ }, /** - * Determines whether the model casts shadows from each light source. - * - * @memberof Model.prototype - * - * @type {Boolean} - * - * @deprecated - */ - castShadows : { - get : function() { - deprecationWarning('Model.castShadows', 'Model.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Model.shadows instead.'); - return ShadowMode.castShadows(this.shadows); - }, - set : function(value) { - deprecationWarning('Model.castShadows', 'Model.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Model.shadows instead.'); - var castShadows = value; - var receiveShadows = ShadowMode.receiveShadows(this.shadows); - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); - } - }, - - /** - * Determines whether the model receives shadows from shadow casters in the scene. - * + * Gets or sets the condition specifying at what distance from the camera that this model will be displayed. * @memberof Model.prototype - * - * @type {Boolean} - * - * @deprecated + * @type {DistanceDisplayCondition} + * @default undefined */ - receiveShadows : { + distanceDisplayCondition : { get : function() { - deprecationWarning('Model.receiveShadows', 'Model.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Model.shadows instead.'); - return ShadowMode.receiveShadows(this.shadows); + return this._distanceDisplayCondition; }, set : function(value) { - deprecationWarning('Model.receiveShadows', 'Model.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Model.shadows instead.'); - var castShadows = ShadowMode.castShadows(this.shadows); - var receiveShadows = value; - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far <= value.near) { + throw new DeveloperError('far must be greater than near'); + } + //>>includeEnd('debug'); + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); } } }); @@ -3603,6 +3576,35 @@ define([ } } + var scratchDisplayConditionCartesian = new Cartesian3(); + var scratchDistanceDisplayConditionCartographic = new Cartographic(); + + function distanceDisplayConditionVisible(model, frameState) { + var distance2; + var ddc = model.distanceDisplayCondition; + var nearSquared = ddc.near * ddc.near; + var farSquared = ddc.far * ddc.far; + + if (frameState.mode === SceneMode.SCENE2D) { + var frustum2DWidth = frameState.camera.frustum.right - frameState.camera.frustum.left; + distance2 = frustum2DWidth * 0.5; + distance2 = distance2 * distance2; + } else { + // Distance to center of primitive's reference frame + var position = Matrix4.getTranslation(model.modelMatrix, scratchDisplayConditionCartesian); + if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + var projection = frameState.mapProjection; + var ellipsoid = projection.ellipsoid; + var cartographic = ellipsoid.cartesianToCartographic(position, scratchDistanceDisplayConditionCartographic); + position = projection.project(cartographic, position); + Cartesian3.fromElements(position.z, position.x, position.y, position); + } + distance2 = Cartesian3.distanceSquared(position, frameState.camera.positionWC); + } + + return (distance2 >= nearSquared) && (distance2 <= farSquared); + } + /** * Called when {@link Viewer} or {@link CesiumWidget} render the scene to * get the draw commands needed to render this primitive. @@ -3719,7 +3721,8 @@ define([ } } - var show = this.show && (this.scale !== 0.0); + var displayConditionPassed = defined(this.distanceDisplayCondition) ? distanceDisplayConditionVisible(this, frameState) : true; + var show = this.show && displayConditionPassed && (this.scale !== 0.0); if ((show && this._state === ModelState.LOADED) || justLoaded) { var animated = this.activeAnimations.update(frameState) || this._cesiumAnimationsDirty; diff --git a/Source/Scene/OIT.js b/Source/Scene/OIT.js index b7fd40090c9e..c2f39b15fcbe 100644 --- a/Source/Scene/OIT.js +++ b/Source/Scene/OIT.js @@ -525,6 +525,8 @@ define([ var framebuffer = passState.framebuffer; var length = commands.length; + var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled; + passState.framebuffer = oit._adjustTranslucentFBO; oit._adjustTranslucentCommand.execute(context, passState); passState.framebuffer = oit._adjustAlphaFBO; @@ -535,7 +537,7 @@ define([ for (j = 0; j < length; ++j) { command = commands[j]; - derivedCommand = command.derivedCommands.oit.translucentCommand; + derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -543,7 +545,7 @@ define([ for (j = 0; j < length; ++j) { command = commands[j]; - derivedCommand = command.derivedCommands.oit.alphaCommand; + derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -555,6 +557,8 @@ define([ var framebuffer = passState.framebuffer; var length = commands.length; + var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled; + passState.framebuffer = oit._adjustTranslucentFBO; oit._adjustTranslucentCommand.execute(context, passState); @@ -563,7 +567,7 @@ define([ for (var j = 0; j < length; ++j) { var command = commands[j]; - var derivedCommand = command.derivedCommands.oit.translucentCommand; + var derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } diff --git a/Source/Scene/PointPrimitive.js b/Source/Scene/PointPrimitive.js index bead41db9798..cf2ca7323b17 100644 --- a/Source/Scene/PointPrimitive.js +++ b/Source/Scene/PointPrimitive.js @@ -1,5 +1,6 @@ /*global define*/ define([ + '../Core/BoundingRectangle', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', @@ -8,11 +9,13 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/Matrix4', '../Core/NearFarScalar', './SceneMode', './SceneTransforms' ], function( + BoundingRectangle, Cartesian2, Cartesian3, Cartesian4, @@ -21,6 +24,7 @@ define([ defined, defineProperties, DeveloperError, + DistanceDisplayCondition, Matrix4, NearFarScalar, SceneMode, @@ -43,6 +47,7 @@ define([ * * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near + * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near * * @see PointPrimitiveCollection * @see PointPrimitiveCollection#add @@ -61,6 +66,9 @@ define([ if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) { throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); } + if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); + } //>>includeEnd('debug'); this._show = defaultValue(options.show, true); @@ -72,9 +80,12 @@ define([ this._pixelSize = defaultValue(options.pixelSize, 10.0); this._scaleByDistance = options.scaleByDistance; this._translucencyByDistance = options.translucencyByDistance; + this._distanceDisplayCondition = options.distanceDisplayCondition; this._id = options.id; this._collection = defaultValue(options.collection, pointPrimitiveCollection); + this._clusterShow = true; + this._pickId = undefined; this._pointPrimitiveCollection = pointPrimitiveCollection; this._dirty = false; @@ -89,7 +100,8 @@ define([ var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX = 5; var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6; var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7; - PointPrimitive.NUMBER_OF_PROPERTIES = 8; + var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION = 8; + PointPrimitive.NUMBER_OF_PROPERTIES = 9; function makeDirty(pointPrimitive, propertyChanged) { var pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; @@ -339,6 +351,29 @@ define([ } }, + /** + * Gets or sets the condition specifying at what distance from the camera that this point will be displayed. + * @memberof PointPrimitive.prototype + * @type {DistanceDisplayCondition} + * @default undefined + */ + distanceDisplayCondition : { + get : function() { + return this._distanceDisplayCondition; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far <= value.near) { + throw new DeveloperError('far must be greater than near'); + } + //>>includeEnd('debug'); + if (!DistanceDisplayCondition.equals(this._distanceDisplayCondition, value)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX); + } + } + }, + /** * Gets or sets the user-defined object returned when the point is picked. * @memberof PointPrimitive.prototype @@ -354,6 +389,24 @@ define([ this._pickId.object.id = value; } } + }, + + /** + * Determines whether or not this point will be shown or hidden because it was clustered. + * @memberof PointPrimitive.prototype + * @type {Boolean} + * @private + */ + clusterShow : { + get : function() { + return this._clusterShow; + }, + set : function(value) { + if (this._clusterShow !== value) { + this._clusterShow = value; + makeDirty(this, SHOW_INDEX); + } + } } }); @@ -429,10 +482,44 @@ define([ var modelMatrix = pointPrimitiveCollection.modelMatrix; var windowCoordinates = PointPrimitive._computeScreenSpacePosition(modelMatrix, this._actualPosition, scene, result); + if (!defined(windowCoordinates)) { + return undefined; + } + windowCoordinates.y = scene.canvas.clientHeight - windowCoordinates.y; return windowCoordinates; }; + /** + * Gets a point's screen space bounding box centered around screenSpacePosition. + * @param {PointPrimitive} point The point to get the screen space bounding box for. + * @param {Cartesian2} screenSpacePosition The screen space center of the label. + * @param {BoundingRectangle} [result] The object onto which to store the result. + * @returns {BoundingRectangle} The screen space bounding box. + * + * @private + */ + PointPrimitive.getScreenSpaceBoundingBox = function(point, screenSpacePosition, result) { + var size = point.pixelSize; + var halfSize = size * 0.5; + + var x = screenSpacePosition.x - halfSize; + var y = screenSpacePosition.y - halfSize; + var width = size; + var height = size; + + if (!defined(result)) { + result = new BoundingRectangle(); + } + + result.x = x; + result.y = y; + result.width = width; + result.height = height; + + return result; + }; + /** * Determines if this point equals another point. Points are equal if all their properties * are equal. Points in different collections can be equal. @@ -451,7 +538,8 @@ define([ this._show === other._show && Color.equals(this._outlineColor, other._outlineColor) && NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) && - NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance); + NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) && + DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition); }; PointPrimitive.prototype._destroy = function() { diff --git a/Source/Scene/PointPrimitiveCollection.js b/Source/Scene/PointPrimitiveCollection.js index eb9c513e6f89..1d4a241b7dc4 100644 --- a/Source/Scene/PointPrimitiveCollection.js +++ b/Source/Scene/PointPrimitiveCollection.js @@ -65,6 +65,7 @@ define([ var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX; var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX; var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX; + var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX; var NUMBER_OF_PROPERTIES = PointPrimitive.NUMBER_OF_PROPERTIES; var attributeLocations = { @@ -72,7 +73,8 @@ define([ positionLowAndOutline : 1, compressedAttribute0 : 2, // color, outlineColor, pick color compressedAttribute1 : 3, // show, translucency by distance, some free space - scaleByDistance : 4 + scaleByDistance : 4, + distanceDisplayCondition : 5 }; /** @@ -133,6 +135,10 @@ define([ this._compiledShaderTranslucencyByDistance = false; this._compiledShaderTranslucencyByDistancePick = false; + this._shaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayConditionPick = false; + this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); this._maxPixelSize = 1.0; @@ -205,7 +211,8 @@ define([ BufferUsage.STATIC_DRAW, // OUTLINE_WIDTH_INDEX BufferUsage.STATIC_DRAW, // PIXEL_SIZE_INDEX BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX - BufferUsage.STATIC_DRAW // TRANSLUCENCY_BY_DISTANCE_INDEX + BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX + BufferUsage.STATIC_DRAW // DISTANCE_DISPLAY_CONDITION_INDEX ]; var that = this; @@ -465,6 +472,11 @@ define([ componentsPerAttribute : 4, componentDatatype : ComponentDatatype.FLOAT, usage : buffersUsage[SCALE_BY_DISTANCE_INDEX] + }, { + index : attributeLocations.distanceDisplayCondition, + componentsPerAttribute : 2, + componentDatatype : ComponentDatatype.FLOAT, + usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX] }], numberOfPointPrimitives); // 1 vertex per pointPrimitive } @@ -555,7 +567,7 @@ define([ } } - var show = pointPrimitive.show; + var show = pointPrimitive.show && pointPrimitive.clusterShow; // If the color alphas are zero, do not show this pointPrimitive. This lets us avoid providing // color during the pick pass and also eliminates a discard in the fragment shader. @@ -600,11 +612,28 @@ define([ writer(i, near, nearValue, far, farValue); } + function writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { + var i = pointPrimitive._index; + var writer = vafWriters[attributeLocations.distanceDisplayCondition]; + var near = 0.0; + var far = Number.MAX_VALUE; + + var distanceDisplayCondition = pointPrimitive.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + near = distanceDisplayCondition.near; + far = distanceDisplayCondition.far; + pointPrimitiveCollection._shaderDistanceDisplayCondition = true; + } + + writer(i, near, far); + } + function writePointPrimitive(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive); writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive); writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive); writeScaleByDistance(pointPrimitiveCollection, context, vafWriters, pointPrimitive); + writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive); } function recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, length, frameState, modelMatrix, recomputeBoundingVolume) { @@ -743,6 +772,10 @@ define([ writers.push(writeScaleByDistance); } + if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) { + writers.push(writeDistanceDisplayCondition); + } + var numWriters = writers.length; vafWriters = this._vaf.writers; @@ -827,7 +860,8 @@ define([ if (!defined(this._sp) || (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance)) { + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition)) { vs = new ShaderSource({ sources : [PointPrimitiveCollectionVS] @@ -838,6 +872,9 @@ define([ if (this._shaderTranslucencyByDistance) { vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } this._sp = ShaderProgram.replaceCache({ context : context, @@ -849,6 +886,7 @@ define([ this._compiledShaderScaleByDistance = this._shaderScaleByDistance; this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; } va = this._vaf.va; @@ -882,7 +920,8 @@ define([ if (!defined(this._spPick) || (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick)) { + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick)) { vs = new ShaderSource({ defines : ['RENDER_FOR_PICK'], @@ -895,6 +934,9 @@ define([ if (this._shaderTranslucencyByDistance) { vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } fs = new ShaderSource({ defines : ['RENDER_FOR_PICK'], @@ -911,6 +953,7 @@ define([ this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; + this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; } va = this._vaf.va; diff --git a/Source/Scene/Polyline.js b/Source/Scene/Polyline.js index 7e2032296b99..e88365516fdf 100644 --- a/Source/Scene/Polyline.js +++ b/Source/Scene/Polyline.js @@ -8,6 +8,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', '../Core/Matrix4', '../Core/PolylinePipeline', './Material' @@ -20,6 +21,7 @@ define([ defined, defineProperties, DeveloperError, + DistanceDisplayCondition, Matrix4, PolylinePipeline, Material) { @@ -38,6 +40,7 @@ define([ * @param {Material} [options.material=Material.ColorType] The material. * @param {Cartesian3[]} [options.positions] The positions. * @param {Object} [options.id] The user-defined object to be returned when this polyline is picked. + * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this polyline will be displayed. * * @see PolylineCollection * @@ -48,6 +51,7 @@ define([ this._show = defaultValue(options.show, true); this._width = defaultValue(options.width, 1.0); this._loop = defaultValue(options.loop, false); + this._distanceDisplayCondition = options.distanceDisplayCondition; this._material = options.material; if (!defined(this._material)) { @@ -93,12 +97,13 @@ define([ this._boundingVolume2D = new BoundingSphere(); // modified in PolylineCollection } - var SHOW_INDEX = Polyline.SHOW_INDEX = 0; - var WIDTH_INDEX = Polyline.WIDTH_INDEX = 1; - var POSITION_INDEX = Polyline.POSITION_INDEX = 2; + var POSITION_INDEX = Polyline.POSITION_INDEX = 0; + var SHOW_INDEX = Polyline.SHOW_INDEX = 1; + var WIDTH_INDEX = Polyline.WIDTH_INDEX = 2; var MATERIAL_INDEX = Polyline.MATERIAL_INDEX = 3; var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX = 4; - var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES = 5; + var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION = 5; + var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES = 6; function makeDirty(polyline, propertyChanged) { ++polyline._propertiesChanged[propertyChanged]; @@ -285,6 +290,29 @@ define([ this._pickId.object.id = value; } } + }, + + /** + * Gets or sets the condition specifying at what distance from the camera that this polyline will be displayed. + * @memberof Polyline.prototype + * @type {DistanceDisplayCondition} + * @default undefined + */ + distanceDisplayCondition : { + get : function() { + return this._distanceDisplayCondition; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far <= value.near) { + throw new DeveloperError('far distance must be greater than near distance.'); + } + //>>includeEnd('debug'); + if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION); + } + } } }); diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index b6efa5504bbc..eb0b35c292d3 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -1,6 +1,7 @@ /*global define*/ define([ '../Core/BoundingSphere', + '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', @@ -17,8 +18,10 @@ define([ '../Core/Math', '../Core/Matrix4', '../Core/Plane', + '../Core/RuntimeError', '../Renderer/Buffer', '../Renderer/BufferUsage', + '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/RenderState', '../Renderer/ShaderProgram', @@ -27,6 +30,7 @@ define([ '../Shaders/PolylineCommon', '../Shaders/PolylineFS', '../Shaders/PolylineVS', + './BatchTable', './BlendingState', './Material', './Pass', @@ -34,6 +38,7 @@ define([ './SceneMode' ], function( BoundingSphere, + Cartesian2, Cartesian3, Cartesian4, Cartographic, @@ -50,8 +55,10 @@ define([ CesiumMath, Matrix4, Plane, + RuntimeError, Buffer, BufferUsage, + ContextLimits, DrawCommand, RenderState, ShaderProgram, @@ -60,6 +67,7 @@ define([ PolylineCommon, PolylineFS, PolylineVS, + BatchTable, BlendingState, Material, Pass, @@ -74,10 +82,11 @@ define([ //POSITION_SIZE_INDEX is needed for when the polyline's position array changes size. //When it does, we need to recreate the indicesBuffer. var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX; + var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION; var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES; var attributeLocations = { - texCoordExpandWidthAndShow : 0, + texCoordExpandAndBatchIndex : 0, position3DHigh : 1, position3DLow : 2, position2DHigh : 3, @@ -89,8 +98,7 @@ define([ nextPosition3DHigh : 9, nextPosition3DLow : 10, nextPosition2DHigh : 11, - nextPosition2DLow : 12, - pickColor : 13 + nextPosition2DLow : 12 }; /** @@ -183,20 +191,18 @@ define([ this._polylines = []; this._polylineBuckets = {}; - // The buffer usage for each attribute is determined based on the usage of the attribute over time. - this._buffersUsage = [ - {bufferUsage: BufferUsage.STATIC_DRAW, frameCount:0}, // SHOW_INDEX - {bufferUsage: BufferUsage.STATIC_DRAW, frameCount:0}, // WIDTH_INDEX - {bufferUsage: BufferUsage.STATIC_DRAW, frameCount:0} // POSITION_INDEX - ]; + // The buffer usage is determined based on the usage of the attribute over time. + this._positionBufferUsage = { bufferUsage : BufferUsage.STATIC_DRAW, frameCount : 0 }; this._mode = undefined; this._polylinesToUpdate = []; this._vertexArrays = []; this._positionBuffer = undefined; - this._pickColorBuffer = undefined; - this._texCoordExpandWidthAndShowBuffer = undefined; + this._texCoordExpandAndBatchIndexBuffer = undefined; + + this._batchTable = undefined; + this._createBatchTable = false; } defineProperties(PolylineCollection.prototype, { @@ -248,6 +254,7 @@ define([ p._index = this._polylines.length; this._polylines.push(p); this._createVertexArray = true; + this._createBatchTable = true; return p; }; @@ -280,6 +287,7 @@ define([ this._polylines[polyline._index] = undefined; // Removed later this._polylinesRemoved = true; this._createVertexArray = true; + this._createBatchTable = true; if (defined(polyline._bucket)) { var bucket = polyline._bucket; bucket.shaderProgram = bucket.shaderProgram && bucket.shaderProgram.destroy(); @@ -369,10 +377,56 @@ define([ return this._polylines[index]; }; + function createBatchTable(collection, context) { + if (defined(collection._batchTable)) { + collection._batchTable.destroy(); + } + + var attributes = [{ + functionName : 'batchTable_getWidthAndShow', + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 2 + }, { + functionName : 'batchTable_getPickColor', + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true + }]; + + if (defined(context.floatingPointTexture)) { + attributes.push({ + functionName : 'batchTable_getCenterHigh', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + }, { + functionName : 'batchTable_getCenterLowAndRadius', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 4 + }, { + functionName : 'batchTable_getDistanceDisplayCondition', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2 + }); + } + + collection._batchTable = new BatchTable(attributes, collection._polylines.length); + } + + var scratchUpdatePolylineEncodedCartesian = new EncodedCartesian3(); + var scratchUpdatePolylineCartesian4 = new Cartesian4(); + var scratchNearFarCartesian2 = new Cartesian2(); + /** - * @private + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ * + * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero. */ - PolylineCollection.prototype.update = function(frameState, commandList) { + PolylineCollection.prototype.update = function(frameState) { removePolylines(this); if (this._polylines.length === 0) { @@ -386,6 +440,14 @@ define([ var polyline; var properties = this._propertiesChanged; + if (this._createBatchTable) { + if (ContextLimits.maximumVertexTextureImageUnits === 0) { + throw new RuntimeError('Vertex texture fetch support is required to render polylines. The maximum number of vertex texture image units must be greater than zero.'); + } + createBatchTable(this, context); + this._createBatchTable = false; + } + if (this._createVertexArray || computeNewBuffersUsage(this)) { createVertexArrays(this, context, projection); } else if (this._polylinesUpdated) { @@ -400,7 +462,7 @@ define([ } // if a polyline's positions size changes, we need to recreate the vertex arrays and vertex buffers because the indices will be different. - // if a polyline's material changes, we need to recreate the VAOs and VBOs because they will be batched differenty. + // if a polyline's material changes, we need to recreate the VAOs and VBOs because they will be batched differently. if (properties[POSITION_SIZE_INDEX] || properties[MATERIAL_INDEX]) { createVertexArrays(this, context, projection); } else { @@ -411,17 +473,46 @@ define([ properties = polyline._propertiesChanged; var bucket = polyline._bucket; var index = 0; - for ( var x in polylineBuckets) { + for (var x in polylineBuckets) { if (polylineBuckets.hasOwnProperty(x)) { if (polylineBuckets[x] === bucket) { - if (properties[POSITION_INDEX] || properties[SHOW_INDEX] || properties[WIDTH_INDEX]) { - bucket.writeUpdate(index, polyline, this._positionBuffer, this._texCoordExpandWidthAndShowBuffer, projection); + if (properties[POSITION_INDEX]) { + bucket.writeUpdate(index, polyline, this._positionBuffer, projection); } break; } index += polylineBuckets[x].lengthOfPositions; } } + + if (properties[SHOW_INDEX] || properties[WIDTH_INDEX]) { + this._batchTable.setBatchedAttribute(polyline._index, 0, new Cartesian2(polyline._width, polyline._show)); + } + + if (this._batchTable.attributes.length > 2) { + if (properties[POSITION_INDEX] || properties[POSITION_SIZE_INDEX]) { + var boundingSphere = frameState.mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC; + var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian); + var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4); + this._batchTable.setBatchedAttribute(polyline._index, 2, encodedCenter.high); + this._batchTable.setBatchedAttribute(polyline._index, 3, low); + } + + if (properties[DISTANCE_DISPLAY_CONDITION]) { + var nearFarCartesian = scratchNearFarCartesian2; + nearFarCartesian.x = 0.0; + nearFarCartesian.y = Number.MAX_VALUE; + + var distanceDisplayCondition = polyline.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + nearFarCartesian.x = distanceDisplayCondition.near; + nearFarCartesian.x = distanceDisplayCondition.far; + } + + this._batchTable.setBatchedAttribute(polyline._index, 4, nearFarCartesian); + } + } + polyline._clean(); } } @@ -461,6 +552,8 @@ define([ }); } + this._batchTable.update(frameState); + if (pass.render) { var colorList = this._colorCommands; createCommandLists(this, frameState, colorList, modelMatrix, true); @@ -486,6 +579,9 @@ define([ var vertexArrays = polylineCollection._vertexArrays; var debugShowBoundingVolume = polylineCollection.debugShowBoundingVolume; + var batchTable = polylineCollection._batchTable; + var uniformCallback = batchTable.getUniformMapCallback(); + var length = vertexArrays.length; for ( var m = 0; m < length; ++m) { var va = vertexArrays[m]; @@ -531,7 +627,7 @@ define([ command.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false; - command.uniformMap = currentMaterial._uniforms; + command.uniformMap = uniformCallback(currentMaterial._uniforms); command.count = count; command.offset = offset; @@ -598,7 +694,7 @@ define([ command.pass = currentMaterial.isTranslucent() ? Pass.TRANSLUCENT : Pass.OPAQUE; command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false; - command.uniformMap = currentMaterial._uniforms; + command.uniformMap = uniformCallback(currentMaterial._uniforms); command.count = count; command.offset = offset; @@ -650,36 +746,33 @@ define([ destroyVertexArrays(this); releaseShaders(this); destroyPolylines(this); + this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); }; function computeNewBuffersUsage(collection) { - var buffersUsage = collection._buffersUsage; var usageChanged = false; - var properties = collection._propertiesChanged; - //subtract 2 from NUMBER_OF_PROPERTIES because we don't care about POSITION_SIZE_INDEX or MATERIAL_INDEX property change. - for ( var k = 0; k < NUMBER_OF_PROPERTIES - 2; ++k) { - var bufferUsage = buffersUsage[k]; - if (properties[k]) { - if (bufferUsage.bufferUsage !== BufferUsage.STREAM_DRAW) { + var bufferUsage = collection._positionBufferUsage; + if (properties[POSITION_INDEX]) { + if (bufferUsage.bufferUsage !== BufferUsage.STREAM_DRAW) { + usageChanged = true; + bufferUsage.bufferUsage = BufferUsage.STREAM_DRAW; + bufferUsage.frameCount = 100; + } else { + bufferUsage.frameCount = 100; + } + } else { + if (bufferUsage.bufferUsage !== BufferUsage.STATIC_DRAW) { + if (bufferUsage.frameCount === 0) { usageChanged = true; - bufferUsage.bufferUsage = BufferUsage.STREAM_DRAW; - bufferUsage.frameCount = 100; + bufferUsage.bufferUsage = BufferUsage.STATIC_DRAW; } else { - bufferUsage.frameCount = 100; - } - } else { - if (bufferUsage.bufferUsage !== BufferUsage.STATIC_DRAW) { - if (bufferUsage.frameCount === 0) { - usageChanged = true; - bufferUsage.bufferUsage = BufferUsage.STATIC_DRAW; - } else { - bufferUsage.frameCount--; - } + bufferUsage.frameCount--; } } } + return usageChanged; } @@ -695,6 +788,8 @@ define([ var totalIndices = [[]]; var indices = totalIndices[0]; + var batchTable = collection._batchTable; + //used to determine the vertexBuffer offset if the indicesArray goes over 64k. //if it's the same polyline while it goes over 64k, the offset needs to backtrack componentsPerAttribute * componentDatatype bytes //so that the polyline looks contiguous. @@ -709,7 +804,7 @@ define([ for (x in polylineBuckets) { if (polylineBuckets.hasOwnProperty(x)) { bucket = polylineBuckets[x]; - bucket.updateShader(context); + bucket.updateShader(context, batchTable); totalLength += bucket.lengthOfPositions; } } @@ -718,17 +813,16 @@ define([ var mode = collection._mode; var positionArray = new Float32Array(6 * totalLength * 3); - var pickColorArray = new Uint8Array(totalLength * 4); - var texCoordExpandWidthAndShowArray = new Float32Array(totalLength * 4); + var texCoordExpandAndBatchIndexArray = new Float32Array(totalLength * 4); var position3DArray; var positionIndex = 0; var colorIndex = 0; - var texCoordExpandWidthAndShowIndex = 0; + var texCoordExpandAndBatchIndexIndex = 0; for (x in polylineBuckets) { if (polylineBuckets.hasOwnProperty(x)) { bucket = polylineBuckets[x]; - bucket.write(positionArray, pickColorArray, texCoordExpandWidthAndShowArray, positionIndex, colorIndex, texCoordExpandWidthAndShowIndex, context, projection); + bucket.write(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection); if (mode === SceneMode.MORPHING) { if (!defined(position3DArray)) { @@ -740,15 +834,13 @@ define([ var bucketLength = bucket.lengthOfPositions; positionIndex += 6 * bucketLength * 3; colorIndex += bucketLength * 4; - texCoordExpandWidthAndShowIndex += bucketLength * 4; + texCoordExpandAndBatchIndexIndex += bucketLength * 4; offset = bucket.updateIndices(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset); } } - var positionBufferUsage = collection._buffersUsage[POSITION_INDEX].bufferUsage; - var showBufferUsage = collection._buffersUsage[SHOW_INDEX].bufferUsage; - var widthBufferUsage = collection._buffersUsage[WIDTH_INDEX].bufferUsage; - var texCoordExpandWidthAndShowBufferUsage = (showBufferUsage === BufferUsage.STREAM_DRAW || widthBufferUsage === BufferUsage.STREAM_DRAW) ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW; + var positionBufferUsage = collection._positionBufferUsage.bufferUsage; + var texCoordExpandAndBatchIndexBufferUsage = BufferUsage.STATIC_DRAW; collection._positionBuffer = Buffer.createVertexBuffer({ context : context, @@ -763,20 +855,14 @@ define([ usage : positionBufferUsage }); } - collection._pickColorBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : pickColorArray, - usage : BufferUsage.STATIC_DRAW - }); - collection._texCoordExpandWidthAndShowBuffer = Buffer.createVertexBuffer({ + collection._texCoordExpandAndBatchIndexBuffer = Buffer.createVertexBuffer({ context : context, - typedArray : texCoordExpandWidthAndShowArray, - usage : texCoordExpandWidthAndShowBufferUsage + typedArray : texCoordExpandAndBatchIndexArray, + usage : texCoordExpandAndBatchIndexBufferUsage }); - var pickColorSizeInBytes = 4 * Uint8Array.BYTES_PER_ELEMENT; var positionSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT; - var texCoordExpandWidthAndShowSizeInBytes = 4 * Float32Array.BYTES_PER_ELEMENT; + var texCoordExpandAndBatchIndexSizeInBytes = 4 * Float32Array.BYTES_PER_ELEMENT; var vbo = 0; var numberOfIndicesArrays = totalIndices.length; @@ -800,8 +886,7 @@ define([ var prevPositionLowOffset = positionSizeInBytes + prevPositionHighOffset; var nextPositionHighOffset = positionSizeInBytes + prevPositionLowOffset; var nextPositionLowOffset = positionSizeInBytes + nextPositionHighOffset; - var vertexPickColorBufferOffset = k * (pickColorSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * pickColorSizeInBytes; - var vertexTexCoordExpandWidthAndShowBufferOffset = k * (texCoordExpandWidthAndShowSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * texCoordExpandWidthAndShowSizeInBytes; + var vertexTexCoordExpandAndBatchIndexBufferOffset = k * (texCoordExpandAndBatchIndexSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * texCoordExpandAndBatchIndexSizeInBytes; var attributes = [{ index : attributeLocations.position3DHigh, @@ -876,18 +961,11 @@ define([ offsetInBytes : nextPositionLowOffset, strideInBytes : 6 * positionSizeInBytes }, { - index : attributeLocations.texCoordExpandWidthAndShow, + index : attributeLocations.texCoordExpandAndBatchIndex, componentsPerAttribute : 4, componentDatatype : ComponentDatatype.FLOAT, - vertexBuffer : collection._texCoordExpandWidthAndShowBuffer, - offsetInBytes : vertexTexCoordExpandWidthAndShowBufferOffset - }, { - index : attributeLocations.pickColor, - componentsPerAttribute : 4, - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - vertexBuffer : collection._pickColorBuffer, - offsetInBytes : vertexPickColorBufferOffset, - normalize : true + vertexBuffer : collection._texCoordExpandAndBatchIndexBuffer, + offsetInBytes : vertexTexCoordExpandAndBatchIndexBufferOffset }]; var buffer3D; @@ -1058,6 +1136,7 @@ define([ this.mode = mode; this.modelMatrix = modelMatrix; } + PolylineBucket.prototype.addPolyline = function(p) { var polylines = this.polylines; polylines.push(p); @@ -1066,13 +1145,20 @@ define([ p._bucket = this; }; - PolylineBucket.prototype.updateShader = function(context) { + PolylineBucket.prototype.updateShader = function(context, batchTable) { if (defined(this.shaderProgram)) { return; } + var defines = []; + if (context.floatingPointTexture) { + defines.push('DISTANCE_DISPLAY_CONDITION'); + } + + var vsSource = batchTable.getVertexShaderCallback()(PolylineVS); var vs = new ShaderSource({ - sources : [PolylineCommon, PolylineVS] + defines : defines, + sources : [PolylineCommon, vsSource] }); var fs = new ShaderSource({ sources : [this.material.shaderSource, PolylineFS] @@ -1123,8 +1209,10 @@ define([ var scratchWritePrevPosition = new Cartesian3(); var scratchWriteNextPosition = new Cartesian3(); var scratchWriteVector = new Cartesian3(); + var scratchPickColorCartesian = new Cartesian4(); + var scratchWidthShowCartesian = new Cartesian2(); - PolylineBucket.prototype.write = function(positionArray, pickColorArray, texCoordExpandWidthAndShowArray, positionIndex, colorIndex, texCoordExpandWidthAndShowIndex, context, projection) { + PolylineBucket.prototype.write = function(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection) { var mode = this.mode; var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; @@ -1134,6 +1222,7 @@ define([ var polyline = polylines[i]; var width = polyline.width; var show = polyline.show && width > 0.0; + var polylineBatchIndex = polyline._index; var segments = this.getSegments(polyline, projection); var positions = segments.positions; var lengths = segments.lengths; @@ -1212,22 +1301,50 @@ define([ EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6); EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12); - pickColorArray[colorIndex] = Color.floatToByte(pickColor.red); - pickColorArray[colorIndex + 1] = Color.floatToByte(pickColor.green); - pickColorArray[colorIndex + 2] = Color.floatToByte(pickColor.blue); - pickColorArray[colorIndex + 3] = Color.floatToByte(pickColor.alpha); - var direction = (k - 2 < 0) ? -1.0 : 1.0; - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex] = j / (positionsLength - 1); // s tex coord - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 1] = 2 * (k % 2) - 1; // expand direction - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 2] = direction * width; - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 3] = show; + texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex] = j / (positionsLength - 1); // s tex coord + texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 1] = 2 * (k % 2) - 1; // expand direction + texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 2] = direction; + texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 3] = polylineBatchIndex; positionIndex += 6 * 3; - colorIndex += 4; - texCoordExpandWidthAndShowIndex += 4; + texCoordExpandAndBatchIndexIndex += 4; } } + + var colorCartesian = scratchPickColorCartesian; + colorCartesian.x = Color.floatToByte(pickColor.red); + colorCartesian.y = Color.floatToByte(pickColor.green); + colorCartesian.z = Color.floatToByte(pickColor.blue); + colorCartesian.w = Color.floatToByte(pickColor.alpha); + + var widthShowCartesian = scratchWidthShowCartesian; + widthShowCartesian.x = width; + widthShowCartesian.y = show ? 1.0 : 0.0; + + var boundingSphere = mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC; + var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian); + var high = encodedCenter.high; + var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4); + + var nearFarCartesian = scratchNearFarCartesian2; + nearFarCartesian.x = 0.0; + nearFarCartesian.y = Number.MAX_VALUE; + + var distanceDisplayCondition = polyline.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + nearFarCartesian.x = distanceDisplayCondition.near; + nearFarCartesian.y = distanceDisplayCondition.far; + } + + batchTable.setBatchedAttribute(polylineBatchIndex, 0, widthShowCartesian); + batchTable.setBatchedAttribute(polylineBatchIndex, 1, colorCartesian); + + if (batchTable.attributes.length > 2) { + batchTable.setBatchedAttribute(polylineBatchIndex, 2, high); + batchTable.setBatchedAttribute(polylineBatchIndex, 3, low); + batchTable.setBatchedAttribute(polylineBatchIndex, 4, nearFarCartesian); + } } }; @@ -1454,9 +1571,8 @@ define([ }; var scratchPositionsArray; - var scratchTexCoordArray; - PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, texCoordExpandWidthAndShowBuffer, projection) { + PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, projection) { var mode = this.mode; var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; @@ -1465,32 +1581,23 @@ define([ index += this.getPolylineStartIndex(polyline); var positionArray = scratchPositionsArray; - var texCoordExpandWidthAndShowArray = scratchTexCoordArray; - var positionsArrayLength = 6 * positionsLength * 3; if (!defined(positionArray) || positionArray.length < positionsArrayLength) { positionArray = scratchPositionsArray = new Float32Array(positionsArrayLength); - texCoordExpandWidthAndShowArray = scratchTexCoordArray = new Float32Array(positionsLength * 4); } else if (positionArray.length > positionsArrayLength) { positionArray = new Float32Array(positionArray.buffer, 0, positionsArrayLength); - texCoordExpandWidthAndShowArray = new Float32Array(texCoordExpandWidthAndShowArray.buffer, 0, positionsLength * 4); } - var positionIndex = 0; - var texCoordExpandWidthAndShowIndex = 0; - var segments = this.getSegments(polyline, projection); var positions = segments.positions; var lengths = segments.lengths; + var positionIndex = 0; var segmentIndex = 0; var count = 0; var position; - var width = polyline.width; - var show = polyline.show && width > 0.0; - positionsLength = positions.length; for ( var i = 0; i < positionsLength; ++i) { if (i === 0) { @@ -1558,20 +1665,11 @@ define([ EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex); EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6); EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12); - - var direction = (j - 2 < 0) ? -1.0 : 1.0; - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex] = i / (positionsLength - 1); // s tex coord - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 1] = 2 * (j % 2) - 1; // expand direction - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 2] = direction * width; - texCoordExpandWidthAndShowArray[texCoordExpandWidthAndShowIndex + 3] = show; - positionIndex += 6 * 3; - texCoordExpandWidthAndShowIndex += 4; } } positionBuffer.copyFromArrayView(positionArray, 6 * 3 * Float32Array.BYTES_PER_ELEMENT * index); - texCoordExpandWidthAndShowBuffer.copyFromArrayView(texCoordExpandWidthAndShowArray, 4 * Float32Array.BYTES_PER_ELEMENT * index); } }; diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 995078d0fe1b..29f69c199c36 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -3,15 +3,18 @@ define([ '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Cartographic', '../Core/clone', + '../Core/Color', '../Core/combine', '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/deprecationWarning', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/EncodedCartesian3', '../Core/FeatureDetection', '../Core/Geometry', '../Core/GeometryAttribute', @@ -20,16 +23,19 @@ define([ '../Core/GeometryInstanceAttribute', '../Core/isArray', '../Core/Matrix4', + '../Core/RuntimeError', '../Core/subdivideArray', '../Core/TaskProcessor', '../Renderer/Buffer', '../Renderer/BufferUsage', + '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', '../Renderer/VertexArray', '../ThirdParty/when', + './BatchTable', './CullFace', './Pass', './PrimitivePipeline', @@ -40,15 +46,18 @@ define([ BoundingSphere, Cartesian2, Cartesian3, + Cartesian4, + Cartographic, clone, + Color, combine, ComponentDatatype, defaultValue, defined, defineProperties, - deprecationWarning, destroyObject, DeveloperError, + EncodedCartesian3, FeatureDetection, Geometry, GeometryAttribute, @@ -57,16 +66,19 @@ define([ GeometryInstanceAttribute, isArray, Matrix4, + RuntimeError, subdivideArray, TaskProcessor, Buffer, BufferUsage, + ContextLimits, DrawCommand, RenderState, ShaderProgram, ShaderSource, VertexArray, when, + BatchTable, CullFace, Pass, PrimitivePipeline, @@ -112,8 +124,6 @@ define([ * @param {Boolean} [options.cull=true] When true, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume. Set this to false for a small performance gain if you are manually culling the primitive. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. - * @param {Boolean} [options.castShadows=true] Deprecated, use options.shadows instead. Determines whether the primitive casts shadows from each light source. - * @param {Boolean} [options.receiveShadows=true] Deprecated, use options.shadows instead. Determines whether the primitive receives shadows from shadow casters in the scene. * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from each light source. * * @example @@ -288,10 +298,6 @@ define([ } //>>includeEnd('debug'); - // Deprecated options - var castShadows = defaultValue(options.castShadows, false); - var receiveShadows = defaultValue(options.receiveShadows, false); - /** * Determines whether this primitive casts or receives shadows from each light source. * @@ -299,27 +305,23 @@ define([ * * @default ShadowMode.DISABLED */ - this.shadows = defaultValue(options.shadows, ShadowMode.fromCastReceive(castShadows, receiveShadows)); + this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED); this._translucent = undefined; this._state = PrimitiveState.READY; this._geometries = []; - this._vaAttributes = undefined; this._error = undefined; this._numberOfInstances = 0; - this._validModelMatrix = false; this._boundingSpheres = []; this._boundingSphereWC = []; this._boundingSphereCV = []; this._boundingSphere2D = []; this._boundingSphereMorph = []; - this._perInstanceAttributeLocations = undefined; this._perInstanceAttributeCache = []; this._instanceIds = []; this._lastPerInstanceAttributeIndex = 0; - this._dirtyAttributes = []; this._va = []; this._attributeLocations = undefined; @@ -350,6 +352,13 @@ define([ this._createGeometryResults = undefined; this._ready = false; this._readyPromise = when.defer(); + + this._batchTable = undefined; + this._batchTableAttributeIndices = undefined; + this._instanceBoundingSpheres = undefined; + this._instanceBoundingSpheresCV = undefined; + this._batchTableBoundingSpheresUpdated = false; + this._batchTableBoundingSphereAttributeIndices = undefined; } defineProperties(Primitive.prototype, { @@ -475,48 +484,177 @@ define([ get : function() { return this._readyPromise.promise; } - }, + } + }); - /** - * Determines whether the primitive casts shadows from each light source. - * - * @memberof Primitive.prototype - * @type {Boolean} - * @deprecated - */ - castShadows : { - get : function() { - deprecationWarning('Primitive.castShadows', 'Primitive.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Primitive.shadows instead.'); - return ShadowMode.castShadows(this.shadows); - }, - set : function(value) { - deprecationWarning('Primitive.castShadows', 'Primitive.castShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Primitive.shadows instead.'); - var castShadows = value; - var receiveShadows = ShadowMode.receiveShadows(this.shadows); - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); + 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 (!defined(otherAttribute) || + (attribute.componentDatatype !== otherAttribute.componentDatatype) || + (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || + (attribute.normalize !== otherAttribute.normalize)) { + + inAllInstances = false; + break; + } + } + + if (inAllInstances) { + attributesInAllInstances.push(name); + } } - }, + } - /** - * Determines whether the primitive receives shadows from shadow casters in the scene. - * - * @memberof Primitive.prototype - * @type {Boolean} - * @deprecated - */ - receiveShadows : { - get : function() { - deprecationWarning('Primitive.receiveShadows', 'Primitive.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Primitive.shadows instead.'); - return ShadowMode.receiveShadows(this.shadows); - }, - set : function(value) { - deprecationWarning('Primitive.receiveShadows', 'Primitive.receiveShadows was deprecated in Cesium 1.25. It will be removed in 1.26. Use Primitive.shadows instead.'); - var castShadows = ShadowMode.castShadows(this.shadows); - var receiveShadows = value; - this.shadows = ShadowMode.fromCastReceive(castShadows, receiveShadows); + return attributesInAllInstances; + } + + var scratchGetAttributeCartesian2 = new Cartesian2(); + var scratchGetAttributeCartesian3 = new Cartesian3(); + var scratchGetAttributeCartesian4 = new Cartesian4(); + + function getAttributeValue(value) { + var componentsPerAttribute = value.length; + if (componentsPerAttribute === 1) { + return value[0]; + } else if (componentsPerAttribute === 2) { + return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2); + } else if (componentsPerAttribute === 3) { + return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3); + } else if (componentsPerAttribute === 4) { + return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4); + } + } + + function createBatchTable(primitive, context) { + var geometryInstances = primitive.geometryInstances; + var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances]; + var numberOfInstances = instances.length; + if (numberOfInstances === 0) { + return; + } + + var names = getCommonPerInstanceAttributeNames(instances); + var length = names.length; + + var allowPicking = primitive.allowPicking; + var attributes = []; + var attributeIndices = {}; + var boundingSphereAttributeIndices = {}; + + var firstInstance = instances[0]; + var instanceAttributes = firstInstance.attributes; + + var i; + var name; + var attribute; + + for (i = 0; i < length; ++i) { + name = names[i]; + attribute = instanceAttributes[name]; + + attributeIndices[name] = i; + attributes.push({ + functionName : 'czm_batchTable_' + name, + componentDatatype : attribute.componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize + }); + } + + if (names.indexOf('distanceDisplayCondition') !== -1) { + attributes.push({ + functionName : 'czm_batchTable_boundingSphereCenter3DHigh', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + },{ + functionName : 'czm_batchTable_boundingSphereCenter3DLow', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + },{ + functionName : 'czm_batchTable_boundingSphereCenter2DHigh', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + },{ + functionName : 'czm_batchTable_boundingSphereCenter2DLow', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + },{ + functionName : 'czm_batchTable_boundingSphereRadius', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1 + }); + boundingSphereAttributeIndices.center3DHigh = attributes.length - 5; + boundingSphereAttributeIndices.center3DLow = attributes.length - 4; + boundingSphereAttributeIndices.center2DHigh = attributes.length - 3; + boundingSphereAttributeIndices.center2DLow = attributes.length - 2; + boundingSphereAttributeIndices.radius = attributes.length - 1; + } + + if (allowPicking) { + attributes.push({ + functionName : 'czm_batchTable_pickColor', + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true + }); + } + + var attributesLength = attributes.length; + var batchTable = new BatchTable(attributes, numberOfInstances); + + for (i = 0; i < numberOfInstances; ++i) { + var instance = instances[i]; + instanceAttributes = instance.attributes; + + for (var j = 0; j < length; ++j) { + name = names[j]; + attribute = instanceAttributes[name]; + var value = getAttributeValue(attribute.value); + var attributeIndex = attributeIndices[name]; + batchTable.setBatchedAttribute(i, attributeIndex, value); + } + + if (allowPicking) { + var pickObject = { + primitive : defaultValue(instance.pickPrimitive, primitive) + }; + + if (defined(instance.id)) { + pickObject.id = instance.id; + } + + var pickId = context.createPickId(pickObject); + primitive._pickIds.push(pickId); + + var pickColor = pickId.color; + var color = scratchGetAttributeCartesian4; + color.x = Color.floatToByte(pickColor.red); + color.y = Color.floatToByte(pickColor.green); + color.z = Color.floatToByte(pickColor.blue); + color.w = Color.floatToByte(pickColor.alpha); + + batchTable.setBatchedAttribute(i, attributesLength - 1, color); } } - }); + + primitive._batchTable = batchTable; + primitive._batchTableAttributeIndices = attributeIndices; + primitive._batchTableBoundingSphereAttributeIndices = boundingSphereAttributeIndices; + } function cloneAttribute(attribute) { var clonedValues; @@ -556,37 +694,13 @@ define([ }); } - function cloneGeometryInstanceAttribute(attribute) { - var clonedValue; - if (isArray(attribute.value)) { - clonedValue = attribute.value.slice(0); - } else { - clonedValue = new attribute.value.constructor(attribute.value); - } - return new GeometryInstanceAttribute({ - componentDatatype : attribute.componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - normalize : attribute.normalize, - value : clonedValue - }); - } - function cloneInstance(instance, geometry) { - var attributes = instance.attributes; - var newAttributes = {}; - for (var property in attributes) { - if (attributes.hasOwnProperty(property)) { - newAttributes[property] = cloneGeometryInstanceAttribute(attributes[property]); - } - } - - return new GeometryInstance({ + return { geometry : geometry, modelMatrix : Matrix4.clone(instance.modelMatrix), - attributes : newAttributes, pickPrimitive : instance.pickPrimitive, id : instance.id - }); + }; } var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g; @@ -667,22 +781,104 @@ define([ }; Primitive._appendShowToShader = function(primitive, vertexShaderSource) { - if (!defined(primitive._attributeLocations.show)) { + if (!defined(primitive._batchTableAttributeIndices.show)) { return vertexShaderSource; } var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main'); var showMain = - 'attribute float show;\n' + 'void main() \n' + '{ \n' + ' czm_non_show_main(); \n' + - ' gl_Position *= show; \n' + + ' gl_Position *= czm_batchTable_show(batchId); \n' + '}'; return renamedVS + '\n' + showMain; }; + Primitive._updateColorAttribute = function(primitive, vertexShaderSource) { + // some appearances have a color attribute for per vertex color. + // only remove if color is a per instance attribute. + if (!defined(primitive._batchTableAttributeIndices.color)) { + return vertexShaderSource; + } + + if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) { + return vertexShaderSource; + } + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, ''); + modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2'); + return modifiedVS; + }; + + Primitive._updatePickColorAttribute = function(source) { + var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, ''); + vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2'); + return vsPick; + }; + + Primitive._appendDistanceDisplayConditionToShader = function(primitive, vertexShaderSource, scene3DOnly) { + if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition)) { + return vertexShaderSource; + } + + var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_distanceDisplayCondition_main'); + var distanceDisplayConditionMain = + 'void main() \n' + + '{ \n' + + ' czm_non_distanceDisplayCondition_main(); \n' + + ' vec2 distanceDisplayCondition = czm_batchTable_distanceDisplayCondition(batchId);\n' + + ' vec3 boundingSphereCenter3DHigh = czm_batchTable_boundingSphereCenter3DHigh(batchId);\n' + + ' vec3 boundingSphereCenter3DLow = czm_batchTable_boundingSphereCenter3DLow(batchId);\n' + + ' vec3 boundingSphereCenter2DHigh = czm_batchTable_boundingSphereCenter2DHigh(batchId);\n' + + ' vec3 boundingSphereCenter2DLow = czm_batchTable_boundingSphereCenter2DLow(batchId);\n' + + ' float boundingSphereRadius = czm_batchTable_boundingSphereRadius(batchId);\n'; + + if (!scene3DOnly) { + distanceDisplayConditionMain += + ' vec4 centerRTE;\n' + + ' if (czm_morphTime == 1.0)\n' + + ' {\n' + + ' centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n' + + ' }\n' + + ' else if (czm_morphTime == 0.0)\n' + + ' {\n' + + ' centerRTE = czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy);\n' + + ' }\n' + + ' else\n' + + ' {\n' + + ' centerRTE = czm_columbusViewMorph(\n' + + ' czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy),\n' + + ' czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow),\n' + + ' czm_morphTime);\n' + + ' }\n'; + } else { + distanceDisplayConditionMain += + ' vec4 centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n'; + } + + distanceDisplayConditionMain += + ' float radiusSq = boundingSphereRadius * boundingSphereRadius; \n' + + ' float distanceSq; \n' + + ' if (czm_sceneMode == czm_sceneMode2D) \n' + + ' { \n' + + ' distanceSq = czm_eyeHeight2D.y - radiusSq; \n' + + ' } \n' + + ' else \n' + + ' { \n' + + ' distanceSq = dot(centerRTE.xyz, centerRTE.xyz) - radiusSq; \n' + + ' } \n' + + ' distanceSq = max(distanceSq, 0.0); \n' + + ' float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; \n' + + ' float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; \n' + + ' float show = (distanceSq >= nearSq && distanceSq <= farSq) ? 1.0 : 0.0; \n' + + ' gl_Position *= show; \n' + + '}'; + return renamedVS + '\n' + distanceDisplayConditionMain; + }; + function modifyForEncodedNormals(primitive, vertexShaderSource) { if (!primitive.compressVertices) { return vertexShaderSource; @@ -777,27 +973,6 @@ define([ //>>includeEnd('debug'); } - function createPickIds(context, primitive, instances) { - var pickColors = []; - var length = instances.length; - - for (var i = 0; i < length; ++i) { - var pickObject = { - primitive : defaultValue(instances[i].pickPrimitive, primitive) - }; - - if (defined(instances[i].id)) { - pickObject.id = instances[i].id; - } - - var pickId = context.createPickId(pickObject); - primitive._pickIds.push(pickId); - pickColors.push(pickId.color); - } - - return pickColors; - } - function getUniformFunction(uniforms, name) { return function() { return uniforms[name]; @@ -894,19 +1069,16 @@ define([ var transferableObjects = []; instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; - var allowPicking = primitive.allowPicking; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({ createGeometryResults : primitive._createGeometryResults, instances : instances, - pickIds : allowPicking ? createPickIds(frameState.context, primitive, instances) : undefined, ellipsoid : projection.ellipsoid, projection : projection, elementIndexUintSupported : frameState.context.elementIndexUint, scene3DOnly : scene3DOnly, - allowPicking : allowPicking, vertexCacheOptimize : primitive.vertexCacheOptimize, compressVertices : primitive.compressVertices, modelMatrix : primitive.modelMatrix, @@ -920,30 +1092,12 @@ define([ var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult); primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; - primitive._vaAttributes = result.vaAttributes; - primitive._perInstanceAttributeLocations = result.perInstanceAttributeLocations; primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix); - primitive._validModelMatrix = !Matrix4.equals(primitive.modelMatrix, Matrix4.IDENTITY); primitive._pickOffsets = result.pickOffsets; + primitive._instanceBoundingSpheres = result.boundingSpheres; + primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; - var validInstancesIndices = packedResult.validInstancesIndices; - var invalidInstancesIndices = packedResult.invalidInstancesIndices; - var instanceIds = primitive._instanceIds; - var reorderedInstanceIds = new Array(instanceIds.length); - - var validLength = validInstancesIndices.length; - for (var i = 0; i < validLength; ++i) { - reorderedInstanceIds[i] = instanceIds[validInstancesIndices[i]]; - } - - var invalidLength = invalidInstancesIndices.length; - for (var j = 0; j < invalidLength; ++j) { - reorderedInstanceIds[validLength + j] = instanceIds[invalidInstancesIndices[j]]; - } - - primitive._instanceIds = reorderedInstanceIds; - - if (defined(primitive._geometries)) { + if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); @@ -957,11 +1111,7 @@ define([ function loadSynchronous(primitive, frameState) { var instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; var length = primitive._numberOfInstances = instances.length; - - var geometries = new Array(length); var clonedInstances = new Array(length); - - var invalidInstances = []; var instanceIds = primitive._instanceIds; var instance; @@ -979,31 +1129,21 @@ define([ createdGeometry = geometry.constructor.createGeometry(geometry); } - if (defined(createdGeometry)) { - geometries[geometryIndex] = createdGeometry; - clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); - instanceIds.push(instance.id); - } else { - invalidInstances.push(instance); - } + clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); + instanceIds.push(instance.id); } - geometries.length = geometryIndex; clonedInstances.length = geometryIndex; - var allowPicking = primitive.allowPicking; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var result = PrimitivePipeline.combineGeometry({ instances : clonedInstances, - invalidInstances : invalidInstances, - pickIds : allowPicking ? createPickIds(frameState.context, primitive, clonedInstances) : undefined, ellipsoid : projection.ellipsoid, projection : projection, elementIndexUintSupported : frameState.context.elementIndexUint, scene3DOnly : scene3DOnly, - allowPicking : allowPicking, vertexCacheOptimize : primitive.vertexCacheOptimize, compressVertices : primitive.compressVertices, modelMatrix : primitive.modelMatrix, @@ -1012,28 +1152,65 @@ define([ primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; - primitive._vaAttributes = result.vaAttributes; - primitive._perInstanceAttributeLocations = result.vaAttributeLocations; primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix); - primitive._validModelMatrix = !Matrix4.equals(primitive.modelMatrix, Matrix4.IDENTITY); primitive._pickOffsets = result.pickOffsets; + primitive._instanceBoundingSpheres = result.boundingSpheres; + primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; - for (i = 0; i < invalidInstances.length; ++i) { - instance = invalidInstances[i]; - instanceIds.push(instance.id); - } - - if (defined(primitive._geometries)) { + if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); } } + var scratchBoundingSphereCenterEncoded = new EncodedCartesian3(); + var scratchBoundingSphereCartographic = new Cartographic(); + var scratchBoundingSphereCenter2D = new Cartesian3(); + + function updateBatchTableBoundingSpheres(primitive, frameState) { + if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition) || primitive._batchTableBoundingSpheresUpdated) { + return; + } + + var indices = primitive._batchTableBoundingSphereAttributeIndices; + var center3DHighIndex = indices.center3DHigh; + var center3DLowIndex = indices.center3DLow; + var center2DHighIndex = indices.center2DHigh; + var center2DLowIndex = indices.center2DLow; + var radiusIndex = indices.radius; + + var projection = frameState.mapProjection; + var ellipsoid = projection.ellipsoid; + + var batchTable = primitive._batchTable; + var boundingSpheres = primitive._instanceBoundingSpheres; + var length = boundingSpheres.length; + + for (var i = 0; i < length; ++i) { + var boundingSphere = boundingSpheres[i]; + var center = boundingSphere.center; + var radius = boundingSphere.radius; + + var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded); + batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high); + batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low); + + var cartographic = ellipsoid.cartesianToCartographic(center, scratchBoundingSphereCartographic); + var center2D = projection.project(cartographic, scratchBoundingSphereCenter2D); + encodedCenter = EncodedCartesian3.fromCartesian(center2D, scratchBoundingSphereCenterEncoded); + batchTable.setBatchedAttribute(i, center2DHighIndex, encodedCenter.high); + batchTable.setBatchedAttribute(i, center2DLowIndex, encodedCenter.low); + + batchTable.setBatchedAttribute(i, radiusIndex, radius); + } + + primitive._batchTableBoundingSpheresUpdated = true; + } + function createVertexArray(primitive, frameState) { var attributeLocations = primitive._attributeLocations; var geometries = primitive._geometries; - var vaAttributes = primitive._vaAttributes; var scene3DOnly = frameState.scene3DOnly; var context = frameState.context; @@ -1042,24 +1219,12 @@ define([ for (var i = 0; i < length; ++i) { var geometry = geometries[i]; - var attributes = vaAttributes[i]; - var vaLength = attributes.length; - for (var j = 0; j < vaLength; ++j) { - var attribute = attributes[j]; - attribute.vertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : attribute.values, - usage : BufferUsage.DYNAMIC_DRAW}); - delete attribute.values; - } - va.push(VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, bufferUsage : BufferUsage.STATIC_DRAW, - interleave : primitive._interleave, - vertexArrayAttributes : attributes + interleave : primitive._interleave })); if (defined(primitive._createBoundingVolumeFunction)) { @@ -1149,17 +1314,23 @@ define([ var attributeLocations = primitive._attributeLocations; - var vs = Primitive._modifyShaderPosition(primitive, appearance.vertexShaderSource, frameState.scene3DOnly); + var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource); vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs); vs = modifyForEncodedNormals(primitive, vs); + vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); var fs = appearance.getFragmentShaderSource(); // Create pick program if (primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + primitive._pickSP = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._pickSP, - vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), + vertexShaderSource : vsPick, fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'), attributeLocations : attributeLocations }); @@ -1205,6 +1376,7 @@ define([ } } var uniforms = combine(appearanceUniformMap, materialUniformMap); + uniforms = primitive._batchTable.getUniformMapCallback()(uniforms); if (defined(primitive.rtcCenter)) { uniforms.u_modifiedModelView = function() { @@ -1275,41 +1447,6 @@ define([ } } - function updatePerInstanceAttributes(primitive) { - if (primitive._dirtyAttributes.length === 0) { - return; - } - - var attributes = primitive._dirtyAttributes; - var length = attributes.length; - for (var i = 0; i < length; ++i) { - var attribute = attributes[i]; - 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(componentDatatype, count * componentsPerAttribute); - for (var k = 0; k < count; ++k) { - typedArray.set(value, k * componentsPerAttribute); - } - - var offsetInBytes = offset * componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype); - vaAttribute.vertexBuffer.copyFromArrayView(typedArray, offsetInBytes); - } - attribute.dirty = false; - } - - attributes.length = 0; - } - function updateBoundingVolumes(primitive, frameState) { // Update bounding volumes for primitives that are sized in pixels. // The pixel size in meters varies based on the distance from the camera. @@ -1406,6 +1543,7 @@ define([ * @exception {DeveloperError} All instance geometries must have the same primitiveType. * @exception {DeveloperError} Appearance and material have a uniform with the same name. * @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode. + * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero. */ Primitive.prototype.update = function(frameState) { if (((!defined(this.geometryInstances)) && (this._va.length === 0)) || @@ -1428,6 +1566,17 @@ define([ return; } + var context = frameState.context; + if (!defined(this._batchTable)) { + createBatchTable(this, context); + } + if (this._batchTable.attributes.length > 0) { + if (ContextLimits.maximumVertexTextureImageUnits === 0) { + throw new RuntimeError('Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.'); + } + this._batchTable.update(frameState); + } + if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) { if (this.asynchronous) { loadAsynchronous(this, frameState); @@ -1437,6 +1586,7 @@ define([ } if (this._state === PrimitiveState.COMBINED) { + updateBatchTableBoundingSpheres(this, frameState); createVertexArray(this, frameState); } @@ -1466,7 +1616,6 @@ define([ createRS = true; } - var context = frameState.context; if (defined(this._material)) { this._material.update(context); } @@ -1488,41 +1637,50 @@ define([ commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState); } - updatePerInstanceAttributes(this); - var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands); updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses); }; - function createGetFunction(name, perInstanceAttributes) { - var attribute = perInstanceAttributes[name]; + function createGetFunction(batchTable, instanceIndex, attributeIndex) { return function() { - if (defined(attribute) && defined(attribute.value)) { - return perInstanceAttributes[name].value; + var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex); + var attribute = batchTable.attributes[attributeIndex]; + var componentsPerAttribute = attribute.componentsPerAttribute; + var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute); + if (defined(attributeValue.constructor.pack)) { + attributeValue.constructor.pack(attributeValue, value, 0); + } else { + value[0] = attributeValue; } - return attribute; + return value; }; } - function createSetFunction(name, perInstanceAttributes, dirtyList) { - return function (value) { + function createSetFunction(batchTable, instanceIndex, attributeIndex) { + return function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) { throw new DeveloperError('value must be and array with length between 1 and 4.'); } //>>includeEnd('debug'); + var attributeValue = getAttributeValue(value); + batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue); + }; + } - var attribute = perInstanceAttributes[name]; - attribute.value = value; - if (!attribute.dirty && attribute.valid) { - dirtyList.push(attribute); - attribute.dirty = true; + function createBoundingSphereProperties(primitive, properties, index) { + properties.boundingSphere = { + get : function() { + return primitive._instanceBoundingSpheres[index]; + } + }; + properties.boundingSphereCV = { + get : function() { + return primitive._instanceBoundingSpheresCV[index]; } }; } - var readOnlyInstanceAttributesScratch = ['boundingSphere', 'boundingSphereCV']; - /** * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. * @@ -1535,13 +1693,14 @@ define([ * var attributes = primitive.getGeometryInstanceAttributes('an id'); * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true); + * attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(100.0, 10000.0); */ Primitive.prototype.getGeometryInstanceAttributes = function(id) { //>>includeStart('debug', pragmas.debug); if (!defined(id)) { throw new DeveloperError('id is required'); } - if (!defined(this._perInstanceAttributeLocations)) { + if (!defined(this._batchTable)) { throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); } //>>includeEnd('debug'); @@ -1561,34 +1720,26 @@ define([ if (index === -1) { return undefined; } + var attributes = this._perInstanceAttributeCache[index]; if (defined(attributes)) { return attributes; } - var perInstanceAttributes = this._perInstanceAttributeLocations[index]; + var batchTable = this._batchTable; + var perInstanceAttributeIndices = this._batchTableAttributeIndices; attributes = {}; var properties = {}; - var hasProperties = false; - for (var name in perInstanceAttributes) { - if (perInstanceAttributes.hasOwnProperty(name)) { - hasProperties = true; + for (var name in perInstanceAttributeIndices) { + if (perInstanceAttributeIndices.hasOwnProperty(name)) { + var attributeIndex = perInstanceAttributeIndices[name]; properties[name] = { - get : createGetFunction(name, perInstanceAttributes) + get : createGetFunction(batchTable, index, attributeIndex) }; var createSetter = true; - var readOnlyAttributes = readOnlyInstanceAttributesScratch; - length = readOnlyAttributes.length; - for (var j = 0; j < length; ++j) { - if (name === readOnlyInstanceAttributesScratch[j]) { - createSetter = false; - break; - } - } - - readOnlyAttributes = this._readOnlyInstanceAttributes; + var readOnlyAttributes = this._readOnlyInstanceAttributes; if (createSetter && defined(readOnlyAttributes)) { length = readOnlyAttributes.length; for (var k = 0; k < length; ++k) { @@ -1600,14 +1751,13 @@ define([ } if (createSetter) { - properties[name].set = createSetFunction(name, perInstanceAttributes, this._dirtyAttributes); + properties[name].set = createSetFunction(batchTable, index, attributeIndex); } } } - if (hasProperties) { - defineProperties(attributes, properties); - } + createBoundingSphereProperties(this, properties, index); + defineProperties(attributes, properties); this._lastPerInstanceAttributeIndex = index; this._perInstanceAttributeCache[index] = attributes; @@ -1669,14 +1819,14 @@ define([ } this._pickIds = undefined; + this._batchTable = this._batchTable && this._batchTable.destroy(); + //These objects may be fairly large and reference other large objects (like Entities) //We explicitly set them to undefined here so that the memory can be freed //even if a reference to the destroyed Primitive has been kept around. this._instanceIds = undefined; this._perInstanceAttributeCache = undefined; - this._perInstanceAttributeLocations = undefined; this._attributeLocations = undefined; - this._dirtyAttributes = undefined; return destroyObject(this); }; diff --git a/Source/Scene/PrimitivePipeline.js b/Source/Scene/PrimitivePipeline.js index c4986f352daa..a09a764f06e2 100644 --- a/Source/Scene/PrimitivePipeline.js +++ b/Source/Scene/PrimitivePipeline.js @@ -59,7 +59,9 @@ define([ if (toWorld) { for (i = 0; i < length; ++i) { - GeometryPipeline.transformToWorldCoordinates(instances[i]); + if (defined(instances[i].geometry)) { + GeometryPipeline.transformToWorldCoordinates(instances[i]); + } } } else { // Leave geometry in local coordinate system; auto update model-matrix. @@ -67,142 +69,61 @@ define([ } } - function addGeometryPickColor(geometry, pickColor) { + function addGeometryBatchId(geometry, batchId) { var attributes = geometry.attributes; var positionAttr = attributes.position; - var numberOfComponents = 4 * (positionAttr.values.length / positionAttr.componentsPerAttribute); + var numberOfComponents = positionAttr.values.length / positionAttr.componentsPerAttribute; - attributes.pickColor = new GeometryAttribute({ - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 4, - normalize : true, - values : new Uint8Array(numberOfComponents) + attributes.batchId = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : new Float32Array(numberOfComponents) }); - 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 addPickColorAttribute(instances, pickIds) { - var length = instances.length; - - for (var i = 0; i < length; ++i) { - var instance = instances[i]; - var pickColor = pickIds[i]; - - if (defined(instance.geometry)) { - addGeometryPickColor(instance.geometry, pickColor); - } else { - addGeometryPickColor(instance.westHemisphereGeometry, pickColor); - addGeometryPickColor(instance.eastHemisphereGeometry, pickColor); - } + var values = attributes.batchId.values; + for (var j = 0; j < numberOfComponents; ++j) { + values[j] = batchId; } } - function getCommonPerInstanceAttributeNames(instances) { + function addBatchIds(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 (!defined(otherAttribute) || - (attribute.componentDatatype !== otherAttribute.componentDatatype) || - (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || - (attribute.normalize !== otherAttribute.normalize)) { - - inAllInstances = false; - break; - } - } - - if (inAllInstances) { - attributesInAllInstances.push(name); - } - } - } - - return attributesInAllInstances; - } - - function addPerInstanceAttributesToGeometry(instanceAttributes, geometry, names) { - 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 = attribute.componentsPerAttribute; - - var buffer = ComponentDatatype.createTypedArray(componentDatatype, 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 - }); - } - } - - function addPerInstanceAttributes(instances, names) { - var length = instances.length; for (var i = 0; i < length; ++i) { var instance = instances[i]; - var instanceAttributes = instance.attributes; - if (defined(instance.geometry)) { - addPerInstanceAttributesToGeometry(instanceAttributes, instance.geometry, names); - } else { - addPerInstanceAttributesToGeometry(instanceAttributes, instance.westHemisphereGeometry, names); - addPerInstanceAttributesToGeometry(instanceAttributes, instance.eastHemisphereGeometry, names); + addGeometryBatchId(instance.geometry, i); + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { + addGeometryBatchId(instance.westHemisphereGeometry, i); + addGeometryBatchId(instance.eastHemisphereGeometry, i); } } } function geometryPipeline(parameters) { var instances = parameters.instances; - var pickIds = parameters.pickIds; var projection = parameters.projection; var uintIndexSupport = parameters.elementIndexUintSupported; var scene3DOnly = parameters.scene3DOnly; - var allowPicking = parameters.allowPicking; var vertexCacheOptimize = parameters.vertexCacheOptimize; var compressVertices = parameters.compressVertices; var modelMatrix = parameters.modelMatrix; var i; var geometry; + var primitiveType; var length = instances.length; - var primitiveType = instances[0].geometry.primitiveType; + + for (i = 0 ; i < length; ++i) { + if (defined(instances[i].geometry)) { + primitiveType = instances[i].geometry.primitiveType; + break; + } + } //>>includeStart('debug', pragmas.debug); for (i = 1; i < length; ++i) { - if (instances[i].geometry.primitiveType !== primitiveType) { + if (defined(instances[i].geometry) && instances[i].geometry.primitiveType !== primitiveType) { throw new DeveloperError('All instance geometries must have the same primitiveType.'); } } @@ -214,18 +135,13 @@ define([ // Clip to IDL if (!scene3DOnly) { for (i = 0; i < length; ++i) { - GeometryPipeline.splitLongitude(instances[i]); + if (defined(instances[i].geometry)) { + GeometryPipeline.splitLongitude(instances[i]); + } } } - // Add pickColor attribute for picking individual instances - if (allowPicking) { - addPickColorAttribute(instances, pickIds); - } - - // add attributes to the geometry for each per-instance attribute - var perInstanceAttributeNames = getCommonPerInstanceAttributeNames(instances); - addPerInstanceAttributes(instances, perInstanceAttributeNames); + addBatchIds(instances); // Optimize for vertex shader caches if (vertexCacheOptimize) { @@ -234,7 +150,7 @@ define([ if (defined(instance.geometry)) { GeometryPipeline.reorderForPostVertexCache(instance.geometry); GeometryPipeline.reorderForPreVertexCache(instance.geometry); - } else { + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { GeometryPipeline.reorderForPostVertexCache(instance.westHemisphereGeometry); GeometryPipeline.reorderForPreVertexCache(instance.westHemisphereGeometry); @@ -299,159 +215,6 @@ define([ return geometries; } - function createPerInstanceVAAttributes(geometry, attributeLocations, names) { - var vaAttributes = []; - 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 typedArray = ComponentDatatype.createTypedArray(componentDatatype, attribute.values); - vaAttributes.push({ - index : attributeLocations[name], - componentDatatype : componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - normalize : attribute.normalize, - values : typedArray - }); - - delete attributes[name]; - } - - return vaAttributes; - } - - function computePerInstanceAttributeLocationsForGeometry(instanceIndex, geometry, instanceAttributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices) { - var numberOfVertices = Geometry.computeNumberOfVertices(geometry); - - if (!defined(indices[instanceIndex])) { - indices[instanceIndex] = { - boundingSphere : geometry.boundingSphere, - boundingSphereCV : geometry.boundingSphereCV - }; - } - - var namesLength = names.length; - for (var j = 0; j < namesLength; ++j) { - var name = names[j]; - var index = attributeLocations[name]; - - var tempVertexCount = numberOfVertices; - while (tempVertexCount > 0) { - var vaIndex = defaultValue(vaIndices[name], 0); - var va = vertexArrays[vaIndex]; - var vaLength = va.length; - - var attribute; - for (var k = 0; k < vaLength; ++k) { - attribute = va[k]; - if (attribute.index === index) { - break; - } - } - - if (!defined(indices[instanceIndex][name])) { - indices[instanceIndex][name] = { - dirty : false, - valid : true, - value : instanceAttributes[name].value, - indices : [] - }; - } - - var size = attribute.values.length / attribute.componentsPerAttribute; - var offset = defaultValue(offsets[name], 0); - - var count; - if (offset + tempVertexCount < size) { - count = tempVertexCount; - indices[instanceIndex][name].indices.push({ - attribute : attribute, - offset : offset, - count : count - }); - offsets[name] = offset + tempVertexCount; - } else { - count = size - offset; - indices[instanceIndex][name].indices.push({ - attribute : attribute, - offset : offset, - count : count - }); - offsets[name] = 0; - vaIndices[name] = vaIndex + 1; - } - - tempVertexCount -= count; - } - } - } - - function computePerInstanceAttributeLocations(instances, invalidInstances, vertexArrays, attributeLocations, names) { - var indices = []; - - var length = instances.length; - var offsets = {}; - var vaIndices = {}; - - var i; - var instance; - var attributes; - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.geometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.geometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.westHemisphereGeometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.westHemisphereGeometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.eastHemisphereGeometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.eastHemisphereGeometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - length = invalidInstances.length; - for (i = 0; i < length; ++i) { - instance = invalidInstances[i]; - attributes = instance.attributes; - - var instanceAttributes = {}; - indices.push(instanceAttributes); - - var namesLength = names.length; - for (var j = 0; j < namesLength; ++j) { - var name = names[j]; - instanceAttributes[name] = { - dirty : false, - valid : false, - value : attributes[name].value, - indices : [] - }; - } - } - - return indices; - } - function createPickOffsets(instances, geometryName, geometries, pickOffsets) { var offset; var indexCount; @@ -512,44 +275,50 @@ define([ PrimitivePipeline.combineGeometry = function(parameters) { var geometries; var attributeLocations; - var perInstanceAttributes; - var perInstanceAttributeNames; - var length; - var instances = parameters.instances; - var invalidInstances = parameters.invalidInstances; + var length = instances.length; - if (instances.length > 0) { + if (length > 0) { geometries = geometryPipeline(parameters); - attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]); - - perInstanceAttributeNames = getCommonPerInstanceAttributeNames(instances); - - perInstanceAttributes = []; - length = geometries.length; - for (var i = 0; i < length; ++i) { - var geometry = geometries[i]; - perInstanceAttributes.push(createPerInstanceVAAttributes(geometry, attributeLocations, perInstanceAttributeNames)); + if (geometries.length > 0) { + attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]); } } - perInstanceAttributeNames = defined(perInstanceAttributeNames) ? perInstanceAttributeNames : getCommonPerInstanceAttributeNames(invalidInstances); - var indices = computePerInstanceAttributeLocations(instances, invalidInstances, perInstanceAttributes, attributeLocations, perInstanceAttributeNames); - var pickOffsets; - if (parameters.createPickOffsets && defined(geometries)) { + if (parameters.createPickOffsets && geometries.length > 0) { pickOffsets = createInstancePickOffsets(instances, geometries); } + var boundingSpheres = new Array(length); + var boundingSpheresCV = new Array(length); + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var geometry = instance.geometry; + if (defined(geometry)) { + boundingSpheres[i] = geometry.boundingSphere; + boundingSpheresCV[i] = geometry.boundingSphereCV; + } + + var eastHemisphereGeometry = instance.eastHemisphereGeometry; + var westHemisphereGeometry = instance.westHemisphereGeometry; + if (defined(eastHemisphereGeometry) && defined(westHemisphereGeometry)) { + if (defined(eastHemisphereGeometry.boundingSphere) && defined(westHemisphereGeometry.boundingSphere)) { + boundingSpheres[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphere, westHemisphereGeometry.boundingSphere); + } + if (defined(eastHemisphereGeometry.boundingSphereCV) && defined(westHemisphereGeometry.boundingSphereCV)) { + boundingSpheresCV[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphereCV, westHemisphereGeometry.boundingSphereCV); + } + } + } + return { geometries : geometries, modelMatrix : parameters.modelMatrix, attributeLocations : attributeLocations, - vaAttributes : perInstanceAttributes, - vaAttributeLocations : indices, - validInstancesIndices : parameters.validInstancesIndices, - invalidInstancesIndices : parameters.invalidInstancesIndices, - pickOffsets : pickOffsets + pickOffsets : pickOffsets, + boundingSpheres : boundingSpheres, + boundingSpheresCV : boundingSpheresCV }; }; @@ -577,20 +346,6 @@ define([ } } - /** - * @private - */ - function transferPerInstanceAttributes(perInstanceAttributes, transferableObjects) { - var length = perInstanceAttributes.length; - for (var i = 0; i < length; ++i) { - var vaAttributes = perInstanceAttributes[i]; - var vaLength = vaAttributes.length; - for (var j = 0; j < vaLength; ++j) { - transferableObjects.push(vaAttributes[j].values.buffer); - } - } - } - // This function was created by simplifying packCreateGeometryResults into a count-only operation. function countCreateGeometryResults(items) { var count = 1; @@ -779,6 +534,7 @@ define([ primitiveType : primitiveType, geometryType : geometryType, boundingSphere : boundingSphere, + boundingSphereCV : boundingSphereCV, indices : indices, attributes : attributes }); @@ -787,47 +543,9 @@ define([ return result; }; - function packPickIds(pickIds, transferableObjects) { - var length = pickIds.length; - var packedPickIds = new Uint32Array(pickIds.length); - for (var i = 0; i < length; ++i) { - packedPickIds[i] = pickIds[i].toRgba(); - } - transferableObjects.push(packedPickIds.buffer); - return packedPickIds; - } - - function unpackPickIds(packedPickIds) { - var length = packedPickIds.length; - var pickIds = new Array(length); - for (var i = 0; i < length; i++) { - pickIds[i] = Color.fromRgba(packedPickIds[i]); - } - return pickIds; - } - - // This function was created by simplifying packInstancesForCombine into a count-only operation. - function countInstancesForCombine(instances) { - var length = instances.length; - var count = 1 + (length * 17); - for (var i = 0; i < length; i++) { - var attributes = instances[i].attributes; - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && defined(attributes[property])) { - var attribute = attributes[property]; - count += 5 + attribute.value.length; - } - } - } - return count; - } - function packInstancesForCombine(instances, transferableObjects) { - var packedData = new Float64Array(countInstancesForCombine(instances)); - var stringHash = {}; - var stringTable = []; - var length = instances.length; + var packedData = new Float64Array(1 + (length * 16)); var count = 0; packedData[count++] = length; for (var i = 0; i < length; i++) { @@ -835,43 +553,14 @@ define([ Matrix4.pack(instance.modelMatrix, packedData, count); count += Matrix4.packedLength; - - var attributes = instance.attributes; - var attributesToWrite = []; - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && defined(attributes[property])) { - attributesToWrite.push(property); - if (!defined(stringHash[property])) { - stringHash[property] = stringTable.length; - stringTable.push(property); - } - } - } - - packedData[count++] = attributesToWrite.length; - for (var q = 0; q < attributesToWrite.length; q++) { - var name = attributesToWrite[q]; - var attribute = attributes[name]; - packedData[count++] = stringHash[name]; - packedData[count++] = attribute.componentDatatype; - packedData[count++] = attribute.componentsPerAttribute; - packedData[count++] = attribute.normalize; - packedData[count++] = attribute.value.length; - packedData.set(attribute.value, count); - count += attribute.value.length; - } } transferableObjects.push(packedData.buffer); - return { - stringTable : stringTable, - packedData : packedData - }; + return packedData; } function unpackInstancesForCombine(data) { - var packedInstances = data.packedData; - var stringTable = data.stringTable; + var packedInstances = data; var result = new Array(packedInstances[0]); var count = 0; @@ -880,29 +569,7 @@ define([ var modelMatrix = Matrix4.unpack(packedInstances, i); i += Matrix4.packedLength; - var attributes = {}; - var numAttributes = packedInstances[i++]; - for (var x = 0; x < numAttributes; x++) { - var name = stringTable[packedInstances[i++]]; - var componentDatatype = packedInstances[i++]; - var componentsPerAttribute = packedInstances[i++]; - var normalize = packedInstances[i++] !== 0; - var length = packedInstances[i++]; - var value = ComponentDatatype.createTypedArray(componentDatatype, length); - for (var valueIndex = 0; valueIndex < length; valueIndex++) { - value[valueIndex] = packedInstances[i++]; - } - - attributes[name] = { - componentDatatype : componentDatatype, - componentsPerAttribute : componentsPerAttribute, - normalize : normalize, - value : value - }; - } - result[count++] = { - attributes : attributes, modelMatrix : modelMatrix }; } @@ -910,163 +577,6 @@ define([ return result; } - // This function was created by simplifying packAttributeLocations into a count-only operation. - function countAttributeLocations(attributeLocations) { - var length = attributeLocations.length; - var count = 1 + length; - for (var i = 0; i < length; i++) { - var instance = attributeLocations[i]; - - count += 2; - count += defined(instance.boundingSphere) ? BoundingSphere.packedLength : 0.0; - count += defined(instance.boundingSphereCV) ? BoundingSphere.packedLength : 0.0; - - for ( var propertyName in instance) { - if (instance.hasOwnProperty(propertyName) && defined(instance[propertyName]) && - propertyName !== 'boundingSphere' && propertyName !== 'boundingSphereCV') { - var property = instance[propertyName]; - count += 4 + (property.indices.length * 3) + property.value.length; - } - } - } - return count; - } - - function packAttributeLocations(attributeLocations, transferableObjects) { - var packedData = new Float64Array(countAttributeLocations(attributeLocations)); - var stringTable = []; - var attributeTable = []; - - var stringHash = {}; - var length = attributeLocations.length; - var count = 0; - packedData[count++] = length; - for (var i = 0; i < length; i++) { - var instance = attributeLocations[i]; - - var boundingSphere = instance.boundingSphere; - var hasBoundingSphere = defined(boundingSphere); - packedData[count++] = hasBoundingSphere ? 1.0 : 0.0; - if (hasBoundingSphere) { - BoundingSphere.pack(boundingSphere, packedData, count); - count += BoundingSphere.packedLength; - } - - boundingSphere = instance.boundingSphereCV; - hasBoundingSphere = defined(boundingSphere); - packedData[count++] = hasBoundingSphere ? 1.0 : 0.0; - if (hasBoundingSphere) { - BoundingSphere.pack(boundingSphere, packedData, count); - count += BoundingSphere.packedLength; - } - - var propertiesToWrite = []; - for ( var propertyName in instance) { - if (instance.hasOwnProperty(propertyName) && defined(instance[propertyName]) && - propertyName !== 'boundingSphere' && propertyName !== 'boundingSphereCV') { - propertiesToWrite.push(propertyName); - if (!defined(stringHash[propertyName])) { - stringHash[propertyName] = stringTable.length; - stringTable.push(propertyName); - } - } - } - - packedData[count++] = propertiesToWrite.length; - for (var q = 0; q < propertiesToWrite.length; q++) { - var name = propertiesToWrite[q]; - var property = instance[name]; - packedData[count++] = stringHash[name]; - packedData[count++] = property.valid ? 1.0 : 0.0; - - var indices = property.indices; - var indicesLength = indices.length; - packedData[count++] = indicesLength; - for (var x = 0; x < indicesLength; x++) { - var index = indices[x]; - packedData[count++] = index.count; - packedData[count++] = index.offset; - var tableIndex = attributeTable.indexOf(index.attribute); - if (tableIndex === -1) { - tableIndex = attributeTable.length; - attributeTable.push(index.attribute); - } - packedData[count++] = tableIndex; - } - - packedData[count++] = property.value.length; - packedData.set(property.value, count); - count += property.value.length; - } - } - - transferableObjects.push(packedData.buffer); - - return { - stringTable : stringTable, - packedData : packedData, - attributeTable : attributeTable - }; - } - - function unpackAttributeLocations(packedAttributeLocations, vaAttributes) { - var stringTable = packedAttributeLocations.stringTable; - var attributeTable = packedAttributeLocations.attributeTable; - var packedData = packedAttributeLocations.packedData; - - var attributeLocations = new Array(packedData[0]); - var attributeLocationsIndex = 0; - var i = 1; - var packedDataLength = packedData.length; - while (i < packedDataLength) { - var instance = {}; - - var hasBoundingSphere = packedData[i++] === 1.0; - if (hasBoundingSphere) { - instance.boundingSphere = BoundingSphere.unpack(packedData, i); - i += BoundingSphere.packedLength; - } - - hasBoundingSphere = packedData[i++] === 1.0; - if (hasBoundingSphere) { - instance.boundingSphereCV = BoundingSphere.unpack(packedData, i); - i += BoundingSphere.packedLength; - } - - var numAttributes = packedData[i++]; - for (var x = 0; x < numAttributes; x++) { - var name = stringTable[packedData[i++]]; - var valid = packedData[i++] === 1.0; - - var indicesLength = packedData[i++]; - var indices = indicesLength > 0 ? new Array(indicesLength) : undefined; - for (var indicesIndex = 0; indicesIndex < indicesLength; indicesIndex++) { - var index = {}; - index.count = packedData[i++]; - index.offset = packedData[i++]; - index.attribute = attributeTable[packedData[i++]]; - indices[indicesIndex] = index; - } - - var valueLength = packedData[i++]; - var value = valid ? ComponentDatatype.createTypedArray(indices[0].attribute.componentDatatype, valueLength) : new Array(valueLength); - for (var valueIndex = 0; valueIndex < valueLength; valueIndex++) { - value[valueIndex] = packedData[i++]; - } - - instance[name] = { - dirty : false, - valid : valid, - indices : indices, - value : value - }; - } - attributeLocations[attributeLocationsIndex++] = instance; - } - - return attributeLocations; - } - /** * @private */ @@ -1078,20 +588,13 @@ define([ transferableObjects.push(createGeometryResults[i].packedData.buffer); } - var packedPickIds; - if (parameters.allowPicking) { - packedPickIds = packPickIds(parameters.pickIds, transferableObjects); - } - return { createGeometryResults : parameters.createGeometryResults, packedInstances : packInstancesForCombine(parameters.instances, transferableObjects), - packedPickIds : packedPickIds, ellipsoid : parameters.ellipsoid, isGeographic : parameters.projection instanceof GeographicProjection, elementIndexUintSupported : parameters.elementIndexUintSupported, scene3DOnly : parameters.scene3DOnly, - allowPicking : parameters.allowPicking, vertexCacheOptimize : parameters.vertexCacheOptimize, compressVertices : parameters.compressVertices, modelMatrix : parameters.modelMatrix, @@ -1104,37 +607,17 @@ define([ */ PrimitivePipeline.unpackCombineGeometryParameters = function(packedParameters) { var instances = unpackInstancesForCombine(packedParameters.packedInstances); - var allowPicking = packedParameters.allowPicking; - var pickIds = allowPicking ? unpackPickIds(packedParameters.packedPickIds) : undefined; var createGeometryResults = packedParameters.createGeometryResults; var length = createGeometryResults.length; var instanceIndex = 0; - var validInstances = []; - var invalidInstances = []; - var validInstancesIndices = []; - var invalidInstancesIndices = []; - var validPickIds = []; - for (var resultIndex = 0; resultIndex < length; resultIndex++) { var geometries = PrimitivePipeline.unpackCreateGeometryResults(createGeometryResults[resultIndex]); var geometriesLength = geometries.length; for (var geometryIndex = 0; geometryIndex < geometriesLength; geometryIndex++) { var geometry = geometries[geometryIndex]; var instance = instances[instanceIndex]; - - if (defined(geometry)) { - instance.geometry = geometry; - validInstances.push(instance); - validInstancesIndices.push(instanceIndex); - if (allowPicking) { - validPickIds.push(pickIds[instanceIndex]); - } - } else { - invalidInstances.push(instance); - invalidInstancesIndices.push(instanceIndex); - } - + instance.geometry = geometry; ++instanceIndex; } } @@ -1143,16 +626,11 @@ define([ var projection = packedParameters.isGeographic ? new GeographicProjection(ellipsoid) : new WebMercatorProjection(ellipsoid); return { - instances : validInstances, - invalidInstances : invalidInstances, - validInstancesIndices : validInstancesIndices, - invalidInstancesIndices : invalidInstancesIndices, - pickIds : validPickIds, + instances : instances, ellipsoid : ellipsoid, projection : projection, elementIndexUintSupported : packedParameters.elementIndexUintSupported, scene3DOnly : packedParameters.scene3DOnly, - allowPicking : packedParameters.allowPicking, vertexCacheOptimize : packedParameters.vertexCacheOptimize, compressVertices : packedParameters.compressVertices, modelMatrix : Matrix4.clone(packedParameters.modelMatrix), @@ -1160,24 +638,62 @@ define([ }; }; + function packBoundingSpheres(boundingSpheres) { + var length = boundingSpheres.length; + var bufferLength = 1 + (BoundingSphere.packedLength + 1) * length; + var buffer = new Float32Array(bufferLength); + + var bufferIndex = 0; + buffer[bufferIndex++] = length; + + for (var i = 0; i < length; ++i) { + var bs = boundingSpheres[i]; + if (!defined(bs)) { + buffer[bufferIndex++] = 0.0; + } else { + buffer[bufferIndex++] = 1.0; + BoundingSphere.pack(boundingSpheres[i], buffer, bufferIndex); + } + bufferIndex += BoundingSphere.packedLength; + } + + return buffer; + } + + function unpackBoundingSpheres(buffer) { + var result = new Array(buffer[0]); + var count = 0; + + var i = 1; + while (i < buffer.length) { + if (buffer[i++] === 1.0) { + result[count++] = BoundingSphere.unpack(buffer, i); + } + i += BoundingSphere.packedLength; + } + + return result; + } + /** * @private */ PrimitivePipeline.packCombineGeometryResults = function(results, transferableObjects) { if (defined(results.geometries)) { transferGeometries(results.geometries, transferableObjects); - transferPerInstanceAttributes(results.vaAttributes, transferableObjects); } + var packedBoundingSpheres = packBoundingSpheres(results.boundingSpheres); + var packedBoundingSpheresCV = packBoundingSpheres(results.boundingSpheresCV); + transferableObjects.push(packedBoundingSpheres.buffer, packedBoundingSpheresCV.buffer); + return { geometries : results.geometries, attributeLocations : results.attributeLocations, - vaAttributes : results.vaAttributes, - packedVaAttributeLocations : packAttributeLocations(results.vaAttributeLocations, transferableObjects), modelMatrix : results.modelMatrix, - validInstancesIndices : results.validInstancesIndices, - invalidInstancesIndices : results.invalidInstancesIndices, - pickOffsets : results.pickOffsets + pickOffsets : results.pickOffsets, + boundingSpheres : packedBoundingSpheres, + boundingSpheresCV : packedBoundingSpheresCV }; }; @@ -1188,10 +704,10 @@ define([ return { geometries : packedResult.geometries, attributeLocations : packedResult.attributeLocations, - vaAttributes : packedResult.vaAttributes, - perInstanceAttributeLocations : unpackAttributeLocations(packedResult.packedVaAttributeLocations, packedResult.vaAttributes), modelMatrix : packedResult.modelMatrix, - pickOffsets : packedResult.pickOffsets + pickOffsets : packedResult.pickOffsets, + boundingSpheres : unpackBoundingSpheres(packedResult.boundingSpheres), + boundingSpheresCV : unpackBoundingSpheres(packedResult.boundingSpheresCV) }; }; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index d43b448aab00..23189bcbb990 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1108,15 +1108,13 @@ define([ var lightShadowMaps = frameState.shadowHints.lightShadowMaps; var lightShadowsEnabled = shadowsEnabled && (lightShadowMaps.length > 0); + // Update derived commands when any shadow maps become dirty var shadowsDirty = false; - if (shadowsEnabled && (command.receiveShadows || command.castShadows)) { - // Update derived commands when any shadow maps become dirty - var lastDirtyTime = frameState.shadowHints.lastDirtyTime; - if (command.lastDirtyTime !== lastDirtyTime) { - command.lastDirtyTime = lastDirtyTime; - command.dirty = true; - shadowsDirty = true; - } + var lastDirtyTime = frameState.shadowHints.lastDirtyTime; + if (command.lastDirtyTime !== lastDirtyTime) { + command.lastDirtyTime = lastDirtyTime; + command.dirty = true; + shadowsDirty = true; } if (command.dirty) { @@ -1131,7 +1129,8 @@ define([ var oit = scene._oit; if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) { if (lightShadowsEnabled && command.receiveShadows) { - derivedCommands.oit = oit.createDerivedCommands(command.derivedCommands.shadows.receiveCommand, context, derivedCommands.oit); + derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {}; + derivedCommands.oit.shadows = oit.createDerivedCommands(command.derivedCommands.shadows.receiveCommand, context, derivedCommands.oit.shadows); } else { derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit); } @@ -2144,8 +2143,14 @@ define([ var shadowMaps = frameState.shadowMaps; var length = shadowMaps.length; - frameState.shadowHints.shadowsEnabled = (length > 0) && !frameState.passes.pick && (scene.mode === SceneMode.SCENE3D); - if (!frameState.shadowHints.shadowsEnabled) { + var shadowsEnabled = (length > 0) && !frameState.passes.pick && (scene.mode === SceneMode.SCENE3D); + if (shadowsEnabled !== frameState.shadowHints.shadowsEnabled) { + // Update derived commands when shadowsEnabled changes + ++frameState.shadowHints.lastDirtyTime; + frameState.shadowHints.shadowsEnabled = shadowsEnabled; + } + + if (!shadowsEnabled) { return; } diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index 6007e8c2059d..b26b03f516ff 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -11,16 +11,12 @@ define([ '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', - '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/FeatureDetection', - '../Core/Geometry', - '../Core/GeometryAttribute', - '../Core/GeometryAttributes', '../Core/GeometryInstance', '../Core/Intersect', '../Core/Math', @@ -50,6 +46,7 @@ define([ './Camera', './CullFace', './CullingVolume', + './DebugCameraPrimitive', './OrthographicFrustum', './Pass', './PerInstanceColorAppearance', @@ -68,16 +65,12 @@ define([ Color, ColorGeometryInstanceAttribute, combine, - ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, DeveloperError, FeatureDetection, - Geometry, - GeometryAttribute, - GeometryAttributes, GeometryInstance, Intersect, CesiumMath, @@ -107,6 +100,7 @@ define([ Camera, CullFace, CullingVolume, + DebugCameraPrimitive, OrthographicFrustum, Pass, PerInstanceColorAppearance, @@ -877,54 +871,6 @@ define([ }); } - function createDebugFrustum(camera, color) { - var view = camera.viewMatrix; - var projection = camera.frustum.projectionMatrix; - var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); - var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); - - var positions = new Float64Array(8 * 3); - for (var i = 0; i < 8; ++i) { - var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]); - Matrix4.multiplyByVector(inverseViewProjection, corner, corner); - Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide - positions[i * 3 + 0] = corner.x; - positions[i * 3 + 1] = corner.y; - positions[i * 3 + 2] = corner.z; - } - - var attributes = new GeometryAttributes(); - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : positions - }); - - var indices = new Uint16Array([0,1,1,2,2,3,3,0,0,4,4,7,7,3,7,6,6,2,2,1,1,5,5,4,5,6]); - var geometry = new Geometry({ - attributes : attributes, - indices : indices, - primitiveType : PrimitiveType.LINES, - boundingSphere : new BoundingSphere.fromVertices(positions) - }); - - var debugFrustum = new Primitive({ - geometryInstances : new GeometryInstance({ - geometry : geometry, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(color) - } - }), - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false - }); - - return debugFrustum; - } - var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA]; var scratchScale = new Cartesian3(); @@ -939,7 +885,11 @@ define([ if (enterFreezeFrame) { // Recreate debug camera when entering freeze frame mode shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy(); - shadowMap._debugCameraFrustum = createDebugFrustum(shadowMap._sceneCamera, Color.CYAN); + shadowMap._debugCameraFrustum = new DebugCameraPrimitive({ + camera : shadowMap._sceneCamera, + color : Color.CYAN, + updateOnChange : false + }); } shadowMap._debugCameraFrustum.update(frameState); } @@ -950,7 +900,11 @@ define([ if (enterFreezeFrame) { // Recreate debug frustum when entering freeze frame mode shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy(); - shadowMap._debugLightFrustum = createDebugFrustum(shadowMap._shadowMapCamera, Color.YELLOW); + shadowMap._debugLightFrustum = new DebugCameraPrimitive({ + camera : shadowMap._shadowMapCamera, + color : Color.YELLOW, + updateOnChange : false + }); } shadowMap._debugLightFrustum.update(frameState); @@ -958,7 +912,11 @@ define([ if (enterFreezeFrame) { // Recreate debug frustum when entering freeze frame mode shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy(); - shadowMap._debugCascadeFrustums[i] = createDebugFrustum(shadowMap._passes[i].camera, debugOutlineColors[i]); + shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({ + camera : shadowMap._passes[i].camera, + color : debugOutlineColors[i], + updateOnChange : false + }); } shadowMap._debugCascadeFrustums[i].update(frameState); } @@ -977,7 +935,11 @@ define([ shadowMap._debugLightFrustum.update(frameState); } else { if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) { - shadowMap._debugLightFrustum = createDebugFrustum(shadowMap._shadowMapCamera, Color.YELLOW); + shadowMap._debugLightFrustum = new DebugCameraPrimitive({ + camera : shadowMap._shadowMapCamera, + color : Color.YELLOW, + updateOnChange : false + }); } shadowMap._debugLightFrustum.update(frameState); } diff --git a/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl index 59d74a27b25a..72e2081604ef 100644 --- a/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl @@ -4,6 +4,7 @@ attribute vec3 normal; attribute vec3 tangent; attribute vec3 binormal; attribute vec2 st; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl index 6fe9772ba516..0d496a877e7c 100644 --- a/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl index 61f12dcb32e2..1bd13c9a5382 100644 --- a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec2 st; +attribute float batchId; varying vec3 v_positionMC; varying vec3 v_positionEC; diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl index aa8de58a0ba9..b429e64c6896 100644 --- a/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl @@ -2,6 +2,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec4 color; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl index 44c759da268e..ce015c678423 100644 --- a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec4 color; +attribute float batchId; varying vec4 v_color; diff --git a/Source/Shaders/Appearances/PointAppearanceVS.glsl b/Source/Shaders/Appearances/PointAppearanceVS.glsl index dd4a639ea7fa..4d41d523be98 100644 --- a/Source/Shaders/Appearances/PointAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PointAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 color; +attribute float batchId; uniform float pointSize; diff --git a/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl b/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl index f096e4a0d676..db7c610d10d6 100644 --- a/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl @@ -6,6 +6,7 @@ attribute vec3 nextPosition3DHigh; attribute vec3 nextPosition3DLow; attribute vec2 expandAndWidth; attribute vec4 color; +attribute float batchId; varying vec4 v_color; diff --git a/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl index 9148abc52bce..dcb40b5ad322 100644 --- a/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl @@ -6,6 +6,7 @@ attribute vec3 nextPosition3DHigh; attribute vec3 nextPosition3DLow; attribute vec2 expandAndWidth; attribute vec2 st; +attribute float batchId; varying float v_width; varying vec2 v_st; diff --git a/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl index b606f12a0ef1..19c102bba321 100644 --- a/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl @@ -2,6 +2,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec2 st; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/BillboardCollectionVS.glsl b/Source/Shaders/BillboardCollectionVS.glsl index 9ceae522f107..232661ddb6eb 100644 --- a/Source/Shaders/BillboardCollectionVS.glsl +++ b/Source/Shaders/BillboardCollectionVS.glsl @@ -9,6 +9,7 @@ attribute vec4 compressedAttribute2; // image height, color, pick color, attribute vec4 eyeOffset; // eye offset in meters, 4 bytes free (texture range) attribute vec4 scaleByDistance; // near, nearScale, far, farScale attribute vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale +attribute vec2 distanceDisplayCondition; // near, far varying vec2 v_textureCoordinates; @@ -39,22 +40,8 @@ vec4 computePositionWindowCoordinates(vec4 positionEC, vec2 imageSize, float sca { vec2 halfSize = imageSize * scale * czm_resolutionScale; halfSize *= ((direction * 2.0) - 1.0); - - if (sizeInMeters) - { - positionEC.xy += halfSize; - } - - vec4 positionWC = czm_eyeToWindowCoordinates(positionEC); - - if (sizeInMeters) - { - positionWC.xy += (origin * abs(halfSize)) / czm_metersPerPixel(positionEC); - } - else - { - positionWC.xy += (origin * abs(halfSize)); - } + + vec2 originTranslate = origin * abs(halfSize); #if defined(ROTATION) || defined(ALIGNED_AXIS) if (validAlignedAxis || rotation != 0.0) @@ -75,7 +62,20 @@ vec4 computePositionWindowCoordinates(vec4 positionEC, vec2 imageSize, float sca halfSize = rotationMatrix * halfSize; } #endif - + + if (sizeInMeters) + { + positionEC.xy += halfSize; + } + + vec4 positionWC = czm_eyeToWindowCoordinates(positionEC); + + if (sizeInMeters) + { + originTranslate += originTranslate / czm_metersPerPixel(positionEC); + } + + positionWC.xy += originTranslate; if (!sizeInMeters) { positionWC.xy += halfSize; @@ -203,7 +203,7 @@ void main() /////////////////////////////////////////////////////////////////////////// -#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) +#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) || defined(DISTANCE_DISPLAY_CONDITION) float lengthSq; if (czm_sceneMode == czm_sceneMode2D) { @@ -241,6 +241,15 @@ void main() pixelOffset *= pixelOffsetScale; #endif +#ifdef DISTANCE_DISPLAY_CONDITION + float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; + float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; + if (lengthSq < nearSq || lengthSq > farSq) + { + positionEC.xyz = vec3(0.0); + } +#endif + vec4 positionWC = computePositionWindowCoordinates(positionEC, imageSize, scale, direction, origin, translate, pixelOffset, alignedAxis, validAlignedAxis, rotation, sizeInMeters); gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0); v_textureCoordinates = textureCoordinates; diff --git a/Source/Shaders/Builtin/Functions/decompressTextureCoordinates.glsl b/Source/Shaders/Builtin/Functions/decompressTextureCoordinates.glsl index c91a5e87473c..fa2a6814382c 100644 --- a/Source/Shaders/Builtin/Functions/decompressTextureCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/decompressTextureCoordinates.glsl @@ -10,7 +10,8 @@ vec2 czm_decompressTextureCoordinates(float encoded) { float temp = encoded / 4096.0; - float stx = floor(temp) / 4096.0; - float sty = temp - floor(temp); + float xZeroTo4095 = floor(temp); + float stx = xZeroTo4095 / 4095.0; + float sty = (encoded - xZeroTo4095 * 4096.0) / 4095.0; return vec2(stx, sty); } diff --git a/Source/Shaders/PointPrimitiveCollectionVS.glsl b/Source/Shaders/PointPrimitiveCollectionVS.glsl index 629c8693f210..a11ea4b95f6b 100644 --- a/Source/Shaders/PointPrimitiveCollectionVS.glsl +++ b/Source/Shaders/PointPrimitiveCollectionVS.glsl @@ -5,6 +5,7 @@ attribute vec4 positionLowAndOutline; attribute vec4 compressedAttribute0; // color, outlineColor, pick color attribute vec4 compressedAttribute1; // show, translucency by distance, some free space attribute vec4 scaleByDistance; // near, nearScale, far, farScale +attribute vec2 distanceDisplayCondition; // near, far varying vec4 v_color; varying vec4 v_outlineColor; @@ -101,7 +102,7 @@ void main() /////////////////////////////////////////////////////////////////////////// -#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) +#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(DISTANCE_DISPLAY_CONDITION) float lengthSq; if (czm_sceneMode == czm_sceneMode2D) { @@ -138,6 +139,14 @@ void main() } #endif +#ifdef DISTANCE_DISPLAY_CONDITION + float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; + float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; + if (lengthSq < nearSq || lengthSq > farSq) { + positionEC.xyz = vec3(0.0); + } +#endif + vec4 positionWC = czm_eyeToWindowCoordinates(positionEC); gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0); diff --git a/Source/Shaders/PolylineVS.glsl b/Source/Shaders/PolylineVS.glsl index 50c6b9d2e8e3..6b72184c19a2 100644 --- a/Source/Shaders/PolylineVS.glsl +++ b/Source/Shaders/PolylineVS.glsl @@ -10,8 +10,7 @@ attribute vec3 nextPosition3DHigh; attribute vec3 nextPosition3DLow; attribute vec3 nextPosition2DHigh; attribute vec3 nextPosition2DLow; -attribute vec4 texCoordExpandWidthAndShow; -attribute vec4 pickColor; +attribute vec4 texCoordExpandAndBatchIndex; varying vec2 v_st; varying float v_width; @@ -19,11 +18,21 @@ varying vec4 czm_pickColor; void main() { - float texCoord = texCoordExpandWidthAndShow.x; - float expandDir = texCoordExpandWidthAndShow.y; - float width = abs(texCoordExpandWidthAndShow.z) + 0.5; - bool usePrev = texCoordExpandWidthAndShow.z < 0.0; - float show = texCoordExpandWidthAndShow.w; + float texCoord = texCoordExpandAndBatchIndex.x; + float expandDir = texCoordExpandAndBatchIndex.y; + bool usePrev = texCoordExpandAndBatchIndex.z < 0.0; + float batchTableIndex = texCoordExpandAndBatchIndex.w; + + vec2 widthAndShow = batchTable_getWidthAndShow(batchTableIndex); + float width = widthAndShow.x + 0.5; + float show = widthAndShow.y; + + if (width < 1.0) + { + show = 0.0; + } + + vec4 pickColor = batchTable_getPickColor(batchTableIndex); vec4 p, prev, next; if (czm_morphTime == 1.0) @@ -53,6 +62,32 @@ void main() czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz), czm_morphTime); } + + #ifdef DISTANCE_DISPLAY_CONDITION + vec3 centerHigh = batchTable_getCenterHigh(batchTableIndex); + vec4 centerLowAndRadius = batchTable_getCenterLowAndRadius(batchTableIndex); + vec3 centerLow = centerLowAndRadius.xyz; + float radius = centerLowAndRadius.w; + vec2 distanceDisplayCondition = batchTable_getDistanceDisplayCondition(batchTableIndex); + + float lengthSq; + if (czm_sceneMode == czm_sceneMode2D) + { + lengthSq = czm_eyeHeight2D.y; + } + else + { + vec4 center = czm_translateRelativeToEye(centerHigh.xyz, centerLow.xyz); + lengthSq = max(0.0, dot(center.xyz, center.xyz) - radius * radius); + } + + float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; + float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; + if (lengthSq < nearSq || lengthSq > farSq) + { + show = 0.0; + } + #endif vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev); gl_Position = czm_viewportOrthographic * positionWC * show; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 81b3c90294c6..a97b96c1752b 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec4 color; +attribute float batchId; // emulated noperspective varying float v_WindowZ; diff --git a/Source/ThirdParty/kdbush.js b/Source/ThirdParty/kdbush.js new file mode 100644 index 000000000000..bdcf198311a7 --- /dev/null +++ b/Source/ThirdParty/kdbush.js @@ -0,0 +1,216 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kdbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]); + } + continue; + } + + var m = Math.floor((left + right) / 2); + + x = coords[2 * m]; + y = coords[2 * m + 1]; + + if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); + + var nextAxis = (axis + 1) % 2; + + if (axis === 0 ? minX <= x : minY <= y) { + stack.push(left); + stack.push(m - 1); + stack.push(nextAxis); + } + if (axis === 0 ? maxX >= x : maxY >= y) { + stack.push(m + 1); + stack.push(right); + stack.push(nextAxis); + } + } + + return result; +} + +},{}],3:[function(require,module,exports){ +'use strict'; + +module.exports = sortKD; + +function sortKD(ids, coords, nodeSize, left, right, depth) { + if (right - left <= nodeSize) return; + + var m = Math.floor((left + right) / 2); + + select(ids, coords, m, left, right, depth % 2); + + sortKD(ids, coords, nodeSize, left, m - 1, depth + 1); + sortKD(ids, coords, nodeSize, m + 1, right, depth + 1); +} + +function select(ids, coords, k, left, right, inc) { + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + select(ids, coords, k, newLeft, newRight, inc); + } + + var t = coords[2 * k + inc]; + var i = left; + var j = right; + + swapItem(ids, coords, left, k); + if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right); + + while (i < j) { + swapItem(ids, coords, i, j); + i++; + j--; + while (coords[2 * i + inc] < t) i++; + while (coords[2 * j + inc] > t) j--; + } + + if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j); + else { + j++; + swapItem(ids, coords, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} + +function swapItem(ids, coords, i, j) { + swap(ids, i, j); + swap(coords, 2 * i, 2 * j); + swap(coords, 2 * i + 1, 2 * j + 1); +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +},{}],4:[function(require,module,exports){ +'use strict'; + +module.exports = within; + +function within(ids, coords, qx, qy, r, nodeSize) { + var stack = [0, ids.length - 1, 0]; + var result = []; + var r2 = r * r; + + while (stack.length) { + var axis = stack.pop(); + var right = stack.pop(); + var left = stack.pop(); + + if (right - left <= nodeSize) { + for (var i = left; i <= right; i++) { + if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]); + } + continue; + } + + var m = Math.floor((left + right) / 2); + + var x = coords[2 * m]; + var y = coords[2 * m + 1]; + + if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]); + + var nextAxis = (axis + 1) % 2; + + if (axis === 0 ? qx - r <= x : qy - r <= y) { + stack.push(left); + stack.push(m - 1); + stack.push(nextAxis); + } + if (axis === 0 ? qx + r >= x : qy + r >= y) { + stack.push(m + 1); + stack.push(right); + stack.push(nextAxis); + } + } + + return result; +} + +function sqDist(ax, ay, bx, by) { + var dx = ax - bx; + var dy = ay - by; + return dx * dx + dy * dy; +} + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/Source/Widgets/CesiumWidget/CesiumWidget.js b/Source/Widgets/CesiumWidget/CesiumWidget.js index 2e6c6f6e38ff..8e2da0c334a3 100644 --- a/Source/Widgets/CesiumWidget/CesiumWidget.js +++ b/Source/Widgets/CesiumWidget/CesiumWidget.js @@ -282,15 +282,7 @@ define([ } if (globe !== false) { scene.globe = globe; - // If the passed in value is a boolean, convert to the ShadowMode enum. - var terrainShadows = options.terrainShadows; - if (terrainShadows === true) { - scene.globe.shadows = ShadowMode.ENABLED; - } else if (terrainShadows === false) { - scene.globe.shadows = ShadowMode.RECEIVE_ONLY; - } else { - scene.globe.shadows = defaultValue(terrainShadows, ShadowMode.RECEIVE_ONLY); - } + scene.globe.shadows = defaultValue(options.terrainShadows, ShadowMode.RECEIVE_ONLY); } var skyBox = options.skyBox; diff --git a/Source/Widgets/Timeline/Timeline.js b/Source/Widgets/Timeline/Timeline.js index 32eef7c42fca..4521a69d65a4 100644 --- a/Source/Widgets/Timeline/Timeline.js +++ b/Source/Widgets/Timeline/Timeline.js @@ -702,9 +702,9 @@ define([ var len = e.touches.length, leftX = timeline._topDiv.getBoundingClientRect().left; if (timeline._touchMode === timelineTouchMode.singleTap) { timeline._touchMode = timelineTouchMode.scrub; - timeline._handleTouchMove(e); + timeline._onTouchMove(e); } else if (timeline._touchMode === timelineTouchMode.scrub) { - timeline._handleTouchMove(e); + timeline._onTouchMove(e); } timeline._mouseMode = timelineMouseMode.touchOnly; if (len !== 1) { @@ -790,4 +790,4 @@ define([ }; return Timeline; -}); +}); \ No newline at end of file diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 8bf59948fc4f..3b780eae4d53 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -974,14 +974,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to return this.scene.globe.shadows; }, set : function(value) { - // If the passed in value is a boolean, convert to the ShadowMode enum. - if (value === true) { - this.scene.globe.shadows = ShadowMode.ENABLED; - } else if (value === false) { - this.scene.globe.shadows = ShadowMode.RECEIVE_ONLY; - } else { - this.scene.globe.shadows = value; - } + this.scene.globe.shadows = value; } }, diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index f5c0d28a34e1..bfb21466cda1 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -79,7 +79,7 @@ define([ //Local variables to be closed over by defineProperties. var dropEnabled = true; - var flyToOnDrop = true; + var flyToOnDrop = defaultValue(options.flyToOnDrop, true); var dropError = new Event(); var clearOnDrop = defaultValue(options.clearOnDrop, true); var dropTarget = defaultValue(options.dropTarget, viewer.container); diff --git a/Specs/Core/AttributeCompressionSpec.js b/Specs/Core/AttributeCompressionSpec.js index 1a37151f3380..574bda8995bf 100644 --- a/Specs/Core/AttributeCompressionSpec.js +++ b/Specs/Core/AttributeCompressionSpec.js @@ -490,24 +490,49 @@ defineSuite([ it('compresses texture coordinates', function() { var coords = new Cartesian2(0.5, 0.5); - expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqual(coords); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqualEpsilon(coords, 1.0 / 4096.0); }); - + it('compress texture coordinates throws without texture coordinates', function() { expect(function() { AttributeCompression.compressTextureCoordinates(undefined); }).toThrowDeveloperError(); }); - + it('decompress texture coordinates throws without encoded texture coordinates', function() { expect(function() { AttributeCompression.decompressTextureCoordinates(undefined, new Cartesian2()); }).toThrowDeveloperError(); }); - + it('decompress texture coordinates throws without result', function() { expect(function() { AttributeCompression.decompressTextureCoordinates(0.0, undefined); }).toThrowDeveloperError(); }); + + it('compresses/decompresses 1.0', function() { + var coords = new Cartesian2(1.0, 1.0); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqual(coords); + }); + + it('compresses/decompresses 0.0', function() { + var coords = new Cartesian2(1.0, 1.0); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqual(coords); + }); + + it('compresses/decompresses 0.5 / 1.0', function() { + var coords = new Cartesian2(0.5, 1.0); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqualEpsilon(coords, 1.0 / 4095.0); + }); + + it('compresses/decompresses 1.0 / 0.5', function() { + var coords = new Cartesian2(1.0, 0.5); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqualEpsilon(coords, 1.0 / 4095.0); + }); + + it('compresses/decompresses values very close but not equal to 1.0', function() { + var coords = new Cartesian2(0.99999999999999, 0.99999999999999); + expect(AttributeCompression.decompressTextureCoordinates(AttributeCompression.compressTextureCoordinates(coords), new Cartesian2())).toEqualEpsilon(coords, 1.0 / 4095.0); + }); }); diff --git a/Specs/Core/DistanceDisplayConditionGeometryInstanceAttributeSpec.js b/Specs/Core/DistanceDisplayConditionGeometryInstanceAttributeSpec.js new file mode 100644 index 000000000000..ee16c16be984 --- /dev/null +++ b/Specs/Core/DistanceDisplayConditionGeometryInstanceAttributeSpec.js @@ -0,0 +1,71 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/ComponentDatatype', + 'Core/DistanceDisplayCondition' + ], function( + DistanceDisplayConditionGeometryInstanceAttribute, + ComponentDatatype, + DistanceDisplayCondition) { + 'use strict'; + + it('constructor', function() { + var attribute = new DistanceDisplayConditionGeometryInstanceAttribute(10.0, 100.0); + expect(attribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(attribute.componentsPerAttribute).toEqual(2); + expect(attribute.normalize).toEqual(false); + + var value = new Float32Array([10.0, 100.0]); + expect(attribute.value).toEqual(value); + }); + + it('constructor throws with far > near', function() { + expect(function() { + return new DistanceDisplayConditionGeometryInstanceAttribute(100.0, 10.0); + }).toThrowDeveloperError(); + }); + + it('fromDistanceDisplayCondition', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var attribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(dc); + expect(attribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(attribute.componentsPerAttribute).toEqual(2); + expect(attribute.normalize).toEqual(false); + + var value = new Float32Array([dc.near, dc.far]); + expect(attribute.value).toEqual(value); + }); + + it('fromDistanceDisplayCondition throws without distanceDisplayCondition', function() { + expect(function() { + DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(); + }).toThrowDeveloperError(); + }); + + it('fromDistanceDisplayCondition throws with far >= near', function() { + expect(function() { + DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(new DistanceDisplayCondition(100.0, 10.0)); + }).toThrowDeveloperError(); + }); + + it('toValue', function() { + var dc = new DistanceDisplayCondition(10.0, 200.0); + var expectedResult = new Float32Array([dc.near, dc.far]); + expect(DistanceDisplayConditionGeometryInstanceAttribute.toValue(dc)).toEqual(expectedResult); + }); + + it('toValue works with result parameter', function() { + var dc = new DistanceDisplayCondition(10.0, 200.0); + var expectedResult = new Float32Array([dc.near, dc.far]); + var result = new Float32Array(2); + var returnedResult = DistanceDisplayConditionGeometryInstanceAttribute.toValue(dc, result); + expect(returnedResult).toBe(result); + expect(returnedResult).toEqual(expectedResult); + }); + + it('toValue throws without a distanceDisplayCondition', function() { + expect(function() { + DistanceDisplayConditionGeometryInstanceAttribute.toValue(); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/Core/DistanceDisplayConditionSpec.js b/Specs/Core/DistanceDisplayConditionSpec.js new file mode 100644 index 000000000000..759bd871e2f7 --- /dev/null +++ b/Specs/Core/DistanceDisplayConditionSpec.js @@ -0,0 +1,95 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/DistanceDisplayCondition', + 'Core/Cartesian3', + 'Core/Matrix4' + ], function( + DistanceDisplayCondition, + Cartesian3, + Matrix4) { + 'use strict'; + + it('default constructs', function() { + var dc = new DistanceDisplayCondition(); + expect(dc.near).toEqual(0.0); + expect(dc.far).toEqual(Number.MAX_VALUE); + }); + + it('constructs with parameters', function() { + var near = 10.0; + var far = 100.0; + var dc = new DistanceDisplayCondition(near, far); + expect(dc.near).toEqual(near); + expect(dc.far).toEqual(far); + }); + + it('gets and sets properties', function() { + var dc = new DistanceDisplayCondition(); + + var near = 10.0; + var far = 100.0; + dc.near = near; + dc.far = far; + + expect(dc.near).toEqual(near); + expect(dc.far).toEqual(far); + }); + + it('determines equality with static function', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + expect(DistanceDisplayCondition.equals(dc, new DistanceDisplayCondition(10.0, 100.0))).toEqual(true); + expect(DistanceDisplayCondition.equals(dc, new DistanceDisplayCondition(11.0, 100.0))).toEqual(false); + expect(DistanceDisplayCondition.equals(dc, new DistanceDisplayCondition(10.0, 101.0))).toEqual(false); + expect(DistanceDisplayCondition.equals(dc, undefined)).toEqual(false); + }); + + it('determines equality with prototype function', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + expect(dc.equals(new DistanceDisplayCondition(10.0, 100.0))).toEqual(true); + expect(dc.equals(new DistanceDisplayCondition(11.0, 100.0))).toEqual(false); + expect(dc.equals(new DistanceDisplayCondition(10.0, 101.0))).toEqual(false); + expect(dc.equals(undefined)).toEqual(false); + }); + + it('static clones', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var result = DistanceDisplayCondition.clone(dc); + expect(dc).toEqual(result); + }); + + it('static clone with a result parameter', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var result = new DistanceDisplayCondition(); + var returnedResult = DistanceDisplayCondition.clone(dc, result); + expect(dc).not.toBe(result); + expect(result).toBe(returnedResult); + expect(dc).toEqual(result); + }); + + it('static clone works with a result parameter that is an input parameter', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var returnedResult = DistanceDisplayCondition.clone(dc, dc); + expect(dc).toBe(returnedResult); + }); + + it('clones', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var result = dc.clone(); + expect(dc).toEqual(result); + }); + + it('clone with a result parameter', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var result = new DistanceDisplayCondition(); + var returnedResult = dc.clone(result); + expect(dc).not.toBe(result); + expect(result).toBe(returnedResult); + expect(dc).toEqual(result); + }); + + it('clone works with a result parameter that is an input parameter', function() { + var dc = new DistanceDisplayCondition(10.0, 100.0); + var returnedResult = dc.clone(dc); + expect(dc).toBe(returnedResult); + }); +}); \ No newline at end of file diff --git a/Specs/Core/HeightmapTerrainDataSpec.js b/Specs/Core/HeightmapTerrainDataSpec.js index f67827de3459..809ba6b61585 100644 --- a/Specs/Core/HeightmapTerrainDataSpec.js +++ b/Specs/Core/HeightmapTerrainDataSpec.js @@ -246,6 +246,29 @@ defineSuite([ expect(upsampled._buffer).toEqual([2, 1, 0, 3, 1, 0, 3, 1, 0, 4, 1, 0, 4, 1, 0, 5, 1, 0, 5, 1, 0, 6, 1, 0, 6, 1, 0, 7, 1, 0, 7, 1, 0, 8, 1, 0, 8, 1, 0, 9, 1, 0, 9, 1, 0, 10, 1, 0]); }); }); + + it('upsample clamps out of range data', function() { + data = new HeightmapTerrainData({ + buffer : new Float32Array([-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]), + width : 4, + height : 4, + structure : { + stride: 1, + elementsPerHeight: 1, + lowestEncodedHeight : 1, + highestEncodedHeight : 7 + } + }); + + return data.createMesh(tilingScheme, 0, 0, 0, 1).then(function() { + return data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); + }).then(function(upsampled) { + expect(upsampled.wasCreatedByUpsampling()).toBe(true); + expect(upsampled._width).toBe(4); + expect(upsampled._height).toBe(4); + expect(upsampled._buffer).toEqual([1, 1, 1, 1, 2, 1.5, 2, 1.5, 5, 5.5, 6, 6.5, 7, 7, 7, 7]); + }); + }); }); describe('isChildAvailable', function() { diff --git a/Specs/Core/MathSpec.js b/Specs/Core/MathSpec.js index e28b9bd9f571..a52fa67e1bff 100644 --- a/Specs/Core/MathSpec.js +++ b/Specs/Core/MathSpec.js @@ -156,6 +156,20 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('clampToLatitudeRange (1)', function() { + expect(CesiumMath.clampToLatitudeRange(Math.PI)).toEqual(CesiumMath.PI_OVER_TWO); + }); + + it('clampToLatitudeRange (2)', function() { + expect(CesiumMath.clampToLatitudeRange(-Math.PI)).toEqual(-CesiumMath.PI_OVER_TWO); + }); + + it('clampToLatitudeRange throws for undefined', function() { + expect(function() { + CesiumMath.clampToLatitudeRange(); + }).toThrowDeveloperError(); + }); + it('negativePiToPi positive', function() { expect(CesiumMath.negativePiToPi((Math.PI / 2) * Math.PI)).toEqualEpsilon((Math.PI / 2) * Math.PI - CesiumMath.TWO_PI, CesiumMath.EPSILON16); expect(CesiumMath.negativePiToPi(Math.PI / 0.5)).toEqualEpsilon(0.0, CesiumMath.EPSILON16); diff --git a/Specs/Core/TerrainEncodingSpec.js b/Specs/Core/TerrainEncodingSpec.js index e2f4b3195bf0..9928217ae848 100644 --- a/Specs/Core/TerrainEncodingSpec.js +++ b/Specs/Core/TerrainEncodingSpec.js @@ -183,7 +183,7 @@ defineSuite([ expect(encoding.getStride()).toEqual(3); expect(buffer.length).toEqual(encoding.getStride()); - expect(encoding.decodeTextureCoordinates(buffer, 0)).toEqualEpsilon(texCoords, CesiumMath.EPSILON14); + expect(encoding.decodeTextureCoordinates(buffer, 0)).toEqualEpsilon(texCoords, 1.0 / 4095.0); }); it('encodes textureCoordinates with quantization and normals', function() { @@ -198,7 +198,7 @@ defineSuite([ expect(encoding.getStride()).toEqual(4); expect(buffer.length).toEqual(encoding.getStride()); - expect(encoding.decodeTextureCoordinates(buffer, 0)).toEqualEpsilon(texCoords, CesiumMath.EPSILON14); + expect(encoding.decodeTextureCoordinates(buffer, 0)).toEqualEpsilon(texCoords, 1.0 / 4095.0); }); it('encodes height with quantization and without normals', function() { @@ -214,7 +214,7 @@ defineSuite([ expect(encoding.getStride()).toEqual(3); expect(buffer.length).toEqual(encoding.getStride()); - expect(encoding.decodeHeight(buffer, 0)).toEqualEpsilon(height, CesiumMath.EPSILON14); + expect(encoding.decodeHeight(buffer, 0)).toEqualEpsilon(height, 200.0 / 4095.0); }); it('encodes height with quantization and normals', function() { @@ -230,7 +230,7 @@ defineSuite([ expect(encoding.getStride()).toEqual(4); expect(buffer.length).toEqual(encoding.getStride()); - expect(encoding.decodeHeight(buffer, 0)).toEqualEpsilon(height, CesiumMath.EPSILON14); + expect(encoding.decodeHeight(buffer, 0)).toEqualEpsilon(height, 200.0 / 4095.0); }); it('gets oct-encoded normal', function() { diff --git a/Specs/Core/TrustedServersSpec.js b/Specs/Core/TrustedServersSpec.js new file mode 100644 index 000000000000..0eee872ec672 --- /dev/null +++ b/Specs/Core/TrustedServersSpec.js @@ -0,0 +1,80 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/TrustedServers' +], function( + TrustedServers) { + 'use strict'; + + afterEach(function() { + TrustedServers.clear(); + }); + + it('add without argument throws', function() { + expect(function() { + TrustedServers.add(); + }).toThrowDeveloperError(); + }); + + it('remove without argument throws', function() { + expect(function() { + TrustedServers.remove(); + }).toThrowDeveloperError(); + }); + + it('isTrusted without argument throws', function() { + expect(function() { + TrustedServers.contains(); + }).toThrowDeveloperError(); + }); + + it('http without a port', function() { + TrustedServers.add('cesiumjs.org', 80); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(true); + expect(TrustedServers.contains('https://cesiumjs.org/index.html')).toBe(false); + }); + + it('https without a port', function() { + TrustedServers.add('cesiumjs.org', 443); + expect(TrustedServers.contains('https://cesiumjs.org/index.html')).toBe(true); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(false); + }); + + it('add', function() { + expect(TrustedServers.contains('http://cesiumjs.org:81/index.html')).toBe(false); + TrustedServers.add('cesiumjs.org', 81); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(false); + expect(TrustedServers.contains('http://cesiumjs.org:81/index.html')).toBe(true); + }); + + it('remove', function() { + TrustedServers.add('cesiumjs.org', 81); + expect(TrustedServers.contains('http://cesiumjs.org:81/index.html')).toBe(true); + TrustedServers.remove('cesiumjs.org', 8080); + expect(TrustedServers.contains('http://cesiumjs.org:81/index.html')).toBe(true); + TrustedServers.remove('cesiumjs.org', 81); + expect(TrustedServers.contains('http://cesiumjs.org:81/index.html')).toBe(false); + }); + + it('handles username/password credentials', function() { + TrustedServers.add('cesiumjs.org', 81); + expect(TrustedServers.contains('http://user:pass@cesiumjs.org:81/index.html')).toBe(true); + }); + + it('always returns false for relative paths', function() { + expect(TrustedServers.contains('./data/index.html')).toBe(false); + }); + + it('handles protocol relative URLs', function() { + TrustedServers.add('cesiumjs.org', 80); + expect(TrustedServers.contains('//cesiumjs.org/index.html')).toBe(true); + }); + + it('clear', function() { + TrustedServers.add('cesiumjs.org', 80); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(true); + TrustedServers.clear(); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(false); + TrustedServers.add('cesiumjs.org', 80); + expect(TrustedServers.contains('http://cesiumjs.org/index.html')).toBe(true); + }); +}); diff --git a/Specs/Core/writeTextToCanvasSpec.js b/Specs/Core/writeTextToCanvasSpec.js index 56136f872066..96870daf1d5b 100644 --- a/Specs/Core/writeTextToCanvasSpec.js +++ b/Specs/Core/writeTextToCanvasSpec.js @@ -93,4 +93,41 @@ defineSuite([ // canvas2 is stroked, so there should be four "edges" expect(getColorChangeCount(canvas2)).toEqual(4); }); + + it('background color defaults to transparent', function() { + var canvas = writeTextToCanvas('a', { + font : '90px "Open Sans"' + }); + + var context = canvas.getContext('2d'); + var pixel = context.getImageData(0, 0, 1, 1).data; + expect(pixel).toEqual([0,0,0,0]); + }); + + it('background can be set', function() { + var canvas = writeTextToCanvas('a', { + font : '90px "Open Sans"', + backgroundColor : Color.RED + }); + + var context = canvas.getContext('2d'); + var pixel = context.getImageData(0, 0, 1, 1).data; + expect(pixel).toEqual([255,0,0,255]); + }); + + it('border can be set', function() { + var canvas1 = writeTextToCanvas('a', { + font : '90px "Open Sans"', + backgroundColor : Color.RED + }); + + var canvas2 = writeTextToCanvas('a', { + font : '90px "Open Sans"', + backgroundColor : Color.RED, + padding : 2 + }); + + expect(canvas2.width).toEqual(canvas1.width+4); + expect(canvas2.height).toEqual(canvas1.height+4); + }); }); diff --git a/Specs/DataSources/BillboardGraphicsSpec.js b/Specs/DataSources/BillboardGraphicsSpec.js index 123bdba159a4..efdbb17cb80d 100644 --- a/Specs/DataSources/BillboardGraphicsSpec.js +++ b/Specs/DataSources/BillboardGraphicsSpec.js @@ -4,6 +4,7 @@ defineSuite([ 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', 'Core/NearFarScalar', 'DataSources/ConstantProperty', 'Scene/HeightReference', @@ -14,6 +15,7 @@ defineSuite([ Cartesian2, Cartesian3, Color, + DistanceDisplayCondition, NearFarScalar, ConstantProperty, HeightReference, @@ -39,7 +41,8 @@ defineSuite([ scaleByDistance : new NearFarScalar(13, 14, 15, 16), translucencyByDistance : new NearFarScalar(17, 18, 19, 20), pixelOffsetScaleByDistance : new NearFarScalar(21, 22, 23, 24), - sizeInMeters : true + sizeInMeters : true, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) }; var billboard = new BillboardGraphics(options); @@ -59,6 +62,7 @@ defineSuite([ expect(billboard.translucencyByDistance).toBeInstanceOf(ConstantProperty); expect(billboard.pixelOffsetScaleByDistance).toBeInstanceOf(ConstantProperty); expect(billboard.sizeInMeters).toBeInstanceOf(ConstantProperty); + expect(billboard.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(billboard.image.getValue()).toEqual(options.image); expect(billboard.rotation.getValue()).toEqual(options.rotation); @@ -76,6 +80,7 @@ defineSuite([ expect(billboard.translucencyByDistance.getValue()).toEqual(options.translucencyByDistance); expect(billboard.pixelOffsetScaleByDistance.getValue()).toEqual(options.pixelOffsetScaleByDistance); expect(billboard.sizeInMeters.getValue()).toEqual(options.sizeInMeters); + expect(billboard.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -98,6 +103,7 @@ defineSuite([ source.translucencyByDistance = new ConstantProperty(new NearFarScalar()); source.pixelOffsetScaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.sizeInMeters = new ConstantProperty(true); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var target = new BillboardGraphics(); target.merge(source); @@ -120,6 +126,7 @@ defineSuite([ expect(target.translucencyByDistance).toBe(source.translucencyByDistance); expect(target.pixelOffsetScaleByDistance).toBe(source.pixelOffsetScaleByDistance); expect(target.sizeInMeters).toBe(source.sizeInMeters); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -142,6 +149,7 @@ defineSuite([ source.translucencyByDistance = new ConstantProperty(new NearFarScalar()); source.pixelOffsetScaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.sizeInMeters = new ConstantProperty(true); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var image = new ConstantProperty(''); var imageSubRegion = new ConstantProperty(); @@ -161,6 +169,7 @@ defineSuite([ var translucencyByDistance = new ConstantProperty(new NearFarScalar()); var pixelOffsetScaleByDistance = new ConstantProperty(new NearFarScalar()); var sizeInMeters = new ConstantProperty(true); + var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var target = new BillboardGraphics(); target.image = image; @@ -181,6 +190,7 @@ defineSuite([ target.translucencyByDistance = translucencyByDistance; target.pixelOffsetScaleByDistance = pixelOffsetScaleByDistance; target.sizeInMeters = sizeInMeters; + target.distanceDisplayCondition = distanceDisplayCondition; target.merge(source); @@ -202,6 +212,7 @@ defineSuite([ expect(target.translucencyByDistance).toBe(translucencyByDistance); expect(target.pixelOffsetScaleByDistance).toBe(pixelOffsetScaleByDistance); expect(target.sizeInMeters).toBe(sizeInMeters); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); }); it('clone works', function() { @@ -224,6 +235,7 @@ defineSuite([ source.translucencyByDistance = new ConstantProperty(new NearFarScalar()); source.pixelOffsetScaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.sizeInMeters = new ConstantProperty(true); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var result = source.clone(); expect(result.image).toBe(source.image); @@ -244,6 +256,7 @@ defineSuite([ expect(result.translucencyByDistance).toBe(source.translucencyByDistance); expect(result.pixelOffsetScaleByDistance).toBe(source.pixelOffsetScaleByDistance); expect(result.sizeInMeters).toBe(source.sizeInMeters); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/BillboardVisualizerSpec.js b/Specs/DataSources/BillboardVisualizerSpec.js index 10b032f27d04..9c9bfb03cd40 100644 --- a/Specs/DataSources/BillboardVisualizerSpec.js +++ b/Specs/DataSources/BillboardVisualizerSpec.js @@ -6,11 +6,13 @@ defineSuite([ 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', 'Core/JulianDate', 'Core/NearFarScalar', 'DataSources/BillboardGraphics', 'DataSources/BoundingSphereState', 'DataSources/ConstantProperty', + 'DataSources/EntityCluster', 'DataSources/EntityCollection', 'Scene/BillboardCollection', 'Scene/HeightReference', @@ -26,11 +28,13 @@ defineSuite([ Cartesian2, Cartesian3, Color, + DistanceDisplayCondition, JulianDate, NearFarScalar, BillboardGraphics, BoundingSphereState, ConstantProperty, + EntityCluster, EntityCollection, BillboardCollection, HeightReference, @@ -42,6 +46,7 @@ defineSuite([ 'use strict'; var scene; + var entityCluster; var visualizer; beforeAll(function() { @@ -53,11 +58,17 @@ defineSuite([ scene.destroyForSpecs(); }); + beforeEach(function() { + entityCluster = new EntityCluster(); + entityCluster._initialize(scene); + }); + afterEach(function() { visualizer = visualizer && visualizer.destroy(); + entityCluster.destroy(); }); - it('constructor throws if no scene is passed.', function() { + it('constructor throws if no entityCluster is passed.', function() { expect(function() { return new BillboardVisualizer(); }).toThrowDeveloperError(); @@ -65,7 +76,7 @@ defineSuite([ it('update throws if no time specified.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); expect(function() { visualizer.update(); }).toThrowDeveloperError(); @@ -73,7 +84,7 @@ defineSuite([ it('isDestroy returns false until destroyed.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); expect(visualizer.isDestroyed()).toEqual(false); visualizer.destroy(); expect(visualizer.isDestroyed()).toEqual(true); @@ -82,25 +93,25 @@ defineSuite([ it('removes the listener from the entity collection when destroyed', function() { var entityCollection = new EntityCollection(); - var visualizer = new BillboardVisualizer(scene, entityCollection); + var visualizer = new BillboardVisualizer(entityCluster, entityCollection); expect(entityCollection.collectionChanged.numberOfListeners).toEqual(1); - visualizer = visualizer.destroy(); + visualizer.destroy(); expect(entityCollection.collectionChanged.numberOfListeners).toEqual(0); }); it('object with no billboard does not create a billboard.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); visualizer.update(JulianDate.now()); - expect(scene.primitives.length).toEqual(0); + expect(entityCluster._billboardCollection).not.toBeDefined(); }); it('object with no position does not create a billboard.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); var billboard = testObject.billboard = new BillboardGraphics(); @@ -108,12 +119,12 @@ defineSuite([ billboard.image = new ConstantProperty('Data/Images/Blue.png'); visualizer.update(JulianDate.now()); - expect(scene.primitives.length).toEqual(0); + expect(entityCluster._billboardCollection).not.toBeDefined(); }); it('object with no image does not create a billboard.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); @@ -121,12 +132,12 @@ defineSuite([ billboard.show = new ConstantProperty(true); visualizer.update(JulianDate.now()); - expect(scene.primitives.length).toEqual(0); + expect(entityCluster._billboardCollection).not.toBeDefined(); }); it('A BillboardGraphics causes a Billboard to be created and updated.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); @@ -153,10 +164,11 @@ defineSuite([ billboard.translucencyByDistance = new ConstantProperty(new NearFarScalar()); billboard.pixelOffsetScaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); billboard.sizeInMeters = new ConstantProperty(true); + billboard.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); visualizer.update(time); - var billboardCollection = scene.primitives.get(0); + var billboardCollection = entityCluster._billboardCollection; expect(billboardCollection.length).toEqual(1); bb = billboardCollection.get(0); @@ -180,6 +192,7 @@ defineSuite([ expect(bb.translucencyByDistance).toEqual(testObject.billboard.translucencyByDistance.getValue(time)); expect(bb.pixelOffsetScaleByDistance).toEqual(testObject.billboard.pixelOffsetScaleByDistance.getValue(time)); expect(bb.sizeInMeters).toEqual(testObject.billboard.sizeInMeters.getValue(time)); + expect(bb.distanceDisplayCondition).toEqual(testObject.billboard.distanceDisplayCondition.getValue(time)); expect(bb._imageSubRegion).toEqual(testObject.billboard.imageSubRegion.getValue(time)); billboard.show = new ConstantProperty(false); @@ -194,7 +207,7 @@ defineSuite([ it('Reuses primitives when hiding one and showing another', function() { var time = JulianDate.now(); var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); @@ -204,7 +217,7 @@ defineSuite([ visualizer.update(time); - var billboardCollection = scene.primitives.get(0); + var billboardCollection = entityCluster._billboardCollection; expect(billboardCollection.length).toEqual(1); testObject.billboard.show = new ConstantProperty(false); @@ -225,7 +238,7 @@ defineSuite([ it('clear hides billboards.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); @@ -237,7 +250,7 @@ defineSuite([ billboard.image = new ConstantProperty('Data/Images/Blue.png'); visualizer.update(time); - var billboardCollection = scene.primitives.get(0); + var billboardCollection = entityCluster._billboardCollection; expect(billboardCollection.length).toEqual(1); var bb = billboardCollection.get(0); @@ -255,7 +268,7 @@ defineSuite([ it('Visualizer sets entity property.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); var time = JulianDate.now(); @@ -266,7 +279,7 @@ defineSuite([ billboard.image = new ConstantProperty('Data/Images/Blue.png'); visualizer.update(time); - var billboardCollection = scene.primitives.get(0); + var billboardCollection = entityCluster._billboardCollection; expect(billboardCollection.length).toEqual(1); var bb = billboardCollection.get(0); expect(bb.id).toEqual(testObject); @@ -274,7 +287,7 @@ defineSuite([ it('Computes bounding sphere.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); var time = JulianDate.now(); @@ -296,7 +309,7 @@ defineSuite([ it('Fails bounding sphere for entity without billboard.', function() { var entityCollection = new EntityCollection(); var testObject = entityCollection.getOrCreateEntity('test'); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); visualizer.update(JulianDate.now()); var result = new BoundingSphere(); var state = visualizer.getBoundingSphere(testObject, result); @@ -305,7 +318,7 @@ defineSuite([ it('Compute bounding sphere throws without entity.', function() { var entityCollection = new EntityCollection(); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); var result = new BoundingSphere(); expect(function() { visualizer.getBoundingSphere(undefined, result); @@ -315,7 +328,7 @@ defineSuite([ it('Compute bounding sphere throws without result.', function() { var entityCollection = new EntityCollection(); var testObject = entityCollection.getOrCreateEntity('test'); - visualizer = new BillboardVisualizer(scene, entityCollection); + visualizer = new BillboardVisualizer(entityCluster, entityCollection); expect(function() { visualizer.getBoundingSphere(testObject, undefined); }).toThrowDeveloperError(); diff --git a/Specs/DataSources/BoxGeometryUpdaterSpec.js b/Specs/DataSources/BoxGeometryUpdaterSpec.js index 288dd3f064bc..65a0a343f7ea 100644 --- a/Specs/DataSources/BoxGeometryUpdaterSpec.js +++ b/Specs/DataSources/BoxGeometryUpdaterSpec.js @@ -4,6 +4,8 @@ defineSuite([ 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/JulianDate', 'Core/ShowGeometryInstanceAttribute', 'Core/TimeInterval', @@ -26,6 +28,8 @@ defineSuite([ Cartesian3, Color, ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, JulianDate, ShowGeometryInstanceAttribute, TimeInterval, @@ -81,6 +85,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toBe(undefined); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); @@ -122,6 +127,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); }); @@ -157,6 +163,7 @@ defineSuite([ box.outline = new ConstantProperty(options.outline); box.outlineColor = new ConstantProperty(options.outlineColor); box.dimensions = new ConstantProperty(options.dimensions); + box.distanceDisplayCondition = options.distanceDisplayCondition; var updater = new BoxGeometryUpdater(entity, scene); @@ -175,6 +182,9 @@ defineSuite([ expect(attributes.color).toBeUndefined(); } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } if (options.outline) { @@ -185,6 +195,9 @@ defineSuite([ attributes = instance.attributes; expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } } @@ -210,6 +223,18 @@ defineSuite([ }); }); + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + fill : true, + outline : true, + outlineColor : Color.BLUE, + dimensions : new Cartesian3(1, 2, 3), + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + it('Correctly exposes outlineWidth', function() { var entity = createBasicBox(); entity.box.outlineWidth = new ConstantProperty(8); diff --git a/Specs/DataSources/BoxGraphicsSpec.js b/Specs/DataSources/BoxGraphicsSpec.js index 70c4ec7d55d1..5ef15e8b12a0 100644 --- a/Specs/DataSources/BoxGraphicsSpec.js +++ b/Specs/DataSources/BoxGraphicsSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'DataSources/BoxGraphics', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'Scene/ShadowMode', @@ -12,6 +13,7 @@ defineSuite([ BoxGraphics, Cartesian3, Color, + DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, ShadowMode, @@ -28,7 +30,8 @@ defineSuite([ outlineColor : Color.RED, outlineWidth : 1, dimensions : new Cartesian3(2, 3, 4), - shadows : ShadowMode.DISABLED + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) }; var box = new BoxGraphics(options); @@ -40,6 +43,7 @@ defineSuite([ expect(box.outlineWidth).toBeInstanceOf(ConstantProperty); expect(box.dimensions).toBeInstanceOf(ConstantProperty); expect(box.shadows).toBeInstanceOf(ConstantProperty); + expect(box.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(box.material.color.getValue()).toEqual(options.material); expect(box.show.getValue()).toEqual(options.show); @@ -49,6 +53,7 @@ defineSuite([ expect(box.outlineWidth.getValue()).toEqual(options.outlineWidth); expect(box.dimensions.getValue()).toEqual(options.dimensions); expect(box.shadows.getValue()).toEqual(options.shadows); + expect(box.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -61,6 +66,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.dimensions = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var target = new BoxGraphics(); target.merge(source); @@ -73,6 +79,7 @@ defineSuite([ expect(target.outlineWidth).toBe(source.outlineWidth); expect(target.dimensions).toBe(source.dimensions); expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -86,6 +93,7 @@ defineSuite([ var outlineWidth = new ConstantProperty(); var dimensions = new ConstantProperty(); var shadows = new ConstantProperty(); + var distanceDisplayCondition = new ConstantProperty(); var target = new BoxGraphics(); target.material = material; @@ -96,6 +104,7 @@ defineSuite([ target.outlineWidth = outlineWidth; target.dimensions = dimensions; target.shadows = shadows; + target.distanceDisplayCondition = distanceDisplayCondition; target.merge(source); @@ -107,6 +116,7 @@ defineSuite([ expect(target.outlineWidth).toBe(outlineWidth); expect(target.dimensions).toBe(dimensions); expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); }); it('clone works', function() { @@ -119,6 +129,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.dimensions = new ConstantProperty(); source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -129,6 +140,7 @@ defineSuite([ expect(result.outlineWidth).toBe(source.outlineWidth); expect(result.dimensions).toBe(source.dimensions); expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { @@ -148,5 +160,6 @@ defineSuite([ testDefinitionChanged(property, 'outlineWidth', 2, 3); testDefinitionChanged(property, 'dimensions', new Cartesian3(0, 0, 0), new Cartesian3(1, 1, 1)); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); }); }); diff --git a/Specs/DataSources/CorridorGeometryUpdaterSpec.js b/Specs/DataSources/CorridorGeometryUpdaterSpec.js index 792933e5f7cc..4ca42465d2d5 100644 --- a/Specs/DataSources/CorridorGeometryUpdaterSpec.js +++ b/Specs/DataSources/CorridorGeometryUpdaterSpec.js @@ -5,6 +5,8 @@ defineSuite([ 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/CornerType', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/JulianDate', 'Core/ShowGeometryInstanceAttribute', 'Core/TimeInterval', @@ -31,6 +33,8 @@ defineSuite([ Color, ColorGeometryInstanceAttribute, CornerType, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, JulianDate, ShowGeometryInstanceAttribute, TimeInterval, @@ -111,6 +115,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toBe(undefined); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); @@ -152,6 +157,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); }); @@ -246,11 +252,11 @@ defineSuite([ corridor.outline = new ConstantProperty(options.outline); corridor.outlineColor = new ConstantProperty(options.outlineColor); corridor.cornerType = new ConstantProperty(options.cornerType); - corridor.width = new ConstantProperty(options.width); corridor.height = new ConstantProperty(options.height); corridor.extrudedHeight = new ConstantProperty(options.extrudedHeight); corridor.granularity = new ConstantProperty(options.granularity); + corridor.distanceDisplayCondition = options.distanceDisplayCondition; var updater = new CorridorGeometryUpdater(entity, scene); @@ -272,6 +278,9 @@ defineSuite([ expect(attributes.color).toBeUndefined(); } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } if (options.outline) { @@ -285,6 +294,9 @@ defineSuite([ attributes = instance.attributes; expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } } @@ -318,6 +330,22 @@ defineSuite([ }); }); + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + height : 431, + extrudedHeight : 123, + granularity : 0.97, + width : 12, + fill : true, + outline : true, + outlineColor : Color.BLUE, + cornerType : CornerType.MITERED, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + it('Correctly exposes outlineWidth', function() { var entity = createBasicCorridor(); entity.corridor.outlineWidth = new ConstantProperty(8); diff --git a/Specs/DataSources/CorridorGraphicsSpec.js b/Specs/DataSources/CorridorGraphicsSpec.js index 2fd38a6e6052..bdbbc4434a9b 100644 --- a/Specs/DataSources/CorridorGraphicsSpec.js +++ b/Specs/DataSources/CorridorGraphicsSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'DataSources/CorridorGraphics', 'Core/Color', 'Core/CornerType', + 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'Scene/ShadowMode', @@ -12,6 +13,7 @@ defineSuite([ CorridorGraphics, Color, CornerType, + DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, ShadowMode, @@ -33,7 +35,8 @@ defineSuite([ outlineColor : Color.RED, outlineWidth : 5, cornerType : CornerType.BEVELED, - shadows : ShadowMode.DISABLED + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) }; var corridor = new CorridorGraphics(options); @@ -50,6 +53,7 @@ defineSuite([ expect(corridor.outlineWidth).toBeInstanceOf(ConstantProperty); expect(corridor.cornerType).toBeInstanceOf(ConstantProperty); expect(corridor.shadows).toBeInstanceOf(ConstantProperty); + expect(corridor.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(corridor.material.color.getValue()).toEqual(options.material); expect(corridor.positions.getValue()).toEqual(options.positions); @@ -64,6 +68,7 @@ defineSuite([ expect(corridor.outlineWidth.getValue()).toEqual(options.outlineWidth); expect(corridor.cornerType.getValue()).toEqual(options.cornerType); expect(corridor.shadows.getValue()).toEqual(options.shadows); + expect(corridor.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -81,6 +86,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.cornerType = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var target = new CorridorGraphics(); target.merge(source); @@ -98,6 +104,7 @@ defineSuite([ expect(target.outlineWidth).toBe(source.outlineWidth); expect(target.cornerType).toBe(source.cornerType); expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -116,6 +123,7 @@ defineSuite([ var outlineWidth = new ConstantProperty(); var cornerType = new ConstantProperty(); var shadows = new ConstantProperty(); + var distanceDisplayCondition = new ConstantProperty(); var target = new CorridorGraphics(); target.material = material; @@ -131,6 +139,7 @@ defineSuite([ target.outlineWidth = outlineWidth; target.cornerType = cornerType; target.shadows = shadows; + target.distanceDisplayCondition = distanceDisplayCondition; target.merge(source); @@ -147,6 +156,7 @@ defineSuite([ expect(target.outlineWidth).toBe(outlineWidth); expect(target.cornerType).toBe(cornerType); expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); }); it('clone works', function() { @@ -164,6 +174,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.cornerType = new ConstantProperty(); source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -179,6 +190,7 @@ defineSuite([ expect(result.outlineWidth).toBe(source.outlineWidth); expect(result.cornerType).toBe(source.cornerType); expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { @@ -203,5 +215,6 @@ defineSuite([ testDefinitionChanged(property, 'outlineWidth', 2, 3); testDefinitionChanged(property, 'cornerType', CornerType.BEVELED, CornerType.MITERED); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); }); }); diff --git a/Specs/DataSources/CylinderGeometryUpdaterSpec.js b/Specs/DataSources/CylinderGeometryUpdaterSpec.js index 21411b2ae099..3f43262cd991 100644 --- a/Specs/DataSources/CylinderGeometryUpdaterSpec.js +++ b/Specs/DataSources/CylinderGeometryUpdaterSpec.js @@ -4,6 +4,8 @@ defineSuite([ 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/JulianDate', 'Core/Quaternion', 'Core/ShowGeometryInstanceAttribute', @@ -28,6 +30,8 @@ defineSuite([ Cartesian3, Color, ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, JulianDate, Quaternion, ShowGeometryInstanceAttribute, @@ -88,6 +92,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toBe(undefined); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); @@ -149,6 +154,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); }); @@ -219,10 +225,10 @@ defineSuite([ cylinder.outline = new ConstantProperty(options.outline); cylinder.outlineColor = new ConstantProperty(options.outlineColor); cylinder.numberOfVerticalLines = new ConstantProperty(options.numberOfVerticalLines); - cylinder.length = new ConstantProperty(options.length); cylinder.topRadius = new ConstantProperty(options.topRadius); cylinder.bottomRadius = new ConstantProperty(options.bottomRadius); + cylinder.distanceDisplayCondition = options.distanceDisplayCondition; entity.cylinder = cylinder; var updater = new CylinderGeometryUpdater(entity, scene); @@ -244,6 +250,9 @@ defineSuite([ expect(attributes.color).toBeUndefined(); } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } if (options.outline) { @@ -257,6 +266,9 @@ defineSuite([ attributes = instance.attributes; expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } } @@ -292,6 +304,23 @@ defineSuite([ }); }); + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + position : new Cartesian3(4, 5, 6), + orientation : Quaternion.IDENTITY, + length : 1, + topRadius : 3, + bottomRadius : 2, + show : true, + material : new ColorMaterialProperty(Color.RED), + fill : true, + outline : true, + outlineColor : Color.BLUE, + numberOfVerticalLines : 15, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + it('Correctly exposes outlineWidth', function() { var entity = createBasicCylinder(); entity.cylinder.outlineWidth = new ConstantProperty(8); diff --git a/Specs/DataSources/CylinderGraphicsSpec.js b/Specs/DataSources/CylinderGraphicsSpec.js index 5a30f2c1bf6c..8e6c99d065f5 100644 --- a/Specs/DataSources/CylinderGraphicsSpec.js +++ b/Specs/DataSources/CylinderGraphicsSpec.js @@ -2,6 +2,7 @@ defineSuite([ 'DataSources/CylinderGraphics', 'Core/Color', + 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'Scene/ShadowMode', @@ -10,6 +11,7 @@ defineSuite([ ], function( CylinderGraphics, Color, + DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, ShadowMode, @@ -30,7 +32,8 @@ defineSuite([ outline : false, outlineColor : Color.RED, outlineWidth : 6, - shadows : ShadowMode.DISABLED + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) }; var cylinder = new CylinderGraphics(options); @@ -46,6 +49,7 @@ defineSuite([ expect(cylinder.outlineColor).toBeInstanceOf(ConstantProperty); expect(cylinder.outlineWidth).toBeInstanceOf(ConstantProperty); expect(cylinder.shadows).toBeInstanceOf(ConstantProperty); + expect(cylinder.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(cylinder.material.color.getValue()).toEqual(options.material); expect(cylinder.show.getValue()).toEqual(options.show); @@ -59,6 +63,7 @@ defineSuite([ expect(cylinder.outlineColor.getValue()).toEqual(options.outlineColor); expect(cylinder.outlineWidth.getValue()).toEqual(options.outlineWidth); expect(cylinder.shadows.getValue()).toEqual(options.shadows); + expect(cylinder.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -74,6 +79,7 @@ defineSuite([ source.outlineColor = new ConstantProperty(); source.outlineWidth = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(); var target = new CylinderGraphics(); target.merge(source); @@ -89,6 +95,7 @@ defineSuite([ expect(target.outlineColor).toBe(source.outlineColor); expect(target.outlineWidth).toBe(source.outlineWidth); expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -105,6 +112,7 @@ defineSuite([ var outlineColor = new ConstantProperty(); var outlineWidth = new ConstantProperty(); var shadows = new ConstantProperty(); + var distanceDisplayCondition = new ConstantProperty(); var target = new CylinderGraphics(); target.material = material; @@ -118,6 +126,7 @@ defineSuite([ target.outlineColor = outlineColor; target.outlineWidth = outlineWidth; target.shadows = shadows; + target.distanceDisplayCondition = distanceDisplayCondition; target.merge(source); @@ -132,6 +141,7 @@ defineSuite([ expect(target.outlineColor).toBe(outlineColor); expect(target.outlineWidth).toBe(outlineWidth); expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); }); it('clone works', function() { @@ -147,6 +157,7 @@ defineSuite([ source.outlineColor = new ConstantProperty(); source.outlineWidth = new ConstantProperty(); source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -160,6 +171,7 @@ defineSuite([ expect(result.outlineColor).toBe(source.outlineColor); expect(result.outlineWidth).toBe(source.outlineWidth); expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { @@ -182,5 +194,6 @@ defineSuite([ testDefinitionChanged(property, 'outlineColor', Color.RED, Color.BLUE); testDefinitionChanged(property, 'outlineWidth', 2, 3); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); }); }); diff --git a/Specs/DataSources/EllipseGeometryUpdaterSpec.js b/Specs/DataSources/EllipseGeometryUpdaterSpec.js index 4df887e1a075..6b2068239e6e 100644 --- a/Specs/DataSources/EllipseGeometryUpdaterSpec.js +++ b/Specs/DataSources/EllipseGeometryUpdaterSpec.js @@ -4,6 +4,8 @@ defineSuite([ 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/JulianDate', 'Core/ShowGeometryInstanceAttribute', 'Core/TimeInterval', @@ -29,6 +31,8 @@ defineSuite([ Cartesian3, Color, ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, JulianDate, ShowGeometryInstanceAttribute, TimeInterval, @@ -103,6 +107,7 @@ defineSuite([ expect(updater.hasConstantOutline).toBe(true); expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); @@ -166,6 +171,7 @@ defineSuite([ expect(updater.outlineWidth).toBe(1.0); expect(updater.isDynamic).toBe(false); expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); }); it('Ellipse material is correctly exposed.', function() { @@ -281,6 +287,7 @@ defineSuite([ ellipse.height = new ConstantProperty(options.height); ellipse.extrudedHeight = new ConstantProperty(options.extrudedHeight); ellipse.granularity = new ConstantProperty(options.granularity); + ellipse.distanceDisplayCondition = options.distanceDisplayCondition; entity.ellipse = ellipse; var updater = new EllipseGeometryUpdater(entity, scene); @@ -307,6 +314,9 @@ defineSuite([ expect(attributes.color).toBeUndefined(); } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } if (options.outline) { @@ -324,6 +334,9 @@ defineSuite([ attributes = instance.attributes; expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } } @@ -365,6 +378,26 @@ defineSuite([ }); }); + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + center : new Cartesian3(4, 5, 6), + rotation : 1, + semiMajorAxis : 3, + semiMinorAxis : 2, + show : true, + material : new ColorMaterialProperty(Color.RED), + height : 123, + extrudedHeight : 431, + granularity : 0.97, + stRotation : 12, + fill : true, + outline : true, + outlineColor : Color.BLUE, + numberOfVerticalLines : 15, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + it('Correctly exposes outlineWidth', function() { var entity = createBasicEllipse(); entity.ellipse.outlineWidth = new ConstantProperty(8); diff --git a/Specs/DataSources/EllipseGraphicsSpec.js b/Specs/DataSources/EllipseGraphicsSpec.js index 48a3d1d019b5..e32f7345cebe 100644 --- a/Specs/DataSources/EllipseGraphicsSpec.js +++ b/Specs/DataSources/EllipseGraphicsSpec.js @@ -2,6 +2,7 @@ defineSuite([ 'DataSources/EllipseGraphics', 'Core/Color', + 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'Scene/ShadowMode', @@ -10,6 +11,7 @@ defineSuite([ ], function( EllipseGraphics, Color, + DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, ShadowMode, @@ -33,7 +35,8 @@ defineSuite([ outline : false, outlineColor : Color.RED, outlineWidth : 9, - shadows : ShadowMode.DISABLED + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition() }; var ellipse = new EllipseGraphics(options); @@ -52,6 +55,7 @@ defineSuite([ expect(ellipse.outlineColor).toBeInstanceOf(ConstantProperty); expect(ellipse.outlineWidth).toBeInstanceOf(ConstantProperty); expect(ellipse.shadows).toBeInstanceOf(ConstantProperty); + expect(ellipse.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(ellipse.material.color.getValue()).toEqual(options.material); expect(ellipse.show.getValue()).toEqual(options.show); @@ -68,6 +72,7 @@ defineSuite([ expect(ellipse.outlineColor.getValue()).toEqual(options.outlineColor); expect(ellipse.outlineWidth.getValue()).toEqual(options.outlineWidth); expect(ellipse.shadows.getValue()).toEqual(options.shadows); + expect(ellipse.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -87,6 +92,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.numberOfVerticalLines = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); var target = new EllipseGraphics(); target.merge(source); @@ -106,6 +112,7 @@ defineSuite([ expect(target.outlineWidth).toBe(source.outlineWidth); expect(target.numberOfVerticalLines).toBe(source.numberOfVerticalLines); expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -126,6 +133,7 @@ defineSuite([ var outlineWidth = new ConstantProperty(); var numberOfVerticalLines = new ConstantProperty(); var shadows = new ConstantProperty(); + var distanceDisplayCondition = new ConstantProperty(); var target = new EllipseGraphics(); target.material = material; @@ -143,6 +151,7 @@ defineSuite([ target.outlineWidth = outlineWidth; target.numberOfVerticalLines = numberOfVerticalLines; target.shadows = shadows; + target.distanceDisplayCondition = distanceDisplayCondition; target.merge(source); @@ -161,6 +170,7 @@ defineSuite([ expect(target.outlineWidth).toBe(outlineWidth); expect(target.numberOfVerticalLines).toBe(numberOfVerticalLines); expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); }); it('clone works', function() { @@ -180,6 +190,7 @@ defineSuite([ source.outlineWidth = new ConstantProperty(); source.numberOfVerticalLines = new ConstantProperty(); source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -197,6 +208,7 @@ defineSuite([ expect(result.outlineWidth).toBe(source.outlineWidth); expect(result.numberOfVerticalLines).toBe(source.numberOfVerticalLines); expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { @@ -223,5 +235,6 @@ defineSuite([ testDefinitionChanged(property, 'outlineWidth', 2, 3); testDefinitionChanged(property, 'numberOfVerticalLines', 16, 32); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); }); }); diff --git a/Specs/DataSources/EllipsoidGeometryUpdaterSpec.js b/Specs/DataSources/EllipsoidGeometryUpdaterSpec.js index 5df189f584dc..15c4d4c5d840 100644 --- a/Specs/DataSources/EllipsoidGeometryUpdaterSpec.js +++ b/Specs/DataSources/EllipsoidGeometryUpdaterSpec.js @@ -4,6 +4,8 @@ defineSuite([ 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/JulianDate', 'Core/Quaternion', 'Core/ShowGeometryInstanceAttribute', @@ -28,6 +30,8 @@ defineSuite([ Cartesian3, Color, ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, JulianDate, Quaternion, ShowGeometryInstanceAttribute, @@ -85,6 +89,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toBe(undefined); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); @@ -135,6 +140,7 @@ defineSuite([ expect(updater.outlineColorProperty).toBe(undefined); expect(updater.outlineWidth).toBe(1.0); expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); }); @@ -209,6 +215,7 @@ defineSuite([ ellipsoid.stackPartitions = new ConstantProperty(options.stackPartitions); ellipsoid.slicePartitions = new ConstantProperty(options.slicePartitions); ellipsoid.subdivisions = new ConstantProperty(options.subdivisions); + ellipsoid.distanceDisplayCondition = options.distanceDisplayCondition; entity.ellipsoid = ellipsoid; var updater = new EllipsoidGeometryUpdater(entity, scene); @@ -231,6 +238,9 @@ defineSuite([ expect(attributes.color).toBeUndefined(); } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } if (options.outline) { @@ -245,6 +255,9 @@ defineSuite([ attributes = instance.attributes; expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } } } @@ -280,6 +293,23 @@ defineSuite([ }); }); + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + position : new Cartesian3(4, 5, 6), + orientation : Quaternion.IDENTITY, + radii : new Cartesian3(1, 2, 3), + show : true, + material : new ColorMaterialProperty(Color.RED), + fill : true, + outline : true, + outlineColor : Color.BLUE, + stackPartitions : 32, + slicePartitions : 64, + subdivisions : 15, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + it('Correctly exposes outlineWidth', function() { var entity = createBasicEllipsoid(); entity.ellipsoid.outlineWidth = new ConstantProperty(8); diff --git a/Specs/DataSources/EllipsoidGraphicsSpec.js b/Specs/DataSources/EllipsoidGraphicsSpec.js index aac4cc2bc0b9..d42c8a7c03e5 100644 --- a/Specs/DataSources/EllipsoidGraphicsSpec.js +++ b/Specs/DataSources/EllipsoidGraphicsSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'DataSources/EllipsoidGraphics', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'Scene/ShadowMode', @@ -12,6 +13,7 @@ defineSuite([ EllipsoidGraphics, Cartesian3, Color, + DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, ShadowMode, @@ -30,7 +32,8 @@ defineSuite([ outline : false, outlineColor : Color.RED, outlineWidth : 4, - shadows : ShadowMode.DISABLED + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition() }; var ellipsoid = new EllipsoidGraphics(options); @@ -44,6 +47,7 @@ defineSuite([ expect(ellipsoid.outlineColor).toBeInstanceOf(ConstantProperty); expect(ellipsoid.outlineWidth).toBeInstanceOf(ConstantProperty); expect(ellipsoid.shadows).toBeInstanceOf(ConstantProperty); + expect(ellipsoid.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(ellipsoid.material.color.getValue()).toEqual(options.material); expect(ellipsoid.show.getValue()).toEqual(options.show); @@ -55,6 +59,7 @@ defineSuite([ expect(ellipsoid.outlineColor.getValue()).toEqual(options.outlineColor); expect(ellipsoid.outlineWidth.getValue()).toEqual(options.outlineWidth); expect(ellipsoid.shadows.getValue()).toEqual(options.shadows); + expect(ellipsoid.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); }); it('merge assigns unassigned properties', function() { @@ -70,6 +75,7 @@ defineSuite([ source.outlineColor = new ConstantProperty(); source.outlineWidth = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var target = new EllipsoidGraphics(); target.merge(source); @@ -85,6 +91,7 @@ defineSuite([ expect(target.outlineColor).toBe(source.outlineColor); expect(target.outlineWidth).toBe(source.outlineWidth); expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge does not assign assigned properties', function() { @@ -101,6 +108,7 @@ defineSuite([ var outlineColor = new ConstantProperty(); var outlineWidth = new ConstantProperty(); var shadows = new ConstantProperty(); + var distanecDisplayCondition = new ConstantProperty(); var target = new EllipsoidGraphics(); target.material = material; @@ -110,6 +118,7 @@ defineSuite([ target.slicePartitions = slicePartitions; target.subdivisions = subdivisions; target.shadows = shadows; + target.distanceDisplayCondition = distanecDisplayCondition; source.fill = fill; source.outline = outline; @@ -129,6 +138,7 @@ defineSuite([ expect(target.outlineColor).toBe(outlineColor); expect(target.outlineWidth).toBe(outlineWidth); expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanecDisplayCondition); }); it('clone works', function() { @@ -144,6 +154,7 @@ defineSuite([ source.outlineColor = new ConstantProperty(); source.outlineWidth = new ConstantProperty(); source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -157,6 +168,7 @@ defineSuite([ expect(result.outlineColor).toBe(source.outlineColor); expect(result.outlineWidth).toBe(source.outlineWidth); expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); }); it('merge throws if source undefined', function() { @@ -179,5 +191,6 @@ defineSuite([ testDefinitionChanged(property, 'outlineColor', Color.RED, Color.BLUE); testDefinitionChanged(property, 'outlineWidth', 2, 3); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); }); }); diff --git a/Specs/DataSources/EntityClusterSpec.js b/Specs/DataSources/EntityClusterSpec.js new file mode 100644 index 000000000000..86401b56790e --- /dev/null +++ b/Specs/DataSources/EntityClusterSpec.js @@ -0,0 +1,381 @@ +/*global defineSuite*/ +defineSuite([ + 'DataSources/EntityCluster', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/defineProperties', + 'Core/Ellipsoid', + 'Core/Event', + 'DataSources/Entity', + 'Scene/SceneTransforms', + 'Specs/createCanvas', + 'Specs/createGlobe', + 'Specs/createScene', + ], function( + EntityCluster, + Cartesian2, + Cartesian3, + defineProperties, + Ellipsoid, + Event, + Entity, + SceneTransforms, + createCanvas, + createGlobe, + createScene) { + 'use strict'; + + var scene; + var cluster; + + beforeAll(function() { + scene = createScene({ + canvas : createCanvas(10, 10) + }); + scene.globe = { + ellipsoid : Ellipsoid.WGS84, + _surface : { + tileProvider : { + ready : true + }, + _tileLoadQueue : {}, + _debug : { + tilesWaitingForChildren : 0 + } + }, + beginFrame : function() {}, + update : function() {}, + endFrame : function() {} + + }; + + scene.globe.getHeight = function() { + return 0.0; + }; + + scene.globe.destroy = function() { + }; + + scene.globe._surface.updateHeight = function() { + }; + + scene.globe.terrainProviderChanged = new Event(); + defineProperties(scene.globe, { + terrainProvider : { + set : function(value) { + this.terrainProviderChanged.raiseEvent(value); + } + } + }); + + var camera = scene.camera; + camera.setView({ + destination : Cartesian3.fromDegrees(0.0, 0.0, 10000.0) + }); + + scene.initializeFrame(); + scene.render(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + afterEach(function() { + cluster = cluster && cluster.destroy(); + }); + + it('constructor sets default properties', function() { + cluster = new EntityCluster(); + expect(cluster.enabled).toEqual(false); + expect(cluster.pixelRange).toEqual(80); + expect(cluster.minimumClusterSize).toEqual(2); + + cluster.enabled = true; + expect(cluster.enabled).toEqual(true); + + cluster.pixelRange = 30; + expect(cluster.pixelRange).toEqual(30); + + cluster.minimumClusterSize = 5; + expect(cluster.minimumClusterSize).toEqual(5); + }); + + it('constructor sets expected properties', function() { + var options = { + enabled : true, + pixelRange : 30, + minimumClusterSize : 5 + }; + cluster = new EntityCluster(options); + expect(cluster.enabled).toEqual(options.enabled); + expect(cluster.pixelRange).toEqual(options.pixelRange); + expect(cluster.minimumClusterSize).toEqual(options.minimumClusterSize); + }); + + function createBillboardImage() { + var canvas = document.createElement('canvas'); + canvas.height = canvas.width = 1; + + var context2D = canvas.getContext('2d'); + context2D.clearRect(0, 0, length, length); + context2D.fillStyle="#FF0000"; + context2D.fillRect(0, 0, length, length); + + return canvas; + } + + it('clusters billboards', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.5); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.5); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + cluster.enabled = false; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + }); + + it('clusters labels', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var label = cluster.getLabel(entity); + label.id = entity; + label.text = 'a'; + label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.5); + + entity = new Entity(); + label = cluster.getLabel(entity); + label.id = entity; + label.text = 'b'; + label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.5); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + cluster.enabled = false; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + }); + + it('clusters points', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var point = cluster.getPoint(entity); + point.id = entity; + point.pixelSize = 1; + point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.5); + + entity = new Entity(); + point = cluster.getPoint(entity); + point.id = entity; + point.pixelSize = 1; + point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.5); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + cluster.enabled = false; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + }); + + it('pixel range', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.5); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.5); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + cluster.pixelRange = 1; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + }); + + it('minimum cluster size', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.5); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, 0), 0.5); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0, scene.canvas.clientHeight), 0.5); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.5); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + cluster.minimumClusterSize = 5; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + }); + + it('clusters around the same point', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + var entity = new Entity(); + var billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.9); + + entity = new Entity(); + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = createBillboardImage(); + billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.9); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + + var position = Cartesian3.clone(cluster._clusterLabelCollection.get(0).position); + + scene.camera.moveForward(1.0e-6); + cluster.pixelRange = cluster.pixelRange - 1; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + expect(cluster._clusterLabelCollection.get(0).position).toEqual(position); + }); + + it('custom cluster styling', function() { + cluster = new EntityCluster(); + cluster._initialize(scene); + + cluster.clusterEvent.addEventListener(function(clusteredEntities, cluster) { + cluster.billboard.show = true; + cluster.billboard.image = createBillboardImage(); + cluster.label.text = 'cluster'; + }); + + var entity = new Entity(); + var point = cluster.getPoint(entity); + point.id = entity; + point.pixelSize = 1; + point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), 0.9); + + entity = new Entity(); + point = cluster.getPoint(entity); + point.id = entity; + point.pixelSize = 1; + point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), 0.9); + + var frameState = scene.frameState; + cluster.update(frameState); + + expect(cluster._clusterBillboardCollection).not.toBeDefined(); + expect(cluster._clusterLabelCollection).not.toBeDefined(); + + cluster.enabled = true; + cluster.update(frameState); + + expect(cluster._clusterLabelCollection).toBeDefined(); + expect(cluster._clusterLabelCollection.length).toEqual(1); + expect(cluster._clusterLabelCollection.get(0).text).toEqual('cluster'); + + expect(cluster._clusterBillboardCollection).toBeDefined(); + expect(cluster._clusterBillboardCollection.length).toEqual(1); + }); +}, 'WebGL'); diff --git a/Specs/DataSources/KmlDataSourceSpec.js b/Specs/DataSources/KmlDataSourceSpec.js index 8b5c304c592a..2502124ecf28 100644 --- a/Specs/DataSources/KmlDataSourceSpec.js +++ b/Specs/DataSources/KmlDataSourceSpec.js @@ -786,6 +786,24 @@ defineSuite([ }); }); + it('GroundOverlay: Handles out-of-range latitudes.', function() { + var kml = '\ + \ + \ + -180\ + -100\ + 180\ + 100\ + \ + '; + + return KmlDataSource.load(parser.parseFromString(kml, "text/xml"), options).then(function(dataSource) { + var entity = dataSource.entities.values[0]; + expect(entity.polygon).toBeUndefined(); + expect(entity.rectangle.coordinates.getValue()).toEqual(Rectangle.fromDegrees(-180, -90, 180, 90)); + }); + }); + it('GroundOverlay: Sets polygon coordinates for gx:LatLonQuad', function() { var kml = '\ = far)', function() { + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + billboards.add({ + distanceDisplayCondition : dc + }); + }).toThrowDeveloperError(); + }); + + it('throws distanceDisplayCondition with near >= far', function() { + var b = billboards.add(); + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + b.distanceDisplayCondition = dc; + }).toThrowDeveloperError(); + }); + it('sets a removed billboard property', function() { var b = billboards.add(); billboards.remove(b); @@ -560,7 +603,7 @@ defineSuite([ }); billboards.add({ position : new Cartesian3(-1.0, 0.0, 0.0), - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]); @@ -589,7 +632,7 @@ defineSuite([ billboards.add({ position : new Cartesian3(1.0, 0.0, 0.0), // Closer to camera - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); @@ -602,7 +645,7 @@ defineSuite([ }); var blueBillboard = billboards.add({ position : new Cartesian3(1.0, 0.0, 0.0), // Closer to camera - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); @@ -634,7 +677,7 @@ defineSuite([ billboards.removeAll(); billboards.add({ position : Cartesian3.ZERO, - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); @@ -661,7 +704,7 @@ defineSuite([ var blueBillboard = billboards.add({ show : false, position : Cartesian3.ZERO, - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]); @@ -710,7 +753,7 @@ defineSuite([ expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]); - b.image = blueImage; + b.image = largeBlueImage; expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); }); @@ -722,7 +765,7 @@ defineSuite([ expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]); - b.setImage(blueImage.src, blueImage); + b.setImage(largeBlueImage.src, largeBlueImage); expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); }); @@ -821,7 +864,7 @@ defineSuite([ var b2 = billboards.add({ position : new Cartesian3(1.0, 0.0, 0.0), // Closer to camera - image : blueImage + image : largeBlueImage }); expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); @@ -1055,6 +1098,114 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('computes screen space bounding box', function() { + var width = 10; + var height = 15; + var scale = 1.5; + + var b = billboards.add({ + width : width, + height : height, + scale : scale + }); + + var halfWidth = width * scale * 0.5; + var halfHeight = height * scale * 0.5; + width = width * scale; + height = height * scale; + + var bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + + it('computes screen space bounding box with result', function() { + var width = 10; + var height = 15; + var scale = 1.5; + + var b = billboards.add({ + width : width, + height : height, + scale : scale + }); + + var halfWidth = width * scale * 0.5; + var halfHeight = height * scale * 0.5; + width = width * scale; + height = height * scale; + + var result = new BoundingRectangle(); + var bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO, result); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + expect(bbox).toBe(result); + }); + + it('computes screen space bounding box with vertical origin', function() { + var width = 10; + var height = 15; + var scale = 1.5; + + var b = billboards.add({ + width : width, + height : height, + scale : scale, + verticalOrigin : VerticalOrigin.BOTTOM + }); + + var halfWidth = width * scale * 0.5; + width = width * scale; + height = height * scale; + + var bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(0); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + + b.verticalOrigin = VerticalOrigin.TOP; + bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(-height); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + + it('computes screen space bounding box with horizontal origin', function() { + var width = 10; + var height = 15; + var scale = 1.5; + + var b = billboards.add({ + width : width, + height : height, + scale : scale, + horizontalOrigin : HorizontalOrigin.LEFT + }); + + var halfHeight = height * scale * 0.5; + height = height * scale; + width = width * scale; + + var bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); + expect(bbox.x).toEqual(0); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + + b.horizontalOrigin = HorizontalOrigin.RIGHT; + bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); + expect(bbox.x).toEqual(-width); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + it('equals another billboard', function() { var b = billboards.add({ position : new Cartesian3(1.0, 2.0, 3.0), @@ -1463,10 +1614,10 @@ defineSuite([ // switch to blue while green is in-flight - one.image = './Data/Images/Blue.png'; + one.image = './Data/Images/Blue10x10.png'; expect(one.ready).toEqual(false); - expect(one.image).toEqual('./Data/Images/Blue.png'); + expect(one.image).toEqual('./Data/Images/Blue10x10.png'); return pollToPromise(function() { return one.ready; diff --git a/Specs/Scene/DebugAppearanceSpec.js b/Specs/Scene/DebugAppearanceSpec.js index 591fa791a495..229e31d72582 100644 --- a/Specs/Scene/DebugAppearanceSpec.js +++ b/Specs/Scene/DebugAppearanceSpec.js @@ -67,7 +67,8 @@ defineSuite([ it('default construct with normal, binormal, or tangent attribute name', function() { var a = new DebugAppearance({ - attributeName : 'normal' + attributeName : 'normal', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -87,7 +88,8 @@ defineSuite([ it('default construct with st attribute name', function() { var a = new DebugAppearance({ - attributeName : 'st' + attributeName : 'st', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -108,7 +110,8 @@ defineSuite([ it('debug appearance with float attribute name', function() { var a = new DebugAppearance({ attributeName : 'rotation', - glslDatatype : 'float' + glslDatatype : 'float', + perInstanceAttribute : true }); expect(a.vertexShaderSource).toBeDefined(); @@ -129,7 +132,8 @@ defineSuite([ it('debug appearance with vec3 attribute name', function() { var a = new DebugAppearance({ attributeName : 'str', - glslDatatype : 'vec3' + glslDatatype : 'vec3', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -150,7 +154,8 @@ defineSuite([ it('debug appearance with vec4 attribute name', function() { var a = new DebugAppearance({ attributeName : 'quaternion', - glslDatatype : 'vec4' + glslDatatype : 'vec4', + perInstanceAttribute : true }); expect(a.vertexShaderSource).toBeDefined(); @@ -172,7 +177,8 @@ defineSuite([ expect(function() { return new DebugAppearance({ attributeName : 'invalid_datatype', - glslDatatype : 'invalid' + glslDatatype : 'invalid', + perInstanceAttribute : true }); }).toThrowDeveloperError(); }); @@ -185,7 +191,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'normal' + attributeName : 'normal', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -206,7 +213,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'binormal' + attributeName : 'binormal', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -227,7 +235,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'tangent' + attributeName : 'tangent', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -247,7 +256,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'st' + attributeName : 'st', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -272,7 +282,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'float' + glslDatatype : 'float', + perInstanceAttribute : true }), asynchronous : false }); @@ -296,7 +307,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec2' + glslDatatype : 'vec2', + perInstanceAttribute : true }), asynchronous : false }); @@ -320,7 +332,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec3' + glslDatatype : 'vec3', + perInstanceAttribute : true }), asynchronous : false }); @@ -344,7 +357,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec4' + glslDatatype : 'vec4', + perInstanceAttribute : true }), asynchronous : false }); diff --git a/Specs/Scene/DebugCameraPrimitiveSpec.js b/Specs/Scene/DebugCameraPrimitiveSpec.js new file mode 100644 index 000000000000..2d3d9b00c5d9 --- /dev/null +++ b/Specs/Scene/DebugCameraPrimitiveSpec.js @@ -0,0 +1,124 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/DebugCameraPrimitive', + 'Core/Cartesian3', + 'Core/Color', + 'Scene/Camera', + 'Specs/createScene' + ], function( + DebugCameraPrimitive, + Cartesian3, + Color, + Camera, + createScene) { + 'use strict'; + + var scene; + var camera; + + beforeAll(function() { + scene = createScene(); + + camera = new Camera(scene); + camera.position = new Cartesian3(0.0, 0.0, 0.0); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + + scene.camera.position = new Cartesian3(0.0, 0.0, 0.0); + scene.camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + scene.camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + scene.camera.zoomOut(1.0); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + afterEach(function() { + scene.primitives.removeAll(); + }); + + it('throws if options.camera is undefined', function() { + expect(function() { + return new DebugCameraPrimitive(); + }).toThrowDeveloperError(); + }); + + it('gets the default properties', function() { + var p = new DebugCameraPrimitive({ + camera : camera + }); + expect(p.show).toEqual(true); + expect(p.id).not.toBeDefined(); + p.destroy(); + }); + + it('constructs with options', function() { + var p = new DebugCameraPrimitive({ + camera : camera, + color : Color.YELLOW, + updateOnChange : false, + show : false, + id : 'id' + }); + expect(p.show).toEqual(false); + expect(p.id).toEqual('id'); + p.destroy(); + }); + + it('renders', function() { + scene.primitives.add(new DebugCameraPrimitive({ + camera : camera + })); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + }); + + it('does not render when show is false', function() { + scene.primitives.add(new DebugCameraPrimitive({ + camera : camera, + show : false + })); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + + it('updates when underlying camera changes', function() { + var p = scene.primitives.add(new DebugCameraPrimitive({ + camera : camera + })); + scene.renderForSpecs(); + var primitive = p._outlinePrimitive; + scene.renderForSpecs(); + expect(p._outlinePrimitive).not.toBe(primitive); + }); + + it('does not update when updateOnChange is false', function() { + var p = scene.primitives.add(new DebugCameraPrimitive({ + camera : camera, + updateOnChange : false + })); + scene.renderForSpecs(); + var primitive = p._primitive; + scene.renderForSpecs(); + expect(p._primitive).toBe(primitive); + }); + + it('is picked', function() { + var p = scene.primitives.add(new DebugCameraPrimitive({ + camera : camera, + id : 'id' + })); + + var pick = scene.pickForSpecs(); + expect(pick.primitive).toBe(p); + expect(pick.id).toBe('id'); + }); + + it('isDestroyed', function() { + var p = scene.primitives.add(new DebugCameraPrimitive({ + camera : camera + })); + expect(p.isDestroyed()).toEqual(false); + scene.primitives.remove(p); + expect(p.isDestroyed()).toEqual(true); + }); +}, 'WebGL'); diff --git a/Specs/Scene/DebugModelMatrixPrimitiveSpec.js b/Specs/Scene/DebugModelMatrixPrimitiveSpec.js index 535f038deec5..8f95c8f24a4d 100644 --- a/Specs/Scene/DebugModelMatrixPrimitiveSpec.js +++ b/Specs/Scene/DebugModelMatrixPrimitiveSpec.js @@ -78,9 +78,9 @@ defineSuite([ id : 'id' })); - var pick = scene.pick(new Cartesian2(0, 0)); - expect(pick.primitive).toEqual(p); - expect(pick.id).toEqual('id'); + var pick = scene.pickForSpecs(); + expect(pick.primitive).toBe(p); + expect(pick.id).toBe('id'); }); it('isDestroyed', function() { diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index 470bbfc27578..a09afbb7360e 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -6,11 +6,14 @@ defineSuite([ 'Core/ColorGeometryInstanceAttribute', 'Core/ComponentDatatype', 'Core/destroyObject', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/Ellipsoid', 'Core/Geometry', 'Core/GeometryAttribute', 'Core/GeometryInstance', 'Core/GeometryInstanceAttribute', + 'Core/HeadingPitchRange', + 'Core/Math', 'Core/PolygonGeometry', 'Core/PrimitiveType', 'Core/Rectangle', @@ -34,11 +37,14 @@ defineSuite([ ColorGeometryInstanceAttribute, ComponentDatatype, destroyObject, + DistanceDisplayConditionGeometryInstanceAttribute, Ellipsoid, Geometry, GeometryAttribute, GeometryInstance, GeometryInstanceAttribute, + HeadingPitchRange, + CesiumMath, PolygonGeometry, PrimitiveType, Rectangle, @@ -131,7 +137,8 @@ defineSuite([ } }), appearance : new PerInstanceColorAppearance({ - translucent : false + translucent : false, + flat : true }), asynchronous : false }); @@ -507,6 +514,70 @@ defineSuite([ verifyGroundPrimitiveRender(primitive, depthColor); }); + it('renders with distance display condition per instance attribute', function() { + var near = 10000.0; + var far = 1000000.0; + var rect = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); + var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); + depthColor = depthColorAttribute.value; + var primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : rectangle + }), + id : 'depth rectangle', + attributes : { + color : depthColorAttribute + } + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + + // wrap rectangle primitive so it gets executed during the globe pass to lay down depth + depthPrimitive = new MockGlobePrimitive(primitive); + + var rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); + var rectInstance = new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : rectangle + }), + id : 'rect', + attributes : { + color : rectColorAttribute, + distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) + } + }); + + primitive = new GroundPrimitive({ + geometryInstances : rectInstance, + asynchronous : false + }); + + scene.groundPrimitives.add(depthPrimitive); + scene.groundPrimitives.add(primitive); + scene.camera.setView({ destination : rect }); + scene.renderForSpecs(); + + var boundingSphere = primitive.getGeometryInstanceAttributes('rect').boundingSphere; + var center = boundingSphere.center; + var radius = boundingSphere.radius; + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius)); + expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + near + 1.0)); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 255, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + far + 1.0)); + expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); + }); + it('get bounding sphere from per instance attribute', function() { if (!GroundPrimitive.isSupported(scene)) { return; diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index ccf0b59de6f6..58f1984088ce 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1,11 +1,13 @@ /*global defineSuite*/ defineSuite([ 'Scene/LabelCollection', + 'Core/BoundingRectangle', 'Core/BoundingSphere', 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Color', 'Core/defined', + 'Core/DistanceDisplayCondition', 'Core/Ellipsoid', 'Core/Math', 'Core/NearFarScalar', @@ -13,6 +15,7 @@ defineSuite([ 'Renderer/ContextLimits', 'Scene/HeightReference', 'Scene/HorizontalOrigin', + 'Scene/Label', 'Scene/LabelStyle', 'Scene/OrthographicFrustum', 'Scene/VerticalOrigin', @@ -20,11 +23,13 @@ defineSuite([ 'Specs/createScene' ], function( LabelCollection, + BoundingRectangle, BoundingSphere, Cartesian2, Cartesian3, Color, defined, + DistanceDisplayCondition, Ellipsoid, CesiumMath, NearFarScalar, @@ -32,6 +37,7 @@ defineSuite([ ContextLimits, HeightReference, HorizontalOrigin, + Label, LabelStyle, OrthographicFrustum, VerticalOrigin, @@ -89,6 +95,7 @@ defineSuite([ expect(label.id).not.toBeDefined(); expect(label.translucencyByDistance).not.toBeDefined(); expect(label.pixelOffsetScaleByDistance).not.toBeDefined(); + expect(label.distanceDisplayCondition).not.toBeDefined(); }); it('can add a label with specified values', function() { @@ -118,6 +125,7 @@ defineSuite([ var scale = 2.0; var translucency = new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0); var pixelOffsetScale = new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0); + var distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); var label = labels.add({ show : show, position : position, @@ -134,7 +142,8 @@ defineSuite([ scale : scale, id : 'id', translucencyByDistance : translucency, - pixelOffsetScaleByDistance : pixelOffsetScale + pixelOffsetScaleByDistance : pixelOffsetScale, + distanceDisplayCondition : distanceDisplayCondition }); expect(label.show).toEqual(show); @@ -153,6 +162,7 @@ defineSuite([ expect(label.id).toEqual('id'); expect(label.translucencyByDistance).toEqual(translucency); expect(label.pixelOffsetScaleByDistance).toEqual(pixelOffsetScale); + expect(label.distanceDisplayCondition).toEqual(distanceDisplayCondition); }); it('can specify font using units other than pixels', function() { @@ -324,7 +334,7 @@ defineSuite([ it('can render after adding a label', function() { labels.add({ position : Cartesian3.ZERO, - text : 'x', + text : 'w', horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER }); @@ -502,6 +512,44 @@ defineSuite([ expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); }); + it('renders label with distanceDisplayCondition', function() { + labels.add({ + position : Cartesian3.ZERO, + text : 'm', + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), + horizontalOrigin : HorizontalOrigin.CENTER, + verticalOrigin : VerticalOrigin.CENTER + }); + + camera.position = new Cartesian3(200.0, 0.0, 0.0); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + camera.position = new Cartesian3(50.0, 0.0, 0.0); + expect(scene.renderForSpecs()[0]).toBeGreaterThan(200); + expect(scene.renderForSpecs()[1]).toBeGreaterThan(200); + expect(scene.renderForSpecs()[2]).toBeGreaterThan(200); + + camera.position = new Cartesian3(5.0, 0.0, 0.0); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + + it('throws new label with invalid distanceDisplayCondition (near >= far)', function() { + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + labels.add({ + distanceDisplayCondition : dc + }); + }).toThrowDeveloperError(); + }); + + it('throws distanceDisplayCondition with near >= far', function() { + var l = labels.add(); + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + l.distanceDisplayCondition = dc; + }).toThrowDeveloperError(); + }); + it('can pick a label', function() { var label = labels.add({ position : Cartesian3.ZERO, @@ -810,6 +858,168 @@ defineSuite([ expect(label.computeScreenSpacePosition(scene)).toEqualEpsilon(new Cartesian2(0.5, 0.5), CesiumMath.EPSILON1); }); + it('computes screen space bounding box', function() { + var scale = 1.5; + + var label = labels.add({ + text : 'abc', + scale : scale + }); + scene.renderForSpecs(); + + var width = 0; + var height = 0; + + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } + + width += billboard.width; + height = Math.max(height, billboard.height); + } + + width *= scale; + height *= scale; + + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toEqual(0); + expect(bbox.y).toEqual(0); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + + it('computes screen space bounding box with result', function() { + var scale = 1.5; + + var label = labels.add({ + text : 'abc', + scale : scale + }); + scene.renderForSpecs(); + + var width = 0; + var height = 0; + + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } + + width += billboard.width; + height = Math.max(height, billboard.height); + } + + width *= scale; + height *= scale; + + var result = new BoundingRectangle(); + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO, result); + expect(bbox.x).toEqual(0); + expect(bbox.y).toEqual(0); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + expect(bbox).toBe(result); + }); + + it('computes screen space bounding box with vertical origin', function() { + var scale = 1.5; + + var label = labels.add({ + text : 'abc', + scale : scale, + verticalOrigin : VerticalOrigin.CENTER + }); + scene.renderForSpecs(); + + var width = 0; + var height = 0; + + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } + + width += billboard.width; + height = Math.max(height, billboard.height); + } + + width *= scale; + height *= scale; + + var halfHeight = height * 0.5; + + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toEqual(0); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + + label.verticalOrigin = VerticalOrigin.TOP; + bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toEqual(0); + expect(bbox.y).toEqual(-height); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + + it('computes screen space bounding box with horizontal origin', function() { + var scale = 1.5; + + var label = labels.add({ + text : 'abc', + scale : scale, + horizontalOrigin : HorizontalOrigin.CENTER + }); + scene.renderForSpecs(); + + var width = 0; + var height = 0; + + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } + + width += billboard.width; + height = Math.max(height, billboard.height); + } + + width *= scale; + height *= scale; + + var halfWidth = width * 0.5; + + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(0); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + + label.horizontalOrigin = HorizontalOrigin.RIGHT; + bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toEqual(-width); + expect(bbox.y).toEqual(0); + expect(bbox.width).toEqual(width); + expect(bbox.height).toEqual(height); + }); + it('can equal another label', function() { var label = labels.add({ position : new Cartesian3(1.0, 2.0, 3.0), diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index 6378a68a6bdd..2699f497616c 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -10,6 +10,7 @@ defineSuite([ 'Core/defaultValue', 'Core/defined', 'Core/defineProperties', + 'Core/DistanceDisplayCondition', 'Core/Ellipsoid', 'Core/Event', 'Core/FeatureDetection', @@ -41,6 +42,7 @@ defineSuite([ defaultValue, defined, defineProperties, + DistanceDisplayCondition, Ellipsoid, Event, FeatureDetection, @@ -214,22 +216,23 @@ defineSuite([ it('sets model properties', function() { var modelMatrix = Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(0.0, 0.0, 100.0)); - expect(texturedBoxModel.gltf).toBeDefined(); - expect(texturedBoxModel.basePath).toEqual('./Data/Models/Box-Textured/'); - expect(texturedBoxModel.show).toEqual(false); - expect(texturedBoxModel.modelMatrix).toEqual(modelMatrix); - expect(texturedBoxModel.scale).toEqual(1.0); - expect(texturedBoxModel.minimumPixelSize).toEqual(0.0); - expect(texturedBoxModel.maximumScale).toBeUndefined(); - expect(texturedBoxModel.id).toEqual(texturedBoxUrl); - expect(texturedBoxModel.allowPicking).toEqual(true); - expect(texturedBoxModel.activeAnimations).toBeDefined(); - expect(texturedBoxModel.ready).toEqual(true); - expect(texturedBoxModel.asynchronous).toEqual(true); - expect(texturedBoxModel.releaseGltfJson).toEqual(false); - expect(texturedBoxModel.cacheKey).toEndWith('Data/Models/Box-Textured/CesiumTexturedBoxTest.gltf'); - expect(texturedBoxModel.debugShowBoundingVolume).toEqual(false); - expect(texturedBoxModel.debugWireframe).toEqual(false); + expect(texturedBoxModel.gltf).toBeDefined(); + expect(texturedBoxModel.basePath).toEqual('./Data/Models/Box-Textured/'); + expect(texturedBoxModel.show).toEqual(false); + expect(texturedBoxModel.modelMatrix).toEqual(modelMatrix); + expect(texturedBoxModel.scale).toEqual(1.0); + expect(texturedBoxModel.minimumPixelSize).toEqual(0.0); + expect(texturedBoxModel.maximumScale).toBeUndefined(); + expect(texturedBoxModel.id).toEqual(texturedBoxUrl); + expect(texturedBoxModel.allowPicking).toEqual(true); + expect(texturedBoxModel.activeAnimations).toBeDefined(); + expect(texturedBoxModel.ready).toEqual(true); + expect(texturedBoxModel.asynchronous).toEqual(true); + expect(texturedBoxModel.releaseGltfJson).toEqual(false); + expect(texturedBoxModel.cacheKey).toEndWith('Data/Models/Box-Textured/CesiumTexturedBoxTest.gltf'); + expect(texturedBoxModel.debugShowBoundingVolume).toEqual(false); + expect(texturedBoxModel.debugWireframe).toEqual(false); + expect(texturedBoxModel.distanceDisplayCondition).toBeUndefined(); }); it('renders', function() { @@ -373,6 +376,48 @@ defineSuite([ texturedBoxModel.debugWireframe = false; }); + it('renders with distance display condition', function() { + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + var center = Matrix4.getTranslation(texturedBoxModel.modelMatrix, new Cartesian3()); + var near = 10.0; + var far = 100.0; + + texturedBoxModel.show = true; + texturedBoxModel.distanceDisplayCondition = new DistanceDisplayCondition(near, far); + + var frameState = scene.frameState; + var commands = frameState.commandList; + + frameState.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, far + 10.0)); + frameState.camera.lookAtTransform(Matrix4.IDENTITY); + frameState.commandList = []; + texturedBoxModel.update(frameState); + expect(frameState.commandList.length).toEqual(0); + + frameState.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, (far + near) * 0.5)); + frameState.camera.lookAtTransform(Matrix4.IDENTITY); + frameState.commandList = []; + texturedBoxModel.update(frameState); + expect(frameState.commandList.length).toBeGreaterThan(0); + + frameState.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, near - 1.0)); + frameState.camera.lookAtTransform(Matrix4.IDENTITY); + frameState.commandList = []; + texturedBoxModel.update(frameState); + expect(frameState.commandList.length).toEqual(0); + + scene.frameState.commandList = commands; + texturedBoxModel.show = false; + texturedBoxModel.distanceDisplayCondition = undefined; + }); + + it('distanceDisplayCondition throws when ner >= far', function() { + expect(function() { + texturedBoxModel.distanceDisplayCondition = new DistanceDisplayCondition(100.0, 10.0); + }).toThrowDeveloperError(); + }); + it('getNode throws when model is not loaded', function() { var m = new Model(); expect(function() { diff --git a/Specs/Scene/PointPrimitiveCollectionSpec.js b/Specs/Scene/PointPrimitiveCollectionSpec.js index fc66dabb553e..ef8f95a99aaa 100644 --- a/Specs/Scene/PointPrimitiveCollectionSpec.js +++ b/Specs/Scene/PointPrimitiveCollectionSpec.js @@ -1,25 +1,31 @@ /*global defineSuite*/ defineSuite([ 'Scene/PointPrimitiveCollection', + 'Core/BoundingRectangle', 'Core/BoundingSphere', 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', 'Core/Math', 'Core/NearFarScalar', 'Core/Rectangle', 'Scene/OrthographicFrustum', + 'Scene/PointPrimitive', 'Specs/createScene' ], function( PointPrimitiveCollection, + BoundingRectangle, BoundingSphere, Cartesian2, Cartesian3, Color, + DistanceDisplayCondition, CesiumMath, NearFarScalar, Rectangle, OrthographicFrustum, + PointPrimitive, createScene) { 'use strict'; @@ -66,6 +72,7 @@ defineSuite([ expect(p.outlineWidth).toEqual(0.0); expect(p.scaleByDistance).not.toBeDefined(); expect(p.translucencyByDistance).not.toBeDefined(); + expect(p.distanceDisplayCondition).not.toBeDefined(); expect(p.id).not.toBeDefined(); }); @@ -95,6 +102,7 @@ defineSuite([ outlineWidth : 4.0, scaleByDistance : new NearFarScalar(1.0, 3.0, 1.0e6, 0.0), translucencyByDistance : new NearFarScalar(1.0, 1.0, 1.0e6, 0.0), + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), id : 'id' }); @@ -112,6 +120,7 @@ defineSuite([ expect(p.outlineWidth).toEqual(4.0); expect(p.scaleByDistance).toEqual(new NearFarScalar(1.0, 3.0, 1.0e6, 0.0)); expect(p.translucencyByDistance).toEqual(new NearFarScalar(1.0, 1.0, 1.0e6, 0.0)); + expect(p.distanceDisplayCondition).toEqual(new DistanceDisplayCondition(10.0, 100.0)); expect(p.id).toEqual('id'); }); @@ -125,6 +134,7 @@ defineSuite([ p.outlineWidth = 4.0; p.scaleByDistance = new NearFarScalar(1.0e6, 3.0, 1.0e8, 0.0); p.translucencyByDistance = new NearFarScalar(1.0e6, 1.0, 1.0e8, 0.0); + p.distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); expect(p.show).toEqual(false); expect(p.position).toEqual(new Cartesian3(1.0, 2.0, 3.0)); @@ -140,6 +150,7 @@ defineSuite([ expect(p.outlineWidth).toEqual(4.0); expect(p.scaleByDistance).toEqual(new NearFarScalar(1.0e6, 3.0, 1.0e8, 0.0)); expect(p.translucencyByDistance).toEqual(new NearFarScalar(1.0e6, 1.0, 1.0e8, 0.0)); + expect(p.distanceDisplayCondition).toEqual(new DistanceDisplayCondition(10.0, 100.0)); }); it('is not destroyed', function() { @@ -240,6 +251,40 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('renders pointPrimitive with distanceDisplayCondition', function() { + pointPrimitives.add({ + position : Cartesian3.ZERO, + color : Color.LIME, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + + camera.position = new Cartesian3(200.0, 0.0, 0.0); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + camera.position = new Cartesian3(50.0, 0.0, 0.0); + expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]); + + camera.position = new Cartesian3(5.0, 0.0, 0.0); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + + it('throws new pointPrimitive with invalid distanceDisplayCondition (near >= far)', function() { + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + pointPrimitives.add({ + distanceDisplayCondition : dc + }); + }).toThrowDeveloperError(); + }); + + it('throws distanceDisplayCondition with near >= far', function() { + var p = pointPrimitives.add(); + var dc = new DistanceDisplayCondition(100.0, 10.0); + expect(function() { + p.distanceDisplayCondition = dc; + }).toThrowDeveloperError(); + }); + it('set a removed pointPrimitive property', function() { var p = pointPrimitives.add(); pointPrimitives.remove(p); @@ -679,6 +724,42 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('computes screen space bounding box', function() { + var size = 10; + + var p = pointPrimitives.add({ + size : size + }); + + var halfWidth = size * 0.5; + var halfHeight = halfWidth; + + var bbox = PointPrimitive.getScreenSpaceBoundingBox(p, Cartesian2.ZERO); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(size); + expect(bbox.height).toEqual(size); + }); + + it('computes screen space bounding box with result', function() { + var size = 10; + + var p = pointPrimitives.add({ + size : size + }); + + var halfWidth = size * 0.5; + var halfHeight = halfWidth; + + var result = new BoundingRectangle(); + var bbox = PointPrimitive.getScreenSpaceBoundingBox(p, Cartesian2.ZERO, result); + expect(bbox.x).toEqual(-halfWidth); + expect(bbox.y).toEqual(-halfHeight); + expect(bbox.width).toEqual(size); + expect(bbox.height).toEqual(size); + expect(bbox).toBe(result); + }); + it('equals another pointPrimitive', function() { var p = pointPrimitives.add({ position : new Cartesian3(1.0, 2.0, 3.0), diff --git a/Specs/Scene/PolylineCollectionSpec.js b/Specs/Scene/PolylineCollectionSpec.js index 40d071037716..6a2cb9753638 100644 --- a/Specs/Scene/PolylineCollectionSpec.js +++ b/Specs/Scene/PolylineCollectionSpec.js @@ -4,6 +4,8 @@ defineSuite([ 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', + 'Core/DistanceDisplayCondition', + 'Core/HeadingPitchRange', 'Core/Math', 'Scene/Camera', 'Scene/Material', @@ -14,6 +16,8 @@ defineSuite([ BoundingSphere, Cartesian3, Color, + DistanceDisplayCondition, + HeadingPitchRange, CesiumMath, Camera, Material, @@ -1222,6 +1226,45 @@ defineSuite([ expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); }); + it('renders with a distance display condition', function() { + if (!scene.context.floatingPointTexture) { + return; + } + + var near = 100.0; + var far = 10000.0; + + var line = polylines.add({ + positions : [{ + x : 10.0, + y : -10.0, + z : 0.0 + }, { + x : 10.0, + y : 10.0, + z : 0.0 + }], + width : 7, + distanceDisplayCondition : new DistanceDisplayCondition(near, far) + }); + + scene.primitives.add(polylines); + scene.renderForSpecs(); + + var boundingSphere = line._boundingVolumeWC; + var center = boundingSphere.center; + var radius = boundingSphere.radius; + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + near - 10.0)); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + near + 1.0)); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + far + 10.0)); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + it('changes polyline position size recreates vertex arrays', function() { var positions = []; for(var i = 0; i < 20; ++i){ diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js index 36a4aafc0916..2731adb01b92 100644 --- a/Specs/Scene/PrimitiveSpec.js +++ b/Specs/Scene/PrimitiveSpec.js @@ -7,11 +7,14 @@ defineSuite([ 'Core/ColorGeometryInstanceAttribute', 'Core/ComponentDatatype', 'Core/defined', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/Ellipsoid', 'Core/Geometry', 'Core/GeometryAttribute', 'Core/GeometryInstance', 'Core/GeometryInstanceAttribute', + 'Core/HeadingPitchRange', + 'Core/Math', 'Core/Matrix4', 'Core/PolygonGeometry', 'Core/PrimitiveType', @@ -36,11 +39,14 @@ defineSuite([ ColorGeometryInstanceAttribute, ComponentDatatype, defined, + DistanceDisplayConditionGeometryInstanceAttribute, Ellipsoid, Geometry, GeometryAttribute, GeometryInstance, GeometryInstanceAttribute, + HeadingPitchRange, + CesiumMath, Matrix4, PolygonGeometry, PrimitiveType, @@ -712,6 +718,49 @@ defineSuite([ expect(attributes.boundingSphere).toBeDefined(); }); + it('renders with distance display condition per instance attribute', function() { + var near = 10000.0; + var far = 1000000.0; + var rect = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); + var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rect)), new Cartesian3()), 2.0, new Cartesian3()); + var rectInstance = new GeometryInstance({ + geometry : new RectangleGeometry({ + vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, + ellipsoid : ellipsoid, + rectangle : rect + }), + modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), + id : 'rect', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), + distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) + } + }); + + primitive = new Primitive({ + geometryInstances : rectInstance, + appearance : new PerInstanceColorAppearance(), + asynchronous : false + }); + + scene.primitives.add(primitive); + scene.camera.setView({ destination : rect }); + scene.renderForSpecs(); + + var boundingSphere = primitive.getGeometryInstanceAttributes('rect').boundingSphere; + var center = boundingSphere.center; + var radius = boundingSphere.radius; + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius)); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + near + 1.0)); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + far + 1.0)); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + it('getGeometryInstanceAttributes returns same object each time', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, diff --git a/Specs/Scene/ShadowMapSpec.js b/Specs/Scene/ShadowMapSpec.js index bf20df19a9a6..dfd14519819c 100644 --- a/Specs/Scene/ShadowMapSpec.js +++ b/Specs/Scene/ShadowMapSpec.js @@ -861,11 +861,11 @@ defineSuite([ it('enable debugShow for cascaded shadow map', function() { createCascadedShadowMap(); - // Shadow overlay command, shadow volume outline, camera outline, and four cascade outlines + // Shadow overlay command, shadow volume outline, camera outline, four cascade outlines, four cascade planes scene.shadowMap.debugShow = true; scene.shadowMap.debugFreezeFrame = true; render(); - expect(scene.frameState.commandList.length).toBe(7); + expect(scene.frameState.commandList.length).toBe(13); scene.shadowMap.debugShow = false; render(); @@ -875,10 +875,10 @@ defineSuite([ it('enable debugShow for fixed shadow map', function() { createShadowMapForDirectionalLight(); - // Overlay command and shadow volume outline + // Overlay command, shadow volume outline, shadow volume planes scene.shadowMap.debugShow = true; render(); - expect(scene.frameState.commandList.length).toBe(2); + expect(scene.frameState.commandList.length).toBe(3); scene.shadowMap.debugShow = false; render(); diff --git a/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js b/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js index 3a2b9e3ec7a5..7e3f1c5ecb82 100644 --- a/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js +++ b/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js @@ -58,6 +58,7 @@ defineSuite([ expect(viewer.dropEnabled).toEqual(true); expect(viewer.clearOnDrop).toEqual(true); expect(viewer.clampToGround).toEqual(true); + expect(viewer.flyToOnDrop).toEqual(true); }); it('clearOnDrop defaults to true when dataSourceBrowser is not used', function() { @@ -73,12 +74,14 @@ defineSuite([ viewer.extend(viewerDragDropMixin, { dropTarget : document.body, clearOnDrop : false, - clampToGround : false + clampToGround : false, + flyToOnDrop: false }); expect(viewer.dropTarget).toBe(document.body); expect(viewer.dropEnabled).toEqual(true); expect(viewer.clearOnDrop).toEqual(false); expect(viewer.clampToGround).toEqual(false); + expect(viewer.flyToOnDrop).toEqual(false); }); it('mixin works with dropTarget id string', function() { diff --git a/package.json b/package.json index 77221d7424a8..e9b57d74028f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cesium", - "version": "1.25.0", + "version": "1.26.0", "description": "Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "homepage": "http://cesiumjs.org", "license": "Apache-2.0", @@ -8,10 +8,12 @@ "name": "Analytical Graphics, Inc.", "url": "http://www.agi.com" }, - "contributors": [{ - "name": "Cesium community", - "url": "https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTORS.md" - }], + "contributors": [ + { + "name": "Cesium community", + "url": "https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTORS.md" + } + ], "keywords": [ "3D", "webgl", @@ -28,15 +30,15 @@ "email": "cesium-dev@googlegroups.com" }, "dependencies": { - "requirejs": "2.2.0" + "requirejs": "2.3.2" }, "devDependencies": { - "almond": "0.3.2", - "aws-sdk": "2.5.4", - "bluebird": "3.4.5", + "almond": "0.3.3", + "aws-sdk": "2.6.5", + "bluebird": "3.4.6", "compressible": "2.0.8", "compression": "1.6.2", - "electron-prebuilt": "1.3.4", + "electron-prebuilt": "1.4.1", "event-stream": "3.3.4", "express": "4.14.0", "globby": "6.0.0", @@ -46,23 +48,23 @@ "gulp-rename": "1.2.2", "gulp-replace": "0.5.4", "gulp-zip": "3.0.2", - "jasmine-core": "2.4.1", - "jsdoc": "3.4.0", + "jasmine-core": "2.5.2", + "jsdoc": "3.4.1", "jshint": "2.9.3", "jshint-stylish": "2.2.1", - "karma": "1.2.0", + "karma": "1.3.0", "karma-chrome-launcher": "2.0.0", "karma-detect-browsers": "2.1.0", "karma-electron-launcher": "0.1.0", "karma-firefox-launcher": "1.0.0", "karma-ie-launcher": "1.0.0", "karma-jasmine": "1.0.2", - "karma-requirejs": "1.0.0", + "karma-requirejs": "1.1.0", "karma-safari-launcher": "1.0.0", "karma-spec-reporter": "0.0.26", "mime": "1.3.4", "mkdirp": "0.5.1", - "request": "2.74.0", + "request": "2.75.0", "rimraf": "2.5.3", "strip-comments": "0.3.2", "yargs": "5.0.0"