From 872b8dc0398a1e349995fa0045a4afc2694b9fb9 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 9 Jan 2018 14:26:27 -0500
Subject: [PATCH 01/16] changed zoom for tileset so 1 line call instead of 3

---
 Source/Widgets/Viewer/Viewer.js | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 4c2dcadc2721..b9e31d4a3f12 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1,6 +1,7 @@
 define([
         '../../Core/BoundingSphere',
         '../../Core/Cartesian3',
+        '../../Core/Check',
         '../../Core/Clock',
         '../../Core/defaultValue',
         '../../Core/defined',
@@ -9,6 +10,7 @@ define([
         '../../Core/DeveloperError',
         '../../Core/Event',
         '../../Core/EventHelper',
+        '../../Core/HeadingPitchRange',
         '../../Core/isArray',
         '../../Core/Matrix4',
         '../../Core/Rectangle',
@@ -46,6 +48,7 @@ define([
     ], function(
         BoundingSphere,
         Cartesian3,
+        Check,
         Clock,
         defaultValue,
         defined,
@@ -54,6 +57,7 @@ define([
         DeveloperError,
         Event,
         EventHelper,
+        HeadingPitchRange,
         isArray,
         Matrix4,
         Rectangle,
@@ -1846,6 +1850,26 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         }
     }
 
+    /**
+     * Zooms to a {@link Cesium3DTileset} with an optional offset. If offset is not supplied then will zoom to origin at
+     * a distance of the diameter.
+     *
+     * @param {Cesium3dTileset} tileset The tileset to which this zooms.
+     * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
+     */
+    Viewer.prototype.zoomToTileset = function(tileset, offset) {
+        //>>includeStart('debug', pragmas.debug);
+        Check.typeOf.object('tileset', tileset);
+        //>>includeEnd('debug');
+
+        var boundingSphere = tileset.boundingSphere;
+        if (!defined(offset)) {
+            offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+        }
+        this.camera.viewBoundingSphere(boundingSphere, offset);
+        this.camera.lookAtTransform(Matrix4.IDENTITY);
+    };
+
     /**
      * @private
      */

From 4d31962dba136880f18a07f7abe79021b283a129 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 9 Jan 2018 15:36:37 -0500
Subject: [PATCH 02/16] created function that zooms to a 3DTileset

---
 CHANGES.md                         |  1 +
 Source/Widgets/Viewer/Viewer.js    | 26 ++++++++++-------
 Specs/Widgets/Viewer/ViewerSpec.js | 47 ++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 11 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 797f57baa209..2773197b9bff 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,6 +26,7 @@ Change Log
    * Only one mesh per node is supported.
    * Only one primitive per mesh is supported.
 * Updated documentation links to reflect new locations on cesiumjs.org and cesium.com. 
+* Added `Viewer.zoomToTileset` to zoom to a tileset.
 
 ### 1.41 - 2018-01-02
 
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index be8e5b9775a0..95aed1bab780 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -209,13 +209,13 @@ define([
         if (defined(homeButton)) {
             homeButton.container.style.visibility = visibility;
         }
-        if(defined(sceneModePicker)) {
+        if (defined(sceneModePicker)) {
             sceneModePicker.container.style.visibility = visibility;
         }
         if (defined(projectionPicker)) {
             projectionPicker.container.style.visibility = visibility;
         }
-        if(defined(baseLayerPicker)) {
+        if (defined(baseLayerPicker)) {
             baseLayerPicker.container.style.visibility = visibility;
         }
         if (defined(animation)) {
@@ -360,7 +360,7 @@ define([
         options = defaultValue(options, defaultValue.EMPTY_OBJECT);
 
         var createBaseLayerPicker = (!defined(options.globe) || options.globe !== false) &&
-            (!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);
+                                    (!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);
 
         //>>includeStart('debug', pragmas.debug);
         // If using BaseLayerPicker, imageryProvider is an invalid option
@@ -435,7 +435,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             targetFrameRate : options.targetFrameRate,
             showRenderLoopErrors : options.showRenderLoopErrors,
             creditContainer : defined(options.creditContainer) ? options.creditContainer : bottomContainer,
-            creditViewport: options.creditViewport,
+            creditViewport : options.creditViewport,
             scene3DOnly : scene3DOnly,
             terrainExaggeration : options.terrainExaggeration,
             shadows : options.shadows,
@@ -495,7 +495,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             toolbar.appendChild(geocoderContainer);
             geocoder = new Geocoder({
                 container : geocoderContainer,
-                geocoderServices: defined(options.geocoder) ? (isArray(options.geocoder) ? options.geocoder : [options.geocoder]) : undefined,
+                geocoderServices : defined(options.geocoder) ? (isArray(options.geocoder) ? options.geocoder : [options.geocoder]) : undefined,
                 scene : cesiumWidget.scene
             });
             // Subscribe to search so that we can clear the trackedEntity when it is clicked.
@@ -1856,18 +1856,22 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      *
      * @param {Cesium3dTileset} tileset The tileset to which this zooms.
      * @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 when the loaded tileset is zoomed to.
      */
     Viewer.prototype.zoomToTileset = function(tileset, offset) {
         //>>includeStart('debug', pragmas.debug);
         Check.typeOf.object('tileset', tileset);
         //>>includeEnd('debug');
 
-        var boundingSphere = tileset.boundingSphere;
-        if (!defined(offset)) {
-            offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
-        }
-        this.camera.viewBoundingSphere(boundingSphere, offset);
-        this.camera.lookAtTransform(Matrix4.IDENTITY);
+        var camera = this.camera;
+        return tileset.readyPromise.then(function() {
+            var boundingSphere = tileset.boundingSphere;
+            if (!defined(offset)) {
+                offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+            }
+            camera.viewBoundingSphere(boundingSphere, offset);
+            camera.lookAtTransform(Matrix4.IDENTITY);
+        });
     };
 
     /**
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index e3b39921df1e..972e1f624deb 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -3,6 +3,7 @@ defineSuite([
         'Core/ClockRange',
         'Core/ClockStep',
         'Core/EllipsoidTerrainProvider',
+        'Core/HeadingPitchRange',
         'Core/JulianDate',
         'Core/Matrix4',
         'Core/WebMercatorProjection',
@@ -14,6 +15,7 @@ defineSuite([
         'DataSources/Entity',
         'Scene/Camera',
         'Scene/CameraFlightPath',
+        'Scene/Cesium3DTileSet',
         'Scene/ImageryLayerCollection',
         'Scene/SceneMode',
         'Scene/ShadowMode',
@@ -38,6 +40,7 @@ defineSuite([
         ClockRange,
         ClockStep,
         EllipsoidTerrainProvider,
+        HeadingPitchRange,
         JulianDate,
         Matrix4,
         WebMercatorProjection,
@@ -49,6 +52,7 @@ defineSuite([
         Entity,
         Camera,
         CameraFlightPath,
+        Cesium3DTileSet,
         ImageryLayerCollection,
         SceneMode,
         ShadowMode,
@@ -1021,4 +1025,47 @@ defineSuite([
         expect(preMixinDataSource.entities.collectionChanged._listeners.length).not.toEqual(preMixinListenerCount);
         expect(postMixinDataSource.entities.collectionChanged._listeners.length).not.toEqual(postMixinListenerCount);
     });
+
+    it('zoomToTileset throws if tileset is not defined', function() {
+        viewer = createViewer(container);
+
+        expect(function() {
+            viewer.zoomToTileset();
+        }).toThrowDeveloperError();
+    });
+
+    it('zoomToTileset uses default offset if not defined', function() {
+       viewer = createViewer(container);
+
+       var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+       var tileset = new Cesium3DTileSet({
+           url : path
+       });
+
+       spyOn(viewer.camera, 'viewBoundingSphere');
+
+       return viewer.zoomToTileset(tileset).then(function() {
+           var boundingSphere = tileset.boundingSphere;
+           var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+           expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
+       });
+    });
+
+    it('zooms to tileset', function() {
+        viewer = createViewer(container);
+
+        // stored for movement check
+        var camPos = Cartesian3.clone(viewer.camera.position);
+        var camDir = Cartesian3.clone(viewer.camera.direction);
+
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileSet({
+            url : path
+        });
+
+        return viewer.zoomToTileset(tileset).then(function() {
+            expect(viewer.camera.position).not.toEqual(camPos);
+            expect(viewer.camera.direction).not.toEqual(camDir);
+        });
+    });
 }, 'WebGL');

From 4c8a5be8d7b6f59bfa182eda1bf4ca1f0f56f8f2 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 9 Jan 2018 16:12:41 -0500
Subject: [PATCH 03/16] updated 3DTiles files in sandcastle to use zoomToTiles

---
 Apps/Sandcastle/gallery/3D Tiles Adjust Height.html         | 4 +---
 Apps/Sandcastle/gallery/3D Tiles BIM.html                   | 4 +---
 Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html | 6 +++---
 Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html       | 3 +--
 Apps/Sandcastle/gallery/3D Tiles Formats.html               | 6 ++----
 Apps/Sandcastle/gallery/3D Tiles Inspector.html             | 4 +---
 .../gallery/3D Tiles Photogrammetry Classification.html     | 4 +---
 Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html        | 4 +---
 Apps/Sandcastle/gallery/3D Tiles Point Cloud.html           | 4 +---
 9 files changed, 12 insertions(+), 27 deletions(-)

diff --git a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html
index 09813d39da55..9cec5e64deaf 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
@@ -61,9 +61,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius * 2));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles BIM.html b/Apps/Sandcastle/gallery/3D Tiles BIM.html
index e87706a39f3e..ce7f71f7ff19 100644
--- a/Apps/Sandcastle/gallery/3D Tiles BIM.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles BIM.html	
@@ -34,9 +34,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, boundingSphere.radius * 4.0));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 4.0));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html
index 13252ef12ec6..239e6a24bce5 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html	
@@ -89,9 +89,9 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0));
+}).otherwise(function(error) {
+        throw(error);
 });
 
 var styles = [];
diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html
index 8c962002ef2c..b58e96be4080 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html	
@@ -129,8 +129,7 @@
         var boundingSphere = tileset.boundingSphere;
         var radius = boundingSphere.radius;
 
-        viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0));
-        viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+        viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0));
 
         for (var i = 0; i < clippingPlanes.length; ++i) {
             var plane = clippingPlanes[i];
diff --git a/Apps/Sandcastle/gallery/3D Tiles Formats.html b/Apps/Sandcastle/gallery/3D Tiles Formats.html
index 960ab825fc5e..455f5d6cafcd 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Formats.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Formats.html	
@@ -111,10 +111,8 @@
     inspectorViewModel.tileset = tileset;
     scene.primitives.add(tileset);
     tileset.readyPromise.then(function(tileset) {
-        var boundingSphere = tileset.boundingSphere;
-        var range = Math.max(100.0 - boundingSphere.radius, 0.0); // Set a minimum offset of 100 meters
-        viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0, -2.0, range));
-        viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+
+        viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0, -2.0, Math.max(100.0 - tileset.boundingSphere.radius, 0.0)));
 
         var properties = tileset.properties;
         if (Cesium.defined(properties) && Cesium.defined(properties.Height)) {
diff --git a/Apps/Sandcastle/gallery/3D Tiles Inspector.html b/Apps/Sandcastle/gallery/3D Tiles Inspector.html
index 2ed6d579def0..70e939602e02 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Inspector.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Inspector.html	
@@ -36,9 +36,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius / 4.0));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius / 4.0));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html
index 5052d05af3c9..88d408ffe0ce 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
@@ -37,9 +37,7 @@
 
 // Move the camera to view the tileset on load.
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html
index 77e1c820c9c8..4cc38fdb7917 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
@@ -33,9 +33,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html
index 1c8ebe13c6aa..675ad88f52c3 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
@@ -34,9 +34,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });

From ad555b4206508796731107f4eb1b0656371ca7a0 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 9 Jan 2018 16:17:12 -0500
Subject: [PATCH 04/16] added update note to changes.md

---
 CHANGES.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGES.md b/CHANGES.md
index 2773197b9bff..e0c1d819add5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,6 +27,7 @@ Change Log
    * Only one primitive per mesh is supported.
 * Updated documentation links to reflect new locations on cesiumjs.org and cesium.com. 
 * Added `Viewer.zoomToTileset` to zoom to a tileset.
+* Updated the sandcastle 3DTileset examples to use the Viewer.zoomToTileset function
 
 ### 1.41 - 2018-01-02
 

From 01af1c7b829f4b1ffba4ca34ca525bc3fde46ec2 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 9 Jan 2018 16:20:57 -0500
Subject: [PATCH 05/16] added in missing

---
 CHANGES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index e0c1d819add5..6ed7953eb9cc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,7 +27,7 @@ Change Log
    * Only one primitive per mesh is supported.
 * Updated documentation links to reflect new locations on cesiumjs.org and cesium.com. 
 * Added `Viewer.zoomToTileset` to zoom to a tileset.
-* Updated the sandcastle 3DTileset examples to use the Viewer.zoomToTileset function
+* Updated the sandcastle 3DTileset examples to use the `Viewer.zoomToTileset` function
 
 ### 1.41 - 2018-01-02
 

From e11e281313e5ce8cd754133778c73fa943aec7dc Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Thu, 11 Jan 2018 10:57:11 -0500
Subject: [PATCH 06/16] modified zoomToOrFly to include target of
 Cesium3DTileset

---
 Source/Widgets/Viewer/Viewer.js | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 95aed1bab780..99b9b3df811c 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -22,6 +22,7 @@ define([
         '../../DataSources/Entity',
         '../../DataSources/EntityView',
         '../../DataSources/Property',
+        '../../Scene/Cesium3DTileset',
         '../../Scene/ImageryLayer',
         '../../Scene/SceneMode',
         '../../ThirdParty/knockout',
@@ -69,6 +70,7 @@ define([
         Entity,
         EntityView,
         Property,
+        Cesium3DTileset,
         ImageryLayer,
         SceneMode,
         knockout,
@@ -1729,7 +1731,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.</p>
      *
-     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer>} target The entity, array of entities, entity collection, data source 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|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} target The entity, array of entities, entity collection, data source 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.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the entity is not currently visualized in the scene or the zoom was cancelled.
      */
@@ -1752,7 +1754,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.</p>
      *
-     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer>} target The entity, array of entities, entity collection, data source 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|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} target The entity, array of entities, entity collection, data source 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.
@@ -1811,6 +1813,27 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
                 return;
             }
 
+            //If the zoom target is a Cesium3DTileset
+            if (zoomTarget instanceof Cesium3DTileset) {
+                var camera = this.camera;
+
+                // If the Cesium3DTileset is still loading, wait for it to finish loading before zooming
+                return zoomTarget.readyPromise.then(function() {
+
+                    // Only perform the zoom if it wasn't cancelled before the Cesium3DTileset finished loading
+                    if (that._zoomPromise === zoomPromise) {
+                        // that._zoomTarget is already the tileset zoomTarget
+
+                        var boundingSphere = zoomTarget.boundingSphere;
+                        if (!defined(options.offset)) {
+                            options.offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+                        }
+                        camera.viewBoundingSphere(boundingSphere, options.offset);
+                        camera.lookAtTransform(Matrix4.IDENTITY);
+                    }
+                });
+            }
+
             //Zoom target is already an array, just copy it and return.
             if (isArray(zoomTarget)) {
                 that._zoomTarget = zoomTarget.slice(0);
@@ -1825,6 +1848,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
                 zoomTarget = zoomTarget.entities.values;
             }
 
+            //Zoom target is already an array, just copy it and return.
             if (isArray(zoomTarget)) {
                 that._zoomTarget = zoomTarget.slice(0);
             } else {
@@ -1854,7 +1878,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      * Zooms to a {@link Cesium3DTileset} with an optional offset. If offset is not supplied then will zoom to origin at
      * a distance of the diameter.
      *
-     * @param {Cesium3dTileset} tileset The tileset to which this zooms.
+     * @param {Cesium3DTileset} tileset The tileset to which this zooms.
      * @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 when the loaded tileset is zoomed to.
      */

From d24e99facee6b6546050d43c0c17f91bef2c9151 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Thu, 11 Jan 2018 11:42:49 -0500
Subject: [PATCH 07/16] updated sandbox 3D Tiles to use zoomTo instead of
 separate function

---
 .../gallery/3D Tiles Adjust Height.html       |  2 +-
 Apps/Sandcastle/gallery/3D Tiles BIM.html     |  2 +-
 .../gallery/3D Tiles Clipping Planes.html     |  2 +-
 ...D Tiles Photogrammetry Classification.html |  2 +-
 .../gallery/3D Tiles Photogrammetry.html      |  2 +-
 .../gallery/3D Tiles Point Cloud.html         |  2 +-
 Source/Widgets/Viewer/Viewer.js               | 35 ++++++++++---------
 7 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html
index 9cec5e64deaf..8d931d8c6b5a 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
@@ -61,7 +61,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles BIM.html b/Apps/Sandcastle/gallery/3D Tiles BIM.html
index ce7f71f7ff19..7968388f78f1 100644
--- a/Apps/Sandcastle/gallery/3D Tiles BIM.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles BIM.html	
@@ -34,7 +34,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 4.0));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 4.0));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html
index b58e96be4080..7622e5f4ff81 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html	
@@ -129,7 +129,7 @@
         var boundingSphere = tileset.boundingSphere;
         var radius = boundingSphere.radius;
 
-        viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0));
+        viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0));
 
         for (var i = 0; i < clippingPlanes.length; ++i) {
             var plane = clippingPlanes[i];
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html
index 88d408ffe0ce..31b62533abd8 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
@@ -37,7 +37,7 @@
 
 // Move the camera to view the tileset on load.
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html
index 4cc38fdb7917..3428026bbb9c 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
@@ -33,7 +33,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html
index 675ad88f52c3..603268c725d1 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
@@ -34,7 +34,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 99b9b3df811c..90890cf74290 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1800,22 +1800,9 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
                 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() {
-                    removeEvent();
-
-                    //Only perform the zoom if it wasn't cancelled before the data source finished.
-                    if (that._zoomPromise === zoomPromise) {
-                        that._zoomTarget = zoomTarget.entities.values.slice(0);
-                    }
-                });
-                return;
-            }
-
             //If the zoom target is a Cesium3DTileset
             if (zoomTarget instanceof Cesium3DTileset) {
-                var camera = this.camera;
+                var camera = that.camera;
 
                 // If the Cesium3DTileset is still loading, wait for it to finish loading before zooming
                 return zoomTarget.readyPromise.then(function() {
@@ -1825,15 +1812,29 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
                         // that._zoomTarget is already the tileset zoomTarget
 
                         var boundingSphere = zoomTarget.boundingSphere;
-                        if (!defined(options.offset)) {
-                            options.offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+                        // since zooming options = offset from zoomTo. if offset not defined then give it base value
+                        if (!defined(options)) {
+                            options = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
                         }
-                        camera.viewBoundingSphere(boundingSphere, options.offset);
+                        camera.viewBoundingSphere(boundingSphere, options);
                         camera.lookAtTransform(Matrix4.IDENTITY);
                     }
                 });
             }
 
+            //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() {
+                    removeEvent();
+
+                    //Only perform the zoom if it wasn't cancelled before the data source finished.
+                    if (that._zoomPromise === zoomPromise) {
+                        that._zoomTarget = zoomTarget.entities.values.slice(0);
+                    }
+                });
+                return;
+            }
+
             //Zoom target is already an array, just copy it and return.
             if (isArray(zoomTarget)) {
                 that._zoomTarget = zoomTarget.slice(0);

From 6690763f5de913b1649b3ee8b3ab05a35247f71d Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Thu, 11 Jan 2018 11:58:38 -0500
Subject: [PATCH 08/16] Deleted Viewer.zoomToTileset. Updated Sandbox 3Dtileset
 examples to use zoomTo. Updated Changes.md.

---
 CHANGES.md                      |  3 +--
 Source/Widgets/Viewer/Viewer.js | 24 ------------------------
 2 files changed, 1 insertion(+), 26 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6ed7953eb9cc..cad72e1e1398 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,8 +26,7 @@ Change Log
    * Only one mesh per node is supported.
    * Only one primitive per mesh is supported.
 * Updated documentation links to reflect new locations on cesiumjs.org and cesium.com. 
-* Added `Viewer.zoomToTileset` to zoom to a tileset.
-* Updated the sandcastle 3DTileset examples to use the `Viewer.zoomToTileset` function
+* Updated 'Viewer.zoomTo' to include Cesium3DTilesets and updated sandcastle 3DTileset examples to reflect this change
 
 ### 1.41 - 2018-01-02
 
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 90890cf74290..a1966f2e4f47 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1875,30 +1875,6 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         }
     }
 
