diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index 64a4de65e926..eed1f9625dd4 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -81,11 +81,7 @@ clock.stopTime = stop; clock.clockRange = Cesium.ClockRange.LOOP_STOP; -viewer.camera.setView({ - destination: new Cesium.Cartesian3(1215034.013185971, -4736376.364704681, 4081587.528471664), - orientation: new Cesium.HeadingPitchRoll(6.2077134961933265, -0.6084278203800215, 6.282880789189662), - endTransform : Cesium.Matrix4.IDENTITY -}); +viewer.zoomTo(pointCloud, new Cesium.HeadingPitchRange(0.0, -0.5, 50.0)); //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index b9049515f892..53bb32d7fe01 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -180,7 +180,7 @@ define([ this.time = 0.0; // For styling this.shadows = ShadowMode.ENABLED; - this.boundingSphere = undefined; + this._boundingSphere = undefined; this.clippingPlanes = undefined; this.isClipped = false; @@ -229,6 +229,17 @@ define([ set : function(value) { this._highlightColor = Color.clone(value, this._highlightColor); } + }, + + boundingSphere : { + get : function() { + if (defined(this._drawCommand)) { + return this._drawCommand.boundingVolume; + } + }, + set : function(value) { + this._boundingSphere = BoundingSphere.clone(value); + } } }); @@ -634,9 +645,9 @@ define([ if (pointCloud._cull) { if (isQuantized || isQuantizedDraco) { - pointCloud.boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.ZERO, pointCloud._quantizedVolumeScale); + pointCloud._boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.ZERO, pointCloud._quantizedVolumeScale); } else { - pointCloud.boundingSphere = computeApproximateBoundingSphereFromPositions(positions); + pointCloud._boundingSphere = computeApproximateBoundingSphereFromPositions(positions); } } @@ -1298,7 +1309,7 @@ define([ } var boundingSphere = this._drawCommand.boundingVolume; - BoundingSphere.clone(this.boundingSphere, boundingSphere); + BoundingSphere.clone(this._boundingSphere, boundingSphere); if (this._cull) { var center = boundingSphere.center; diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 6022db402b15..7e2bc7d7df10 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -222,6 +222,22 @@ define([ get : function() { return this._totalMemoryUsageInBytes; } + }, + + /** + * The bounding sphere of the frame being rendered. Returns undefined if no frame is being rendered. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {BoundingSphere} + * @readonly + */ + boundingSphere : { + get : function() { + if (defined(this._lastRenderedFrame)) { + return this._lastRenderedFrame.pointCloud.boundingSphere; + } + } } }); diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 2c85c9fc522e..a33216b3cc97 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -24,6 +24,7 @@ define([ '../../Scene/Cesium3DTileset', '../../Scene/ImageryLayer', '../../Scene/SceneMode', + '../../Scene/TimeDynamicPointCloud', '../../ThirdParty/knockout', '../../ThirdParty/when', '../Animation/Animation', @@ -71,6 +72,7 @@ define([ Cesium3DTileset, ImageryLayer, SceneMode, + TimeDynamicPointCloud, knockout, when, Animation, @@ -1740,7 +1742,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to * target will be the range. The heading will be determined from the offset. If the heading cannot be * determined from the offset, the heading will be north.

* - * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.} target The entity, array of entities, entity collection, data source, Cesium#DTileset, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. + * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame. * @returns {Promise.} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled. */ @@ -1766,7 +1768,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to * target will be the range. The heading will be determined from the offset. If the heading cannot be * determined from the offset, the heading will be north.

* - * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. + * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. * @param {Object} [options] Object with the following properties: * @param {Number} [options.duration=3.0] The duration of the flight in seconds. * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight. @@ -1818,6 +1820,12 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to return; } + //If the zoom target is a TimeDynamicPointCloud + if (zoomTarget instanceof TimeDynamicPointCloud) { + that._zoomTarget = zoomTarget; + return; + } + //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading. if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) { var removeEvent = zoomTarget.loadingEvent.addEventListener(function() { @@ -1891,12 +1899,13 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to var zoomPromise = viewer._zoomPromise; var zoomOptions = defaultValue(viewer._zoomOptions, {}); var options; + var boundingSphere; // If zoomTarget was Cesium3DTileset if (target instanceof Cesium3DTileset) { return target.readyPromise.then(function() { var boundingSphere = target.boundingSphere; - // if offset was originally undefined then give it base value instead of empty object + // If offset was originally undefined then give it base value instead of empty object if (!defined(zoomOptions.offset)) { zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius); } @@ -1919,7 +1928,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to camera.viewBoundingSphere(boundingSphere, zoomOptions.offset); camera.lookAtTransform(Matrix4.IDENTITY); - // finish the promise + // Finish the promise zoomPromise.resolve(true); } @@ -1927,7 +1936,43 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to }); } - //If zoomTarget was an ImageryLayer + // If zoomTarget was TimeDynamicPointCloud + if (target instanceof TimeDynamicPointCloud) { + boundingSphere = target.boundingSphere; + if (defined(boundingSphere)) { + // If offset was originally undefined then give it base value instead of empty object + if (!defined(zoomOptions.offset)) { + zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius); + } + + options = { + offset : zoomOptions.offset, + duration : zoomOptions.duration, + maximumHeight : zoomOptions.maximumHeight, + complete : function() { + zoomPromise.resolve(true); + }, + cancel : function() { + zoomPromise.resolve(false); + } + }; + + if (viewer._zoomIsFlight) { + camera.flyToBoundingSphere(boundingSphere, options); + } else { + camera.viewBoundingSphere(boundingSphere, zoomOptions.offset); + camera.lookAtTransform(Matrix4.IDENTITY); + + // Finish the promise + zoomPromise.resolve(true); + } + + clearZoom(viewer); + } + return; + } + + // If zoomTarget was an ImageryLayer if (target instanceof Rectangle) { options = { destination : target, @@ -1972,7 +2017,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to //Stop tracking the current entity. viewer.trackedEntity = undefined; - var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); + boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); if (!viewer._zoomIsFlight) { camera.viewBoundingSphere(boundingSphere, viewer._zoomOptions.offset); diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js index 1fb11177df9a..479198de4056 100644 --- a/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Scene/TimeDynamicPointCloud', + 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Clock', 'Core/ClockStep', @@ -26,6 +27,7 @@ defineSuite([ 'ThirdParty/when' ], function( TimeDynamicPointCloud, + BoundingSphere, Cartesian3, Clock, ClockStep, @@ -240,6 +242,22 @@ defineSuite([ }); }); + it('gets bounding sphere of the rendered frame', function() { + var pointCloud = createTimeDynamicPointCloud({ + useTransforms : true + }); + expect(pointCloud.boundingSphere).toBeUndefined(); // Undefined until a frame is rendered + return loadAllFrames(pointCloud).then(function() { + var boundingSphereFrame0 = pointCloud.boundingSphere; + expect(boundingSphereFrame0).toBeDefined(); + goToFrame(1); + scene.renderForSpecs(); + var boundingSphereFrame1 = pointCloud.boundingSphere; + expect(boundingSphereFrame1).toBeDefined(); + expect(BoundingSphere.equals(boundingSphereFrame0, boundingSphereFrame1)).toBe(false); + }); + }); + it('sets show', function() { var pointCloud = createTimeDynamicPointCloud(); diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js index e26ad717d42e..2fd86c1ad760 100644 --- a/Specs/Widgets/Viewer/ViewerSpec.js +++ b/Specs/Widgets/Viewer/ViewerSpec.js @@ -5,10 +5,12 @@ defineSuite([ 'Core/ClockRange', 'Core/ClockStep', 'Core/Color', + 'Core/defined', 'Core/EllipsoidTerrainProvider', 'Core/HeadingPitchRange', 'Core/JulianDate', 'Core/Matrix4', + 'Core/TimeIntervalCollection', 'Core/WebMercatorProjection', 'DataSources/BoundingSphereState', 'DataSources/ConstantPositionProperty', @@ -23,6 +25,7 @@ defineSuite([ 'Scene/ImageryLayerCollection', 'Scene/SceneMode', 'Scene/ShadowMode', + 'Scene/TimeDynamicPointCloud', 'Specs/createViewer', 'Specs/DomEventSimulator', 'Specs/MockDataSource', @@ -46,10 +49,12 @@ defineSuite([ ClockRange, ClockStep, Color, + defined, EllipsoidTerrainProvider, HeadingPitchRange, JulianDate, Matrix4, + TimeIntervalCollection, WebMercatorProjection, BoundingSphereState, ConstantPositionProperty, @@ -64,6 +69,7 @@ defineSuite([ ImageryLayerCollection, SceneMode, ShadowMode, + TimeDynamicPointCloud, createViewer, DomEventSimulator, MockDataSource, @@ -1108,6 +1114,92 @@ defineSuite([ }); }); + function loadTimeDynamicPointCloud(viewer) { + var scene = viewer.scene; + var clock = viewer.clock; + + var uri = './Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts'; + var dates = [ + '2018-07-19T15:18:00Z', + '2018-07-19T15:18:00.5Z' + ]; + + function dataCallback() { + return { + uri: uri + }; + } + + var timeIntervalCollection = TimeIntervalCollection.fromIso8601DateArray({ + iso8601Dates: dates, + dataCallback: dataCallback + }); + + var pointCloud = new TimeDynamicPointCloud({ + intervals : timeIntervalCollection, + clock : clock + }); + viewer.scene.primitives.add(pointCloud); + + var start = JulianDate.fromIso8601(dates[0]); + + clock.startTime = start; + clock.currentTime = start; + clock.multiplier = 0.0; + + return pollToPromise(function() { + pointCloud.update(scene.frameState); + var frame = pointCloud._frames[0]; + return defined(frame) && frame.ready; + }).then(function() { + return pointCloud; + }); + } + + it('zoomTo zooms to TimeDynamicPointCloud with default offset when offset not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var expectedBoundingSphere = pointCloud.boundingSphere; + var expectedOffset = new HeadingPitchRange(0.0, -0.5, expectedBoundingSphere.radius); + + var promise = viewer.zoomTo(pointCloud); + var wasCompleted = false; + spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('zoomTo zooms to TimeDynamicPointCloud with offset', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var expectedBoundingSphere = pointCloud.boundingSphere; + var expectedOffset = new HeadingPitchRange(0.4, 1.2, 4.0 * expectedBoundingSphere.radius); + + var promise = viewer.zoomTo(pointCloud, expectedOffset); + var wasCompleted = false; + spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + it('zoomTo zooms to entity with undefined offset when offset not defined', function() { viewer = createViewer(container); viewer.entities.add({ @@ -1208,7 +1300,6 @@ defineSuite([ expect(wasCompleted).toEqual(true); }); }); - }); it('flyTo flies to Cesium3DTileset with default offset when offset not defined', function() { @@ -1239,11 +1330,10 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); - }); }); - it('flyTo flies to target when target is Cesium3DTileset and options are defined', function() { + it('flyTo flies to Cesium3DTileset when options are defined', function() { viewer = createViewer(container); var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json'; @@ -1275,7 +1365,78 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud with default offset when options not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var promise = viewer.flyTo(pointCloud); + var wasCompleted = false; + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud with default offset when offset not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var options = {}; + var promise = viewer.flyTo(pointCloud, options); + var wasCompleted = false; + + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud when options are defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3); + var options = { + offset : offsetVal, + duration : 3.0, + maximumHeight : 5.0 + }; + var promise = viewer.flyTo(pointCloud, options); + var wasCompleted = false; + + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.duration).toBeDefined(); + expect(options.maximumHeight).toBeDefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); }); }); @@ -1309,7 +1470,6 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); - }); it('flyTo flys to entity with default offset when offset not defined', function() {