/*global define*/
define([
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/Ellipsoid',
'../Core/Math',
'../Core/Matrix4',
'../Core/ScreenSpaceEventHandler',
'../Core/ScreenSpaceEventType',
'./Camera',
'./OrthographicFrustum',
'./PerspectiveFrustum',
'./SceneMode'
], function(
Cartesian3,
defaultValue,
defined,
destroyObject,
DeveloperError,
EasingFunction,
Ellipsoid,
CesiumMath,
Matrix4,
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Camera,
OrthographicFrustum,
PerspectiveFrustum,
SceneMode) {
"use strict";
/**
* @private
*/
var SceneTransitioner = function(scene, ellipsoid) {
//>>includeStart('debug', pragmas.debug);
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
//>>includeEnd('debug');
this._scene = scene;
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
// Position camera and size frustum so the entire 2D map is visible
var maxRadii = ellipsoid.maximumRadius;
var position = new Cartesian3(0.0, 0.0, 2.0 * maxRadii);
var direction = new Cartesian3();
direction = Cartesian3.normalize(Cartesian3.negate(position, direction), direction);
var up = Cartesian3.clone(Cartesian3.UNIT_Y);
var position2D = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, position, new Cartesian3());
var direction2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, direction, new Cartesian3());
var up2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, up, new Cartesian3());
var frustum = new OrthographicFrustum();
frustum.right = maxRadii * Math.PI;
frustum.left = -frustum.right;
frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
frustum.bottom = -frustum.top;
this._camera2D = {
position : position,
direction : direction,
up : up,
position2D : position2D,
direction2D : direction2D,
up2D : up2D,
frustum : frustum
};
position = new Cartesian3(0.0, -1.0, 1.0);
position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * maxRadii, position);
direction = new Cartesian3();
direction = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.ZERO, position, direction), direction);
var right = new Cartesian3();
right = Cartesian3.normalize(Cartesian3.cross(direction, Cartesian3.UNIT_Z, right), right);
up = new Cartesian3();
up = Cartesian3.normalize(Cartesian3.cross(right, direction, up), up);
position2D = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, position, new Cartesian3());
direction2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, direction, new Cartesian3());
var right2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, right, new Cartesian3());
up2D = new Cartesian3();
up2D = Cartesian3.normalize(Cartesian3.cross(right2D, direction2D, up2D), up2D);
frustum = new PerspectiveFrustum();
frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
frustum.fov = CesiumMath.toRadians(60.0);
this._cameraCV = {
position : position,
direction : direction,
up : up,
position2D : position2D,
direction2D : direction2D,
up2D : up2D,
frustum : frustum
};
position = new Cartesian3();
position = Cartesian3.multiplyByScalar(Cartesian3.normalize(new Cartesian3(0.0, -2.0, 1.0), position), 2.0 * maxRadii, position);
direction = new Cartesian3();
direction = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.ZERO, position, direction), direction);
right = new Cartesian3();
right = Cartesian3.normalize(Cartesian3.cross(direction, Cartesian3.UNIT_Z, right), right);
up = new Cartesian3();
up = Cartesian3.normalize(Cartesian3.cross(right, direction, up), up);
this._camera3D = {
position : position,
direction : direction,
up : up,
frustum : frustum
};
this._currentTweens = [];
this._morphHandler = undefined;
this._morphCancelled = false;
this._completeMorph = undefined;
};
SceneTransitioner.prototype.completeMorph = function() {
if (defined(this._completeMorph)) {
this._completeMorph();
}
};
SceneTransitioner.prototype.morphTo2D = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.SCENE2D || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE2D, true);
updateFrustums(this);
scene.mode = SceneMode.MORPHING;
createMorphHandler(this, complete2DCallback);
if (this._previousMode === SceneMode.COLUMBUS_VIEW) {
morphFromColumbusViewTo2D(this, duration, ellipsoid, complete2DCallback);
} else {
morphFrom3DTo2D(this, duration, ellipsoid, complete2DCallback);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
SceneTransitioner.prototype.morphToColumbusView = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.COLUMBUS_VIEW || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.COLUMBUS_VIEW, true);
updateFrustums(this);
scene.mode = SceneMode.MORPHING;
createMorphHandler(this, completeColumbusViewCallback);
if (this._previousMode === SceneMode.SCENE2D) {
morphFrom2DToColumbusView(this, duration, ellipsoid, completeColumbusViewCallback);
} else {
morphFrom3DToColumbusView(this, duration, this._cameraCV, completeColumbusViewCallback);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
SceneTransitioner.prototype.morphTo3D = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.SCENE3D || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE3D, true);
updateFrustums(this);
scene.mode = SceneMode.MORPHING;
createMorphHandler(this, complete3DCallback);
if (this._previousMode === SceneMode.SCENE2D) {
morphFrom2DTo3D(this, duration, ellipsoid, complete3DCallback);
} else {
morphFromColumbusViewTo3D(this, duration, ellipsoid, complete3DCallback);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*/
SceneTransitioner.prototype.isDestroyed = function() {
return false;
};
/**
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* transitioner = transitioner && transitioner.destroy();
*/
SceneTransitioner.prototype.destroy = function() {
destroyMorphHandler(this);
return destroyObject(this);
};
function createMorphHandler(transitioner, completeMorphFunction) {
if (transitioner._scene.completeMorphOnUserInput) {
transitioner._morphHandler = new ScreenSpaceEventHandler(transitioner._scene.canvas);
var completeMorph = function() {
transitioner._morphCancelled = true;
completeMorphFunction(transitioner);
};
transitioner._completeMorph = completeMorph;
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.LEFT_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.MIDDLE_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.RIGHT_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.WHEEL);
}
}
function destroyMorphHandler(transitioner) {
var tweens = transitioner._currentTweens;
for ( var i = 0; i < tweens.length; ++i) {
tweens[i].cancelTween();
}
transitioner._currentTweens.length = 0;
transitioner._morphHandler = transitioner._morphHandler && transitioner._morphHandler.destroy();
}
function morphFromColumbusViewTo3D(transitioner, duration, ellipsoid, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
camera.setTransform(Matrix4.IDENTITY);
var startPos = camera.position;
var startDir = camera.direction;
var startUp = camera.up;
var endPos = Cartesian3.clone(transitioner._camera2D.position);
var endDir = Cartesian3.clone(transitioner._camera2D.direction);
var endUp = Cartesian3.clone(transitioner._camera2D.up);
var update = function(value) {
camera.position = columbusViewMorph(startPos, endPos, value.time);
camera.direction = columbusViewMorph(startDir, endDir, value.time);
camera.up = columbusViewMorph(startUp, endUp, value.time);
camera.right = Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
};
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update
});
transitioner._currentTweens.push(tween);
addMorphTimeAnimations(transitioner, scene, 0.0, 1.0, duration, complete);
}
function morphFrom2DTo3D(transitioner, duration, ellipsoid, complete) {
duration *= 0.5;
var camera = transitioner._scene.camera;
camera.setTransform(Matrix4.IDENTITY);
morphOrthographicToPerspective(transitioner, duration, ellipsoid, function() {
camera.frustum = transitioner._cameraCV.frustum.clone();
morphFromColumbusViewTo3D(transitioner, duration, ellipsoid, complete);
});
}
function columbusViewMorph(startPosition, endPosition, time) {
// Just linear for now.
return Cartesian3.lerp(startPosition, endPosition, time, new Cartesian3());
}
function morphPerspectiveToOrthographic(transitioner, duration, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
var startPos = camera.position;
var startFOV = camera.frustum.fov;
var endFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5;
var d = Cartesian3.magnitude(startPos) * Math.tan(startFOV * 0.5);
camera.frustum.far = d / Math.tan(endFOV * 0.5) + 10000000.0;
var update = function(value) {
camera.frustum.fov = CesiumMath.lerp(startFOV, endFOV, value.time);
var distance = d / Math.tan(camera.frustum.fov * 0.5);
var pos = new Cartesian3();
camera.position = Cartesian3.multiplyByScalar(Cartesian3.normalize(camera.position, pos), distance, pos);
};
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
camera.frustum = transitioner._camera2D.frustum.clone();
complete(transitioner);
}
});
transitioner._currentTweens.push(tween);
}
function morphFromColumbusViewTo2D(transitioner, duration, ellipsoid, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
camera.setTransform(Matrix4.IDENTITY);
var maxRadii = ellipsoid.maximumRadius;
var startPos = Cartesian3.clone(camera.position);
var startDir = Cartesian3.clone(camera.direction);
var startUp = Cartesian3.clone(camera.up);
var tanPhi = Math.tan(transitioner._cameraCV.frustum.fovy * 0.5);
var tanTheta = transitioner._cameraCV.frustum.aspectRatio * tanPhi;
var d = (maxRadii * Math.PI) / tanTheta;
var endPos = new Cartesian3();
endPos = Cartesian3.multiplyByScalar(Cartesian3.normalize(transitioner._camera2D.position, endPos), d, endPos);
var endDir = Cartesian3.clone(transitioner._camera2D.direction);
var endUp = Cartesian3.clone(transitioner._camera2D.up);
var updateCV = function(value) {
camera.position = columbusViewMorph(startPos, endPos, value.time);
camera.direction = columbusViewMorph(startDir, endDir, value.time);
camera.up = columbusViewMorph(startUp, endUp, value.time);
camera.right = Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
};
duration *= 0.5;
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : updateCV,
complete : function() {
morphPerspectiveToOrthographic(transitioner, duration, complete);
}
});
transitioner._currentTweens.push(tween);
}
function morphFrom3DTo2D(transitioner, duration, ellipsoid, complete) {
duration *= 0.5;
var maxRadii = ellipsoid.maximumRadius;
var tanPhi = Math.tan(transitioner._camera3D.frustum.fovy * 0.5);
var tanTheta = transitioner._camera3D.frustum.aspectRatio * tanPhi;
var d = (maxRadii * Math.PI) / tanTheta;
var camera3DTo2D = {};
var pos2D = new Cartesian3();
camera3DTo2D.position2D = Cartesian3.multiplyByScalar(Cartesian3.normalize(transitioner._camera2D.position2D, pos2D), d, pos2D);
camera3DTo2D.direction2D = Cartesian3.clone(transitioner._camera2D.direction2D);
camera3DTo2D.up2D = Cartesian3.clone(transitioner._camera2D.up2D);
var completeCallback = function() {
morphPerspectiveToOrthographic(transitioner, duration, complete);
};
morphFrom3DToColumbusView(transitioner, duration, camera3DTo2D, completeCallback);
}
function morphOrthographicToPerspective(transitioner, duration, ellipsoid, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
var maxRadii = ellipsoid.maximumRadius;
var tanPhi = Math.tan(transitioner._cameraCV.frustum.fovy * 0.5);
var tanTheta = transitioner._cameraCV.frustum.aspectRatio * tanPhi;
var d = (maxRadii * Math.PI) / tanTheta;
var endPos2D = new Cartesian3();
endPos2D = Cartesian3.multiplyByScalar(Cartesian3.normalize(transitioner._camera2D.position, endPos2D), d, endPos2D);
var top = camera.frustum.top;
var bottom = camera.frustum.bottom;
var right = camera.frustum.right;
var left = camera.frustum.left;
var frustum2D = transitioner._camera2D.frustum;
var frustumCV = transitioner._cameraCV.frustum;
var startPos = Cartesian3.clone(camera.position);
var update2D = function(value) {
camera.position = columbusViewMorph(startPos, endPos2D, value.time);
camera.frustum.top = CesiumMath.lerp(top, frustum2D.top, value.time);
camera.frustum.bottom = CesiumMath.lerp(bottom, frustum2D.bottom, value.time);
camera.frustum.right = CesiumMath.lerp(right, frustum2D.right, value.time);
camera.frustum.left = CesiumMath.lerp(left, frustum2D.left, value.time);
};
var startTime = (right - left) / (2.0 * maxRadii * Math.PI);
var endTime = 1.0;
if (startTime > endTime) {
startTime = 0.0;
}
var partialDuration = (endTime - startTime) * duration;
if (partialDuration < CesiumMath.EPSILON6) {
if (!Cartesian3.equalsEpsilon(startPos, endPos2D, CesiumMath.EPSILON6)) {
partialDuration = duration;
startTime = 0.0;
endTime = 1.0;
} else {
// If the camera and frustum are already in position for the switch to
// a perspective projection, nothing needs to be animated.
camera.position = endPos2D;
camera.frustum = frustumCV.clone();
complete(transitioner);
return;
}
}
var tween = scene.tweens.add({
easingFunction : EasingFunction.QUARTIC_OUT,
duration : partialDuration,
startObject : {
time : startTime
},
stopObject : {
time : endTime
},
update : update2D,
complete : function() {
camera.frustum = frustumCV.clone();
complete(transitioner);
}
});
transitioner._currentTweens.push(tween);
}
function morphFrom2DToColumbusView(transitioner, duration, ellipsoid, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
camera.setTransform(Matrix4.IDENTITY);
duration *= 0.5;
var completeFrustumChange = function() {
var startPos = Cartesian3.clone(camera.position);
var startDir = Cartesian3.clone(camera.direction);
var startUp = Cartesian3.clone(camera.up);
var endPos = Cartesian3.clone(transitioner._cameraCV.position);
var endDir = Cartesian3.clone(transitioner._cameraCV.direction);
var endUp = Cartesian3.clone(transitioner._cameraCV.up);
var updateCV = function(value) {
camera.position = columbusViewMorph(startPos, endPos, value.time);
camera.direction = columbusViewMorph(startDir, endDir, value.time);
camera.up = columbusViewMorph(startUp, endUp, value.time);
camera.right = Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
};
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : updateCV,
complete : function() {
complete(transitioner);
}
});
transitioner._currentTweens.push(tween);
};
morphOrthographicToPerspective(transitioner, duration, ellipsoid, completeFrustumChange);
}
function morphFrom3DToColumbusView(transitioner, duration, endCamera, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
camera.setTransform(Matrix4.IDENTITY);
var startPos = Cartesian3.clone(camera.position);
var startDir = Cartesian3.clone(camera.direction);
var startUp = Cartesian3.clone(camera.up);
var endPos = Cartesian3.clone(endCamera.position2D);
var endDir = Cartesian3.clone(endCamera.direction2D);
var endUp = Cartesian3.clone(endCamera.up2D);
var update = function(value) {
camera.position = columbusViewMorph(startPos, endPos, value.time);
camera.direction = columbusViewMorph(startDir, endDir, value.time);
camera.up = columbusViewMorph(startUp, endUp, value.time);
camera.right = Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
};
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
camera.position = endPos;
camera.direction = endDir;
camera.up = endUp;
camera.right = Cartesian3.cross(endDir, endUp, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
});
transitioner._currentTweens.push(tween);
addMorphTimeAnimations(transitioner, scene, 1.0, 0.0, duration, complete);
}
function addMorphTimeAnimations(transitioner, scene, start, stop, duration, complete) {
// Later, this will be linear and each object will adjust, if desired, in its vertex shader.
var options = {
object : scene,
property : 'morphTime',
startValue : start,
stopValue : stop,
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT
};
if (defined(complete)) {
options.complete = function() {
complete(transitioner);
};
}
var tween = scene.tweens.addProperty(options);
transitioner._currentTweens.push(tween);
}
function updateFrustums(transitioner) {
var scene = transitioner._scene;
var ratio = scene.drawingBufferHeight / scene.drawingBufferWidth;
var frustum = transitioner._camera2D.frustum;
frustum.top = frustum.right * ratio;
frustum.bottom = -frustum.top;
ratio = 1.0 / ratio;
frustum = transitioner._cameraCV.frustum;
frustum.aspectRatio = ratio;
frustum = transitioner._camera3D.frustum;
frustum.aspectRatio = ratio;
var camera = scene.camera;
switch (scene.mode) {
case SceneMode.SCENE3D:
camera.frustum = transitioner._camera3D.frustum.clone();
break;
case SceneMode.COLUMBUS_VIEW:
camera.frustum = transitioner._cameraCV.frustum.clone();
break;
case SceneMode.SCENE2D:
camera.frustum = transitioner._camera2D.frustum.clone();
break;
}
}
function complete3DCallback(transitioner) {
var scene = transitioner._scene;
scene.mode = SceneMode.SCENE3D;
scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE3D);
destroyMorphHandler(transitioner);
updateFrustums(transitioner);
if (transitioner._previousMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
transitioner._morphCancelled = false;
// TODO: Match incoming columbus-view or 2D position
var camera = scene.camera;
camera.position = Cartesian3.clone(transitioner._camera3D.position);
camera.direction = Cartesian3.clone(transitioner._camera3D.direction);
camera.up = Cartesian3.clone(transitioner._camera3D.up);
camera.right = Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE3D, wasMorphing);
}
function complete2DCallback(transitioner) {
var scene = transitioner._scene;
scene.mode = SceneMode.SCENE2D;
scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE2D);
destroyMorphHandler(transitioner);
updateFrustums(transitioner);
// TODO: Match incoming columbus-view or 3D position
var camera = scene.camera;
Cartesian3.clone(transitioner._camera2D.position, camera.position);
Cartesian3.clone(transitioner._camera2D.direction, camera.direction);
Cartesian3.clone(transitioner._camera2D.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE2D, wasMorphing);
}
function completeColumbusViewCallback(transitioner) {
var scene = transitioner._scene;
scene.mode = SceneMode.COLUMBUS_VIEW;
scene.morphTime = SceneMode.getMorphTime(SceneMode.COLUMBUS_VIEW);
destroyMorphHandler(transitioner);
updateFrustums(transitioner);
if (transitioner._previousModeMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
transitioner._morphCancelled = false;
// TODO: Match incoming 2D or 3D position
var camera = scene.camera;
Cartesian3.clone(transitioner._cameraCV.position, camera.position);
Cartesian3.clone(transitioner._cameraCV.direction, camera.direction);
Cartesian3.clone(transitioner._cameraCV.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.COLUMBUS_VIEW, wasMorphing);
}
return SceneTransitioner;
});