-    /**
-     * Zooms to a {@link Cesium3DTileset} with an optional offset. If offset is not supplied then will zoom to origin at
-     * a distance of the diameter.
-     *
-     * @param {Cesium3DTileset} tileset The tileset to which this zooms.
-     * @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 when the loaded tileset is zoomed to.
-     */
-    Viewer.prototype.zoomToTileset = function(tileset, offset) {
-        //>>includeStart('debug', pragmas.debug);
-        Check.typeOf.object('tileset', tileset);
-        //>>includeEnd('debug');
-
-        var camera = this.camera;
-        return tileset.readyPromise.then(function() {
-            var boundingSphere = tileset.boundingSphere;
-            if (!defined(offset)) {
-                offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
-            }
-            camera.viewBoundingSphere(boundingSphere, offset);
-            camera.lookAtTransform(Matrix4.IDENTITY);
-        });
-    };
-
     /**
      * @private
      */

From 601e24291a5085e581448e2073b8e52f9140679d Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Fri, 12 Jan 2018 16:32:42 -0500
Subject: [PATCH 09/16] updated zoomTo to use options parameter to helper
 instead of just offset. modified zoomToTarget accordingly. added tests for
 zoomTo and flyTo.

---
 Source/Widgets/Viewer/Viewer.js    |  75 +++++++++-----
 Specs/Widgets/Viewer/ViewerSpec.js | 154 +++++++++++++++++++++++++----
 2 files changed, 186 insertions(+), 43 deletions(-)

diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index a1966f2e4f47..505474ea336c 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1736,7 +1736,10 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      * @returns {Promise.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the entity is not currently visualized in the scene or the zoom was cancelled.
      */
     Viewer.prototype.zoomTo = function(target, offset) {
-        return zoomToOrFly(this, target, offset, false);
+        var options = {
+            offset : offset
+        };
+        return zoomToOrFly(this, target, options, false);
     };
 
     /**
@@ -1759,7 +1762,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      * @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.
      * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
-     * @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the entity is not currently visualized in the scene or the flight was cancelled.
+     * @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the entity is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
      */
     Viewer.prototype.flyTo = function(target, options) {
         return zoomToOrFly(this, target, options, true);
@@ -1784,6 +1787,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         that._zoomOptions = options;
 
         when(zoomTarget, function(zoomTarget) {
+
             //Only perform the zoom if it wasn't cancelled before the promise resolved.
             if (that._zoomPromise !== zoomPromise) {
                 return;
@@ -1801,25 +1805,9 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             }
 
             //If the zoom target is a Cesium3DTileset
-            if (zoomTarget instanceof Cesium3DTileset) {
-                var camera = that.camera;
-
-                // If the Cesium3DTileset is still loading, wait for it to finish loading before zooming
-                return zoomTarget.readyPromise.then(function() {
-
-                    // Only perform the zoom if it wasn't cancelled before the Cesium3DTileset finished loading
-                    if (that._zoomPromise === zoomPromise) {
-                        // that._zoomTarget is already the tileset zoomTarget
-
-                        var boundingSphere = zoomTarget.boundingSphere;
-                        // since zooming options = offset from zoomTo. if offset not defined then give it base value
-                        if (!defined(options)) {
-                            options = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
-                        }
-                        camera.viewBoundingSphere(boundingSphere, options);
-                        camera.lookAtTransform(Matrix4.IDENTITY);
-                    }
-                });
+            if (defined(zoomTarget.readyPromise)) {
+                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.
@@ -1884,8 +1872,8 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
     };
 
     function updateZoomTarget(viewer) {
-        var entities = viewer._zoomTarget;
-        if (!defined(entities) || viewer.scene.mode === SceneMode.MORPHING) {
+        var target = viewer._zoomTarget;
+        if (!defined(target) || viewer.scene.mode === SceneMode.MORPHING) {
             return;
         }
 
@@ -1893,10 +1881,47 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         var camera = scene.camera;
         var zoomPromise = viewer._zoomPromise;
         var zoomOptions = defaultValue(viewer._zoomOptions, {});
+        var options;
+
+        // If zoomTarget was Cesium3DTileset
+        if (defined(target.readyPromise)) {
+            return target.readyPromise.then(function() {
+                var bSphere = target.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.0, 2.0 * bSphere.radius);
+                }
+
+                options = {
+                    offset : zoomOptions.offset,
+                    complete : function() {
+                        zoomPromise.resolve(true);
+                    },
+                    cancel : function() {
+                        zoomPromise.resolve(false);
+                    }
+                };
+
+                if (viewer._zoomIsFlight) {
+                    camera.flyToBoundingSphere(target.boundingSphere, options);
+                    // zoomPromise.resolve(true);
+                } else {
+                    camera.viewBoundingSphere(bSphere, zoomOptions.offset);
+                    camera.lookAtTransform(Matrix4.IDENTITY);
+
+                    // finish the promise
+                    zoomPromise.resolve(true);
+                }
+
+                clearZoom(viewer);
+            });
+        }
+
+        var entities = target;
 
         //If zoomTarget was an ImageryLayer
         if (entities instanceof Rectangle) {
-            var options = {
+            options = {
                 destination : entities,
                 duration : zoomOptions.duration,
                 maximumHeight : zoomOptions.maximumHeight,
@@ -1940,7 +1965,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
 
         if (!viewer._zoomIsFlight) {
-            camera.viewBoundingSphere(boundingSphere, viewer._zoomOptions);
+            camera.viewBoundingSphere(boundingSphere, viewer._zoomOptions.offset);
             camera.lookAtTransform(Matrix4.IDENTITY);
             clearZoom(viewer);
             zoomPromise.resolve(true);
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index 972e1f624deb..356d5935cf14 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1026,32 +1026,106 @@ defineSuite([
         expect(postMixinDataSource.entities.collectionChanged._listeners.length).not.toEqual(postMixinListenerCount);
     });
 
-    it('zoomToTileset throws if tileset is not defined', function() {
+    it('zoomTo throws if target is not defined', function() {
         viewer = createViewer(container);
 
         expect(function() {
-            viewer.zoomToTileset();
+            viewer.zoomTo();
         }).toThrowDeveloperError();
     });
 
-    it('zoomToTileset uses default offset if not defined', function() {
-       viewer = createViewer(container);
+    it('flyTo throws if target is not defined', function() {
+        viewer = createViewer(container);
+
+        expect(function() {
+            viewer.flyTo();
+        }).toThrowDeveloperError();
+    });
+
+    it('zoomTo when target is Cesium3DTileset and uses default offset when offset not defined', function() {
+        viewer = createViewer(container);
+         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+         var tileset = new Cesium3DTileSet({
+            url : path
+         });
+
+        // stored for movement check
+        var camPos = Cartesian3.clone(viewer.camera.position);
+        var camDir = Cartesian3.clone(viewer.camera.direction);
+
+        //spyOn(viewer.camera, 'viewBoundingSphere');
+
+        // load tileset to test
+        return tileset.readyPromise.then(function() {
+
+            var boundingSphere = tileset.boundingSphere;
+            var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+            return viewer.zoomTo(tileset).then(function() {
+                //expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
+
+                expect(viewer.camera.position).not.toEqual(camPos);
+                expect(viewer.camera.direction).not.toEqual(camDir);
+            });
+        });
+    });
+
+    it('flys to target when target is Cesium3DTileset and uses default offset when options is not defined', function() {
+        viewer = createViewer(container);
+
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileSet({
+            url : path
+        });
+
+        // load tileset to test
+        return tileset.readyPromise.then(function() {
+            var promise = viewer.flyTo(tileset);
+            var wasCompleted = false;
+
+            spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+                wasCompleted = true;
+                options.complete();
+            });
+
+            viewer.render();
+
+            return promise.then(function() {
+                expect(wasCompleted).toEqual(true);
+            });
+        });
+
+    });
 
-       var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-       var tileset = new Cesium3DTileSet({
-           url : path
-       });
+    it('flys to when target is Cesium3DTileset and uses default offset when offset not defined in options', function() {
+        viewer = createViewer(container);
+
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileSet({
+            url : path
+        });
+
+        var options = {};
+
+        // load tileset to test
+        return tileset.readyPromise.then(function() {
+            var promise = viewer.flyTo(tileset, options);
+            var wasCompleted = false;
+
+            spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+                wasCompleted = true;
+                options.complete();
+            });
 
-       spyOn(viewer.camera, 'viewBoundingSphere');
+            viewer.render();
 
-       return viewer.zoomToTileset(tileset).then(function() {
-           var boundingSphere = tileset.boundingSphere;
-           var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
-           expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
-       });
+            return promise.then(function() {
+                expect(wasCompleted).toEqual(true);
+            });
+
+        });
     });
 
-    it('zooms to tileset', function() {
+    it('zooms to target when target is Cesium3DTileset and offset is defined', function() {
         viewer = createViewer(container);
 
         // stored for movement check
@@ -1063,9 +1137,53 @@ defineSuite([
             url : path
         });
 
-        return viewer.zoomToTileset(tileset).then(function() {
-            expect(viewer.camera.position).not.toEqual(camPos);
-            expect(viewer.camera.direction).not.toEqual(camDir);
+        //spyOn(viewer.camera, 'viewBoundingSphere');
+
+        // load the tileset then check tests
+        return tileset.readyPromise.then(function() {
+            var boundingSphere = tileset.boundingSphere;
+            var offset = new HeadingPitchRange(0.4, 1.2, 4 * boundingSphere.radius);
+
+            return viewer.zoomTo(tileset, offset).then(function() {
+                // called with proper values
+                //expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
+                // moved to new location
+                expect(viewer.camera.position).not.toEqual(camPos);
+                expect(viewer.camera.direction).not.toEqual(camDir);
+            });
+        });
+    });
+
+    it('flys to target when target is Cesium3DTileset and offset is defined', function() {
+        viewer = createViewer(container);
+
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileSet({
+            url : path
+        });
+
+        // load tileset to test
+        return tileset.readyPromise.then(function() {
+            var boundingSphere = tileset.boundingSphere;
+            var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+            var options = {
+                offset : offsetVal
+            }
+
+            var promise = viewer.flyTo(tileset, options);
+            var wasCompleted = false;
+
+            spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+                wasCompleted = true;
+                options.complete();
+            });
+
+            viewer.render();
+
+            return promise.then(function() {
+                expect(wasCompleted).toEqual(true);
+            });
+
         });
     });
 }, 'WebGL');

From 2893507cafdc882fa344eb5d7aa26d4f330a7a94 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Fri, 12 Jan 2018 16:42:27 -0500
Subject: [PATCH 10/16] updated CHANGED.md and removed items specified by
 eslint

---
 CHANGES.md                         | 2 +-
 Source/Widgets/Viewer/Viewer.js    | 4 ++--
 Specs/Widgets/Viewer/ViewerSpec.js | 6 +++---
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index cad72e1e1398..b91b72b43f59 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,7 +26,7 @@ Change Log
    * Only one mesh per node is supported.
    * Only one primitive per mesh is supported.
 * Updated documentation links to reflect new locations on cesiumjs.org and cesium.com. 
-* Updated 'Viewer.zoomTo' to include Cesium3DTilesets and updated sandcastle 3DTileset examples to reflect this change
+* Updated 'Viewer.zoomTo' and 'Viewer.flyTo' to take in Cesium3DTilesets as a target and updated sandcastle 3DTileset examples to reflect this change
 
 ### 1.41 - 2018-01-02
 
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 505474ea336c..af4d436fa18a 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1731,7 +1731,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.</p>
      *
-     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} target The entity, array of entities, entity collection, data source 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|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} 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 {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
      * @returns {Promise.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the entity is not currently visualized in the scene or the zoom was cancelled.
      */
@@ -1757,7 +1757,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.</p>
      *
-     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} target The entity, array of entities, entity collection, data source 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|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} 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 {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.
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index 356d5935cf14..2903f63b57e1 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1058,8 +1058,8 @@ defineSuite([
         // load tileset to test
         return tileset.readyPromise.then(function() {
 
-            var boundingSphere = tileset.boundingSphere;
-            var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
+            //var boundingSphere = tileset.boundingSphere;
+            //var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
             return viewer.zoomTo(tileset).then(function() {
                 //expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
 
@@ -1168,7 +1168,7 @@ defineSuite([
             var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
             var options = {
                 offset : offsetVal
-            }
+            };
 
             var promise = viewer.flyTo(tileset, options);
             var wasCompleted = false;

From 4e5ba70c6533f3add566bdb35ddd09a8de279585 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Fri, 12 Jan 2018 17:04:37 -0500
Subject: [PATCH 11/16] updated typo

---
 Specs/Widgets/Viewer/ViewerSpec.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index 2903f63b57e1..4bd2fe8d9daf 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -15,7 +15,7 @@ defineSuite([
         'DataSources/Entity',
         'Scene/Camera',
         'Scene/CameraFlightPath',
-        'Scene/Cesium3DTileSet',
+        'Scene/Cesium3DTileset',
         'Scene/ImageryLayerCollection',
         'Scene/SceneMode',
         'Scene/ShadowMode',
@@ -52,7 +52,7 @@ defineSuite([
         Entity,
         Camera,
         CameraFlightPath,
-        Cesium3DTileSet,
+        Cesium3DTileset,
         ImageryLayerCollection,
         SceneMode,
         ShadowMode,

From b1f729604c37144b9775855b705320d4befac112 Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Fri, 12 Jan 2018 17:21:13 -0500
Subject: [PATCH 12/16] updated typos due to last typo

---
 Specs/Widgets/Viewer/ViewerSpec.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index 4bd2fe8d9daf..b9408b950dfd 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1045,7 +1045,7 @@ defineSuite([
     it('zoomTo when target is Cesium3DTileset and uses default offset when offset not defined', function() {
         viewer = createViewer(container);
          var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-         var tileset = new Cesium3DTileSet({
+         var tileset = new Cesium3DTileset({
             url : path
          });
 
@@ -1073,7 +1073,7 @@ defineSuite([
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        var tileset = new Cesium3DTileSet({
+        var tileset = new Cesium3DTileset({
             url : path
         });
 
@@ -1100,7 +1100,7 @@ defineSuite([
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        var tileset = new Cesium3DTileSet({
+        var tileset = new Cesium3DTileset({
             url : path
         });
 
@@ -1133,7 +1133,7 @@ defineSuite([
         var camDir = Cartesian3.clone(viewer.camera.direction);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        var tileset = new Cesium3DTileSet({
+        var tileset = new Cesium3DTileset({
             url : path
         });
 
@@ -1158,7 +1158,7 @@ defineSuite([
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        var tileset = new Cesium3DTileSet({
+        var tileset = new Cesium3DTileset({
             url : path
         });
 

From dfa8c2c290d6afbb7f293e0ab31afbf26604a11a Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 16 Jan 2018 12:03:08 -0500
Subject: [PATCH 13/16] modified as per ggetz's recommendations and added in
 tests for checking zoomTo and flyTo when target is entity

---
 CHANGES.md                         |   1 -
 Source/Widgets/Viewer/Viewer.js    |  26 +--
 Specs/Widgets/Viewer/ViewerSpec.js | 342 +++++++++++++++++++++++++----
 3 files changed, 307 insertions(+), 62 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 0f6f3616c7c5..c0b2b831fa45 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -29,7 +29,6 @@ Change Log
 * Updated 'Viewer.zoomTo' and 'Viewer.flyTo' to take in Cesium3DTilesets as a target and updated sandcastle 3DTileset examples to reflect this change
 * Fixed a glTF animation bug that caused certain animations to jitter. [#5740](https://github.com/AnalyticalGraphicsInc/cesium/pull/5740)
 * Fixed a bug when creating billboard and model entities without a globe. [#6109](https://github.com/AnalyticalGraphicsInc/cesium/pull/6109)
-* Updated documentation links to reflect new locations on cesiumjs.org and cesium.com.
 * Added support for vertex shader uniforms when `tileset.colorBlendMode` is  `MIX` or `REPLACE`. [#5874](https://github.com/AnalyticalGraphicsInc/cesium/pull/5874)
 
 ### 1.41 - 2018-01-02
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index af4d436fa18a..74e5a72880d7 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1733,7 +1733,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      *
      * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset>} 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 {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
-     * @returns {Promise.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the entity is not currently visualized in the scene or the zoom was cancelled.
+     * @returns {Promise.<Boolean>} 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.
      */
     Viewer.prototype.zoomTo = function(target, offset) {
         var options = {
@@ -1762,7 +1762,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
      * @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.
      * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
-     * @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the entity is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
+     * @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the target is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
      */
     Viewer.prototype.flyTo = function(target, options) {
         return zoomToOrFly(this, target, options, true);
@@ -1787,7 +1787,6 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         that._zoomOptions = options;
 
         when(zoomTarget, function(zoomTarget) {
-
             //Only perform the zoom if it wasn't cancelled before the promise resolved.
             if (that._zoomPromise !== zoomPromise) {
                 return;
@@ -1805,7 +1804,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             }
 
             //If the zoom target is a Cesium3DTileset
-            if (defined(zoomTarget.readyPromise)) {
+            if (zoomTarget instanceof Cesium3DTileset) {
                 that._zoomTarget = zoomTarget;
                 return;
             }
@@ -1884,16 +1883,18 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
         var options;
 
         // If zoomTarget was Cesium3DTileset
-        if (defined(target.readyPromise)) {
+        if (target instanceof Cesium3DTileset) {
             return target.readyPromise.then(function() {
-                var bSphere = target.boundingSphere;
+                var boundingSphere = target.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.0, 2.0 * bSphere.radius);
+                    zoomOptions.offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
                 }
 
                 options = {
                     offset : zoomOptions.offset,
+                    duration : zoomOptions.duration,
+                    maximumHeight : zoomOptions.maximumHeight,
                     complete : function() {
                         zoomPromise.resolve(true);
                     },
@@ -1904,9 +1905,8 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
 
                 if (viewer._zoomIsFlight) {
                     camera.flyToBoundingSphere(target.boundingSphere, options);
-                    // zoomPromise.resolve(true);
                 } else {
-                    camera.viewBoundingSphere(bSphere, zoomOptions.offset);
+                    camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
                     camera.lookAtTransform(Matrix4.IDENTITY);
 
                     // finish the promise
@@ -1917,12 +1917,10 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             });
         }
 
-        var entities = target;
-
         //If zoomTarget was an ImageryLayer
-        if (entities instanceof Rectangle) {
+        if (target instanceof Rectangle) {
             options = {
-                destination : entities,
+                destination : target,
                 duration : zoomOptions.duration,
                 maximumHeight : zoomOptions.maximumHeight,
                 complete : function() {
@@ -1943,6 +1941,8 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
             return;
         }
 
+        var entities = target;
+
         var boundingSpheres = [];
         for (var i = 0, len = entities.length; i < len; i++) {
             var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index b9408b950dfd..daaa10889d44 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1,12 +1,15 @@
 defineSuite([
+        'Core/BoundingSphere',
         'Core/Cartesian3',
         'Core/ClockRange',
         'Core/ClockStep',
+        'Core/Color',
         'Core/EllipsoidTerrainProvider',
         'Core/HeadingPitchRange',
         'Core/JulianDate',
         'Core/Matrix4',
         'Core/WebMercatorProjection',
+        'DataSources/BoundingSphereState',
         'DataSources/ConstantPositionProperty',
         'DataSources/ConstantProperty',
         'DataSources/DataSourceClock',
@@ -36,14 +39,17 @@ defineSuite([
         'Widgets/SelectionIndicator/SelectionIndicator',
         'Widgets/Timeline/Timeline'
     ], 'Widgets/Viewer/Viewer', function(
+        BoundingSphere,
         Cartesian3,
         ClockRange,
         ClockStep,
+        Color,
         EllipsoidTerrainProvider,
         HeadingPitchRange,
         JulianDate,
         Matrix4,
         WebMercatorProjection,
+        BoundingSphereState,
         ConstantPositionProperty,
         ConstantProperty,
         DataSourceClock,
@@ -1034,15 +1040,7 @@ defineSuite([
         }).toThrowDeveloperError();
     });
 
-    it('flyTo throws if target is not defined', function() {
-        viewer = createViewer(container);
-
-        expect(function() {
-            viewer.flyTo();
-        }).toThrowDeveloperError();
-    });
-
-    it('zoomTo when target is Cesium3DTileset and uses default offset when offset not defined', function() {
+    it('zoomTo zooms to Cesium3DTileset with default offset when offset not defined', function() {
         viewer = createViewer(container);
          var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
          var tileset = new Cesium3DTileset({
@@ -1053,23 +1051,113 @@ defineSuite([
         var camPos = Cartesian3.clone(viewer.camera.position);
         var camDir = Cartesian3.clone(viewer.camera.direction);
 
-        //spyOn(viewer.camera, 'viewBoundingSphere');
-
         // load tileset to test
         return tileset.readyPromise.then(function() {
-
-            //var boundingSphere = tileset.boundingSphere;
-            //var offset = new HeadingPitchRange(0.0, 0.0, 2.0 * boundingSphere.radius);
             return viewer.zoomTo(tileset).then(function() {
-                //expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
+                // moved to new location
+                expect(viewer.camera.position).not.toEqual(camPos);
+                expect(viewer.camera.direction).not.toEqual(camDir);
+            });
+        });
+    });
+
+    it('zoomTo zooms to Cesium3DTileset with offset', function() {
+        viewer = createViewer(container);
+
+        // stored for movement check
+        var camPos = Cartesian3.clone(viewer.camera.position);
+        var camDir = Cartesian3.clone(viewer.camera.direction);
 
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileset({
+            url : path
+        });
+
+        // load the tileset then check tests
+        return tileset.readyPromise.then(function() {
+            var boundingSphere = tileset.boundingSphere;
+            var offset = new HeadingPitchRange(0.4, 1.2, 4 * boundingSphere.radius);
+
+            return viewer.zoomTo(tileset, offset).then(function() {
+                // moved to new location
                 expect(viewer.camera.position).not.toEqual(camPos);
                 expect(viewer.camera.direction).not.toEqual(camDir);
             });
         });
     });
 
-    it('flys to target when target is Cesium3DTileset and uses default offset when options is not defined', function() {
+    it('zoomTo zooms to entity with default offset when offset not defined', function() {
+        viewer = createViewer(container);
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        // stored for movement check
+        var camPos = Cartesian3.clone(viewer.camera.position);
+        var camDir = Cartesian3.clone(viewer.camera.direction);
+
+        return viewer.zoomTo(entities).then(function() {
+            // moved to new location
+            expect(viewer.camera.position).not.toEqual(camPos);
+            expect(viewer.camera.direction).not.toEqual(camDir);
+        });
+    });
+
+    it('zoomTo zooms to entity with offset', function() {
+        viewer = createViewer(container);
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        // stored for movement check
+        var camPos = Cartesian3.clone(viewer.camera.position);
+        var camDir = Cartesian3.clone(viewer.camera.direction);
+
+        var boundingSpheres = [];
+        var boundingSphereScratch = new BoundingSphere();
+        for (var i = 0, len = entities.length; i < len; i++) {
+            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
+
+            if (state === BoundingSphereState.PENDING) {
+                return;
+            } else if (state !== BoundingSphereState.FAILED) {
+                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+            }
+        }
+        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
+        var offset = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+
+        return viewer.zoomTo(entities, offset).then(function() {
+            // moved to new location
+            expect(viewer.camera.position).not.toEqual(camPos);
+            expect(viewer.camera.direction).not.toEqual(camDir);
+        });
+    });
+
+
+    it('flyTo throws if target is not defined', function() {
+        viewer = createViewer(container);
+
+        expect(function() {
+            viewer.flyTo();
+        }).toThrowDeveloperError();
+    });
+
+    it('flyTo flys to Cesium3DTileset with default offset when options not defined', function() {
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
@@ -1096,7 +1184,7 @@ defineSuite([
 
     });
 
-    it('flys to when target is Cesium3DTileset and uses default offset when offset not defined in options', function() {
+    it('flyTo flys to Cesium3DTileset with default offset when offset not defined', function() {
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
@@ -1125,36 +1213,7 @@ defineSuite([
         });
     });
 
-    it('zooms to target when target is Cesium3DTileset and offset is defined', function() {
-        viewer = createViewer(container);
-
-        // stored for movement check
-        var camPos = Cartesian3.clone(viewer.camera.position);
-        var camDir = Cartesian3.clone(viewer.camera.direction);
-
-        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        var tileset = new Cesium3DTileset({
-            url : path
-        });
-
-        //spyOn(viewer.camera, 'viewBoundingSphere');
-
-        // load the tileset then check tests
-        return tileset.readyPromise.then(function() {
-            var boundingSphere = tileset.boundingSphere;
-            var offset = new HeadingPitchRange(0.4, 1.2, 4 * boundingSphere.radius);
-
-            return viewer.zoomTo(tileset, offset).then(function() {
-                // called with proper values
-                //expect(viewer.camera.viewBoundingSphere).toHaveBeenCalledWith(boundingSphere, offset);
-                // moved to new location
-                expect(viewer.camera.position).not.toEqual(camPos);
-                expect(viewer.camera.direction).not.toEqual(camDir);
-            });
-        });
-    });
-
-    it('flys to target when target is Cesium3DTileset and offset is defined', function() {
+    it('flyTo flys to target when target is Cesium3DTileset and options are defined', function() {
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
@@ -1167,7 +1226,9 @@ defineSuite([
             var boundingSphere = tileset.boundingSphere;
             var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
             var options = {
-                offset : offsetVal
+                offset : offsetVal,
+                duration : 3.0,
+                maximumHeight : 5.0
             };
 
             var promise = viewer.flyTo(tileset, options);
@@ -1186,4 +1247,189 @@ defineSuite([
 
         });
     });
+
+    it('flyTo flys to entity with default offset when options not defined', function() {
+        viewer = createViewer(container);
+
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        var boundingSpheres = [];
+        var boundingSphereScratch = new BoundingSphere();
+        for (var i = 0, len = entities.length; i < len; i++) {
+            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
+
+            if (state === BoundingSphereState.PENDING) {
+                return;
+            } else if (state !== BoundingSphereState.FAILED) {
+                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+            }
+        }
+        var promise = viewer.flyTo(entities);
+        var wasCompleted = false;
+
+        spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+            expect(options.duration).toBeUndefined();
+            expect(options.maximumHeight).toBeUndefined();
+            wasCompleted = true;
+            options.complete();
+        });
+
+        viewer.render();
+
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
+        });
+
+    });
+
+    it('flyTo flys to entity with default offset when offset not defined', function() {
+        viewer = createViewer(container);
+
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        var boundingSpheres = [];
+        var boundingSphereScratch = new BoundingSphere();
+        for (var i = 0, len = entities.length; i < len; i++) {
+            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
+
+            if (state === BoundingSphereState.PENDING) {
+                return;
+            } else if (state !== BoundingSphereState.FAILED) {
+                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+            }
+        }
+        var options = {};
+
+        var promise = viewer.flyTo(entities, options);
+        var wasCompleted = false;
+
+        spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+            expect(options.duration).toBeUndefined();
+            expect(options.maximumHeight).toBeUndefined();
+            wasCompleted = true;
+            options.complete();
+        });
+
+        viewer.render();
+
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
+        });
+    });
+
+    it('flyTo flys to entity when options are defined', function() {
+        viewer = createViewer(container);
+
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        var boundingSpheres = [];
+        var boundingSphereScratch = new BoundingSphere();
+        for (var i = 0, len = entities.length; i < len; i++) {
+            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
+
+            if (state === BoundingSphereState.PENDING) {
+                return;
+            } else if (state !== BoundingSphereState.FAILED) {
+                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+            }
+        }
+        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
+        var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+        var options = {
+            offset : offsetVal,
+            duration : 3.0,
+            maximumHeight : 5.0
+        };
+
+        var promise = viewer.flyTo(entities, 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.render();
+
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
+        });
+    });
+
+    it('flyTo flys to entity when offset is defined but other options for flyTo are not', function() {
+        viewer = createViewer(container);
+
+        var blueBox = viewer.entities.add({
+            name : 'Blue box',
+            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            box : {
+                dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
+                material : Color.BLUE
+            }
+        });
+
+        var entities = viewer.entities;
+
+        var boundingSpheres = [];
+        var boundingSphereScratch = new BoundingSphere();
+        for (var i = 0, len = entities.length; i < len; i++) {
+            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
+
+            if (state === BoundingSphereState.PENDING) {
+                return;
+            } else if (state !== BoundingSphereState.FAILED) {
+                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+            }
+        }
+        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
+        var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+        var options = {
+            offset : offsetVal
+        };
+
+        var promise = viewer.flyTo(entities, options);
+        var wasCompleted = false;
+
+        spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+            expect(options.duration).toBeUndefined();
+            expect(options.maximumHeight).toBeUndefined();
+            wasCompleted = true;
+            options.complete();
+        });
+
+        viewer.render();
+
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
+        });
+    });
 }, 'WebGL');

From d899c13c2242672684fa1a96e9bfbb0182224a2b Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 16 Jan 2018 12:19:00 -0500
Subject: [PATCH 14/16] removed unused variable

---
 Specs/Widgets/Viewer/ViewerSpec.js | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index daaa10889d44..7403ddc48b6a 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1088,7 +1088,7 @@ defineSuite([
 
     it('zoomTo zooms to entity with default offset when offset not defined', function() {
         viewer = createViewer(container);
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
@@ -1112,7 +1112,7 @@ defineSuite([
 
     it('zoomTo zooms to entity with offset', function() {
         viewer = createViewer(container);
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
@@ -1251,7 +1251,7 @@ defineSuite([
     it('flyTo flys to entity with default offset when options not defined', function() {
         viewer = createViewer(container);
 
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
@@ -1294,7 +1294,7 @@ defineSuite([
     it('flyTo flys to entity with default offset when offset not defined', function() {
         viewer = createViewer(container);
 
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
@@ -1338,7 +1338,7 @@ defineSuite([
     it('flyTo flys to entity when options are defined', function() {
         viewer = createViewer(container);
 
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
@@ -1388,7 +1388,7 @@ defineSuite([
     it('flyTo flys to entity when offset is defined but other options for flyTo are not', function() {
         viewer = createViewer(container);
 
-        var blueBox = viewer.entities.add({
+        viewer.entities.add({
             name : 'Blue box',
             position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {

From 1ca28af7ac6964bd1b023ebbf27a3f32b5978c4b Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 16 Jan 2018 15:11:23 -0500
Subject: [PATCH 15/16] updated 3d tiles sandcastle examples for some to be
 able to use zoomTo's default offset. updated tests for zoomTo and flyTo to
 include entities and certain expectations

---
 .../gallery/3D Tiles Adjust Height.html       |  10 +-
 .../3D Tiles Batch Table Hierarchy.html       |   6 +-
 Apps/Sandcastle/gallery/3D Tiles Formats.html |   2 +-
 .../gallery/3D Tiles Inspector.html           |   2 +-
 ...D Tiles Photogrammetry Classification.html |   4 +-
 .../gallery/3D Tiles Photogrammetry.html      |   4 +-
 .../gallery/3D Tiles Point Cloud Styling.html |   4 +-
 .../gallery/3D Tiles Point Cloud.html         |   4 +-
 Source/Widgets/Viewer/Viewer.js               |   2 +-
 Specs/Widgets/Viewer/ViewerSpec.js            | 219 +++++++++---------
 10 files changed, 121 insertions(+), 136 deletions(-)

diff --git a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html
index 8d931d8c6b5a..638d3a2a1cb0 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html	
@@ -60,11 +60,11 @@
     url : '../../../Specs/Data/Cesium3DTiles/Tilesets/Tileset'
 }));
 
-tileset.readyPromise.then(function() {
-    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2));
-}).otherwise(function(error) {
-    throw(error);
-});
+    tileset.readyPromise.then(function() {
+        viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2.0));
+    }).otherwise(function(error) {
+        throw(error);
+    });
 
 Cesium.knockout.getObservable(viewModel, 'height').subscribe(function(height) {
     height = Number(height);
diff --git a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html
index 239e6a24bce5..b6da5ad00b42 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html	
@@ -88,10 +88,8 @@
     url : '../../../Specs/Data/Cesium3DTiles/Hierarchy/BatchTableHierarchy'
 }));
 
-tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0));
-}).otherwise(function(error) {
-        throw(error);
+viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0)).otherwise(function(error) {
+    throw(error);
 });
 
 var styles = [];
diff --git a/Apps/Sandcastle/gallery/3D Tiles Formats.html b/Apps/Sandcastle/gallery/3D Tiles Formats.html
index 455f5d6cafcd..f06f30585063 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Formats.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Formats.html	
@@ -112,7 +112,7 @@
     scene.primitives.add(tileset);
     tileset.readyPromise.then(function(tileset) {
 
-        viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0, -2.0, Math.max(100.0 - tileset.boundingSphere.radius, 0.0)));
+        viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0, -2.0, Math.max(100.0 - tileset.boundingSphere.radius, 0.0)));
 
         var properties = tileset.properties;
         if (Cesium.defined(properties) && Cesium.defined(properties.Height)) {
diff --git a/Apps/Sandcastle/gallery/3D Tiles Inspector.html b/Apps/Sandcastle/gallery/3D Tiles Inspector.html
index 70e939602e02..dace7c6c98db 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Inspector.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Inspector.html	
@@ -36,7 +36,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    viewer.zoomToTileset(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius / 4.0));
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius / 4.0));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html
index 923f0a3bb40a..bc6322bc4b11 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html	
@@ -36,9 +36,7 @@
 }));
 
 // Move the camera to view the tileset on load.
-tileset.readyPromise.then(function() {
-    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
-}).otherwise(function(error) {
+viewer.zoomTo(tileset).otherwise(function(error) {
     throw(error);
 });
 
diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html
index 3428026bbb9c..8aa0587a167a 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html	
@@ -32,9 +32,7 @@
     url : 'https://beta.cesium.com/api/assets/1458?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxYmJiNTAxOC1lOTg5LTQzN2EtODg1OC0zMWJjM2IxNGNlYmMiLCJpZCI6NDQsImFzc2V0cyI6WzE0NThdLCJpYXQiOjE0OTkyNjM4MjB9.1WKijRa-ILkmG6utrhDWX6rDgasjD7dZv-G5ZyCmkKg'
 }));
 
-tileset.readyPromise.then(function() {
-    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
-}).otherwise(function(error) {
+viewer.zoomTo(tileset).otherwise(function(error) {
     throw(error);
 });
 //Sandcastle_End
diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html
index 614c9563a6d2..ec9dce57a5cd 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html	
@@ -39,9 +39,7 @@
 }));
 
 tileset.readyPromise.then(function() {
-    var boundingSphere = tileset.boundingSphere;
-    viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -1.0, 50.0));
-    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
+    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -1.0, 50.0));
 }).otherwise(function(error) {
     throw(error);
 });
diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html
index 603268c725d1..ffce2edf8ab2 100644
--- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
+++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html	
@@ -33,9 +33,7 @@
     url : 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'
 }));
 
-tileset.readyPromise.then(function() {
-    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius));
-}).otherwise(function(error) {
+viewer.zoomTo(tileset).otherwise(function(error) {
     throw(error);
 });
 //Sandcastle_End
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 74e5a72880d7..fb4ad37900dc 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -1888,7 +1888,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
                 var boundingSphere = target.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.0, 2.0 * boundingSphere.radius);
+                    zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius);
                 }
 
                 options = {
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index 7403ddc48b6a..f931f62bc6a1 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -737,7 +737,7 @@ defineSuite([
         dataSource1.clock.stopTime = JulianDate.fromIso8601('2013-08-21T02:00Z');
         dataSource1.clock.currentTime = JulianDate.fromIso8601('2013-08-02T00:00Z');
 
-        viewer = createViewer(container, { automaticallyTrackDataSourceClocks : false });
+        viewer = createViewer(container, {automaticallyTrackDataSourceClocks : false});
         viewer.dataSources.add(dataSource1);
 
         // Because of the above Viewer option, data sources are not automatically
@@ -942,7 +942,7 @@ defineSuite([
         //Needed to avoid actually creating a flight when we issue the home command.
         spyOn(CameraFlightPath, 'createTween').and.returnValue({
             startObject : {},
-            stopObject: {},
+            stopObject : {},
             duration : 0.0
         });
 
@@ -1041,22 +1041,49 @@ defineSuite([
     });
 
     it('zoomTo zooms to Cesium3DTileset with default offset when offset not defined', function() {
+        // viewer = createViewer(container);
+        // var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        // var tileset = new Cesium3DTileset({
+        //     url : path
+        // });
+        //
+        // // stored for movement check
+        // var camPos = Cartesian3.clone(viewer.camera.position);
+        // var camDir = Cartesian3.clone(viewer.camera.direction);
+        //
+        // // load tileset to test
+        // return tileset.readyPromise.then(function() {
+        //     return viewer.zoomTo(tileset).then(function() {
+        //         // moved to new location
+        //         expect(viewer.camera.position).not.toEqual(camPos);
+        //         expect(viewer.camera.direction).not.toEqual(camDir);
+        //     });
+        // });
+
         viewer = createViewer(container);
-         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-         var tileset = new Cesium3DTileset({
-            url : path
-         });
 
-        // stored for movement check
-        var camPos = Cartesian3.clone(viewer.camera.position);
-        var camDir = Cartesian3.clone(viewer.camera.direction);
+        var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
+        var tileset = new Cesium3DTileset({
+            url : path
+        });
 
-        // load tileset to test
+        // load the tileset then check tests
         return tileset.readyPromise.then(function() {
-            return viewer.zoomTo(tileset).then(function() {
-                // moved to new location
-                expect(viewer.camera.position).not.toEqual(camPos);
-                expect(viewer.camera.direction).not.toEqual(camDir);
+            var expectedBoundingSphere = tileset.boundingSphere;
+            var expectedOffset = new HeadingPitchRange(0.0, -0.5, expectedBoundingSphere.radius);
+
+            var promise = viewer.zoomTo(tileset);
+            var wasCompleted = false;
+            spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) {
+                expect(boundingSphere).toEqual(expectedBoundingSphere);
+                expect(offset).toEqual(expectedOffset);
+                wasCompleted = true;
+            });
+
+            viewer.render();
+
+            return promise.then(function() {
+                expect(wasCompleted).toEqual(true);
             });
         });
     });
@@ -1065,8 +1092,8 @@ defineSuite([
         viewer = createViewer(container);
 
         // stored for movement check
-        var camPos = Cartesian3.clone(viewer.camera.position);
-        var camDir = Cartesian3.clone(viewer.camera.direction);
+        // var camPos = Cartesian3.clone(viewer.camera.position);
+        // var camDir = Cartesian3.clone(viewer.camera.direction);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
         var tileset = new Cesium3DTileset({
@@ -1075,22 +1102,37 @@ defineSuite([
 
         // load the tileset then check tests
         return tileset.readyPromise.then(function() {
-            var boundingSphere = tileset.boundingSphere;
-            var offset = new HeadingPitchRange(0.4, 1.2, 4 * boundingSphere.radius);
+            var expectedBoundingSphere = tileset.boundingSphere;
+            var expectedOffset = new HeadingPitchRange(0.4, 1.2, 4.0 * expectedBoundingSphere.radius);
+
+            //     return viewer.zoomTo(tileset, offset).then(function() {
+            //         // moved to new location
+            //         expect(viewer.camera.position).not.toEqual(camPos);
+            //         expect(viewer.camera.direction).not.toEqual(camDir);
+            //     });
+            // });
+
+            var promise = viewer.zoomTo(tileset, expectedOffset);
+            var wasCompleted = false;
+            spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) {
+                expect(boundingSphere).toEqual(expectedBoundingSphere);
+                expect(offset).toEqual(expectedOffset);
+                wasCompleted = true;
+            });
+
+            viewer.render();
 
-            return viewer.zoomTo(tileset, offset).then(function() {
-                // moved to new location
-                expect(viewer.camera.position).not.toEqual(camPos);
-                expect(viewer.camera.direction).not.toEqual(camDir);
+            return promise.then(function() {
+                expect(wasCompleted).toEqual(true);
             });
         });
     });
 
-    it('zoomTo zooms to entity with default offset when offset not defined', function() {
+    it('zoomTo zooms to entity with undefined offset when offset not defined', function() {
         viewer = createViewer(container);
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1099,14 +1141,20 @@ defineSuite([
 
         var entities = viewer.entities;
 
-        // stored for movement check
-        var camPos = Cartesian3.clone(viewer.camera.position);
-        var camDir = Cartesian3.clone(viewer.camera.direction);
+        var promise = viewer.zoomTo(entities);
+        var wasCompleted = false;
 
-        return viewer.zoomTo(entities).then(function() {
-            // moved to new location
-            expect(viewer.camera.position).not.toEqual(camPos);
-            expect(viewer.camera.direction).not.toEqual(camDir);
+        spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) {
+            expect(boundingSphere).toBeDefined();
+            // expect offset to be undefined - doesnt use default bc of how zoomTo for entities is set up
+            expect(offset).toBeUndefined();
+            wasCompleted = true;
+        });
+
+        viewer.render();
+
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
         });
     });
 
@@ -1114,7 +1162,7 @@ defineSuite([
         viewer = createViewer(container);
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1122,33 +1170,23 @@ defineSuite([
         });
 
         var entities = viewer.entities;
+        // fake temp offset
+        var expectedOffset = new HeadingPitchRange(3.0, 0.2, 2.3);
 
-        // stored for movement check
-        var camPos = Cartesian3.clone(viewer.camera.position);
-        var camDir = Cartesian3.clone(viewer.camera.direction);
-
-        var boundingSpheres = [];
-        var boundingSphereScratch = new BoundingSphere();
-        for (var i = 0, len = entities.length; i < len; i++) {
-            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
-
-            if (state === BoundingSphereState.PENDING) {
-                return;
-            } else if (state !== BoundingSphereState.FAILED) {
-                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
-            }
-        }
-        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
-        var offset = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+        var promise = viewer.zoomTo(entities, expectedOffset);
+        var wasCompleted = false;
+        spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) {
+            expect(expectedOffset).toEqual(offset);
+            wasCompleted = true;
+        });
+
+        viewer.render();
 
-        return viewer.zoomTo(entities, offset).then(function() {
-            // moved to new location
-            expect(viewer.camera.position).not.toEqual(camPos);
-            expect(viewer.camera.direction).not.toEqual(camDir);
+        return promise.then(function() {
+            expect(wasCompleted).toEqual(true);
         });
     });
 
-
     it('flyTo throws if target is not defined', function() {
         viewer = createViewer(container);
 
@@ -1171,6 +1209,9 @@ defineSuite([
             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();
             });
@@ -1200,6 +1241,9 @@ defineSuite([
             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();
             });
@@ -1223,8 +1267,7 @@ defineSuite([
 
         // load tileset to test
         return tileset.readyPromise.then(function() {
-            var boundingSphere = tileset.boundingSphere;
-            var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+            var offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
             var options = {
                 offset : offsetVal,
                 duration : 3.0,
@@ -1235,6 +1278,8 @@ defineSuite([
             var wasCompleted = false;
 
             spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) {
+                expect(options.duration).toBeDefined();
+                expect(options.maximumHeight).toBeDefined();
                 wasCompleted = true;
                 options.complete();
             });
@@ -1253,7 +1298,7 @@ defineSuite([
 
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1261,18 +1306,6 @@ defineSuite([
         });
 
         var entities = viewer.entities;
-
-        var boundingSpheres = [];
-        var boundingSphereScratch = new BoundingSphere();
-        for (var i = 0, len = entities.length; i < len; i++) {
-            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
-
-            if (state === BoundingSphereState.PENDING) {
-                return;
-            } else if (state !== BoundingSphereState.FAILED) {
-                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
-            }
-        }
         var promise = viewer.flyTo(entities);
         var wasCompleted = false;
 
@@ -1296,7 +1329,7 @@ defineSuite([
 
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1304,18 +1337,6 @@ defineSuite([
         });
 
         var entities = viewer.entities;
-
-        var boundingSpheres = [];
-        var boundingSphereScratch = new BoundingSphere();
-        for (var i = 0, len = entities.length; i < len; i++) {
-            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
-
-            if (state === BoundingSphereState.PENDING) {
-                return;
-            } else if (state !== BoundingSphereState.FAILED) {
-                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
-            }
-        }
         var options = {};
 
         var promise = viewer.flyTo(entities, options);
@@ -1340,7 +1361,7 @@ defineSuite([
 
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1348,20 +1369,7 @@ defineSuite([
         });
 
         var entities = viewer.entities;
-
-        var boundingSpheres = [];
-        var boundingSphereScratch = new BoundingSphere();
-        for (var i = 0, len = entities.length; i < len; i++) {
-            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
-
-            if (state === BoundingSphereState.PENDING) {
-                return;
-            } else if (state !== BoundingSphereState.FAILED) {
-                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
-            }
-        }
-        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
-        var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+        var offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
         var options = {
             offset : offsetVal,
             duration : 3.0,
@@ -1390,7 +1398,7 @@ defineSuite([
 
         viewer.entities.add({
             name : 'Blue box',
-            position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+            position : Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
             box : {
                 dimensions : new Cartesian3(400000.0, 300000.0, 500000.0),
                 material : Color.BLUE
@@ -1398,20 +1406,7 @@ defineSuite([
         });
 
         var entities = viewer.entities;
-
-        var boundingSpheres = [];
-        var boundingSphereScratch = new BoundingSphere();
-        for (var i = 0, len = entities.length; i < len; i++) {
-            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);
-
-            if (state === BoundingSphereState.PENDING) {
-                return;
-            } else if (state !== BoundingSphereState.FAILED) {
-                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
-            }
-        }
-        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
-        var offsetVal = new HeadingPitchRange(3, 0.2, 2.3 * boundingSphere.radius);
+        var offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
         var options = {
             offset : offsetVal
         };

From 254d8a466c88f604a04be632baf57a8290ecd11e Mon Sep 17 00:00:00 2001
From: hanbollar <hbollar@seas.upenn.edu>
Date: Tue, 16 Jan 2018 15:24:13 -0500
Subject: [PATCH 16/16] removed commented code

---
 Specs/Widgets/Viewer/ViewerSpec.js | 30 ------------------------------
 1 file changed, 30 deletions(-)

diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index f931f62bc6a1..b0d19110ba78 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -1041,25 +1041,6 @@ defineSuite([
     });
 
     it('zoomTo zooms to Cesium3DTileset with default offset when offset not defined', function() {
-        // viewer = createViewer(container);
-        // var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
-        // var tileset = new Cesium3DTileset({
-        //     url : path
-        // });
-        //
-        // // stored for movement check
-        // var camPos = Cartesian3.clone(viewer.camera.position);
-        // var camDir = Cartesian3.clone(viewer.camera.direction);
-        //
-        // // load tileset to test
-        // return tileset.readyPromise.then(function() {
-        //     return viewer.zoomTo(tileset).then(function() {
-        //         // moved to new location
-        //         expect(viewer.camera.position).not.toEqual(camPos);
-        //         expect(viewer.camera.direction).not.toEqual(camDir);
-        //     });
-        // });
-
         viewer = createViewer(container);
 
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
@@ -1091,10 +1072,6 @@ defineSuite([
     it('zoomTo zooms to Cesium3DTileset with offset', function() {
         viewer = createViewer(container);
 
-        // stored for movement check
-        // var camPos = Cartesian3.clone(viewer.camera.position);
-        // var camDir = Cartesian3.clone(viewer.camera.direction);
-
         var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets';
         var tileset = new Cesium3DTileset({
             url : path
@@ -1105,13 +1082,6 @@ defineSuite([
             var expectedBoundingSphere = tileset.boundingSphere;
             var expectedOffset = new HeadingPitchRange(0.4, 1.2, 4.0 * expectedBoundingSphere.radius);
 
-            //     return viewer.zoomTo(tileset, offset).then(function() {
-            //         // moved to new location
-            //         expect(viewer.camera.position).not.toEqual(camPos);
-            //         expect(viewer.camera.direction).not.toEqual(camDir);
-            //     });
-            // });
-
             var promise = viewer.zoomTo(tileset, expectedOffset);
             var wasCompleted = false;
             spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) {