/*global define*/ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', '../Core/clone', '../Core/defaultValue', '../Core/defined', '../Core/DeveloperError', '../Core/EasingFunction', '../Core/HermiteSpline', '../Core/LinearSpline', '../Core/Math', '../Core/Matrix3', '../Core/Matrix4', '../Core/Quaternion', '../Core/QuaternionSpline', './PerspectiveFrustum', './PerspectiveOffCenterFrustum', './SceneMode' ], function( Cartesian2, Cartesian3, Cartographic, clone, defaultValue, defined, DeveloperError, EasingFunction, HermiteSpline, LinearSpline, CesiumMath, Matrix3, Matrix4, Quaternion, QuaternionSpline, PerspectiveFrustum, PerspectiveOffCenterFrustum, SceneMode) { "use strict"; /** * Creates tweens for camera flights. *

* Mouse interaction is disabled during flights. * * @private */ var CameraFlightPath = { }; var c3destination = new Cartesian3(); var rotMatrix = new Matrix3(); var viewMat = new Matrix3(); var cqRight = new Cartesian3(); var cqUp = new Cartesian3(); function createQuaternion(direction, up, result) { Cartesian3.cross(direction, up, cqRight); Cartesian3.cross(cqRight, direction, cqUp); viewMat[0] = cqRight.x; viewMat[1] = cqUp.x; viewMat[2] = -direction.x; viewMat[3] = cqRight.y; viewMat[4] = cqUp.y; viewMat[5] = -direction.y; viewMat[6] = cqRight.z; viewMat[7] = cqUp.z; viewMat[8] = -direction.z; return Quaternion.fromRotationMatrix(viewMat, result); } function getAltitude(frustum, dx, dy) { var near; var top; var right; if (frustum instanceof PerspectiveFrustum) { var tanTheta = Math.tan(0.5 * frustum.fovy); near = frustum.near; top = frustum.near * tanTheta; right = frustum.aspectRatio * top; return Math.max(dx * near / right, dy * near / top); } else if (frustum instanceof PerspectiveOffCenterFrustum) { near = frustum.near; top = frustum.top; right = frustum.right; return Math.max(dx * near / right, dy * near / top); } return Math.max(dx, dy); } var scratchCart = new Cartesian3(); var scratchCart2 = new Cartesian3(); var scratchCart3 = new Cartesian3(); var scratchCart4 = new Cartesian3(); var rotMatrixScratch = new Matrix3(); function createPath3D(camera, ellipsoid, start, up, right, end, duration) { // get minimum altitude from which the whole ellipsoid is visible var radius = ellipsoid.maximumRadius; var frustum = camera.frustum; var maxStartAlt = getAltitude(frustum, radius, radius); var dot = Cartesian3.dot(Cartesian3.normalize(start, scratchCart), Cartesian3.normalize(end, scratchCart2)); var points; var altitude; var incrementPercentage; if (Cartesian3.magnitude(start) > maxStartAlt) { altitude = radius + 0.6 * (maxStartAlt - radius); incrementPercentage = 0.35; } else { var diff = Cartesian3.subtract(start, end, scratchCart); altitude = Cartesian3.magnitude(Cartesian3.add(Cartesian3.multiplyByScalar(diff, 0.5, scratchCart2), end, scratchCart2)); var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2)); var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2)); altitude += getAltitude(frustum, verticalDistance, horizontalDistance); incrementPercentage = CesiumMath.clamp(dot + 1.0, 0.25, 0.5); } var aboveEnd = Cartesian3.multiplyByScalar(Cartesian3.normalize(end, scratchCart2), altitude, scratchCart2); var afterStart = Cartesian3.multiplyByScalar(Cartesian3.normalize(start, scratchCart), altitude, scratchCart); var axis, angle, rotation; var middle = new Cartesian3(); if (Cartesian3.magnitude(end) > maxStartAlt && dot > 0.75) { middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, end, middle), 0.5, middle), end, middle); points = [ start, middle, end ]; } else if (Cartesian3.magnitude(start) > maxStartAlt && dot > 0) { middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, aboveEnd, middle), 0.5, middle), aboveEnd, middle); points = [ start, middle, end ]; } else { points = [ start ]; angle = CesiumMath.acosClamped(Cartesian3.dot(Cartesian3.normalize(afterStart, scratchCart3), Cartesian3.normalize(aboveEnd, scratchCart4))); axis = Cartesian3.cross(aboveEnd, afterStart, scratchCart3); if (Cartesian3.equalsEpsilon(axis, Cartesian3.ZERO, CesiumMath.EPSILON6)) { axis = Cartesian3.UNIT_Z; } var increment = incrementPercentage * angle; var startCondition = angle - increment; for ( var i = startCondition; i > 0.0; i = i - increment) { rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, i), rotMatrixScratch); points.push(Matrix3.multiplyByVector(rotation, aboveEnd, new Cartesian3())); } points.push(end); } var times = new Array(points.length); var scalar = duration / (points.length - 1); for ( var k = 0; k < points.length; ++k) { times[k] = k * scalar; } return HermiteSpline.createNaturalCubic({ points : points, times : times }); } var direction3D = new Cartesian3(); var right3D = new Cartesian3(); var up3D = new Cartesian3(); var quat3D = new Quaternion(); function createOrientations3D(path, startDirection, startUp, endDirection, endUp) { var points = path.points; var orientations = new Array(points.length); orientations[0] = createQuaternion(startDirection, startUp); var point; var length = points.length - 1; for (var i = 1; i < length; ++i) { point = points[i]; Cartesian3.normalize(Cartesian3.negate(point, direction3D), direction3D); Cartesian3.normalize(Cartesian3.cross(direction3D, Cartesian3.UNIT_Z, right3D), right3D); Cartesian3.cross(right3D, direction3D, up3D); orientations[i] = createQuaternion(direction3D, up3D, quat3D); } point = points[length]; if (defined(endDirection) && defined(endUp)) { orientations[length] = createQuaternion(endDirection, endUp); } else { Cartesian3.normalize(Cartesian3.negate(point, direction3D), direction3D); Cartesian3.normalize(Cartesian3.cross(direction3D, Cartesian3.UNIT_Z, right3D), right3D); Cartesian3.cross(right3D, direction3D, up3D); orientations[length] = createQuaternion(direction3D, up3D, quat3D); } return new QuaternionSpline({ points : orientations, times : path.times }); } var scratchStartPosition = new Cartesian3(); var scratchStartDirection = new Cartesian3(); var scratchStartUp = new Cartesian3(); var scratchStartRight = new Cartesian3(); var currentFrame = new Matrix4(); function createUpdate3D(scene, destination, duration, direction, up) { var camera = scene.camera; var ellipsoid = scene.mapProjection.ellipsoid; var start = camera.cameraToWorldCoordinatesPoint(camera.position, scratchStartPosition); var startDirection = camera.cameraToWorldCoordinatesVector(camera.direction, scratchStartDirection); var startUp = camera.cameraToWorldCoordinatesVector(camera.up, scratchStartUp); var startRight = Cartesian3.cross(startDirection, startUp, scratchStartRight); var path = createPath3D(camera, ellipsoid, start, startUp, startRight, destination, duration); var orientations = createOrientations3D(path, startDirection, startUp, direction, up); var update = function(value) { var time = value.time; var orientation = orientations.evaluate(time); Matrix3.fromQuaternion(orientation, rotMatrix); Matrix4.clone(camera.transform, currentFrame); Matrix4.clone(Matrix4.IDENTITY, camera.transform); camera.position = path.evaluate(time, camera.position); camera.right = Matrix3.getRow(rotMatrix, 0, camera.right); camera.up = Matrix3.getRow(rotMatrix, 1, camera.up); camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction); camera.setTransform(currentFrame); }; return update; } var cartScratch1 = new Cartesian3(); function createPath2D(camera, ellipsoid, start, end, duration) { if (CesiumMath.equalsEpsilon(Cartesian2.magnitude(start), Cartesian2.magnitude(end), 10000.0)) { return new LinearSpline({ points : [start, end], times : [0.0, duration] }); } // get minimum altitude from which the whole map is visible var radius = ellipsoid.maximumRadius; var frustum = camera.frustum; var maxStartAlt = getAltitude(frustum, Math.PI * radius, CesiumMath.PI_OVER_TWO * radius); var points; var altitude; var incrementPercentage = 0.5; if (start.z > maxStartAlt) { altitude = 0.6 * maxStartAlt; } else { var diff = Cartesian3.subtract(start, end, cartScratch1); altitude = getAltitude(frustum, Math.abs(diff.y), Math.abs(diff.x)); } var aboveEnd = Cartesian3.clone(end); aboveEnd.z = altitude; var afterStart = Cartesian3.clone(start); afterStart.z = altitude; var middle = new Cartesian3(); if (end.z > maxStartAlt) { middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, end, middle), 0.5, middle), end, middle); points = [ start, middle, end ]; } else if (start.z > maxStartAlt) { middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, aboveEnd, middle), 0.5, middle), aboveEnd, middle); points = [ start, middle, end ]; } else { points = [ start ]; var v = Cartesian3.subtract(afterStart, aboveEnd, cartScratch1); var distance = Cartesian3.magnitude(v); Cartesian3.normalize(v, v); var increment = incrementPercentage * distance; var startCondition = distance - increment; for ( var i = startCondition; i > 0.0; i = i - increment) { var p = new Cartesian3(); points.push(Cartesian3.add(Cartesian3.multiplyByScalar(v, i, p), aboveEnd, p)); } points.push(end); } var times = new Array(points.length); var scalar = duration / (points.length - 1); for ( var k = 0; k < points.length; ++k) { times[k] = k * scalar; } return HermiteSpline.createNaturalCubic({ points : points, times : times }); } var direction2D = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); var right2D = new Cartesian3(); right2D = Cartesian3.normalize(Cartesian3.cross(direction2D, Cartesian3.UNIT_Y, right2D), right2D); var up2D = Cartesian3.cross(right2D, direction2D, new Cartesian3()); var quat = createQuaternion(direction2D, up2D); function createOrientations2D(camera, path, endDirection, endUp) { var points = path.points; var orientations = new Array(points.length); orientations[0] = createQuaternion(camera.direction, camera.up); var length = points.length - 1; for (var i = 1; i < length; ++i) { orientations[i] = quat; } if (defined(endDirection) && defined(endUp)) { orientations[length] = createQuaternion(endDirection, endUp); } else { orientations[length] = quat; } return new QuaternionSpline({ points : orientations, times : path.times }); } function createUpdateCV(scene, destination, duration, direction, up) { var camera = scene.camera; var ellipsoid = scene.mapProjection.ellipsoid; var path = createPath2D(camera, ellipsoid, Cartesian3.clone(camera.position), destination, duration); var orientations = createOrientations2D(camera, path, direction, up); var update = function(value) { var time = value.time; var orientation = orientations.evaluate(time); Matrix3.fromQuaternion(orientation, rotMatrix); Matrix4.clone(camera.transform, currentFrame); Matrix4.clone(Matrix4.IDENTITY, camera.transform); camera.position = path.evaluate(time, camera.position); camera.right = Matrix3.getRow(rotMatrix, 0, camera.right); camera.up = Matrix3.getRow(rotMatrix, 1, camera.up); camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction); camera.setTransform(currentFrame); }; return update; } function createUpdate2D(scene, destination, duration, direction, up) { var camera = scene.camera; var ellipsoid = scene.mapProjection.ellipsoid; var start = Cartesian3.clone(camera.position); start.z = camera.frustum.right - camera.frustum.left; var path = createPath2D(camera, ellipsoid, start, destination, duration); var orientations = createOrientations2D(camera, path, Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), up); var height = camera.position.z; var update = function(value) { var time = value.time; var orientation = orientations.evaluate(time); Matrix3.fromQuaternion(orientation, rotMatrix); camera.position = path.evaluate(time); var zoom = camera.position.z; camera.position.z = height; camera.right = Matrix3.getRow(rotMatrix, 0, camera.right); camera.up = Matrix3.getRow(rotMatrix, 1, camera.up); camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction); var frustum = camera.frustum; 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; }; return update; } var dirScratch = new Cartesian3(); var rightScratch = new Cartesian3(); var upScratch = new Cartesian3(); var scratchCartographic = new Cartographic(); var scratchDestination = new Cartesian3(); CameraFlightPath.createTween = function(scene, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var destination = options.destination; var direction = options.direction; var up = options.up; //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError('scene is required.'); } if (!defined(destination)) { throw new DeveloperError('destination is required.'); } if ((defined(direction) && !defined(up)) || (defined(up) && !defined(direction))) { throw new DeveloperError('If either direction or up is given, then both are required.'); } //>>includeEnd('debug'); if (scene.mode === SceneMode.MORPHING) { return { startObject : {}, stopObject: {}, duration : 0.0 }; } var convert = defaultValue(options.convert, true); if (convert && scene.mode !== SceneMode.SCENE3D) { var projection = scene.mapProjection; var ellipsoid = projection.ellipsoid; ellipsoid.cartesianToCartographic(destination, scratchCartographic); destination = projection.project(scratchCartographic, scratchDestination); } var duration = defaultValue(options.duration, 3.0); var controller = scene.screenSpaceCameraController; controller.enableInputs = false; var wrapCallback = function(cb) { var wrapped = function() { if (typeof cb === 'function') { cb(); } controller.enableInputs = true; }; return wrapped; }; var complete = wrapCallback(options.complete); var cancel = wrapCallback(options.cancel); var camera = scene.camera; var transform = options.endTransform; if (defined(transform)) { camera.setTransform(transform); } var frustum = camera.frustum; if (scene.mode === SceneMode.SCENE2D) { if (Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6) && (CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6))) { return { startObject : {}, stopObject: {}, duration : 0.0, complete : complete, cancel: cancel }; } } else if (Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON6)) { return { startObject : {}, stopObject: {}, duration : 0.0, complete : complete, cancel: cancel }; } if (duration <= 0.0) { var newOnComplete = function() { var position = destination; if (scene.mode === SceneMode.SCENE3D) { if (!defined(options.direction) && !defined(options.up)){ dirScratch = Cartesian3.normalize(Cartesian3.negate(position, dirScratch), dirScratch); rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, Cartesian3.UNIT_Z, rightScratch), rightScratch); } else { dirScratch = options.direction; rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, options.up, rightScratch), rightScratch); } upScratch = defaultValue(options.up, Cartesian3.cross(rightScratch, dirScratch, upScratch)); } else { if (!defined(options.direction) && !defined(options.up)){ dirScratch = Cartesian3.negate(Cartesian3.UNIT_Z, dirScratch); rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, Cartesian3.UNIT_Y, rightScratch), rightScratch); } else { dirScratch = options.direction; rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, options.up, rightScratch), rightScratch); } upScratch = defaultValue(options.up, Cartesian3.cross(rightScratch, dirScratch, upScratch)); } Cartesian3.clone(position, camera.position); Cartesian3.clone(dirScratch, camera.direction); Cartesian3.clone(upScratch, camera.up); Cartesian3.clone(rightScratch, camera.right); if (scene.mode === SceneMode.SCENE2D) { var zoom = 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 complete === 'function') { complete(); } }; return { startObject : {}, stopObject: {}, duration : 0.0, complete : newOnComplete, cancel: cancel }; } var update; if (scene.mode === SceneMode.SCENE3D) { update = createUpdate3D(scene, destination, duration, direction, up); } else if (scene.mode === SceneMode.SCENE2D) { update = createUpdate2D(scene, destination, duration, direction, up); } else { update = createUpdateCV(scene, destination, duration, direction, up); } return { duration : duration, easingFunction : EasingFunction.SINUSOIDAL_IN_OUT, startObject : { time : 0.0 }, stopObject : { time : duration }, update : update, complete : complete, cancel: cancel }; }; CameraFlightPath.createTweenRectangle = function(scene, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var rectangle = options.destination; //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError('scene is required.'); } if (!defined(rectangle)) { throw new DeveloperError('options.destination is required.'); } //>>includeEnd('debug'); var createAnimationoptions = clone(options); scene.camera.getRectangleCameraCoordinates(rectangle, c3destination); createAnimationoptions.destination = c3destination; createAnimationoptions.convert = false; return this.createTween(scene, createAnimationoptions); }; return CameraFlightPath; });