<commit_before>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/GeographicProjection',
        '../Core/IntersectionTests',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Quaternion',
        '../Core/Ray',
        './SceneMode',
        '../ThirdParty/Tween'
    ], function(
        defaultValue,
        defined,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        DeveloperError,
        Ellipsoid,
        GeographicProjection,
        IntersectionTests,
        CesiumMath,
        Matrix3,
        Matrix4,
        Quaternion,
        Ray,
        SceneMode,
        Tween) {
    "use strict";

    /**
     * Provides methods for common camera manipulations.
     *
     * @alias CameraController
     * @constructor
     *
     * @exception {DeveloperError} camera is required.
     */
    var CameraController = function(camera) {
        if (!defined(camera)) {
            throw new DeveloperError('camera is required.');
        }

        this._camera = camera;
        this._mode = SceneMode.SCENE3D;
        this._projection = new GeographicProjection();

        /**
         * The default amount to move the camera when an argument is not
         * provided to the move methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultMoveAmount = 100000.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the look methods.
         * @type {Number}
         * @default Math.PI / 60.0
         */
        this.defaultLookAmount = Math.PI / 60.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the rotate methods.
         * @type {Number}
         * @default Math.PI / 3600.0
         */
        this.defaultRotateAmount = Math.PI / 3600.0;
        /**
         * The default amount to move the camera when an argument is not
         * provided to the zoom methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultZoomAmount = 100000.0;
        /**
         * If set, the camera will not be able to rotate past this axis in either direction.
         * @type {Cartesian3}
         * @default undefined
         */
        this.constrainedAxis = undefined;
        /**
         * The factor multiplied by the the map size used to determine where to clamp the camera position
         * when translating across the surface. The default is 1.5. Only valid for 2D and Columbus view.
         * @type {Number}
         * @default 1.5
         */
        this.maximumTranslateFactor = 1.5;
        /**
         * The factor multiplied by the the map size used to determine where to clamp the camera position
         * when zooming out from the surface. The default is 2.5. Only valid for 2D.
         * @type {Number}
         * @default 2.5
         */
        this.maximumZoomFactor = 2.5;

        this._maxCoord = new Cartesian3();
        this._frustum = undefined;
    };

    var scratchUpdateCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
    /**
     * @private
     */
    CameraController.prototype.update = function(mode, scene2D) {
        var updateFrustum = false;
        if (mode !== this._mode) {
            this._mode = mode;
            updateFrustum = this._mode === SceneMode.SCENE2D;
        }

        var projection = scene2D.projection;
        if (defined(projection) && projection !== this._projection) {
            this._projection = projection;
            this._maxCoord = projection.project(scratchUpdateCartographic, this._maxCoord);
        }

        if (updateFrustum) {
            var frustum = this._frustum = this._camera.frustum.clone();
            if (!defined(frustum.left) || !defined(frustum.right) ||
               !defined(frustum.top) || !defined(frustum.bottom)) {
                throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
            }

            var maxZoomOut = 2.0;
            var ratio = frustum.top / frustum.right;
            frustum.right = this._maxCoord.x * maxZoomOut;
            frustum.left = -frustum.right;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }
    };

    function clampMove2D(controller, position) {
        var maxX = controller._maxCoord.x * controller.maximumTranslateFactor;
        if (position.x > maxX) {
            position.x = maxX;
        }
        if (position.x < -maxX) {
            position.x = -maxX;
        }

        var maxY = controller._maxCoord.y * controller.maximumTranslateFactor;
        if (position.y > maxY) {
            position.y = maxY;
        }
        if (position.y < -maxY) {
            position.y = -maxY;
        }
    }

    var moveScratch = new Cartesian3();
    /**
     * Translates the camera's position by <code>amount</code> along <code>direction</code>.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} direction The direction to move.
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @exception {DeveloperError} direction is required.
     *
     * @see CameraController#moveBackward
     * @see CameraController#moveForward
     * @see CameraController#moveLeft
     * @see CameraController#moveRight
     * @see CameraController#moveUp
     * @see CameraController#moveDown
     */
    CameraController.prototype.move = function(direction, amount) {
        if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }

        var cameraPosition = this._camera.position;
        Cartesian3.multiplyByScalar(direction, amount, moveScratch);
        Cartesian3.add(cameraPosition, moveScratch, cameraPosition);

        if (this._mode === SceneMode.SCENE2D) {
            clampMove2D(this, cameraPosition);
        }
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveBackward
     */
    CameraController.prototype.moveForward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.direction, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveForward
     */
    CameraController.prototype.moveBackward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.direction, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveDown
     */
    CameraController.prototype.moveUp = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.up, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveUp
     */
    CameraController.prototype.moveDown = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.up, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveLeft
     */
    CameraController.prototype.moveRight = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.right, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveRight
     */
    CameraController.prototype.moveLeft = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.right, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the opposite direction
     * of its right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookRight
     */
    CameraController.prototype.lookLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.up, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the direction
     * of its right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookLeft
     */
    CameraController.prototype.lookRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.up, amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the direction
     * of its up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookDown
     */
    CameraController.prototype.lookUp = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.right, -amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the opposite direction
     * of its up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookUp
     */
    CameraController.prototype.lookDown = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.right, amount);
    };

    var lookScratchQuaternion = new Quaternion();
    var lookScratchMatrix = new Matrix3();
    /**
     * Rotate each of the camera's orientation vectors around <code>axis</code> by <code>angle</code>
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} axis The axis to rotate around.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @exception {DeveloperError} axis is required.
     *
     * @see CameraController#lookUp
     * @see CameraController#lookDown
     * @see CameraController#lookLeft
     * @see CameraController#lookRight
     */
    CameraController.prototype.look = function(axis, angle) {
        if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }

        var turnAngle = defaultValue(angle, this.defaultLookAmount);
        var rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, turnAngle, lookScratchQuaternion), lookScratchMatrix);

        var direction = this._camera.direction;
        var up = this._camera.up;
        var right = this._camera.right;

        Matrix3.multiplyByVector(rotation, direction, direction);
        Matrix3.multiplyByVector(rotation, up, up);
        Matrix3.multiplyByVector(rotation, right, right);
    };

    /**
     * Rotate the camera counter-clockwise around its direction vector by amount, in radians.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#twistRight
     */
    CameraController.prototype.twistLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.direction, amount);
    };

    /**
     * Rotate the camera clockwise around its direction vector by amount, in radians.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#twistLeft
     */
    CameraController.prototype.twistRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.direction, -amount);
    };

    var appendTransformPosition = Cartesian4.UNIT_W.clone();
    var appendTransformUp = Cartesian4.ZERO.clone();
    var appendTransformRight = Cartesian4.ZERO.clone();
    var appendTransformDirection = Cartesian4.ZERO.clone();
    function appendTransform(controller, transform) {
        var camera = controller._camera;
        var oldTransform;
        if (defined(transform)) {
            var position = Cartesian3.clone(camera.getPositionWC(), appendTransformPosition);
            var up = Cartesian3.clone(camera.getUpWC(), appendTransformUp);
            var right = Cartesian3.clone(camera.getRightWC(), appendTransformRight);
            var direction = Cartesian3.clone(camera.getDirectionWC(), appendTransformDirection);

            oldTransform = camera.transform;
            camera.transform = transform.multiply(oldTransform);

            var invTransform = camera.getInverseTransform();
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, position, position), camera.position);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, up, up), camera.up);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, right, right), camera.right);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, direction, direction), camera.direction);
        }
        return oldTransform;
    }

    var revertTransformPosition = Cartesian4.UNIT_W.clone();
    var revertTransformUp = Cartesian4.ZERO.clone();
    var revertTransformRight = Cartesian4.ZERO.clone();
    var revertTransformDirection = Cartesian4.ZERO.clone();
    function revertTransform(controller, transform) {
        if (defined(transform)) {
            var camera = controller._camera;
            var position = Cartesian3.clone(camera.getPositionWC(), revertTransformPosition);
            var up = Cartesian3.clone(camera.getUpWC(), revertTransformUp);
            var right = Cartesian3.clone(camera.getRightWC(), revertTransformRight);
            var direction = Cartesian3.clone(camera.getDirectionWC(), revertTransformDirection);

            camera.transform = transform;
            transform = camera.getInverseTransform();

            position = Cartesian3.clone(Matrix4.multiplyByVector(transform, position, position), camera.position);
            up = Cartesian3.clone(Matrix4.multiplyByVector(transform, up, up), camera.up);
            right = Cartesian3.clone(Matrix4.multiplyByVector(transform, right, right), camera.right);
            direction = Cartesian3.clone(Matrix4.multiplyByVector(transform, direction, direction), camera.direction);
        }
    }

    var rotateScratchQuaternion = new Quaternion();
    var rotateScratchMatrix = new Matrix3();
    /**
     * Rotates the camera around <code>axis</code> by <code>angle</code>. The distance
     * of the camera's position to the center of the camera's reference frame remains the same.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} axis The axis to rotate around given in world coordinates.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @exception {DeveloperError} axis is required.
     *
     * @see CameraController#rotateUp
     * @see CameraController#rotateDown
     * @see CameraController#rotateLeft
     * @see CameraController#rotateRight
     *
     * @example
     * // Rotate about a point on the earth.
     * var center = ellipsoid.cartographicToCartesian(cartographic);
     * var transform = Matrix4.fromTranslation(center);
     * controller.rotate(axis, angle, transform);
    */
    CameraController.prototype.rotate = function(axis, angle, transform) {
        if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }

        var camera = this._camera;

        var turnAngle = defaultValue(angle, this.defaultRotateAmount);
        var rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, turnAngle, rotateScratchQuaternion), rotateScratchMatrix);

        var oldTransform = appendTransform(this, transform);
        Matrix3.multiplyByVector(rotation, camera.position, camera.position);
        Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
        Matrix3.multiplyByVector(rotation, camera.up, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);
        Cartesian3.cross(camera.right, camera.direction, camera.up);
        revertTransform(this, oldTransform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle downwards.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateUp
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateDown = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, angle, transform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle upwards.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateDown
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateUp = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, -angle, transform);
    };

    var rotateVertScratchP = new Cartesian3();
    var rotateVertScratchA = new Cartesian3();
    var rotateVertScratchTan = new Cartesian3();
    function rotateVertical(controller, angle, transform) {
        var camera = controller._camera;
        var oldTransform = appendTransform(controller, transform);

        var position = camera.position;
        var p = Cartesian3.normalize(position, rotateVertScratchP);
        if (defined(controller.constrainedAxis)) {
            var northParallel = p.equalsEpsilon(controller.constrainedAxis, CesiumMath.EPSILON2);
            var southParallel = p.equalsEpsilon(controller.constrainedAxis.negate(), CesiumMath.EPSILON2);
            if ((!northParallel && !southParallel)) {
                var constrainedAxis = Cartesian3.normalize(controller.constrainedAxis, rotateVertScratchA);

                var dot = p.dot(constrainedAxis);
                var angleToAxis = Math.acos(dot);
                if (angle > 0 && angle > angleToAxis) {
                    angle = angleToAxis;
                }

                dot = p.dot(constrainedAxis.negate());
                angleToAxis = Math.acos(dot);
                if (angle < 0 && -angle > angleToAxis) {
                    angle = -angleToAxis;
                }

                var tangent = Cartesian3.cross(constrainedAxis, p, rotateVertScratchTan);
                controller.rotate(tangent, angle);
            } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
                controller.rotate(camera.right, angle);
            }
        } else {
            controller.rotate(camera.right, angle);
        }

        revertTransform(controller, oldTransform);
    }

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the right.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateLeft
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateRight = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, -angle, transform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the left.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateRight
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateLeft = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, angle, transform);
    };

    function rotateHorizontal(controller, angle, transform) {
        if (defined(controller.constrainedAxis)) {
            controller.rotate(controller.constrainedAxis, angle, transform);
        } else {
            controller.rotate(controller._camera.up, angle, transform);
        }
    }

    function zoom2D(controller, amount) {
        var frustum = controller._camera.frustum;

        if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) {
            throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
        }

        amount = amount * 0.5;
        var newRight = frustum.right - amount;
        var newLeft = frustum.left + amount;

        var maxRight = controller._maxCoord.x * controller.maximumZoomFactor;
        if (newRight > maxRight) {
            newRight = maxRight;
            newLeft = -maxRight;
        }

        var ratio = frustum.top / frustum.right;
        frustum.right = newRight;
        frustum.left = newLeft;
        frustum.top = frustum.right * ratio;
        frustum.bottom = -frustum.top;
    }

    function zoom3D(controller, amount) {
        var camera = controller._camera;
        controller.move(camera.direction, amount);
    }

    /**
     * Zooms <code>amount</code> along the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see CameraController#zoomOut
     */
    CameraController.prototype.zoomIn = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, amount);
        } else {
            zoom3D(this, amount);
        }
    };

    /**
     * Zooms <code>amount</code> along the opposite direction of
     * the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see CameraController#zoomIn
     */
    CameraController.prototype.zoomOut = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, -amount);
        } else {
            zoom3D(this, -amount);
        }
    };

    /**
     * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and
     * Columbus view, this is the distance to the map.
     * @memberof CameraController
     * @returns {Number} The magnitude of the position.
     */
    CameraController.prototype.getMagnitude = function() {
        var camera = this._camera;
        if (this._mode === SceneMode.SCENE3D) {
            return camera.position.magnitude();
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return Math.abs(camera.position.z);
        } else if (this._mode === SceneMode.SCENE2D) {
            return  Math.max(camera.frustum.right - camera.frustum.left, camera.frustum.top - camera.frustum.bottom);
        }
    };

    function setPositionCartographic2D(controller, cartographic) {
        var camera = controller._camera;
        var newLeft = -cartographic.height * 0.5;
        var newRight = -newLeft;

        var frustum = camera.frustum;
        if (newRight > newLeft) {
            var ratio = frustum.top / frustum.right;
            frustum.right = newRight;
            frustum.left = newLeft;
            frustum.top = frustum.right * ratio;
            frustum.bottom = -frustum.top;
        }

        //We use Cartesian2 instead of 3 here because Z must be constant in 2D mode.
        Cartesian2.clone(controller._projection.project(cartographic), camera.position);
        Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction);
        Cartesian3.clone(Cartesian3.UNIT_Y, camera.up);
        Cartesian3.clone(Cartesian3.UNIT_X, camera.right);
    }

    function setPositionCartographicCV(controller, cartographic) {
        var camera = controller._camera;
        var projection = controller._projection;
        camera.position = projection.project(cartographic);
        Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction);
        Cartesian3.clone(Cartesian3.UNIT_Y, camera.up);
        Cartesian3.clone(Cartesian3.UNIT_X, camera.right);
    }

    function setPositionCartographic3D(controller, cartographic) {
        var camera = controller._camera;
        var ellipsoid = controller._projection.getEllipsoid();

        ellipsoid.cartographicToCartesian(cartographic, camera.position);
        Cartesian3.negate(camera.position, camera.direction);
        Cartesian3.normalize(camera.direction, camera.direction);
        Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, camera.right);
        Cartesian3.cross(camera.right, camera.direction, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);
    }

    /**
     * Moves the camera to the provided cartographic position.
     * @memberof CameraController
     *
     * @param {Cartographic} cartographic The new camera position.
     *
     * @exception {DeveloperError} cartographic is required.
     */
    CameraController.prototype.setPositionCartographic = function(cartographic) {
        if (!defined(cartographic)) {
            throw new DeveloperError('cartographic is required.');
        }

        if (this._mode === SceneMode.SCENE2D) {
            setPositionCartographic2D(this, cartographic);
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            setPositionCartographicCV(this, cartographic);
        } else if (this._mode === SceneMode.SCENE3D) {
            setPositionCartographic3D(this, cartographic);
        }
    };

    function getHeading2D(controller) {
        var camera = controller._camera;
        return Math.atan2(camera.right.y, camera.right.x);
    }

    var scratchHeadingCartesian4 = new Cartesian4();

    function getHeading3D(controller) {
        var camera = controller._camera;
        var z = Matrix4.multiplyByVector(camera.getViewMatrix(), Cartesian4.UNIT_Z, scratchHeadingCartesian4);
        return CesiumMath.PI_OVER_TWO - Math.atan2(z.y, z.x);
    }

    /**
     * Gets the camera heading.
     * @memberof CameraController
     *
     * @returns {Number} The camera heading in radians.
     */
    CameraController.prototype.getHeading = function() {
        if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) {
            return getHeading2D(this);
        } else if (this._mode === SceneMode.SCENE3D) {
            return getHeading3D(this);
        }

        return undefined;
    };

    function setHeading2D(controller, angle) {
        var rightAngle = getHeading2D(controller);
        angle = rightAngle - angle;
        controller.look(Cartesian3.UNIT_Z, angle);
    }

    var scratchHeadingAxis = new Cartesian3();

    function setHeading3D(controller, angle) {
        var camera = controller._camera;

        var axis = Cartesian3.normalize(camera.position, scratchHeadingAxis);
        var upAngle = getHeading3D(controller);
        angle = upAngle - angle;
        controller.look(axis, angle);
    }

    /**
     * Sets the camera heading.
     * @memberof CameraController
     *
     * @param {Number} angle The heading in radians.
     *
     * @exception {DeveloperError} angle is required.
     */
    CameraController.prototype.setHeading = function(angle) {
        if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }

        if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) {
            setHeading2D(this, angle);
        } else if (this._mode === SceneMode.SCENE3D) {
            setHeading3D(this, angle);
        }
    };

    function getTiltCV(controller) {
        var camera = controller._camera;

        // Math.acos(dot(camera.direction, Cartesian3.UNIT_Z.negate())
        return CesiumMath.PI_OVER_TWO - Math.acos(-camera.direction.z);
    }

    var scratchTiltCartesian3 = new Cartesian3();

    function getTilt3D(controller) {
        var camera = controller._camera;

        var direction = Cartesian3.normalize(camera.position, scratchTiltCartesian3);
        Cartesian3.negate(direction, direction);

        return CesiumMath.PI_OVER_TWO - Math.acos(Cartesian3.dot(camera.direction, direction));
    }

    /**
     * Gets the camera tilt.
     * @memberof CameraController
     *
     * @returns {Number} The camera tilt in radians.
     */
    CameraController.prototype.getTilt = function() {
        if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return getTiltCV(this);
        } else if (this._mode === SceneMode.SCENE3D) {
            return getTilt3D(this);
        }

        return undefined;
    };

    /**
     * Sets the camera tilt.
     * @memberof CameraController
     *
     * @param {Number} angle The tilt in radians.
     *
     * @exception {DeveloperError} angle is required.
     */
    CameraController.prototype.setTilt = function(angle) {
        if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }

        if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) {
            var camera = this._camera;

            angle = CesiumMath.clamp(angle, 0.0, CesiumMath.PI_OVER_TWO);

            var tilt = this.getTilt();
            angle = angle - tilt;

            /*
            if (tilt + angle > CesiumMath.PI_OVER_TWO) {
                angle = tilt - CesiumMath.PI_OVER_TWO;
            } else if (tilt + angle < 0.0) {
                angle = -tilt;
            }
            */

            this.look(camera.right, angle);
        }
    };

    /**
     * Sets the camera position and orientation with an eye position, target, and up vector.
     * This method is not supported in 2D mode because there is only one direction to look.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} eye The position of the camera.
     * @param {Cartesian3} target The position to look at.
     * @param {Cartesian3} up The up vector.
     *
     * @exception {DeveloperError} eye is required.
     * @exception {DeveloperError} target is required.
     * @exception {DeveloperError} up is required.
     * @exception {DeveloperError} lookAt is not supported in 2D mode because there is only one direction to look.
     * @exception {DeveloperError} lookAt is not supported while morphing.
     */
    CameraController.prototype.lookAt = function(eye, target, up) {
        if (!defined(eye)) {
            throw new DeveloperError('eye is required');
        }
        if (!defined(target)) {
            throw new DeveloperError('target is required');
        }
        if (!defined(up)) {
            throw new DeveloperError('up is required');
        }<commit_msg>Remove commented out code.<commit_after>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/GeographicProjection',
        '../Core/IntersectionTests',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Quaternion',
        '../Core/Ray',
        './SceneMode',
        '../ThirdParty/Tween'
    ], function(
        defaultValue,
        defined,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        DeveloperError,
        Ellipsoid,
        GeographicProjection,
        IntersectionTests,
        CesiumMath,
        Matrix3,
        Matrix4,
        Quaternion,
        Ray,
        SceneMode,
        Tween) {
    "use strict";

    /**
     * Provides methods for common camera manipulations.
     *
     * @alias CameraController
     * @constructor
     *
     * @exception {DeveloperError} camera is required.
     */
    var CameraController = function(camera) {
        if (!defined(camera)) {
            throw new DeveloperError('camera is required.');
        }

        this._camera = camera;
        this._mode = SceneMode.SCENE3D;
        this._projection = new GeographicProjection();

        /**
         * The default amount to move the camera when an argument is not
         * provided to the move methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultMoveAmount = 100000.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the look methods.
         * @type {Number}
         * @default Math.PI / 60.0
         */
        this.defaultLookAmount = Math.PI / 60.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the rotate methods.
         * @type {Number}
         * @default Math.PI / 3600.0
         */
        this.defaultRotateAmount = Math.PI / 3600.0;
        /**
         * The default amount to move the camera when an argument is not
         * provided to the zoom methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultZoomAmount = 100000.0;
        /**
         * If set, the camera will not be able to rotate past this axis in either direction.
         * @type {Cartesian3}
         * @default undefined
         */
        this.constrainedAxis = undefined;
        /**
         * The factor multiplied by the the map size used to determine where to clamp the camera position
         * when translating across the surface. The default is 1.5. Only valid for 2D and Columbus view.
         * @type {Number}
         * @default 1.5
         */
        this.maximumTranslateFactor = 1.5;
        /**
         * The factor multiplied by the the map size used to determine where to clamp the camera position
         * when zooming out from the surface. The default is 2.5. Only valid for 2D.
         * @type {Number}
         * @default 2.5
         */
        this.maximumZoomFactor = 2.5;

        this._maxCoord = new Cartesian3();
        this._frustum = undefined;
    };

    var scratchUpdateCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
    /**
     * @private
     */
    CameraController.prototype.update = function(mode, scene2D) {
        var updateFrustum = false;
        if (mode !== this._mode) {
            this._mode = mode;
            updateFrustum = this._mode === SceneMode.SCENE2D;
        }

        var projection = scene2D.projection;
        if (defined(projection) && projection !== this._projection) {
            this._projection = projection;
            this._maxCoord = projection.project(scratchUpdateCartographic, this._maxCoord);
        }

        if (updateFrustum) {
            var frustum = this._frustum = this._camera.frustum.clone();
            if (!defined(frustum.left) || !defined(frustum.right) ||
               !defined(frustum.top) || !defined(frustum.bottom)) {
                throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
            }

            var maxZoomOut = 2.0;
            var ratio = frustum.top / frustum.right;
            frustum.right = this._maxCoord.x * maxZoomOut;
            frustum.left = -frustum.right;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }
    };

    function clampMove2D(controller, position) {
        var maxX = controller._maxCoord.x * controller.maximumTranslateFactor;
        if (position.x > maxX) {
            position.x = maxX;
        }
        if (position.x < -maxX) {
            position.x = -maxX;
        }

        var maxY = controller._maxCoord.y * controller.maximumTranslateFactor;
        if (position.y > maxY) {
            position.y = maxY;
        }
        if (position.y < -maxY) {
            position.y = -maxY;
        }
    }

    var moveScratch = new Cartesian3();
    /**
     * Translates the camera's position by <code>amount</code> along <code>direction</code>.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} direction The direction to move.
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @exception {DeveloperError} direction is required.
     *
     * @see CameraController#moveBackward
     * @see CameraController#moveForward
     * @see CameraController#moveLeft
     * @see CameraController#moveRight
     * @see CameraController#moveUp
     * @see CameraController#moveDown
     */
    CameraController.prototype.move = function(direction, amount) {
        if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }

        var cameraPosition = this._camera.position;
        Cartesian3.multiplyByScalar(direction, amount, moveScratch);
        Cartesian3.add(cameraPosition, moveScratch, cameraPosition);

        if (this._mode === SceneMode.SCENE2D) {
            clampMove2D(this, cameraPosition);
        }
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveBackward
     */
    CameraController.prototype.moveForward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.direction, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveForward
     */
    CameraController.prototype.moveBackward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.direction, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveDown
     */
    CameraController.prototype.moveUp = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.up, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveUp
     */
    CameraController.prototype.moveDown = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.up, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveLeft
     */
    CameraController.prototype.moveRight = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.right, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see CameraController#moveRight
     */
    CameraController.prototype.moveLeft = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this._camera.right, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the opposite direction
     * of its right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookRight
     */
    CameraController.prototype.lookLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.up, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the direction
     * of its right vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookLeft
     */
    CameraController.prototype.lookRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.up, amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the direction
     * of its up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookDown
     */
    CameraController.prototype.lookUp = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.right, -amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the opposite direction
     * of its up vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#lookUp
     */
    CameraController.prototype.lookDown = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.right, amount);
    };

    var lookScratchQuaternion = new Quaternion();
    var lookScratchMatrix = new Matrix3();
    /**
     * Rotate each of the camera's orientation vectors around <code>axis</code> by <code>angle</code>
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} axis The axis to rotate around.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @exception {DeveloperError} axis is required.
     *
     * @see CameraController#lookUp
     * @see CameraController#lookDown
     * @see CameraController#lookLeft
     * @see CameraController#lookRight
     */
    CameraController.prototype.look = function(axis, angle) {
        if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }

        var turnAngle = defaultValue(angle, this.defaultLookAmount);
        var rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, turnAngle, lookScratchQuaternion), lookScratchMatrix);

        var direction = this._camera.direction;
        var up = this._camera.up;
        var right = this._camera.right;

        Matrix3.multiplyByVector(rotation, direction, direction);
        Matrix3.multiplyByVector(rotation, up, up);
        Matrix3.multiplyByVector(rotation, right, right);
    };

    /**
     * Rotate the camera counter-clockwise around its direction vector by amount, in radians.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#twistRight
     */
    CameraController.prototype.twistLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.direction, amount);
    };

    /**
     * Rotate the camera clockwise around its direction vector by amount, in radians.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see CameraController#twistLeft
     */
    CameraController.prototype.twistRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this._camera.direction, -amount);
    };

    var appendTransformPosition = Cartesian4.UNIT_W.clone();
    var appendTransformUp = Cartesian4.ZERO.clone();
    var appendTransformRight = Cartesian4.ZERO.clone();
    var appendTransformDirection = Cartesian4.ZERO.clone();
    function appendTransform(controller, transform) {
        var camera = controller._camera;
        var oldTransform;
        if (defined(transform)) {
            var position = Cartesian3.clone(camera.getPositionWC(), appendTransformPosition);
            var up = Cartesian3.clone(camera.getUpWC(), appendTransformUp);
            var right = Cartesian3.clone(camera.getRightWC(), appendTransformRight);
            var direction = Cartesian3.clone(camera.getDirectionWC(), appendTransformDirection);

            oldTransform = camera.transform;
            camera.transform = transform.multiply(oldTransform);

            var invTransform = camera.getInverseTransform();
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, position, position), camera.position);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, up, up), camera.up);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, right, right), camera.right);
            Cartesian3.clone(Matrix4.multiplyByVector(invTransform, direction, direction), camera.direction);
        }
        return oldTransform;
    }

    var revertTransformPosition = Cartesian4.UNIT_W.clone();
    var revertTransformUp = Cartesian4.ZERO.clone();
    var revertTransformRight = Cartesian4.ZERO.clone();
    var revertTransformDirection = Cartesian4.ZERO.clone();
    function revertTransform(controller, transform) {
        if (defined(transform)) {
            var camera = controller._camera;
            var position = Cartesian3.clone(camera.getPositionWC(), revertTransformPosition);
            var up = Cartesian3.clone(camera.getUpWC(), revertTransformUp);
            var right = Cartesian3.clone(camera.getRightWC(), revertTransformRight);
            var direction = Cartesian3.clone(camera.getDirectionWC(), revertTransformDirection);

            camera.transform = transform;
            transform = camera.getInverseTransform();

            position = Cartesian3.clone(Matrix4.multiplyByVector(transform, position, position), camera.position);
            up = Cartesian3.clone(Matrix4.multiplyByVector(transform, up, up), camera.up);
            right = Cartesian3.clone(Matrix4.multiplyByVector(transform, right, right), camera.right);
            direction = Cartesian3.clone(Matrix4.multiplyByVector(transform, direction, direction), camera.direction);
        }
    }

    var rotateScratchQuaternion = new Quaternion();
    var rotateScratchMatrix = new Matrix3();
    /**
     * Rotates the camera around <code>axis</code> by <code>angle</code>. The distance
     * of the camera's position to the center of the camera's reference frame remains the same.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} axis The axis to rotate around given in world coordinates.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @exception {DeveloperError} axis is required.
     *
     * @see CameraController#rotateUp
     * @see CameraController#rotateDown
     * @see CameraController#rotateLeft
     * @see CameraController#rotateRight
     *
     * @example
     * // Rotate about a point on the earth.
     * var center = ellipsoid.cartographicToCartesian(cartographic);
     * var transform = Matrix4.fromTranslation(center);
     * controller.rotate(axis, angle, transform);
    */
    CameraController.prototype.rotate = function(axis, angle, transform) {
        if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }

        var camera = this._camera;

        var turnAngle = defaultValue(angle, this.defaultRotateAmount);
        var rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, turnAngle, rotateScratchQuaternion), rotateScratchMatrix);

        var oldTransform = appendTransform(this, transform);
        Matrix3.multiplyByVector(rotation, camera.position, camera.position);
        Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
        Matrix3.multiplyByVector(rotation, camera.up, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);
        Cartesian3.cross(camera.right, camera.direction, camera.up);
        revertTransform(this, oldTransform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle downwards.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateUp
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateDown = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, angle, transform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle upwards.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateDown
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateUp = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, -angle, transform);
    };

    var rotateVertScratchP = new Cartesian3();
    var rotateVertScratchA = new Cartesian3();
    var rotateVertScratchTan = new Cartesian3();
    function rotateVertical(controller, angle, transform) {
        var camera = controller._camera;
        var oldTransform = appendTransform(controller, transform);

        var position = camera.position;
        var p = Cartesian3.normalize(position, rotateVertScratchP);
        if (defined(controller.constrainedAxis)) {
            var northParallel = p.equalsEpsilon(controller.constrainedAxis, CesiumMath.EPSILON2);
            var southParallel = p.equalsEpsilon(controller.constrainedAxis.negate(), CesiumMath.EPSILON2);
            if ((!northParallel && !southParallel)) {
                var constrainedAxis = Cartesian3.normalize(controller.constrainedAxis, rotateVertScratchA);

                var dot = p.dot(constrainedAxis);
                var angleToAxis = Math.acos(dot);
                if (angle > 0 && angle > angleToAxis) {
                    angle = angleToAxis;
                }

                dot = p.dot(constrainedAxis.negate());
                angleToAxis = Math.acos(dot);
                if (angle < 0 && -angle > angleToAxis) {
                    angle = -angleToAxis;
                }

                var tangent = Cartesian3.cross(constrainedAxis, p, rotateVertScratchTan);
                controller.rotate(tangent, angle);
            } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
                controller.rotate(camera.right, angle);
            }
        } else {
            controller.rotate(camera.right, angle);
        }

        revertTransform(controller, oldTransform);
    }

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the right.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateLeft
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateRight = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, -angle, transform);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the left.
     *
     * @memberof CameraController
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     * @param {Matrix4} [transform] A transform to append to the camera transform before the rotation. Does not alter the camera's transform.
     *
     * @see CameraController#rotateRight
     * @see CameraController#rotate
     */
    CameraController.prototype.rotateLeft = function(angle, transform) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, angle, transform);
    };

    function rotateHorizontal(controller, angle, transform) {
        if (defined(controller.constrainedAxis)) {
            controller.rotate(controller.constrainedAxis, angle, transform);
        } else {
            controller.rotate(controller._camera.up, angle, transform);
        }
    }

    function zoom2D(controller, amount) {
        var frustum = controller._camera.frustum;

        if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) {
            throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
        }

        amount = amount * 0.5;
        var newRight = frustum.right - amount;
        var newLeft = frustum.left + amount;

        var maxRight = controller._maxCoord.x * controller.maximumZoomFactor;
        if (newRight > maxRight) {
            newRight = maxRight;
            newLeft = -maxRight;
        }

        var ratio = frustum.top / frustum.right;
        frustum.right = newRight;
        frustum.left = newLeft;
        frustum.top = frustum.right * ratio;
        frustum.bottom = -frustum.top;
    }

    function zoom3D(controller, amount) {
        var camera = controller._camera;
        controller.move(camera.direction, amount);
    }

    /**
     * Zooms <code>amount</code> along the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see CameraController#zoomOut
     */
    CameraController.prototype.zoomIn = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, amount);
        } else {
            zoom3D(this, amount);
        }
    };

    /**
     * Zooms <code>amount</code> along the opposite direction of
     * the camera's view vector.
     *
     * @memberof CameraController
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see CameraController#zoomIn
     */
    CameraController.prototype.zoomOut = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, -amount);
        } else {
            zoom3D(this, -amount);
        }
    };

    /**
     * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and
     * Columbus view, this is the distance to the map.
     * @memberof CameraController
     * @returns {Number} The magnitude of the position.
     */
    CameraController.prototype.getMagnitude = function() {
        var camera = this._camera;
        if (this._mode === SceneMode.SCENE3D) {
            return camera.position.magnitude();
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return Math.abs(camera.position.z);
        } else if (this._mode === SceneMode.SCENE2D) {
            return  Math.max(camera.frustum.right - camera.frustum.left, camera.frustum.top - camera.frustum.bottom);
        }
    };

    function setPositionCartographic2D(controller, cartographic) {
        var camera = controller._camera;
        var newLeft = -cartographic.height * 0.5;
        var newRight = -newLeft;

        var frustum = camera.frustum;
        if (newRight > newLeft) {
            var ratio = frustum.top / frustum.right;
            frustum.right = newRight;
            frustum.left = newLeft;
            frustum.top = frustum.right * ratio;
            frustum.bottom = -frustum.top;
        }

        //We use Cartesian2 instead of 3 here because Z must be constant in 2D mode.
        Cartesian2.clone(controller._projection.project(cartographic), camera.position);
        Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction);
        Cartesian3.clone(Cartesian3.UNIT_Y, camera.up);
        Cartesian3.clone(Cartesian3.UNIT_X, camera.right);
    }

    function setPositionCartographicCV(controller, cartographic) {
        var camera = controller._camera;
        var projection = controller._projection;
        camera.position = projection.project(cartographic);
        Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction);
        Cartesian3.clone(Cartesian3.UNIT_Y, camera.up);
        Cartesian3.clone(Cartesian3.UNIT_X, camera.right);
    }

    function setPositionCartographic3D(controller, cartographic) {
        var camera = controller._camera;
        var ellipsoid = controller._projection.getEllipsoid();

        ellipsoid.cartographicToCartesian(cartographic, camera.position);
        Cartesian3.negate(camera.position, camera.direction);
        Cartesian3.normalize(camera.direction, camera.direction);
        Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, camera.right);
        Cartesian3.cross(camera.right, camera.direction, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);
    }

    /**
     * Moves the camera to the provided cartographic position.
     * @memberof CameraController
     *
     * @param {Cartographic} cartographic The new camera position.
     *
     * @exception {DeveloperError} cartographic is required.
     */
    CameraController.prototype.setPositionCartographic = function(cartographic) {
        if (!defined(cartographic)) {
            throw new DeveloperError('cartographic is required.');
        }

        if (this._mode === SceneMode.SCENE2D) {
            setPositionCartographic2D(this, cartographic);
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            setPositionCartographicCV(this, cartographic);
        } else if (this._mode === SceneMode.SCENE3D) {
            setPositionCartographic3D(this, cartographic);
        }
    };

    function getHeading2D(controller) {
        var camera = controller._camera;
        return Math.atan2(camera.right.y, camera.right.x);
    }

    var scratchHeadingCartesian4 = new Cartesian4();

    function getHeading3D(controller) {
        var camera = controller._camera;
        var z = Matrix4.multiplyByVector(camera.getViewMatrix(), Cartesian4.UNIT_Z, scratchHeadingCartesian4);
        return CesiumMath.PI_OVER_TWO - Math.atan2(z.y, z.x);
    }

    /**
     * Gets the camera heading.
     * @memberof CameraController
     *
     * @returns {Number} The camera heading in radians.
     */
    CameraController.prototype.getHeading = function() {
        if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) {
            return getHeading2D(this);
        } else if (this._mode === SceneMode.SCENE3D) {
            return getHeading3D(this);
        }

        return undefined;
    };

    function setHeading2D(controller, angle) {
        var rightAngle = getHeading2D(controller);
        angle = rightAngle - angle;
        controller.look(Cartesian3.UNIT_Z, angle);
    }

    var scratchHeadingAxis = new Cartesian3();

    function setHeading3D(controller, angle) {
        var camera = controller._camera;

        var axis = Cartesian3.normalize(camera.position, scratchHeadingAxis);
        var upAngle = getHeading3D(controller);
        angle = upAngle - angle;
        controller.look(axis, angle);
    }

    /**
     * Sets the camera heading.
     * @memberof CameraController
     *
     * @param {Number} angle The heading in radians.
     *
     * @exception {DeveloperError} angle is required.
     */
    CameraController.prototype.setHeading = function(angle) {
        if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }

        if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) {
            setHeading2D(this, angle);
        } else if (this._mode === SceneMode.SCENE3D) {
            setHeading3D(this, angle);
        }
    };

    function getTiltCV(controller) {
        var camera = controller._camera;

        // Math.acos(dot(camera.direction, Cartesian3.UNIT_Z.negate())
        return CesiumMath.PI_OVER_TWO - Math.acos(-camera.direction.z);
    }

    var scratchTiltCartesian3 = new Cartesian3();

    function getTilt3D(controller) {
        var camera = controller._camera;

        var direction = Cartesian3.normalize(camera.position, scratchTiltCartesian3);
        Cartesian3.negate(direction, direction);

        return CesiumMath.PI_OVER_TWO - Math.acos(Cartesian3.dot(camera.direction, direction));
    }

    /**
     * Gets the camera tilt.
     * @memberof CameraController
     *
     * @returns {Number} The camera tilt in radians.
     */
    CameraController.prototype.getTilt = function() {
        if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return getTiltCV(this);
        } else if (this._mode === SceneMode.SCENE3D) {
            return getTilt3D(this);
        }

        return undefined;
    };

    /**
     * Sets the camera tilt.
     * @memberof CameraController
     *
     * @param {Number} angle The tilt in radians.
     *
     * @exception {DeveloperError} angle is required.
     */
    CameraController.prototype.setTilt = function(angle) {
        if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }

        if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) {
            var camera = this._camera;

            angle = CesiumMath.clamp(angle, 0.0, CesiumMath.PI_OVER_TWO);

            var tilt = this.getTilt();
            angle = angle - tilt;

            this.look(camera.right, angle);
        }
    };

    /**
     * Sets the camera position and orientation with an eye position, target, and up vector.
     * This method is not supported in 2D mode because there is only one direction to look.
     *
     * @memberof CameraController
     *
     * @param {Cartesian3} eye The position of the camera.
     * @param {Cartesian3} target The position to look at.
     * @param {Cartesian3} up The up vector.
     *
     * @exception {DeveloperError} eye is required.
     * @exception {DeveloperError} target is required.
     * @exception {DeveloperError} up is required.
     * @exception {DeveloperError} lookAt is not supported in 2D mode because there is only one direction to look.
     * @exception {DeveloperError} lookAt is not supported while morphing.
     */
    CameraController.prototype.lookAt = function(eye, target, up) {
        if (!defined(eye)) {
            throw new DeveloperError('eye is required');
        }
        if (!defined(target)) {
            throw new DeveloperError('target is required');
        }
        if (!defined(up)) {
            throw new DeveloperError('up is required');
        }
{'fraction_non_alphanumeric': 0.10442897348540957, 'fraction_numerical': 0.006991556806399052, 'mean_word_length': 2.4685059597205097, 'pattern_counts': {'":': 0, '<': 152, '<?xml version=': 0, '>': 160, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 52, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5808969', 'n_tokens_mistral': 19109, 'n_tokens_neox': 18419, 'n_words': 4679}