diff --git a/Source/Scene/AnimationCollection.js b/Source/Scene/AnimationCollection.js index 1ba0a5610119..68a89857455e 100644 --- a/Source/Scene/AnimationCollection.js +++ b/Source/Scene/AnimationCollection.js @@ -35,7 +35,7 @@ define([ throw new DeveloperError('duration is required.'); } - if (options.duration !== 0) { + if (options.duration > 0) { var delayDuration = defaultValue(options.delayDuration, 0); var easingFunction = defaultValue(options.easingFunction, Tween.Easing.Linear.None); diff --git a/Source/Scene/CameraFlightPath.js b/Source/Scene/CameraFlightPath.js index 028d670778f7..ad6d5241a04b 100644 --- a/Source/Scene/CameraFlightPath.js +++ b/Source/Scene/CameraFlightPath.js @@ -1,5 +1,6 @@ /*global define*/ define([ + '../Core/Cartesian2', '../Core/Cartesian3', '../Core/clone', '../Core/defaultValue', @@ -14,6 +15,7 @@ define([ '../Scene/SceneMode', '../ThirdParty/Tween' ], function( + Cartesian2, Cartesian3, clone, defaultValue, @@ -404,6 +406,9 @@ define([ * @see Scene#getFrameState * @see Scene#getAnimations */ + var dirScratch = new Cartesian3(); + var rightScratch = new Cartesian3(); + var upScratch = new Cartesian3(); CameraFlightPath.createAnimation = function(frameState, description) { description = defaultValue(description, defaultValue.EMPTY_OBJECT); var destination = description.destination; @@ -422,11 +427,70 @@ define([ var up = description.up; var duration = defaultValue(description.duration, 3000.0); var onComplete = description.onComplete; + var frustum = frameState.camera.frustum; + + if (frameState.mode === SceneMode.SCENE2D) { + if ((Cartesian2.equalsEpsilon(frameState.camera.position, destination, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6))) { + return { + duration : 0, + onComplete : onComplete + }; + } + } else if (Cartesian3.equalsEpsilon(destination, frameState.camera.position, CesiumMath.EPSILON6)) { + return { + duration : 0, + onComplete : onComplete + }; + } - if (Cartesian3.equalsEpsilon(destination, frameState.camera.position, CesiumMath.EPSILON6)) { + if (duration <= 0) { + var newOnComplete = function() { + var position = destination; + if (frameState.mode === SceneMode.SCENE3D) { + if (typeof description.direction === 'undefined' && typeof description.up === 'undefined'){ + dirScratch = position.negate(dirScratch).normalize(dirScratch); + rightScratch = dirScratch.cross(Cartesian3.UNIT_Z, rightScratch).normalize(rightScratch); + } else { + dirScratch = description.direction; + rightScratch = dirScratch.cross(description.up, rightScratch).normalize(rightScratch); + } + upScratch = defaultValue(description.up, rightScratch.cross(dirScratch, upScratch)); + } else { + if (typeof description.direction === 'undefined' && typeof description.up === 'undefined'){ + dirScratch = Cartesian3.UNIT_Z.negate(dirScratch); + rightScratch = dirScratch.cross(Cartesian3.UNIT_Y, rightScratch).normalize(rightScratch); + } else { + dirScratch = description.direction; + rightScratch = dirScratch.cross(description.up, rightScratch).normalize(rightScratch); + } + upScratch = defaultValue(description.up, rightScratch.cross(dirScratch, upScratch)); + } + + Cartesian3.clone(position, frameState.camera.position); + Cartesian3.clone(dirScratch, frameState.camera.direction); + Cartesian3.clone(upScratch, frameState.camera.up); + Cartesian3.clone(rightScratch, frameState.camera.right); + + if (frameState.mode === SceneMode.SCENE2D) { + var zoom = frameState.camera.position.z; + + + var ratio = frustum.top / frustum.right; + + var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5; + frustum.right += incrementAmount; + frustum.left -= incrementAmount; + frustum.top = ratio * frustum.right; + frustum.bottom = -frustum.top; + } + + if (typeof onComplete === 'function') { + onComplete(); + } + }; return { duration : 0, - onComplete : description.onComplete + onComplete : newOnComplete }; } @@ -494,13 +558,6 @@ define([ throw new DeveloperError('frameState.mode cannot be SceneMode.MORPHING'); } - if (Cartesian3.equalsEpsilon(c3destination, frameState.camera.position, CesiumMath.EPSILON6)) { - return { - duration : 0, - onComplete : description.onComplete - }; - } - var createAnimationDescription = clone(description); createAnimationDescription.destination = c3destination; return this.createAnimation(frameState, createAnimationDescription); @@ -541,13 +598,6 @@ define([ var camera = frameState.camera; camera.controller.getExtentCameraCoordinates(extent, c3destination); - if (Cartesian3.equalsEpsilon(c3destination, frameState.camera.position, CesiumMath.EPSILON6)) { - return { - duration : 0, - onComplete : description.onComplete - }; - } - createAnimationDescription.destination = c3destination; return this.createAnimation(frameState, createAnimationDescription); }; diff --git a/Source/Widgets/HomeButton/HomeButton.js b/Source/Widgets/HomeButton/HomeButton.js index 26b828034696..6905e5a38bc1 100644 --- a/Source/Widgets/HomeButton/HomeButton.js +++ b/Source/Widgets/HomeButton/HomeButton.js @@ -27,7 +27,7 @@ define([ * @exception {DeveloperError} container is required. * @exception {DeveloperError} scene is required. */ - var HomeButton = function(container, scene, transitioner, ellipsoid) { + var HomeButton = function(container, scene, transitioner, ellipsoid, flightDuration) { if (typeof container === 'undefined') { throw new DeveloperError('container is required.'); } @@ -41,7 +41,7 @@ define([ } this._container = container; - this._viewModel = new HomeButtonViewModel(scene, transitioner, ellipsoid); + this._viewModel = new HomeButtonViewModel(scene, transitioner, ellipsoid, flightDuration); this._element = document.createElement('span'); this._element.className = 'cesium-homeButton'; diff --git a/Source/Widgets/HomeButton/HomeButtonViewModel.js b/Source/Widgets/HomeButton/HomeButtonViewModel.js index 3a98a5dc8ed7..50667c0d92d5 100644 --- a/Source/Widgets/HomeButton/HomeButtonViewModel.js +++ b/Source/Widgets/HomeButton/HomeButtonViewModel.js @@ -10,6 +10,7 @@ define([ '../../Core/Matrix4', '../../Scene/Camera', '../../Scene/CameraColumbusViewMode', + '../../Scene/CameraFlightPath', '../../Scene/PerspectiveFrustum', '../../Scene/SceneMode', '../createCommand', @@ -25,13 +26,14 @@ define([ Matrix4, Camera, CameraColumbusViewMode, + CameraFlightPath, PerspectiveFrustum, SceneMode, createCommand, knockout) { "use strict"; - function viewHome(scene, ellipsoid, transitioner) { + function viewHome(scene, ellipsoid, transitioner, flightDuration) { var mode = scene.mode; var camera = scene.getCamera(); @@ -47,41 +49,56 @@ define([ if (typeof transitioner !== 'undefined' && mode === SceneMode.MORPHING) { transitioner.completeMorph(); } + var flight; + var description; if (mode === SceneMode.SCENE2D) { - camera.controller.viewExtent(Extent.MAX_VALUE); + camera.transform = new Matrix4(0, 0, 1, 0, + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1); + description = { + destination: Extent.MAX_VALUE, + duration: flightDuration + }; + flight = CameraFlightPath.createAnimationExtent(scene.getFrameState(), description); + scene.getAnimations().add(flight); } else if (mode === SceneMode.SCENE3D) { + Cartesian3.add(camera.position, Matrix4.getTranslation(camera.transform), camera.position); + var rotation = Matrix4.getRotation(camera.transform); + rotation.multiplyByVector(camera.direction, camera.direction); + rotation.multiplyByVector(camera.up, camera.up); + rotation.multiplyByVector(camera.right, camera.right); + camera.transform = Matrix4.IDENTITY.clone(); var defaultCamera = new Camera(canvas); - defaultCamera.position.clone(camera.position); - defaultCamera.direction.clone(camera.direction); - defaultCamera.up.clone(camera.up); - defaultCamera.right.clone(camera.right); - defaultCamera.transform.clone(camera.transform); - defaultCamera.frustum.clone(camera.frustum); + description = { + destination: defaultCamera.position, + duration: flightDuration, + up: defaultCamera.up, + direction: defaultCamera.direction + }; + flight = CameraFlightPath.createAnimation(scene.getFrameState(), description); + scene.getAnimations().add(flight); } else if (mode === SceneMode.COLUMBUS_VIEW) { - var transform = new Matrix4(0.0, 0.0, 1.0, 0.0, - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0); - + camera.transform = new Matrix4(0.0, 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0); var maxRadii = ellipsoid.getMaximumRadius(); var position = new Cartesian3(0.0, -1.0, 1.0).normalize().multiplyByScalar(5.0 * maxRadii); var direction = Cartesian3.ZERO.subtract(position).normalize(); var right = direction.cross(Cartesian3.UNIT_Z); var up = right.cross(direction); - right = direction.cross(up); - direction = up.cross(right); - - var frustum = new PerspectiveFrustum(); - frustum.fovy = CesiumMath.toRadians(60.0); - frustum.aspectRatio = canvas.clientWidth / canvas.clientHeight; - - camera.position = position; - camera.direction = direction; - camera.up = up; - camera.right = right; - camera.frustum = frustum; - camera.transform = transform; + + description = { + destination: position, + duration: flightDuration, + up: up, + direction: direction + }; + + flight = CameraFlightPath.createAnimation(scene.getFrameState(), description); + scene.getAnimations().add(flight); } } @@ -93,22 +110,25 @@ define([ * @param {Scene} scene The scene instance to use. * @param {SceneTransitioner} [transitioner] The scene transitioner instance to use. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to be viewed when in home position. + * @param {Number} [flightDuration] The duration of the camera flight in milliseconds * * @exception {DeveloperError} scene is required. */ - var HomeButtonViewModel = function(scene, transitioner, ellipsoid) { + var HomeButtonViewModel = function(scene, transitioner, ellipsoid, flightDuration) { if (typeof scene === 'undefined') { throw new DeveloperError('scene is required.'); } ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + flightDuration = defaultValue(flightDuration, 1500); this._scene = scene; this._ellipsoid = ellipsoid; this._transitioner = transitioner; + this._flightDuration = flightDuration; this._command = createCommand(function() { - viewHome(scene, ellipsoid, transitioner); + viewHome(scene, ellipsoid, transitioner, flightDuration); }); /** diff --git a/Specs/Scene/CameraFlightPathSpec.js b/Specs/Scene/CameraFlightPathSpec.js index 77eb1b7c34c0..e388b791f197 100644 --- a/Specs/Scene/CameraFlightPathSpec.js +++ b/Specs/Scene/CameraFlightPathSpec.js @@ -557,4 +557,160 @@ defineSuite([ expect(camera.direction).toEqual(startDirection); expect(camera.up).toEqual(startUp); }); + + it('creates an animation with 0 duration', function() { + var destination = new Cartesian3(1e9, 1e9, 1e9); + var duration = 0; + var onComplete = function() { + return true; + }; + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : destination, + duration : duration, + onComplete : onComplete + }); + + expect(flight.duration).toEqual(duration); + expect(flight.onComplete).not.toEqual(onComplete); + expect(typeof flight.onUpdate).toEqual('undefined'); + expect(frameState.camera.position).not.toEqual(destination); + flight.onComplete(); + expect(frameState.camera.position).toEqual(destination); + }); + + it('duration is 0 when destination is the same as camera position in 2D', function() { + frameState.mode = SceneMode.SCENE2D; + var camera = frameState.camera; + + camera.position = new Cartesian3(0.0, 0.0, 1000.0); + camera.direction = Cartesian3.UNIT_Z.negate(); + camera.up = Cartesian3.UNIT_Y.clone(); + camera.right = camera.direction.cross(camera.up); + camera.frustum = createOrthographicFrustum(); + var frustum = camera.frustum; + var destination = camera.position.clone(); + destination.z = Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : destination + }); + + expect(flight.duration).toEqual(0); + }); + + it('duration is 0 when destination is the same as camera position in 3D', function() { + frameState.mode = SceneMode.SCENE3D; + var camera = frameState.camera; + + camera.position = new Cartesian3(0.0, 0.0, 1000.0); + camera.direction = Cartesian3.UNIT_Z.negate(); + camera.up = Cartesian3.UNIT_Y.clone(); + camera.right = camera.direction.cross(camera.up); + camera.frustum = createOrthographicFrustum(); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : camera.position + }); + + expect(flight.duration).toEqual(0); + }); + + it('duration is 0 when destination is the same as camera position in CV', function() { + frameState.mode = SceneMode.COLUMBUS_VIEW; + var camera = frameState.camera; + + camera.position = new Cartesian3(0.0, 0.0, 1000.0); + camera.direction = Cartesian3.UNIT_Z.negate(); + camera.up = Cartesian3.UNIT_Y.clone(); + camera.right = camera.direction.cross(camera.up); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : camera.position + }); + + expect(flight.duration).toEqual(0); + }); + + it('creates an animation in 2D 0 duration', function() { + frameState.mode = SceneMode.SCENE2D; + var camera = frameState.camera; + + camera.position = new Cartesian3(0.0, 0.0, 1000.0); + camera.direction = Cartesian3.UNIT_Z.negate(); + camera.up = Cartesian3.UNIT_Y.clone(); + camera.right = camera.direction.cross(camera.up); + camera.frustum = createOrthographicFrustum(); + + var startPosition = camera.position.clone(); + var startDirection = camera.direction.clone(); + var startUp = camera.up.clone(); + + var endPosition = startPosition.add(new Cartesian3(-6e6 * Math.PI, 6e6 * CesiumMath.PI_OVER_FOUR, 100.0)); + var endDirection = startDirection.clone(); + var endUp = startUp.negate(); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : endPosition, + direction : endDirection, + up : endUp, + duration : 0 + }); + + expect(typeof flight.onComplete).toEqual('function'); + flight.onComplete(); + expect(camera.position.x).toEqualEpsilon(endPosition.x, CesiumMath.EPSILON12); + expect(camera.position.y).toEqualEpsilon(endPosition.y, CesiumMath.EPSILON12); + expect(camera.direction).toEqualEpsilon(endDirection, CesiumMath.EPSILON12); + expect(camera.up).toEqualEpsilon(endUp, CesiumMath.EPSILON12); + expect(camera.frustum.right - camera.frustum.left).toEqual(endPosition.z); + }); + + it('creates an animation in Columbus view 0 duration', function() { + frameState.mode = SceneMode.COLUMBUS_VIEW; + var camera = frameState.camera; + + camera.position = new Cartesian3(0.0, 0.0, 1000.0); + camera.direction = Cartesian3.UNIT_Z.negate(); + camera.up = Cartesian3.UNIT_Y.clone(); + camera.right = camera.direction.cross(camera.up); + + var startPosition = camera.position.clone(); + var endPosition = startPosition.add(new Cartesian3(-6e6 * Math.PI, 6e6 * CesiumMath.PI_OVER_FOUR, 100.0)); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : endPosition, + duration : 0 + }); + + expect(typeof flight.onComplete).toEqual('function'); + flight.onComplete(); + expect(camera.position).toEqualEpsilon(endPosition, CesiumMath.EPSILON12); + }); + + it('creates an animation in 3d 0 duration', function() { + var camera = frameState.camera; + + var startPosition = camera.position.clone(); + var startDirection = camera.direction.clone(); + var startUp = camera.up.clone(); + + var endPosition = startPosition.negate(); + var endDirection = startDirection.negate(); + var endUp = startUp.negate(); + + var flight = CameraFlightPath.createAnimation(frameState, { + destination : endPosition, + direction : endDirection, + up : endUp, + duration : 0 + }); + + expect(typeof flight.onComplete).toEqual('function'); + flight.onComplete(); + expect(camera.position).toEqualEpsilon(endPosition, CesiumMath.EPSILON12); + expect(camera.direction).toEqualEpsilon(endDirection, CesiumMath.EPSILON12); + expect(camera.up).toEqualEpsilon(endUp, CesiumMath.EPSILON12); + }); + }); \ No newline at end of file diff --git a/Specs/Widgets/HomeButton/HomeButtonViewModelSpec.js b/Specs/Widgets/HomeButton/HomeButtonViewModelSpec.js index 9abc9d9b4eee..eabd7ebd6f37 100644 --- a/Specs/Widgets/HomeButton/HomeButtonViewModelSpec.js +++ b/Specs/Widgets/HomeButton/HomeButtonViewModelSpec.js @@ -51,23 +51,27 @@ defineSuite([ //The actual position of the camera at the end of the command is //tied to the implementation of various camera features. it('works in 3D', function() { + scene.render(); var viewModel = new HomeButtonViewModel(scene, transitioner); viewModel.command(); }); it('works in 2D', function() { + scene.render(); var viewModel = new HomeButtonViewModel(scene, transitioner); transitioner.to2D(); viewModel.command(); }); it('works in Columbus View', function() { + scene.render(); var viewModel = new HomeButtonViewModel(scene, transitioner); transitioner.toColumbusView(); viewModel.command(); }); it('works while morphing', function() { + scene.render(); var viewModel = new HomeButtonViewModel(scene, transitioner); transitioner.morphToColumbusView(); viewModel.command();