import { BoundingSphere } from "../../Source/Cesium.js";
import { Cartesian2 } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Cartesian4 } from "../../Source/Cesium.js";
import { Cartographic } from "../../Source/Cesium.js";
import { defaultValue } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { GeographicProjection } from "../../Source/Cesium.js";
import { HeadingPitchRange } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { Matrix3 } from "../../Source/Cesium.js";
import { Matrix4 } from "../../Source/Cesium.js";
import { OrthographicFrustum } from "../../Source/Cesium.js";
import { OrthographicOffCenterFrustum } from "../../Source/Cesium.js";
import { PerspectiveFrustum } from "../../Source/Cesium.js";
import { Rectangle } from "../../Source/Cesium.js";
import { Transforms } from "../../Source/Cesium.js";
import { WebMercatorProjection } from "../../Source/Cesium.js";
import { Camera } from "../../Source/Cesium.js";
import { CameraFlightPath } from "../../Source/Cesium.js";
import { MapMode2D } from "../../Source/Cesium.js";
import { SceneMode } from "../../Source/Cesium.js";
import { TweenCollection } from "../../Source/Cesium.js";

describe("Scene/Camera", function () {
  var scene;
  var camera;

  var position;
  var up;
  var dir;
  var right;

  var moveAmount = 3.0;
  var turnAmount = CesiumMath.PI_OVER_TWO;
  var rotateAmount = CesiumMath.PI_OVER_TWO;
  var zoomAmount = 1.0;

  function FakeScene(projection) {
    this.canvas = {
      clientWidth: 512,
      clientHeight: 384,
    };
    this.drawingBufferWidth = 1024;
    this.drawingBufferHeight = 768;
    this.mapProjection = defaultValue(projection, new GeographicProjection());
    this.tweens = new TweenCollection();
    this.screenSpaceCameraController = {
      minimumZoomDistance: 0,
      maximumZoomDistance: 5906376272000.0, // distance from the Sun to Pluto in meters.
    };
    this.camera = undefined;
    this.preloadFlightCamera = undefined;
    this.context = {
      drawingBufferWidth: 1024,
      drawingBufferHeight: 768,
    };
    this.mapMode2D = MapMode2D.INFINITE_2D;
  }

  beforeEach(function () {
    position = Cartesian3.clone(Cartesian3.UNIT_Z);
    up = Cartesian3.clone(Cartesian3.UNIT_Y);
    dir = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
    right = Cartesian3.cross(dir, up, new Cartesian3());

    scene = new FakeScene();

    camera = new Camera(scene);
    camera.position = Cartesian3.clone(position);
    camera.up = Cartesian3.clone(up);
    camera.direction = Cartesian3.clone(dir);
    camera.right = Cartesian3.clone(right);

    camera.minimumZoomDistance = 0.0;

    scene.camera = camera;
    scene.preloadFlightCamera = Camera.clone(camera);
    camera._scene = scene;
    scene.mapMode2D = MapMode2D.INFINITE_2D;
  });

  it("constructor throws an exception when there is no canvas", function () {
    expect(function () {
      return new Camera();
    }).toThrowDeveloperError();
  });

  it("get view matrix", function () {
    var viewMatrix = camera.viewMatrix;
    var position = camera.position;
    var up = camera.up;
    var dir = camera.direction;
    var right = camera.right;
    var rotation = new Matrix4(
      right.x,
      right.y,
      right.z,
      0.0,
      up.x,
      up.y,
      up.z,
      0.0,
      -dir.x,
      -dir.y,
      -dir.z,
      0.0,
      0.0,
      0.0,
      0.0,
      1.0
    );
    var translation = new Matrix4(
      1.0,
      0.0,
      0.0,
      -position.x,
      0.0,
      1.0,
      0.0,
      -position.y,
      0.0,
      0.0,
      1.0,
      -position.z,
      0.0,
      0.0,
      0.0,
      1.0
    );
    var expected = Matrix4.multiply(rotation, translation, new Matrix4());
    expect(viewMatrix).toEqual(expected);
  });

  it("get inverse view matrix", function () {
    var expected = Matrix4.inverse(camera.viewMatrix, new Matrix4());
    expect(expected).toEqualEpsilon(
      camera.inverseViewMatrix,
      CesiumMath.EPSILON15
    );
  });

  it("get inverse transform", function () {
    camera._setTransform(
      new Matrix4(
        5.0,
        0.0,
        0.0,
        1.0,
        0.0,
        5.0,
        0.0,
        2.0,
        0.0,
        0.0,
        5.0,
        3.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    var expected = Matrix4.inverseTransformation(
      camera.transform,
      new Matrix4()
    );
    expect(expected).toEqual(camera.inverseTransform);
  });

  it("Computes orthonormal direction, up, and right vectors", function () {
    camera.direction = new Cartesian3(
      -0.32297853365047874,
      0.9461560708446421,
      0.021761351171635013
    );
    camera.up = new Cartesian3(
      0.9327219113001013,
      0.31839266745173644,
      -2.9874778345595487e-10
    );
    camera.right = new Cartesian3(
      0.0069286549295528715,
      -0.020297288960790985,
      0.9853344956450351
    );

    expect(Cartesian3.magnitude(camera.right)).not.toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON8
    );
    expect(Cartesian3.magnitude(camera.up)).not.toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON8
    );

    // Trigger updateMembers which normalizes the axes
    var viewMatrix = camera.viewMatrix;
    expect(Cartesian3.magnitude(camera.right)).toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON8
    );
    expect(Cartesian3.magnitude(camera.up)).toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON8
    );

    var inverseAffine = Matrix4.inverseTransformation(
      viewMatrix,
      new Matrix4()
    );
    var inverse = Matrix4.inverse(viewMatrix, new Matrix4());
    expect(inverseAffine).toEqualEpsilon(inverse, CesiumMath.EPSILON8);
  });

  it("get heading is undefined when morphing", function () {
    camera._mode = SceneMode.MORPHING;
    expect(camera.heading).not.toBeDefined();
  });

  it("get heading in 2D", function () {
    camera._mode = SceneMode.SCENE2D;

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);

    var heading =
      CesiumMath.TWO_PI - Math.atan2(camera.right.y, camera.right.x);
    expect(camera.heading).toEqual(heading);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("get heading in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);

    var heading =
      CesiumMath.TWO_PI - Math.atan2(camera.right.y, camera.right.x);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON8);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("get heading in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    var ellipsoid = Ellipsoid.WGS84;
    var toFixedFrame = Transforms.eastNorthUpToFixedFrame(
      camera.position,
      ellipsoid
    );
    var transform = Matrix4.getMatrix3(toFixedFrame, new Matrix3());
    Matrix3.transpose(transform, transform);

    var right = Matrix3.multiplyByVector(
      transform,
      camera.right,
      new Cartesian3()
    );
    var heading =
      CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(Math.atan2(right.y, right.x));

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    right = Cartesian3.clone(camera.right);

    expect(camera.heading).toEqual(heading);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("sets heading in 2D when the map can be rotated", function () {
    scene.mapMode2D = MapMode2D.ROTATE;
    camera._mode = SceneMode.SCENE2D;

    var heading = camera.heading;
    var positionCartographic = camera.positionCartographic;

    var newHeading = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        heading: newHeading,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.heading).not.toEqual(heading);
    expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON14);
  });

  it("does not set heading in 2D for infinite scrolling mode", function () {
    camera._mode = SceneMode.SCENE2D;

    var heading = camera.heading;
    var positionCartographic = camera.positionCartographic;

    var newHeading = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        heading: newHeading,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.heading).toEqual(heading);
  });

  it("set heading in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    camera.position = Cartesian3.fromDegrees(-72.0, 40.0, 100000.0);
    camera.direction = Cartesian3.negate(
      Cartesian3.normalize(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var heading = camera.heading;
    var positionCartographic = camera.positionCartographic;

    var newHeading = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        heading: newHeading,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.heading).not.toEqual(heading);
    expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON4);
  });

  it("set heading in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    camera.position = Cartesian3.clone(Cartesian3.UNIT_X);
    camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var heading = camera.heading;
    var newHeading = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        heading: newHeading,
      },
    });

    expect(camera.heading).not.toEqual(heading);
    expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON14);
  });

  it("set heading in 3D (2)", function () {
    camera._mode = SceneMode.SCENE3D;

    camera.position = Cartesian3.fromDegrees(136.0, -24.0, 4500000.0);
    Cartesian3.negate(camera.position, camera.direction);
    Cartesian3.normalize(camera.direction, camera.direction);
    Cartesian3.clone(Cartesian3.UNIT_Z, camera.up);
    Cartesian3.cross(camera.direction, camera.up, camera.right);
    Cartesian3.cross(camera.right, camera.direction, camera.up);

    var positionCartographic = camera.positionCartographic;

    camera.setView({
      orientation: {
        heading: CesiumMath.PI,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.heading).toEqualEpsilon(CesiumMath.PI, CesiumMath.EPSILON8);
    expect(camera.up.z).toBeLessThan(0.0);

    camera.setView({
      orientation: {
        heading: CesiumMath.TWO_PI,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.heading).toEqualEpsilon(
      CesiumMath.TWO_PI,
      CesiumMath.EPSILON8
    );
    expect(camera.up.z).toBeGreaterThan(0.0);
  });

  it("pitch is undefined when mode is not 3D or Columbus view", function () {
    camera._mode = SceneMode.MORPHING;
    expect(camera.pitch).not.toBeDefined();
  });

  it("get pitch in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0);
    camera.direction = Cartesian3.negate(
      Cartesian3.normalize(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);

    var pitch = CesiumMath.PI_OVER_TWO - Math.acos(-camera.direction.z);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("get pitch in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    var direction = Cartesian3.normalize(camera.position, new Cartesian3());
    Cartesian3.negate(direction, direction);
    var pitch =
      CesiumMath.PI_OVER_TWO -
      Math.acos(-Cartesian3.dot(camera.direction, direction));

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);

    expect(camera.pitch).toEqual(pitch);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("set pitch in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    camera.position = Cartesian3.fromDegrees(-72.0, 40.0, 100000.0);
    camera.direction = Cartesian3.negate(
      Cartesian3.normalize(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var pitch = camera.pitch;
    var positionCartographic = camera.positionCartographic;

    var newPitch = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        pitch: newPitch,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.pitch).not.toEqual(pitch);
    expect(camera.pitch).toEqualEpsilon(newPitch, CesiumMath.EPSILON4);
  });

  it("set pitch in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    camera.position = Cartesian3.fromDegrees(-72.0, 40.0, 100000.0);
    camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var pitch = camera.pitch;
    var positionCartographic = camera.positionCartographic;

    var newPitch = CesiumMath.toRadians(45.0);
    camera.setView({
      orientation: {
        pitch: newPitch,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.pitch).not.toEqual(pitch);
    expect(camera.pitch).toEqualEpsilon(newPitch, CesiumMath.EPSILON14);
  });

  it("get roll in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0);
    camera.direction = Cartesian3.negate(
      Cartesian3.normalize(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    camera.look(camera.direction, CesiumMath.toRadians(45.0));

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);

    var roll = CesiumMath.zeroToTwoPi(-CesiumMath.toRadians(45.0));
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("get roll in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    var ellipsoid = Ellipsoid.WGS84;
    camera.position = Cartesian3.clone(Cartesian3.UNIT_X);
    Cartesian3.multiplyByScalar(
      camera.position,
      ellipsoid.maximumRadius + 100.0,
      camera.position
    );
    camera.direction = new Cartesian3(-1.0, 0.0, 1.0);
    Cartesian3.normalize(camera.direction, camera.direction);
    camera.right = Cartesian3.cross(
      camera.direction,
      Cartesian3.UNIT_Z,
      new Cartesian3()
    );
    Cartesian3.normalize(camera.right, camera.right);
    camera.up = Cartesian3.cross(
      camera.right,
      camera.direction,
      new Cartesian3()
    );

    var toFixedFrame = Transforms.eastNorthUpToFixedFrame(
      camera.position,
      ellipsoid
    );
    var transform = Matrix4.getMatrix3(toFixedFrame, new Matrix3());
    Matrix3.transpose(transform, transform);

    var right = Matrix3.multiplyByVector(
      transform,
      camera.right,
      new Cartesian3()
    );
    var roll = CesiumMath.TWO_PI - Math.atan2(right.z, right.x);

    var positionWC = Cartesian3.clone(camera.positionWC);
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    right = Cartesian3.clone(camera.right);

    expect(camera.roll).toEqual(roll);

    expect(camera.positionWC).toEqualEpsilon(positionWC, CesiumMath.EPSILON8);
    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON8);
    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON8);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON8);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON8);
  });

  it("get roll returns correct value past 90 degrees", function () {
    var roll = CesiumMath.toRadians(110.0);
    camera.setView({
      destination: Cartesian3.fromDegrees(-72.0, 40.0, 20.0),
      orientation: {
        heading: 0.0,
        pitch: 0.0,
        roll: roll,
      },
    });

    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON14);
  });

  it("set roll in CV", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;

    camera.position = Cartesian3.fromDegrees(-72.0, 40.0, 100000.0);
    camera.direction = Cartesian3.clone(Cartesian3.UNIT_Y);
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var roll = camera.roll;
    var positionCartographic = camera.positionCartographic;

    var newRoll = CesiumMath.PI_OVER_FOUR;
    camera.setView({
      orientation: {
        pitch: camera.pitch,
        roll: newRoll,
        heading: camera.heading,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.roll).not.toEqual(roll);
    expect(camera.roll).toEqualEpsilon(newRoll, CesiumMath.EPSILON6);
  });

  it("set roll in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    camera.position = Cartesian3.fromDegrees(-72.0, 40.0, 100000.0);
    camera.direction = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.up = Cartesian3.clone(Cartesian3.UNIT_X);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var roll = camera.roll;
    var positionCartographic = Cartographic.clone(camera.positionCartographic);

    var newRoll = CesiumMath.PI_OVER_FOUR;
    camera.setView({
      orientation: {
        pitch: camera.pitch,
        roll: newRoll,
        heading: camera.heading,
      },
    });

    expect(camera.positionCartographic).toEqual(positionCartographic);
    expect(camera.roll).not.toEqual(roll);
    expect(camera.roll).toEqualEpsilon(newRoll, CesiumMath.EPSILON6);
  });

  it("update throws without mode", function () {
    expect(function () {
      camera.update();
    }).toThrowDeveloperError();
  });

  it("update throws with frustum not supported in given mode", function () {
    camera.frustum = new PerspectiveFrustum();
    expect(function () {
      camera.update(SceneMode.SCENE2D);
    }).toThrowDeveloperError();

    camera.frustum = new OrthographicOffCenterFrustum();
    expect(function () {
      camera.update(SceneMode.SCENE3D);
    }).toThrowDeveloperError();
    expect(function () {
      camera.update(SceneMode.COLUMBUS_VIEW);
    }).toThrowDeveloperError();
  });

  it("setView with cartesian in 2D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);
    var maxRadii = ellipsoid.maximumRadius;

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;

    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = maxRadii * Math.PI;
    frustum.left = -frustum.right;
    frustum.top =
      frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
    frustum.bottom = -frustum.top;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    var ratio = frustum.top / frustum.right;
    var cartesian = Cartesian3.fromDegrees(-75.0, 42.0, 100.0);
    camera.setView({
      destination: cartesian,
    });

    var cart = ellipsoid.cartesianToCartographic(cartesian);
    expect(camera.positionCartographic).toEqualEpsilon(
      cart,
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON6
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6);
    expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6);
    expect(frustum.right - frustum.left).toEqualEpsilon(
      cart.height,
      CesiumMath.EPSILON6
    );
    expect(frustum.top / frustum.right).toEqual(ratio);
  });

  it("setView with cartesian in Columbus View", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);

    camera._mode = SceneMode.COLUMBUS_VIEW;
    camera._projection = projection;

    var cartesian = Cartesian3.fromDegrees(-75.0, 42.0, 100.0);
    camera.setView({
      destination: cartesian,
    });

    var cart = ellipsoid.cartesianToCartographic(cartesian);
    expect(camera.positionCartographic).toEqualEpsilon(
      cart,
      CesiumMath.EPSILON11
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON6
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6);
    expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6);
  });

  it("setView with cartesian in 3D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);

    camera._mode = SceneMode.SCENE3D;
    camera._projection = projection;

    var cartesian = Cartesian3.fromDegrees(-75.0, 0.0, 100.0);
    camera.setView({
      destination: cartesian,
    });

    expect(camera.positionCartographic).toEqualEpsilon(
      ellipsoid.cartesianToCartographic(cartesian),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.normalize(
        Cartesian3.negate(camera.position, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON6
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON6);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.cross(camera.direction, camera.up, new Cartesian3()),
      CesiumMath.EPSILON6
    );
  });

  it("setView with cartesian in Columbus View and orthographic frustum", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);

    camera._mode = SceneMode.COLUMBUS_VIEW;
    camera._projection = projection;

    camera.frustum = new OrthographicFrustum();
    camera.frustum.aspectRatio =
      scene.drawingBufferWidth / scene.drawingBufferHeight;
    camera.frustum.width = camera.positionCartographic.height;

    var cartesian = Cartesian3.fromDegrees(-75.0, 42.0, 100.0);
    camera.setView({
      destination: cartesian,
    });

    var cart = ellipsoid.cartesianToCartographic(cartesian);
    expect(camera.positionCartographic).toEqualEpsilon(
      cart,
      CesiumMath.EPSILON11
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON6
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6);
    expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6);
    expect(camera.frustum.width).toEqual(cart.height);
  });

  it("setView with cartesian in 3D and orthographic frustum", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);

    camera._mode = SceneMode.SCENE3D;
    camera._projection = projection;

    camera.frustum = new OrthographicFrustum();
    camera.frustum.aspectRatio =
      scene.drawingBufferWidth / scene.drawingBufferHeight;
    camera.frustum.width = camera.positionCartographic.height;

    var cartesian = Cartesian3.fromDegrees(-75.0, 0.0, 100.0);
    camera.setView({
      destination: cartesian,
    });

    var cart = ellipsoid.cartesianToCartographic(cartesian);
    expect(camera.positionCartographic).toEqualEpsilon(
      cart,
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.normalize(
        Cartesian3.negate(camera.position, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON6
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON6);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.cross(camera.direction, camera.up, new Cartesian3()),
      CesiumMath.EPSILON6
    );
    expect(camera.frustum.width).toEqual(cart.height);
  });

  it("setView right rotation order", function () {
    var position = Cartesian3.fromDegrees(-117.16, 32.71, 0.0);
    var heading = CesiumMath.toRadians(180.0);
    var pitch = CesiumMath.toRadians(0.0);
    var roll = CesiumMath.toRadians(45.0);

    camera.setView({
      destination: position,
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);
  });

  it("setView (1)", function () {
    var position = Cartesian3.fromDegrees(-117.16, 32.71, 0.0);
    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(-50.0);
    var roll = CesiumMath.toRadians(45.0);

    camera.setView({
      destination: position,
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);

    heading = CesiumMath.toRadians(200.0);

    camera.setView({
      orientation: {
        pitch: camera.pitch,
        roll: camera.roll,
        heading: heading,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);
  });

  it("setView (2)", function () {
    var position = Cartesian3.fromDegrees(-117.16, 32.71, 0.0);
    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(50.0);
    var roll = CesiumMath.toRadians(45.0);

    camera.setView({
      destination: position,
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);

    pitch = CesiumMath.toRadians(-50.0);

    camera.setView({
      orientation: {
        pitch: pitch,
        roll: camera.roll,
        heading: camera.heading,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);
  });

  it("setView (3)", function () {
    var position = Cartesian3.fromDegrees(-117.16, 32.71, 0.0);
    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(50.0);
    var roll = CesiumMath.toRadians(45.0);

    camera.setView({
      destination: position,
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);

    roll = CesiumMath.toRadians(200.0);

    camera.setView({
      orientation: {
        roll: roll,
        heading: camera.heading,
        pitch: camera.pitch,
      },
    });

    expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON6);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);
  });

  it("setView with direction, up", function () {
    scene.mode = SceneMode.SCENE3D;

    var direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
    var up = Cartesian3.clone(Cartesian3.UNIT_Y);

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      orientation: {
        direction: direction,
        up: up,
      },
      duration: 0.001,
    };
    camera.setView(options);

    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON6);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON6);
  });

  it("worldToCameraCoordinates throws without cartesian", function () {
    expect(function () {
      camera.worldToCameraCoordinates();
    }).toThrowDeveloperError();
  });

  it("worldToCameraCoordinates transforms to the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    expect(camera.worldToCameraCoordinates(Cartesian4.UNIT_X)).toEqual(
      Cartesian4.UNIT_Z
    );
  });

  it("worldToCameraCoordinatesPoint throws without cartesian", function () {
    expect(function () {
      camera.worldToCameraCoordinatesPoint();
    }).toThrowDeveloperError();
  });

  it("worldToCameraCoordinatesPoint transforms to the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        10.0,
        1.0,
        0.0,
        0.0,
        20.0,
        0.0,
        1.0,
        0.0,
        30.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    var expected = Cartesian3.add(
      Matrix4.getColumn(camera.inverseTransform, 3, new Cartesian4()),
      Cartesian3.UNIT_Z,
      new Cartesian3()
    );
    expect(camera.worldToCameraCoordinatesPoint(Cartesian3.UNIT_X)).toEqual(
      expected
    );
  });

  it("worldToCameraCoordinatesVector throws without cartesian", function () {
    expect(function () {
      camera.worldToCameraCoordinatesVector();
    }).toThrowDeveloperError();
  });

  it("worldToCameraCoordinatesVector transforms to the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        10.0,
        1.0,
        0.0,
        0.0,
        20.0,
        0.0,
        1.0,
        0.0,
        30.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    expect(camera.worldToCameraCoordinatesVector(Cartesian3.UNIT_X)).toEqual(
      Cartesian3.UNIT_Z
    );
  });

  it("cameraToWorldCoordinates throws without cartesian", function () {
    expect(function () {
      camera.cameraToWorldCoordinates();
    }).toThrowDeveloperError();
  });

  it("cameraToWorldCoordinates transforms from the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    expect(camera.cameraToWorldCoordinates(Cartesian4.UNIT_Z)).toEqual(
      Cartesian4.UNIT_X
    );
  });

  it("cameraToWorldCoordinatesPoint throws without cartesian", function () {
    expect(function () {
      camera.cameraToWorldCoordinatesPoint();
    }).toThrowDeveloperError();
  });

  it("cameraToWorldCoordinatesPoint transforms from the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        10.0,
        1.0,
        0.0,
        0.0,
        20.0,
        0.0,
        1.0,
        0.0,
        30.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    var expected = Cartesian3.add(
      Cartesian3.UNIT_X,
      Matrix4.getColumn(camera.transform, 3, new Cartesian4()),
      new Cartesian3()
    );
    expect(camera.cameraToWorldCoordinatesPoint(Cartesian3.UNIT_Z)).toEqual(
      expected
    );
  });

  it("cameraToWorldCoordinatesVector throws without cartesian", function () {
    expect(function () {
      camera.cameraToWorldCoordinatesVector();
    }).toThrowDeveloperError();
  });

  it("cameraToWorldCoordinatesVector transforms from the cameras reference frame", function () {
    camera._setTransform(
      new Matrix4(
        0.0,
        0.0,
        1.0,
        10.0,
        1.0,
        0.0,
        0.0,
        20.0,
        0.0,
        1.0,
        0.0,
        30.0,
        0.0,
        0.0,
        0.0,
        1.0
      )
    );
    expect(camera.cameraToWorldCoordinatesVector(Cartesian3.UNIT_Z)).toEqual(
      Cartesian3.UNIT_X
    );
  });

  it("move throws without an axis", function () {
    expect(function () {
      expect(camera.move());
    }).toThrowDeveloperError();
  });

  it("moves", function () {
    var direction = Cartesian3.normalize(
      new Cartesian3(1.0, 1.0, 0.0),
      new Cartesian3()
    );
    camera.move(direction, moveAmount);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(direction.x * moveAmount, direction.y * moveAmount, 1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves up", function () {
    camera.moveUp(moveAmount);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, moveAmount, 1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves down", function () {
    camera.moveDown(moveAmount);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, -moveAmount, 1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves right", function () {
    camera.moveRight(moveAmount);
    expect(camera.position).toEqual(
      new Cartesian3(moveAmount, 0.0, 1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves left", function () {
    camera.moveLeft(moveAmount);
    expect(camera.position).toEqual(
      new Cartesian3(-moveAmount, 0.0, 1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves forward", function () {
    camera.moveForward(moveAmount);
    expect(camera.position).toEqual(
      new Cartesian3(0.0, 0.0, 1.0 - moveAmount),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves backward", function () {
    camera.moveBackward(moveAmount);
    expect(camera.position).toEqual(
      new Cartesian3(0.0, 0.0, 1.0 + moveAmount),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves forward in 2D mode zooms in camera instead of moving it", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.moveForward(moveAmount);

    // want to be at prev location bc moveBackward in 2D uses zoom2D which only adjusts frustum
    expect(camera.position).toEqual(oldCamera.position, CesiumMath.EPSILON10);
    expect(camera.frustum).not.toEqual(oldCamera.frustum);

    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("moves backward in 2D mode zooms out camera instead of moving it", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.moveBackward(moveAmount);

    // want to be at prev location bc moveBackward in 2D uses zoom2D which only adjusts frustum
    expect(camera.position).toEqual(oldCamera.position, CesiumMath.EPSILON10);
    expect(camera.frustum).not.toEqual(oldCamera.frustum);
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("move clamps position in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var max = scene.mapProjection.project(
      new Cartographic(Math.PI, CesiumMath.toRadians(85.05112878))
    );
    var factor = 1000.0;
    var dx = max.x * factor;
    var dy = max.y * factor;

    camera.moveUp(dy);
    camera.moveRight(dx);
    expect(camera.position.x).toBeLessThan(dx);
    expect(camera.position.y).toBeLessThan(dy);

    camera.moveDown(dy);
    camera.moveLeft(dx);
    expect(camera.position.x).toBeGreaterThan(-dx);
    expect(camera.position.y).toBeGreaterThan(-dy);
  });

  it("look throws without an axis", function () {
    expect(function () {
      expect(camera.look());
    }).toThrowDeveloperError();
  });

  it("looks", function () {
    camera.look(Cartesian3.UNIT_X, CesiumMath.PI);
    expect(camera.position).toEqual(position);
    expect(camera.right).toEqual(right);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON10
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.UNIT_Z,
      CesiumMath.EPSILON10
    );
  });

  it("looks left", function () {
    camera.lookLeft(turnAmount);
    expect(camera.position).toEqual(position);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(right, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(dir, CesiumMath.EPSILON15);
  });

  it("looks right", function () {
    camera.lookRight(turnAmount);
    expect(camera.position).toEqual(position);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("looks up", function () {
    camera.lookUp(turnAmount);
    expect(camera.position).toEqual(position);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("looks down", function () {
    camera.lookDown(turnAmount);
    expect(camera.position).toEqual(position);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(up, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.up).toEqualEpsilon(dir, CesiumMath.EPSILON15);
  });

  it("looks left in 2D mode does not modify the camera at all", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.lookLeft(turnAmount);

    // dont want camera look vector to rotate at all in 2D
    expect(camera.position).toEqual(oldCamera.position);
    expect(camera.up).toEqualEpsilon(oldCamera.up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      oldCamera.direction,
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(oldCamera.right, CesiumMath.EPSILON15);
  });

  it("looks right in 2D mode does not modify the camera at all", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.lookRight(turnAmount);

    // dont want camera look vector to rotate at all in 2D
    expect(camera.position).toEqual(oldCamera.position);
    expect(camera.up).toEqualEpsilon(oldCamera.up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      oldCamera.direction,
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(oldCamera.right, CesiumMath.EPSILON15);
  });

  it("looks up in 2D mode does not modify the camera at all", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.lookUp(turnAmount);

    // dont want camera look vector to rotate at all in 2D
    expect(camera.position).toEqual(oldCamera.position);
    expect(camera.up).toEqualEpsilon(oldCamera.up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      oldCamera.direction,
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(oldCamera.right, CesiumMath.EPSILON15);
  });

  it("looks down in 2D mode does not modify the camera at all", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);
    var oldCamera = Camera.clone(camera);

    camera.lookDown(turnAmount);

    // dont want camera look vector to rotate at all in 2D
    expect(camera.position).toEqual(oldCamera.position);
    expect(camera.up).toEqualEpsilon(oldCamera.up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      oldCamera.direction,
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(oldCamera.right, CesiumMath.EPSILON15);
  });

  it("twists left", function () {
    camera.twistLeft(CesiumMath.PI_OVER_TWO);
    expect(camera.position).toEqual(position);
    expect(camera.direction).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(right, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(up, CesiumMath.EPSILON15);
  });

  it("twists right", function () {
    camera.twistRight(CesiumMath.PI_OVER_TWO);
    expect(camera.position).toEqual(position);
    expect(camera.direction).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.up).toEqualEpsilon(right, CesiumMath.EPSILON14);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(up, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotate throws without an axis", function () {
    expect(function () {
      expect(camera.rotate());
    }).toThrowDeveloperError();
  });

  it("rotates up", function () {
    camera.rotateUp(rotateAmount);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.direction).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotates up with constrained axis 0", function () {
    camera.constrainedAxis = Cartesian3.UNIT_Y;
    camera.rotateUp(rotateAmount);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.direction).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotates up with constrained axis 1", function () {
    camera.up = Cartesian3.negate(dir, new Cartesian3());
    camera.direction = right;
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    camera.constrainedAxis = Cartesian3.UNIT_Y;
    camera.rotateUp(rotateAmount);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON14
    );
    expect(camera.direction).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotates down", function () {
    camera.rotateDown(rotateAmount);
    expect(camera.up).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(up, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_Y,
      CesiumMath.EPSILON15
    );
  });

  it("rotates down with constrained axis 0 ", function () {
    camera.constrainedAxis = Cartesian3.UNIT_Y;
    camera.rotateDown(rotateAmount);
    expect(camera.up).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(up, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_Y,
      CesiumMath.EPSILON15
    );
  });

  it("rotates down with constrained axis 1", function () {
    camera.up = Cartesian3.negate(dir, new Cartesian3());
    camera.direction = right;
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    camera.constrainedAxis = Cartesian3.UNIT_Y;
    camera.rotateDown(rotateAmount);
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_Y,
      CesiumMath.EPSILON15
    );
  });

  it("rotates left", function () {
    camera.rotateLeft(rotateAmount);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(right, CesiumMath.EPSILON15);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.position).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotates left with contrained axis", function () {
    camera.constrainedAxis = Cartesian3.UNIT_Z;
    camera.rotateLeft(rotateAmount);
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_Z,
      CesiumMath.EPSILON15
    );
  });

  it("rotates right", function () {
    camera.rotateRight(rotateAmount);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON15);
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(right, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(dir, CesiumMath.EPSILON15);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_X,
      CesiumMath.EPSILON15
    );
  });

  it("rotates right with contrained axis", function () {
    camera.constrainedAxis = Cartesian3.UNIT_Z;
    camera.rotateRight(rotateAmount);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(
      Cartesian3.UNIT_Y,
      CesiumMath.EPSILON15
    );
    expect(camera.position).toEqualEpsilon(
      Cartesian3.UNIT_Z,
      CesiumMath.EPSILON15
    );
  });

  it("rotates", function () {
    var axis = Cartesian3.normalize(
      new Cartesian3(
        Math.cos(CesiumMath.PI_OVER_FOUR),
        Math.sin(CesiumMath.PI_OVER_FOUR),
        0.0
      ),
      new Cartesian3()
    );
    var angle = CesiumMath.PI_OVER_TWO;
    camera.rotate(axis, angle);

    expect(camera.position).toEqualEpsilon(
      new Cartesian3(-axis.x, axis.y, 0.0),
      CesiumMath.EPSILON15
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(camera.position, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON15
    );
    expect(camera.right).toEqualEpsilon(
      Cartesian3.normalize(new Cartesian3(0.5, 0.5, axis.x), new Cartesian3()),
      CesiumMath.EPSILON15
    );
    expect(camera.up).toEqualEpsilon(
      Cartesian3.cross(camera.right, camera.direction, new Cartesian3()),
      CesiumMath.EPSILON15
    );
  });

  it("rotate past constrained axis stops at constained axis", function () {
    camera.constrainedAxis = Cartesian3.UNIT_Y;
    camera.rotateUp(Math.PI);
    expect(camera.up).toEqualEpsilon(
      Cartesian3.negate(dir, new Cartesian3()),
      CesiumMath.EPSILON4
    );
    expect(camera.direction).toEqualEpsilon(up, CesiumMath.EPSILON4);
    expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON4);
    expect(camera.position).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON4
    );
  });

  it("zooms out 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    camera.zoomOut(zoomAmount);
    expect(camera.frustum.right).toEqualEpsilon(2.5, CesiumMath.EPSILON10);
    expect(camera.frustum.left).toEqual(-2.5, CesiumMath.EPSILON10);
    expect(camera.frustum.top).toEqual(1.25, CesiumMath.EPSILON10);
    expect(camera.frustum.bottom).toEqual(-1.25, CesiumMath.EPSILON10);
  });

  it("zooms out 2D when frustrum has greater height than width", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -1.0;
    frustum.right = 1.0;
    frustum.top = 2.0;
    frustum.bottom = -2.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    camera.zoomOut(zoomAmount);
    expect(camera.frustum.right).toEqualEpsilon(1.25, CesiumMath.EPSILON10);
    expect(camera.frustum.left).toEqual(-1.25, CesiumMath.EPSILON10);
    expect(camera.frustum.top).toEqual(2.5, CesiumMath.EPSILON10);
    expect(camera.frustum.bottom).toEqual(-2.5, CesiumMath.EPSILON10);
  });

  it("zooms in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    camera.zoomIn(zoomAmount);
    expect(camera.frustum.right).toEqualEpsilon(1.5, CesiumMath.EPSILON10);
    expect(camera.frustum.left).toEqual(-1.5, CesiumMath.EPSILON10);
    expect(camera.frustum.top).toEqual(0.75, CesiumMath.EPSILON10);
    expect(camera.frustum.bottom).toEqual(-0.75, CesiumMath.EPSILON10);
  });

  it("zooms in 2D when frustrum has greater height than width", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -1.0;
    frustum.right = 1.0;
    frustum.top = 2.0;
    frustum.bottom = -2.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    camera.zoomIn(zoomAmount);
    expect(camera.frustum.right).toEqualEpsilon(0.75, CesiumMath.EPSILON10);
    expect(camera.frustum.left).toEqual(-0.75, CesiumMath.EPSILON10);
    expect(camera.frustum.top).toEqual(1.5, CesiumMath.EPSILON10);
    expect(camera.frustum.bottom).toEqual(-1.5, CesiumMath.EPSILON10);
  });

  it("clamps zoom out in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var max = scene.mapProjection.project(
      new Cartographic(Math.PI, CesiumMath.toRadians(85.05112878))
    );
    var factor = 1000.0;
    var dx = max.x * factor;
    var ratio = frustum.top / frustum.right;

    camera.zoomOut(dx);
    expect(frustum.right).toBeLessThan(dx);
    expect(frustum.left).toBeGreaterThan(-dx);
    expect(frustum.top).toEqual(frustum.right * ratio);
    expect(frustum.bottom).toEqual(-frustum.top);
  });

  it("clamps zoom in in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var max = scene.mapProjection.project(
      new Cartographic(Math.PI, CesiumMath.toRadians(85.05112878))
    );
    var factor = 1000.0;
    var dx = max.x * factor;

    camera.zoomIn(dx * 2.0);
    expect(frustum.right).toEqual(1.0);
    expect(frustum.left).toEqual(-1.0);
  });

  it("zooms in 3D", function () {
    camera.zoomIn(zoomAmount);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, 1.0 - zoomAmount),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("zooms out in 3D", function () {
    camera.zoomOut(zoomAmount);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, 1.0 + zoomAmount),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqual(up);
    expect(camera.direction).toEqual(dir);
    expect(camera.right).toEqual(right);
  });

  it("zooms in throws with undefined OrthogrphicFrustum properties 2D", function () {
    camera._mode = SceneMode.SCENE2D;
    camera.frustum = new OrthographicOffCenterFrustum();
    expect(function () {
      camera.zoomIn(zoomAmount);
    }).toThrowDeveloperError();
  });

  it("lookAt", function () {
    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var offset = new Cartesian3(0.0, -1.0, 0.0);

    var tempCamera = Camera.clone(camera);
    tempCamera.lookAt(target, offset);

    expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11);
    expect(tempCamera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(offset, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.direction,
        Cartesian3.UNIT_Z,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.right,
        tempCamera.direction,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );

    expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(
      CesiumMath.EPSILON14
    );
  });

  it("lookAt with heading, pitch and range", function () {
    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 2.0;

    var tempCamera = Camera.clone(camera);
    tempCamera.lookAt(target, new HeadingPitchRange(heading, pitch, range));

    tempCamera.lookAtTransform(Matrix4.IDENTITY);

    expect(Cartesian3.distance(tempCamera.position, target)).toEqualEpsilon(
      range,
      CesiumMath.EPSILON6
    );
    expect(tempCamera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(tempCamera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);

    expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(
      CesiumMath.EPSILON14
    );
  });

  it("lookAt when target is zero", function () {
    var target = Cartesian3.clone(Cartesian3.ZERO);
    var offset = new Cartesian3(0.0, -1.0, 0.0);

    var tempCamera = Camera.clone(camera);
    tempCamera.lookAt(target, offset);

    expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11);
    expect(tempCamera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(offset, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.direction,
        Cartesian3.UNIT_Z,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.right,
        tempCamera.direction,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
  });

  it("lookAt when target and camera are zero", function () {
    var target = Cartesian3.clone(Cartesian3.ZERO);
    var offset = new Cartesian3(0.0, -1.0, 0.0);

    var tempCamera = Camera.clone(camera);
    tempCamera.position = Cartesian3.clone(Cartesian3.ZERO);
    tempCamera.lookAt(target, offset);

    expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11);
    expect(tempCamera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(offset, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.direction,
        Cartesian3.UNIT_Z,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.right,
        tempCamera.direction,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
  });

  it("lookAt throws with no target parameter", function () {
    expect(function () {
      camera.lookAt(undefined, Cartesian3.ZERO);
    }).toThrowDeveloperError();
  });

  it("lookAt throws with no offset parameter", function () {
    expect(function () {
      camera.lookAt(Cartesian3.ZERO, undefined);
    }).toThrowDeveloperError();
  });

  it("lookAt in 2D mode", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;

    var tempCamera = Camera.clone(camera);
    tempCamera.frustum = frustum;
    tempCamera.update(SceneMode.SCENE2D);

    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var offset = new Cartesian3(10000.0, 10000.0, 30000.0);
    tempCamera.lookAt(target, offset);

    expect(Cartesian2.clone(tempCamera.position)).toEqual(Cartesian2.ZERO);
    expect(tempCamera.direction).toEqual(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.normalize(
        Cartesian3.fromElements(-offset.x, -offset.y, 0.0),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(tempCamera.direction, tempCamera.up, new Cartesian3()),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.frustum.right).toEqual(
      Cartesian3.magnitude(offset) * 0.5
    );
    expect(tempCamera.frustum.left).toEqual(
      -Cartesian3.magnitude(offset) * 0.5
    );
  });

  it("lookAt in 2D mode with heading, pitch and range", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;

    var tempCamera = Camera.clone(camera);
    tempCamera.frustum = frustum;
    tempCamera.update(SceneMode.SCENE2D);

    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var heading = CesiumMath.toRadians(90.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 2.0;

    tempCamera.lookAt(target, new HeadingPitchRange(heading, pitch, range));

    expect(Cartesian2.clone(tempCamera.position)).toEqual(Cartesian2.ZERO);

    tempCamera.lookAtTransform(Matrix4.IDENTITY);
    expect(tempCamera.direction).toEqual(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())
    );
    expect(tempCamera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(tempCamera.frustum.right).toEqual(range * 0.5);
    expect(tempCamera.frustum.left).toEqual(-range * 0.5);
  });

  it("lookAt throws when morphing", function () {
    camera.update(SceneMode.MORPHING);

    expect(function () {
      camera.lookAt(Cartesian3.ZERO, Cartesian3.UNIT_X);
    }).toThrowDeveloperError();
  });

  it("lookAtTransform", function () {
    var target = new Cartesian3(-1.0, -1.0, 0.0);
    var offset = new Cartesian3(1.0, 1.0, 0.0);
    var transform = Transforms.eastNorthUpToFixedFrame(
      target,
      Ellipsoid.UNIT_SPHERE
    );

    var tempCamera = Camera.clone(camera);
    tempCamera.lookAtTransform(transform, offset);

    expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11);
    expect(tempCamera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(offset, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.direction,
        Cartesian3.UNIT_Z,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.right,
        tempCamera.direction,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );

    expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(
      CesiumMath.EPSILON14
    );
  });

  it("lookAtTransform with no offset parameter", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var cartOrigin = Cartographic.fromDegrees(-75.59777, 40.03883);
    var origin = ellipsoid.cartographicToCartesian(cartOrigin);
    var transform = Transforms.eastNorthUpToFixedFrame(origin);

    var height = 1000.0;
    cartOrigin.height = height;

    camera.position = ellipsoid.cartographicToCartesian(cartOrigin);
    camera.direction = Cartesian3.negate(
      Cartesian3.fromCartesian4(
        Matrix4.getColumn(transform, 2, new Cartesian4())
      ),
      new Cartesian3()
    );
    camera.up = Cartesian3.fromCartesian4(
      Matrix4.getColumn(transform, 1, new Cartesian4(), new Matrix4())
    );
    camera.right = Cartesian3.fromCartesian4(
      Matrix4.getColumn(transform, 0, new Cartesian4())
    );

    camera.lookAtTransform(transform);

    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, height),
      CesiumMath.EPSILON9
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
      CesiumMath.EPSILON9
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON9);
    expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON9);
  });

  it("lookAtTransform with heading, pitch and range", function () {
    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 2.0;
    var transform = Transforms.eastNorthUpToFixedFrame(target);

    var tempCamera = Camera.clone(camera);
    tempCamera.lookAtTransform(
      transform,
      new HeadingPitchRange(heading, pitch, range)
    );

    tempCamera.lookAtTransform(Matrix4.IDENTITY);

    expect(Cartesian3.distance(tempCamera.position, target)).toEqualEpsilon(
      range,
      CesiumMath.EPSILON6
    );
    expect(tempCamera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(tempCamera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);

    expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(
      CesiumMath.EPSILON14
    );
  });

  it("lookAtTransform throws with no transform parameter", function () {
    expect(function () {
      camera.lookAtTransform(undefined, Cartesian3.ZERO);
    }).toThrowDeveloperError();
  });

  it("lookAtTransform in 2D mode", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;

    var tempCamera = Camera.clone(camera);
    tempCamera.frustum = frustum;
    tempCamera.update(SceneMode.SCENE2D);

    var transform = Transforms.eastNorthUpToFixedFrame(
      Cartesian3.fromDegrees(0.0, 0.0)
    );
    var offset = new Cartesian3(10000.0, 10000.0, 30000.0);
    tempCamera.lookAtTransform(transform, offset);

    expect(Cartesian2.clone(tempCamera.position)).toEqual(Cartesian2.ZERO);
    expect(tempCamera.direction).toEqual(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.normalize(
        Cartesian3.fromElements(-offset.x, -offset.y, 0.0),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(tempCamera.direction, tempCamera.up, new Cartesian3()),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.frustum.right).toEqual(
      Cartesian3.magnitude(offset) * 0.5
    );
    expect(tempCamera.frustum.left).toEqual(
      -Cartesian3.magnitude(offset) * 0.5
    );
  });

  it("lookAtTransform in 2D mode with heading, pitch and range", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;

    var tempCamera = Camera.clone(camera);
    tempCamera.frustum = frustum;
    tempCamera.update(SceneMode.SCENE2D);

    var target = Cartesian3.fromDegrees(0.0, 0.0);
    var heading = CesiumMath.toRadians(90.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 2.0;
    var transform = Transforms.eastNorthUpToFixedFrame(target);

    tempCamera.lookAtTransform(
      transform,
      new HeadingPitchRange(heading, pitch, range)
    );

    expect(Cartesian2.clone(tempCamera.position)).toEqual(Cartesian2.ZERO);

    tempCamera.lookAtTransform(Matrix4.IDENTITY);
    expect(tempCamera.direction).toEqual(
      Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())
    );
    expect(tempCamera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(tempCamera.frustum.right).toEqual(range * 0.5);
    expect(tempCamera.frustum.left).toEqual(-range * 0.5);
  });

  it("lookAtTransform in 3D with orthographic projection", function () {
    var target = new Cartesian3(-1.0, -1.0, 0.0);
    var offset = new Cartesian3(1.0, 1.0, 0.0);
    var transform = Transforms.eastNorthUpToFixedFrame(
      target,
      Ellipsoid.UNIT_SPHERE
    );

    var tempCamera = Camera.clone(camera);
    tempCamera.frustum = new OrthographicFrustum();
    tempCamera.frustum.aspectRatio =
      scene.drawingBufferWidth / scene.drawingBufferHeight;
    tempCamera.frustum.width = tempCamera.positionCartographic.height;

    tempCamera.lookAtTransform(transform, offset);

    expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11);
    expect(tempCamera.direction).toEqualEpsilon(
      Cartesian3.negate(
        Cartesian3.normalize(offset, new Cartesian3()),
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.right).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.direction,
        Cartesian3.UNIT_Z,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );
    expect(tempCamera.up).toEqualEpsilon(
      Cartesian3.cross(
        tempCamera.right,
        tempCamera.direction,
        new Cartesian3()
      ),
      CesiumMath.EPSILON11
    );

    expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(
      CesiumMath.EPSILON14
    );
    expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(
      CesiumMath.EPSILON14
    );

    expect(tempCamera.frustum.width).toEqual(
      Cartesian3.magnitude(tempCamera.position)
    );
  });

  it("lookAtTransform throws when morphing", function () {
    camera.update(SceneMode.MORPHING);

    expect(function () {
      camera.lookAtTransform(Matrix4.IDENTITY, Cartesian3.UNIT_X);
    }).toThrowDeveloperError();
  });

  it("setView rectangle in 3D (1)", function () {
    var rectangle = new Rectangle(
      -Math.PI,
      -CesiumMath.PI_OVER_TWO,
      Math.PI,
      CesiumMath.PI_OVER_TWO
    );
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(14680290.639204923, 0.0, 0.0),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON10);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.UNIT_Y,
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in 3D (2)", function () {
    var rectangle = new Rectangle(
      CesiumMath.toRadians(21.25),
      CesiumMath.toRadians(41.23),
      CesiumMath.toRadians(21.51),
      CesiumMath.toRadians(41.38)
    );
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(4481555.454147325, 1754498.0086281248, 4200627.581953675),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(
        -0.6995046749050446,
        -0.27385124912628594,
        -0.6600747708691498
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(
        -0.6146504879783901,
        -0.2406314209863035,
        0.7511999047271233
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.right).toEqualEpsilon(
      new Cartesian3(-0.36455176232452213, 0.9311831251617939, 0),
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in 3D (3)", function () {
    var rectangle = new Rectangle(
      CesiumMath.toRadians(90.0),
      CesiumMath.toRadians(-50.0),
      CesiumMath.toRadians(157.0),
      CesiumMath.toRadians(0.0)
    );
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(-6017603.25625715, 9091606.78076493, -5075070.862292178),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(
        0.49978034145251155,
        -0.7550857289433265,
        0.42434084442077485
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(
        -0.2342094064143758,
        0.35385181388649406,
        0.905502538790623
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.right).toEqualEpsilon(
      new Cartesian3(-0.8338858220671682, -0.5519369853120581, 0),
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in 3D (4)", function () {
    var rectangle = new Rectangle(
      CesiumMath.toRadians(90.0),
      CesiumMath.toRadians(-62.0),
      CesiumMath.toRadians(174.0),
      CesiumMath.toRadians(-4.0)
    );
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(-7307919.685704952, 8116267.060310548, -7085995.891547672),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(
        0.5602119862713765,
        -0.6221784429103113,
        0.5468605998017956
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(
        -0.3659211647391443,
        0.40639662500016843,
        0.8372236764356468
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.right).toEqualEpsilon(
      new Cartesian3(-0.7431448254773944, -0.6691306063588581, 0),
      CesiumMath.EPSILON10
    );
  });

  it("views rectangle in 3D across IDL", function () {
    var rectangle = new Rectangle(
      0.1,
      -CesiumMath.PI_OVER_TWO,
      -0.1,
      CesiumMath.PI_OVER_TWO
    );
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(-14680290.639204923, 0.0, 0.0),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      Cartesian3.UNIT_X,
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON10);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in 2D with larger longitude", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.left = -10.0;
    frustum.right = 10.0;
    frustum.bottom = -10.0;
    frustum.top = 10.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_TWO,
      -CesiumMath.PI_OVER_FOUR,
      CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_FOUR
    );
    var projection = new GeographicProjection();
    var edge = projection.project(
      new Cartographic(CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_FOUR)
    );
    var expected = Math.max(edge.x, edge.y);

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;
    camera.setView({ destination: rectangle });

    expect(camera.position.x).toEqual(0);
    expect(camera.position.y).toEqual(0);
    expect(frustum.right - expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.left + expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.top - expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.bottom + expected <= CesiumMath.EPSILON14).toEqual(true);
  });

  it("setView rectangle in 2D with larger latitude", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.left = -10.0;
    frustum.right = 10.0;
    frustum.bottom = -10.0;
    frustum.top = 10.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_FOUR,
      -CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_FOUR,
      CesiumMath.PI_OVER_TWO
    );
    var projection = new GeographicProjection();
    var edge = projection.project(
      new Cartographic(CesiumMath.PI_OVER_FOUR, CesiumMath.PI_OVER_TWO)
    );
    var expected = Math.max(edge.x, edge.y);

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;
    camera.setView({ destination: rectangle });

    expect(camera.position.x).toEqual(0);
    expect(camera.position.y).toEqual(0);
    expect(frustum.right - expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.left + expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.top - expected <= CesiumMath.EPSILON14).toEqual(true);
    expect(frustum.bottom + expected <= CesiumMath.EPSILON14).toEqual(true);
  });

  it("setView rectangle in Columbus View", function () {
    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_TWO,
      -CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO
    );
    var projection = new GeographicProjection();
    camera._mode = SceneMode.COLUMBUS_VIEW;
    camera._projection = projection;
    camera.setView({ destination: rectangle });
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, 23137321.67119748),
      CesiumMath.EPSILON8
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, -1.0),
      CesiumMath.EPSILON2
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(0.0, 1.0, 0.0),
      CesiumMath.EPSILON2
    );
    expect(camera.right).toEqualEpsilon(
      new Cartesian3(1.0, 0.0, 0.0),
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in 3D with orthographic frustum", function () {
    camera.setView({
      destination: Cartesian3.fromDegrees(-75.0, 42.0, 100.0),
    });

    camera.frustum = new OrthographicFrustum();
    camera.frustum.aspectRatio = 1135 / 630;
    camera.frustum.width = camera.positionCartographic.height;

    // force update of off-center frustum
    expect(camera.frustum.projectionMatrix).toBeDefined();

    var rectangle = new Rectangle(
      CesiumMath.toRadians(21.25),
      CesiumMath.toRadians(41.23),
      CesiumMath.toRadians(21.51),
      CesiumMath.toRadians(41.38)
    );

    var projection = new GeographicProjection();
    camera._mode = SceneMode.SCENE3D;
    camera._projection = projection;
    camera.setView({ destination: rectangle });

    expect(camera.position).toEqualEpsilon(
      new Cartesian3(4489090.849577177, 1757448.0638960265, 4207738.07588144),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(
        -0.6995012374560863,
        -0.2738499033887593,
        -0.6600789719506079
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(
        -0.6146543999545513,
        -0.2406329524979527,
        0.7511962132416727
      ),
      CesiumMath.EPSILON10
    );
    expect(camera.right).toEqualEpsilon(
      new Cartesian3(-0.36455176232452197, 0.931183125161794, 0.0),
      CesiumMath.EPSILON10
    );
  });

  it("setView rectangle in Columbus view with orthographic frustum", function () {
    camera.setView({
      destination: Cartesian3.fromDegrees(-75.0, 42.0, 100.0),
    });

    camera.frustum = new OrthographicFrustum();
    camera.frustum.aspectRatio = 1135 / 630;
    camera.frustum.width = camera.positionCartographic.height;

    // force update of off-center frustum
    expect(camera.frustum.projectionMatrix).toBeDefined();

    var rectangle = new Rectangle(
      CesiumMath.toRadians(21.25),
      CesiumMath.toRadians(41.23),
      CesiumMath.toRadians(21.51),
      CesiumMath.toRadians(41.38)
    );

    var projection = new GeographicProjection();
    camera._mode = SceneMode.COLUMBUS_VIEW;
    camera._projection = projection;
    camera.setView({ destination: rectangle });

    expect(camera.position).toEqualEpsilon(
      new Cartesian3(2380010.713160189, 4598051.567216165, 28943.06760625122),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, -1.0),
      CesiumMath.EPSILON10
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON10);
    expect(camera.right).toEqualEpsilon(
      Cartesian3.UNIT_X,
      CesiumMath.EPSILON10
    );
  });

  it("getRectangleCameraCoordinates throws without rectangle", function () {
    expect(function () {
      camera.getRectangleCameraCoordinates();
    }).toThrowDeveloperError();
  });

  it("getRectangleCameraCoordinates rectangle in 3D", function () {
    var rectangle = new Rectangle(
      -Math.PI,
      -CesiumMath.PI_OVER_TWO,
      Math.PI,
      CesiumMath.PI_OVER_TWO
    );
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);
    camera._mode = SceneMode.SCENE3D;
    camera.getRectangleCameraCoordinates(rectangle, position);
    expect(position).toEqualEpsilon(
      new Cartesian3(14680290.639204923, 0.0, 0.0),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqual(direction);
    expect(camera.up).toEqual(up);
    expect(camera.right).toEqual(right);
  });

  it("gets coordinates for rectangle in 3D across IDL", function () {
    var rectangle = new Rectangle(
      0.1,
      -CesiumMath.PI_OVER_TWO,
      -0.1,
      CesiumMath.PI_OVER_TWO
    );
    var position = new Cartesian3();
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);
    camera._mode = SceneMode.SCENE3D;
    position = camera.getRectangleCameraCoordinates(rectangle);
    expect(position).toEqualEpsilon(
      new Cartesian3(-14680290.639204923, 0.0, 0.0),
      CesiumMath.EPSILON6
    );
    expect(camera.direction).toEqual(direction);
    expect(camera.up).toEqual(up);
    expect(camera.right).toEqual(right);
  });

  it("views rectangle in 2D with larger latitude", function () {
    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_FOUR,
      -CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_FOUR,
      CesiumMath.PI_OVER_TWO
    );
    var projection = new GeographicProjection();
    var cam = new Camera(scene);
    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = 1.0;
    frustum.left = -1.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    frustum.near = 1.0;
    frustum.far = 2.0;
    cam.frustum = frustum;
    var z = cam.position.z;

    cam._mode = SceneMode.SCENE2D;
    cam._projection = projection;
    camera.position = cam.getRectangleCameraCoordinates(rectangle);

    expect(camera.position.x).toEqual(0);
    expect(camera.position.y).toEqual(0);
    expect(camera.position.z).not.toEqual(z);

    expect(cam.frustum.left).toEqual(-1.0);
    expect(cam.frustum.far).toEqual(2.0);
  });

  it("gets coordinates for rectangle in Columbus View", function () {
    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_TWO,
      -CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO
    );
    var projection = new GeographicProjection();
    camera._mode = SceneMode.COLUMBUS_VIEW;
    camera._projection = projection;
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);
    camera.position = camera.getRectangleCameraCoordinates(rectangle);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0.0, 0.0, 23137321.67119748),
      CesiumMath.EPSILON8
    );
    expect(camera.direction).toEqual(direction);
    expect(camera.up).toEqual(up);
    expect(camera.right).toEqual(right);
  });

  it("get rectangle coordinate returns camera position if scene mode is morphing", function () {
    var rectangle = new Rectangle(
      -CesiumMath.PI_OVER_TWO,
      -CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO,
      CesiumMath.PI_OVER_TWO
    );
    var projection = new GeographicProjection();
    camera._mode = SceneMode.MORPHING;
    camera._projection = projection;
    var position = Cartesian3.clone(camera.position);
    var direction = Cartesian3.clone(camera.direction);
    var up = Cartesian3.clone(camera.up);
    var right = Cartesian3.clone(camera.right);
    camera.getRectangleCameraCoordinates(rectangle, camera.position);
    expect(camera.position).toEqual(position);
    expect(camera.direction).toEqual(direction);
    expect(camera.up).toEqual(up);
    expect(camera.right).toEqual(right);
  });

  it("pick ellipsoid throws without a position", function () {
    expect(function () {
      camera.pickEllipsoid();
    }).toThrowDeveloperError();
  });

  it("pick ellipsoid returns undefined if height is 0", function () {
    scene.canvas.clientHeight = 0;
    expect(camera.pickEllipsoid(Cartesian2.ZERO)).toBeUndefined();
  });

  it("pick ellipsoid returns undefined if width is 0", function () {
    scene.canvas.clientWidth = 0;
    expect(camera.pickEllipsoid(Cartesian2.ZERO)).toBeUndefined();
  });

  it("pick ellipsoid", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var maxRadii = ellipsoid.maximumRadius;

    camera.position = Cartesian3.multiplyByScalar(
      Cartesian3.UNIT_X,
      2.0 * maxRadii,
      new Cartesian3()
    );
    camera.direction = Cartesian3.normalize(
      Cartesian3.negate(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var frustum = new PerspectiveFrustum();
    frustum.fov = CesiumMath.toRadians(60.0);
    frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
    frustum.near = 100;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth * 0.5,
      scene.canvas.clientHeight * 0.5
    );
    var p = camera.pickEllipsoid(windowCoord, ellipsoid);
    var c = ellipsoid.cartesianToCartographic(p);
    expect(c).toEqual(new Cartographic(0.0, 0.0, 0.0));

    p = camera.pickEllipsoid(Cartesian2.ZERO, ellipsoid);
    expect(p).toBeUndefined();
  });

  it("pickEllipsoid works near the surface", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var minRadii = ellipsoid.minimumRadius;

    // Ten meters above the surface at the north pole, looking down.
    camera.position = new Cartesian3(0.0, 0.0, minRadii + 10.0);
    camera.direction = new Cartesian3(0.0, 0.0, -1.0);
    camera.up = new Cartesian3(1.0, 0.0, 0.0);
    camera.right = new Cartesian3(0.0, 1.0, 0.0);

    var p = camera.pickEllipsoid(Cartesian2.ZERO, ellipsoid);
    expect(p.z).toEqualEpsilon(minRadii, 1e-4);
  });

  it("pick map in 2D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);
    var maxRadii = ellipsoid.maximumRadius;

    camera.position = new Cartesian3(0.0, 0.0, 2.0 * maxRadii);
    camera.direction = Cartesian3.normalize(
      Cartesian3.negate(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Y);

    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = maxRadii * Math.PI;
    frustum.left = -frustum.right;
    frustum.top =
      frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
    frustum.bottom = -frustum.top;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;

    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth * 0.5,
      scene.canvas.clientHeight * 0.5
    );
    var p = camera.pickEllipsoid(windowCoord);
    var c = ellipsoid.cartesianToCartographic(p);
    expect(c).toEqual(new Cartographic(0.0, 0.0, 0.0));
  });

  it("pick rotated map in 2D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);
    var maxRadii = ellipsoid.maximumRadius;

    camera.position = new Cartesian3(0.0, 0.0, 2.0 * maxRadii);
    camera.direction = Cartesian3.normalize(
      Cartesian3.negate(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Y);

    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = maxRadii * Math.PI;
    frustum.left = -frustum.right;
    frustum.top =
      frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
    frustum.bottom = -frustum.top;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;

    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth * 0.5,
      scene.canvas.clientHeight * 0.5 + 1.0
    );
    var p = camera.pickEllipsoid(windowCoord);
    var c = ellipsoid.cartesianToCartographic(p);
    expect(c.longitude).toEqual(0.0);
    expect(c.latitude).toBeLessThan(0.0);

    camera.up = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    camera.right = Cartesian3.clone(Cartesian3.UNIT_Y);

    p = camera.pickEllipsoid(windowCoord);
    c = ellipsoid.cartesianToCartographic(p);
    expect(c.latitude).toEqual(0.0);
    expect(c.longitude).toBeGreaterThan(0.0);
  });

  it("pick map in columbus view", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var maxRadii = ellipsoid.maximumRadius;

    camera.position = Cartesian3.multiplyByScalar(
      Cartesian3.normalize(new Cartesian3(0.0, -1.0, 1.0), new Cartesian3()),
      5.0 * maxRadii,
      new Cartesian3()
    );
    camera.direction = Cartesian3.normalize(
      Cartesian3.subtract(Cartesian3.ZERO, camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.right = Cartesian3.normalize(
      Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.cross(
      camera.right,
      camera.direction,
      new Cartesian3()
    );

    var frustum = new PerspectiveFrustum();
    frustum.fov = CesiumMath.toRadians(60.0);
    frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    camera.update(SceneMode.COLUMBUS_VIEW);

    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth * 0.5,
      scene.canvas.clientHeight * 0.5
    );
    var p = camera.pickEllipsoid(windowCoord);
    var c = ellipsoid.cartesianToCartographic(p);
    expect(c).toEqual(new Cartographic(0.0, 0.0, 0.0));

    p = camera.pickEllipsoid(Cartesian2.ZERO);
    expect(p).toBeUndefined();
  });

  it("pick map in morph", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var maxRadii = ellipsoid.maximumRadius;

    camera.position = Cartesian3.multiplyByScalar(
      Cartesian3.UNIT_X,
      2.0 * maxRadii,
      new Cartesian3()
    );
    camera.direction = Cartesian3.normalize(
      Cartesian3.negate(camera.position, new Cartesian3()),
      new Cartesian3()
    );
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var frustum = new PerspectiveFrustum();
    frustum.fov = CesiumMath.toRadians(60.0);
    frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
    frustum.near = 100;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    camera.update(SceneMode.MORPHING);

    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth * 0.5,
      scene.canvas.clientHeight * 0.5
    );
    var p = camera.pickEllipsoid(windowCoord);
    expect(p).toBeUndefined();
  });

  it("get pick ray throws without a position", function () {
    expect(function () {
      camera.getPickRay();
    }).toThrowDeveloperError();
  });

  it("get pick ray perspective", function () {
    var windowCoord = new Cartesian2(
      scene.canvas.clientWidth / 2,
      scene.canvas.clientHeight
    );
    var ray = camera.getPickRay(windowCoord);

    var windowHeight =
      camera.frustum.near * Math.tan(camera.frustum.fovy * 0.5);
    var expectedDirection = Cartesian3.normalize(
      new Cartesian3(0.0, -windowHeight, -1.0),
      new Cartesian3()
    );
    expect(ray.origin).toEqual(camera.position);
    expect(ray.direction).toEqualEpsilon(
      expectedDirection,
      CesiumMath.EPSILON15
    );
  });

  it("get pick ray orthographic in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.left = -10.0;
    frustum.right = 10.0;
    frustum.bottom = -10.0;
    frustum.top = 10.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var windowCoord = new Cartesian2(
      (3.0 / 5.0) * scene.canvas.clientWidth,
      (1.0 - 3.0 / 5.0) * scene.canvas.clientHeight
    );
    var ray = camera.getPickRay(windowCoord);

    var cameraPosition = camera.position;
    var expectedPosition = new Cartesian3(
      cameraPosition.z,
      cameraPosition.x + 2.0,
      cameraPosition.y + 2.0
    );
    expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14);
    expect(ray.direction).toEqual(camera.directionWC);
  });

  it("get pick ray orthographic in 3D", function () {
    var frustum = new OrthographicFrustum();
    frustum.aspectRatio = 1.0;
    frustum.width = 20.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    // force off center frustum to update
    expect(frustum.projectionMatrix).toBeDefined();

    camera.update(SceneMode.SCENE3D);

    var windowCoord = new Cartesian2(
      (3.0 / 5.0) * scene.canvas.clientWidth,
      (1.0 - 3.0 / 5.0) * scene.canvas.clientHeight
    );
    var ray = camera.getPickRay(windowCoord);

    var cameraPosition = camera.position;
    var expectedPosition = new Cartesian3(
      cameraPosition.x + 2.0,
      cameraPosition.y + 2,
      cameraPosition.z
    );
    expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14);
    expect(ray.direction).toEqual(camera.directionWC);
  });

  it("get pick ray orthographic in Columbus view", function () {
    var frustum = new OrthographicFrustum();
    frustum.aspectRatio = 1.0;
    frustum.width = 20.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    // force off center frustum to update
    expect(frustum.projectionMatrix).toBeDefined();

    camera.update(SceneMode.COLUMBUS_VIEW);

    var windowCoord = new Cartesian2(
      (3.0 / 5.0) * scene.canvas.clientWidth,
      (1.0 - 3.0 / 5.0) * scene.canvas.clientHeight
    );
    var ray = camera.getPickRay(windowCoord);

    var cameraPosition = camera.position;
    var expectedPosition = new Cartesian3(
      cameraPosition.z,
      cameraPosition.x + 2.0,
      cameraPosition.y + 2
    );
    expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14);
    expect(ray.direction).toEqual(camera.directionWC);
  });

  it("gets magnitude in 2D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);
    var maxRadii = ellipsoid.maximumRadius;

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;

    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = maxRadii * Math.PI;
    frustum.left = -frustum.right;
    frustum.top =
      frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
    frustum.bottom = -frustum.top;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    expect(camera.getMagnitude()).toEqual(frustum.right - frustum.left);
  });

  it("gets magnitude in Columbus view", function () {
    camera._mode = SceneMode.COLUMBUS_VIEW;
    expect(camera.getMagnitude()).toEqual(camera.position.z);
  });

  it("gets magnitude in 3D", function () {
    expect(camera.getMagnitude()).toEqual(
      Cartesian3.magnitude(camera.position)
    );
  });

  it("create animation throws without a duration", function () {
    expect(function () {
      camera.createCorrectPositionTween();
    }).toThrowDeveloperError();
  });

  it("does not animate in 3D", function () {
    expect(camera.createCorrectPositionTween(0.05)).not.toBeDefined();
  });

  it("does not animate in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.near = 1.0;
    frustum.far = 2.0;
    frustum.left = -2.0;
    frustum.right = 2.0;
    frustum.top = 1.0;
    frustum.bottom = -1.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var max = scene.mapProjection.project(
      new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)
    );
    var factor = 1000.0;
    var dx = max.x * factor;
    var dy = max.y * factor;

    camera.moveUp(dy);
    camera.moveRight(dx);

    expect(camera.createCorrectPositionTween(0.05)).not.toBeDefined();
  });

  it("animates position to visible map in Columbus view", function () {
    var maxRadii = Ellipsoid.WGS84.maximumRadius;
    var frustum = new PerspectiveFrustum();
    frustum.fov = CesiumMath.toRadians(60.0);
    frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
    frustum.near = 100;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;
    camera.position = Cartesian3.multiplyByScalar(
      Cartesian3.UNIT_Z,
      maxRadii * 5.0,
      new Cartesian3()
    );

    camera.update(SceneMode.COLUMBUS_VIEW);

    var max = scene.mapProjection.project(
      new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)
    );
    var factor = 1000.0;
    var dx = max.x * factor;
    var dy = max.y * factor;
    var tweens = new TweenCollection();

    camera.moveUp(dy);
    camera.moveRight(dx);

    var correctAnimation = camera.createCorrectPositionTween(0.05);
    expect(correctAnimation).toBeDefined();
    var animation = tweens.add(correctAnimation);
    while (tweens.contains(animation)) {
      tweens.update();
    }

    expect(camera.position.x).toEqualEpsilon(max.x, CesiumMath.EPSILON6);
    expect(camera.position.y).toEqualEpsilon(max.y, CesiumMath.EPSILON6);

    camera.moveDown(dy);
    camera.moveLeft(dx);

    correctAnimation = camera.createCorrectPositionTween(0.05);
    expect(correctAnimation).toBeDefined();
    animation = tweens.add(correctAnimation);
    while (tweens.contains(animation)) {
      tweens.update();
    }

    expect(camera.position.x).toEqualEpsilon(-max.x, CesiumMath.EPSILON6);
    expect(camera.position.y).toEqualEpsilon(-max.y, CesiumMath.EPSILON6);
  });

  it("animates position to visible map in Columbus view with web mercator projection", function () {
    var projection = new WebMercatorProjection();
    var mercatorCamera = new Camera(new FakeScene(projection));
    mercatorCamera.position = Cartesian3.clone(position);
    mercatorCamera.up = Cartesian3.clone(up);
    mercatorCamera.direction = Cartesian3.clone(dir);
    mercatorCamera.right = Cartesian3.clone(right);
    mercatorCamera.minimumZoomDistance = 0.0;
    mercatorCamera.update(SceneMode.COLUMBUS_VIEW);

    var maxRadii = Ellipsoid.WGS84.maximumRadius;
    var frustum = new PerspectiveFrustum();
    frustum.fov = CesiumMath.toRadians(60.0);
    frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
    frustum.near = 100;
    frustum.far = 60.0 * maxRadii;
    mercatorCamera.frustum = frustum;
    mercatorCamera.position = Cartesian3.multiplyByScalar(
      Cartesian3.UNIT_Z,
      maxRadii * 5.0,
      new Cartesian3()
    );

    var max = projection.project(
      new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)
    );
    var factor = 1000.0;
    var dx = max.x * factor;
    var dy = max.y * factor;
    var tweens = new TweenCollection();

    mercatorCamera.moveUp(dy);
    mercatorCamera.moveRight(dx);

    var correctAnimation = mercatorCamera.createCorrectPositionTween(0.05);
    expect(correctAnimation).toBeDefined();
    var animation = tweens.add(correctAnimation);
    while (tweens.contains(animation)) {
      tweens.update();
    }

    expect(mercatorCamera.position.x).toEqualEpsilon(
      max.x,
      CesiumMath.EPSILON6
    );
    expect(mercatorCamera.position.y).toEqualEpsilon(
      max.y,
      CesiumMath.EPSILON6
    );

    mercatorCamera.moveDown(dy);
    mercatorCamera.moveLeft(dx);

    correctAnimation = mercatorCamera.createCorrectPositionTween(0.05);
    expect(correctAnimation).toBeDefined();
    animation = tweens.add(correctAnimation);
    while (tweens.contains(animation)) {
      tweens.update();
    }

    expect(mercatorCamera.position.x).toEqualEpsilon(
      -max.x,
      CesiumMath.EPSILON6
    );
    expect(mercatorCamera.position.y).toEqualEpsilon(
      -max.y,
      CesiumMath.EPSILON6
    );
  });

  it("flyTo uses CameraFlightPath", function () {
    spyOn(CameraFlightPath, "createTween").and.returnValue({
      startObject: {},
      stopObject: {},
      duration: 0.001,
    });

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 15000.0),
      orientation: {
        heading: 0,
        pitch: 1,
        roll: 2,
      },
      duration: 3,
      complete: function () {},
      cancel: function () {},
      endTransform: new Matrix4(),
      convert: true,
      maximumHeight: 100,
      pitchAdjustHeight: 101,
      flyOverLongitude: 1,
      flyOverLongitudeWeight: 20,
      easingFunction: function () {},
    };
    camera.flyTo(options);

    var args = CameraFlightPath.createTween.calls.argsFor(0);
    var passedOptions = args[1];

    expect(CameraFlightPath.createTween).toHaveBeenCalled();
    expect(args[0]).toBe(scene);
    expect(passedOptions.destination).toBe(options.destination);
    expect(passedOptions.heading).toBe(options.orientation.heading);
    expect(passedOptions.pitch).toBe(options.orientation.pitch);
    expect(passedOptions.roll).toBe(options.orientation.roll);
    expect(typeof passedOptions.complete).toBe("function"); //complete function is wrapped by camera.
    expect(passedOptions.cancel).toBe(options.cancel);
    expect(passedOptions.endTransform).toBe(options.endTransform);
    expect(passedOptions.convert).toBe(options.convert);
    expect(passedOptions.maximumHeight).toBe(options.maximumHeight);
    expect(passedOptions.easingFunction).toBe(options.easingFunction);
    expect(passedOptions.pitchAdjustHeight).toBe(options.pitchAdjustHeight);
    expect(passedOptions.flyOverLongitude).toBe(options.flyOverLongitude);
    expect(passedOptions.flyOverLongitudeWeight).toBe(
      options.flyOverLongitudeWeight
    );
  });

  it("can cancel a flight", function () {
    spyOn(CameraFlightPath, "createTween").and.returnValue({
      startObject: {},
      stopObject: {},
      duration: 0.001,
      complete: jasmine.createSpy("complete"),
      cancelTween: jasmine.createSpy("cancelTween"),
    });

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 15000.0),
    };
    camera.flyTo(options);

    expect(camera._currentFlight).toBeDefined();

    var createdTween = camera._currentFlight;
    spyOn(createdTween, "cancelTween");
    camera.cancelFlight();

    expect(createdTween.cancelTween).toHaveBeenCalled();
    expect(createdTween.complete).not.toHaveBeenCalled();
    expect(camera._currentFlight).toBeUndefined();
  });

  it("can complete a flight", function () {
    spyOn(CameraFlightPath, "createTween").and.returnValue({
      startObject: {},
      stopObject: {},
      duration: 0.001,
      complete: jasmine.createSpy("complete"),
      cancelTween: jasmine.createSpy("cancelTween"),
    });

    spyOn(camera, "setView");

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 15000.0),
      orientation: {
        heading: 1,
        pitch: 2,
        roll: 3,
      },
    };
    camera.flyTo(options);

    expect(camera._currentFlight).toBeDefined();

    var createdTween = camera._currentFlight;
    spyOn(createdTween, "cancelTween");
    camera.completeFlight();

    expect(createdTween.cancelTween).toHaveBeenCalled();
    expect(createdTween.complete).toHaveBeenCalled();
    expect(camera._currentFlight).toBeUndefined();
    expect(camera.setView).toHaveBeenCalledWith(options);
  });

  it("flyTo with heading, pitch and roll", function () {
    scene.mode = SceneMode.SCENE3D;

    var heading = CesiumMath.toRadians(180.0);
    var pitch = 0.0;
    var roll = CesiumMath.toRadians(45.0);

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
      },
      duration: 0.0,
    };
    camera.flyTo(options);

    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6);
    expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6);
  });

  it("flyTo with direction, up", function () {
    scene.mode = SceneMode.SCENE3D;

    var direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
    var up = Cartesian3.clone(Cartesian3.UNIT_Y);

    var options = {
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      orientation: {
        direction: direction,
        up: up,
      },
      duration: 0.0,
    };
    camera.flyTo(options);

    expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON6);
    expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON6);
  });

  it("flyHome works in 3D", function () {
    camera._mode = SceneMode.SCENE3D;

    var destination = Cartesian3.fromDegrees(30, 20, 1000);
    camera.setView({
      destination: destination,
    });
    camera.flyHome(0);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(
        2515865.110478756,
        -19109892.759980734,
        13550929.353715947
      ),
      CesiumMath.EPSILON8
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(
        -0.10654051334260287,
        0.8092555423939248,
        -0.5777149696185906
      ),
      CesiumMath.EPSILON8
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(
        -0.07540693517283716,
        0.5727725379670786,
        0.8162385765685121
      ),
      CesiumMath.EPSILON8
    );
  });

  it("flyHome works in 2D", function () {
    camera._mode = SceneMode.SCENE2D;

    var destination = Cartesian3.fromDegrees(30, 20, 1000);
    camera.setView({
      destination: destination,
    });
    camera.flyHome(0);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(-9183857.990445068, 3896182.1777645755, 1.0),
      CesiumMath.EPSILON8
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(0, 0, -1),
      CesiumMath.EPSILON8
    );
    expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON8);
  });

  it("flyHome works in CV", function () {
    var sq2Over2 = Math.sqrt(2) * 0.5;
    camera._mode = SceneMode.COLUMBUS_VIEW;

    var destination = Cartesian3.fromDegrees(30, 20, 1000);
    camera.setView({
      destination: destination,
    });
    camera.flyHome(0);
    expect(camera.position).toEqualEpsilon(
      new Cartesian3(0, -22550119.620184112, 22550119.62018411),
      CesiumMath.EPSILON8
    );
    expect(camera.direction).toEqualEpsilon(
      new Cartesian3(0, sq2Over2, -sq2Over2),
      CesiumMath.EPSILON8
    );
    expect(camera.up).toEqualEpsilon(
      new Cartesian3(0, sq2Over2, sq2Over2),
      CesiumMath.EPSILON8
    );
  });

  it("viewBoundingSphere", function () {
    scene.mode = SceneMode.SCENE3D;

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10000.0
    );
    camera.viewBoundingSphere(sphere);
    camera._setTransform(Matrix4.IDENTITY);

    var distance = Cartesian3.distance(camera.position, sphere.center);
    expect(distance).toBeGreaterThan(sphere.radius);
    expect(distance).toBeLessThan(sphere.radius * 3.0);
  });

  it("viewBoundingSphere with offset", function () {
    scene.mode = SceneMode.SCENE3D;

    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 15.0;

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10.0
    );
    camera.viewBoundingSphere(
      sphere,
      new HeadingPitchRange(heading, pitch, range)
    );
    camera._setTransform(Matrix4.IDENTITY);

    var distance = Cartesian3.distance(camera.position, sphere.center);
    expect(distance).toEqualEpsilon(range, CesiumMath.EPSILON10);
    expect(camera.heading).toEqualEpsilon(heading, CesiumMath.EPSILON6);
    expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON5);
  });

  it("viewBoundingSphere does not modify offset.range when it is zero", function () {
    scene.mode = SceneMode.SCENE3D;

    var heading = CesiumMath.toRadians(45.0);
    var pitch = CesiumMath.toRadians(-45.0);
    var range = 0.0;
    var offset = new HeadingPitchRange(heading, pitch, range);

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10.0
    );
    camera.viewBoundingSphere(sphere, offset);
    camera._setTransform(Matrix4.IDENTITY);

    expect(offset.heading).toEqual(CesiumMath.toRadians(45.0));
    expect(offset.pitch).toEqual(CesiumMath.toRadians(-45.0));
    expect(offset.range).toEqual(0.0);
  });

  it("viewBoundingSphere in 2D", function () {
    var frustum = new OrthographicOffCenterFrustum();
    frustum.left = -10.0;
    frustum.right = 10.0;
    frustum.bottom = -10.0;
    frustum.top = 10.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE2D);

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10000.0
    );
    camera.viewBoundingSphere(sphere);
    camera._setTransform(Matrix4.IDENTITY);

    var distance = frustum.right - frustum.left;
    expect(distance).toBeGreaterThan(sphere.radius);
    expect(distance).toBeLessThan(sphere.radius * 3.0);
  });

  it("viewBoundingSphere in 3D with orthographic", function () {
    var frustum = new OrthographicFrustum();
    frustum.aspectRatio = 1.0;
    frustum.width = 20.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    camera.update(SceneMode.SCENE3D);

    // force off center update
    expect(frustum.projectionMatrix).toBeDefined();

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10000.0
    );
    camera.viewBoundingSphere(sphere);
    camera._setTransform(Matrix4.IDENTITY);

    var distance = frustum.width;
    expect(distance).toBeGreaterThan(sphere.radius);
    expect(distance).toBeLessThan(sphere.radius * 3.0);
  });

  it("viewBoundingSphere throws when morphing", function () {
    camera._mode = SceneMode.MORPHING;

    expect(function () {
      camera.viewBoundingSphere(new BoundingSphere());
    }).toThrowDeveloperError();
  });

  it("viewBoundingSphere throws without bounding sphere", function () {
    camera._mode = SceneMode.MORPHING;

    expect(function () {
      camera.viewBoundingSphere(undefined);
    }).toThrowDeveloperError();
  });

  it("flyToBoundingSphere uses CameraFlightPath", function () {
    spyOn(CameraFlightPath, "createTween").and.returnValue({
      startObject: {},
      stopObject: {},
      duration: 0.0,
    });

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      100000.0
    );
    camera.flyToBoundingSphere(sphere);

    expect(CameraFlightPath.createTween).toHaveBeenCalled();
  });

  it("flyToBoundingSphere uses CameraFlightPath", function () {
    scene.mode = SceneMode.SCENE3D;

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10000.0
    );
    camera.flyToBoundingSphere(sphere, {
      duration: 0.0,
    });

    var distance = Cartesian3.distance(camera.position, sphere.center);
    expect(distance).toBeGreaterThan(sphere.radius);
    expect(distance).toBeLessThan(sphere.radius * 3.0);
  });

  it("flyToBoundingSphere does not zoom closer than minimumZoomDistance", function () {
    scene.mode = SceneMode.SCENE3D;
    var minValue = 1000;
    scene.screenSpaceCameraController.minimumZoomDistance = minValue;

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      10.0
    );

    camera.flyToBoundingSphere(sphere, {
      duration: 0.0,
    });

    var distance = Cartesian3.distance(camera.position, sphere.center);
    expect(CesiumMath.equalsEpsilon(distance, minValue, 0.1)).toBe(true);
  });

  it("flyToBoundingSphere does not zoom further than maximumZoomDistance", function () {
    scene.mode = SceneMode.SCENE3D;
    var maxValue = 10000;
    scene.screenSpaceCameraController.maximumZoomDistance = maxValue;

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      100000
    );

    camera.flyToBoundingSphere(sphere, {
      duration: 0.0,
    });

    var distance = Cartesian3.distance(camera.position, sphere.center);
    expect(CesiumMath.equalsEpsilon(distance, maxValue, 0.1)).toBe(true);
  });

  it("flyToBoundingSphere does not modify options.offset range if it is zero ", function () {
    scene.mode = SceneMode.SCENE3D;
    var options = {
      offset: new HeadingPitchRange(0.0, -1.5, 0.0),
    };

    var sphere = new BoundingSphere(
      Cartesian3.fromDegrees(-117.16, 32.71, 0.0),
      100000.0
    );
    camera.flyToBoundingSphere(sphere, options);

    expect(options.offset.heading).toEqual(0.0);
    expect(options.offset.pitch).toEqual(-1.5);
    expect(options.offset.range).toEqual(0.0);
  });

  it("distanceToBoundingSphere", function () {
    scene.mode = SceneMode.SCENE3D;

    var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5);
    var distance = camera.distanceToBoundingSphere(sphere);
    expect(distance).toEqual(0.5);
  });

  it("distanceToBoundingSphere throws when there is no bounding sphere", function () {
    scene.mode = SceneMode.SCENE3D;

    expect(function () {
      camera.distanceToBoundingSphere();
    }).toThrowDeveloperError();
  });

  it("getPixelSize", function () {
    scene.mode = SceneMode.SCENE3D;

    var oldPixelRatio = scene.pixelRatio;
    scene.pixelRatio = 1.0;

    var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5);
    var context = scene.context;
    var drawingBufferWidth = context.drawingBufferWidth;
    var drawingBufferHeight = context.drawingBufferHeight;

    // Compute expected pixel size
    var distance = camera.distanceToBoundingSphere(sphere);
    var pixelDimensions = camera.frustum.getPixelDimensions(
      drawingBufferWidth,
      drawingBufferHeight,
      distance,
      scene.pixelRatio,
      new Cartesian2()
    );
    var expectedPixelSize = Math.max(pixelDimensions.x, pixelDimensions.y);

    var pixelSize = camera.getPixelSize(
      sphere,
      drawingBufferWidth,
      drawingBufferHeight
    );
    expect(pixelSize).toEqual(expectedPixelSize);

    scene.pixelRatio = oldPixelRatio;
  });

  it("getPixelSize throws when there is no bounding sphere", function () {
    scene.mode = SceneMode.SCENE3D;

    expect(function () {
      camera.getPixelSize();
    }).toThrowDeveloperError();
  });

  it("getPixelSize throws when there is no drawing buffer width", function () {
    scene.mode = SceneMode.SCENE3D;
    var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5);

    expect(function () {
      camera.getPixelSize(sphere);
    }).toThrowDeveloperError();
  });

  it("getPixelSize throws when there is no drawing buffer height", function () {
    scene.mode = SceneMode.SCENE3D;
    var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5);

    expect(function () {
      camera.getPixelSize(sphere, 10);
    }).toThrowDeveloperError();
  });

  it("computeViewRectangle when zoomed in", function () {
    scene.mode = SceneMode.SCENE3D;

    var position = Cartesian3.clone(Cartesian3.UNIT_X);
    Cartesian3.multiplyByScalar(position, 7000000, position);

    camera.position = position;
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var correctResult = new Rectangle(
      -0.05789100547374969,
      -0.04365869998457809,
      0.05789100547374969,
      0.04365869998457809
    );

    var rect = camera.computeViewRectangle();
    expect(rect).toEqualEpsilon(correctResult, CesiumMath.EPSILON10);
  });

  it("computeViewRectangle when zoomed in to pole", function () {
    scene.mode = SceneMode.SCENE3D;

    var position = Cartesian3.clone(Cartesian3.UNIT_Z);
    Cartesian3.multiplyByScalar(position, 7000000, position);
    camera.position = position;
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Y);
    camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var correctResult = new Rectangle(
      -CesiumMath.PI,
      1.4961779388065022,
      CesiumMath.PI,
      CesiumMath.PI_OVER_TWO
    );

    var rect = camera.computeViewRectangle();
    expect(rect).toEqual(correctResult);
  });

  it("computeViewRectangle when zoomed in to IDL", function () {
    scene.mode = SceneMode.SCENE3D;

    var position = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    Cartesian3.multiplyByScalar(position, 7000000, position);
    camera.position = position;
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.direction = Cartesian3.clone(Cartesian3.UNIT_X, new Cartesian3());
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var correctResult = new Rectangle(
      3.0837016481160435,
      -0.04365869998457809,
      -3.0837016481160435,
      0.04365869998457809
    );

    var rect = camera.computeViewRectangle();
    expect(rect).toEqualEpsilon(correctResult, CesiumMath.EPSILON10);
  });

  it("computeViewRectangle when zoomed out", function () {
    scene.mode = SceneMode.SCENE3D;

    var position = Cartesian3.clone(Cartesian3.UNIT_X);
    Cartesian3.multiplyByScalar(position, 25000000, position);

    camera.position = position;
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3());
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var rect = camera.computeViewRectangle();
    expect(rect).toEqual(Rectangle.MAX_VALUE);
  });

  it("computeViewRectangle when globe isn't visible", function () {
    scene.mode = SceneMode.SCENE3D;

    var position = Cartesian3.clone(Cartesian3.UNIT_X);
    Cartesian3.multiplyByScalar(position, 7000000, position);

    camera.position = position;
    camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
    camera.direction = Cartesian3.clone(Cartesian3.UNIT_X);
    camera.right = Cartesian3.cross(
      camera.direction,
      camera.up,
      new Cartesian3()
    );

    var rect = camera.computeViewRectangle();
    expect(rect).not.toBeDefined();
  });

  it("flyTo rectangle in 2D", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    camera._mode = SceneMode.SCENE2D;

    var frustum = new OrthographicOffCenterFrustum();
    frustum.left = -10.0;
    frustum.right = 10.0;
    frustum.bottom = -10.0;
    frustum.top = 10.0;
    frustum.near = 1.0;
    frustum.far = 21.0;
    camera.frustum = frustum;

    var west = 0.3323436621771766;
    var south = 0.8292930502744068;
    var east = 0.3325710961342694;
    var north = 0.8297059734014236;
    var rectangle = new Rectangle(west, south, east, north);

    var expectedDestination = camera.getRectangleCameraCoordinates(rectangle);
    camera.flyTo({ destination: rectangle });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("flyTo rectangle in CV", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    camera._mode = SceneMode.COLUMBUS_VIEW;

    var west = 0.3323436621771766;
    var south = 0.8292930502744068;
    var east = 0.3325710961342694;
    var north = 0.8297059734014236;
    var rectangle = new Rectangle(west, south, east, north);

    var expectedDestination = camera.getRectangleCameraCoordinates(rectangle);
    camera.flyTo({ destination: rectangle });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("flyTo rectangle in 3D", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    camera._mode = SceneMode.SCENE3D;

    var west = 0.3323436621771766;
    var south = 0.8292930502744068;
    var east = 0.3325710961342694;
    var north = 0.8297059734014236;
    var rectangle = new Rectangle(west, south, east, north);

    var expectedDestination = camera.getRectangleCameraCoordinates(rectangle);
    camera.flyTo({ destination: rectangle });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("flyTo does not zoom closer than minimumZoomDistance", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    scene.mode = SceneMode.SCENE3D;
    scene.screenSpaceCameraController.minimumZoomDistance = 1000;
    scene.screenSpaceCameraController.maximumZoomDistance = 10000;

    var sourceDestination = Cartesian3.fromDegrees(-117.16, 32.71, 100);
    var expectedDestination = Cartesian3.clone(sourceDestination);
    expectedDestination.z = Cartesian3.fromDegrees(-117.16, 32.71, 1000).z;

    camera.flyTo({ destination: sourceDestination });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("flyTo does not zoom further than maximumZoomDistance", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    scene.mode = SceneMode.SCENE3D;
    scene.screenSpaceCameraController.minimumZoomDistance = 1000;
    scene.screenSpaceCameraController.maximumZoomDistance = 10000;

    var sourceDestination = Cartesian3.fromDegrees(-117.16, 32.71, 100000);
    var expectedDestination = Cartesian3.clone(sourceDestination);
    expectedDestination.z = Cartesian3.fromDegrees(-117.16, 32.71, 10000).z;

    camera.flyTo({ destination: sourceDestination });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("flyTo zooms in between minimumZoomDistance and maximumZoomDistance", function () {
    var tweenSpy = spyOn(CameraFlightPath, "createTween").and.returnValue({});
    spyOn(scene.tweens, "add");

    scene.mode = SceneMode.SCENE3D;
    scene.screenSpaceCameraController.minimumZoomDistance = 1000;
    scene.screenSpaceCameraController.maximumZoomDistance = 10000;

    var sourceDestination = Cartesian3.fromDegrees(-117.16, 32.71, 5000);
    var expectedDestination = Cartesian3.clone(sourceDestination);

    camera.flyTo({ destination: sourceDestination });

    expect(
      tweenSpy.calls
        .mostRecent()
        .args[1].destination.equalsEpsilon(expectedDestination, 0.1)
    ).toBe(true);
  });

  it("_currentFlight is not set for a flight that doesn't go anywhere", function () {
    var complete = jasmine.createSpy("complete");
    spyOn(CameraFlightPath, "createTween").and.returnValue({
      complete: complete,
      duration: 0,
    });
    spyOn(scene.tweens, "add");

    camera.flyTo({
      complete: complete,
      destination: Cartesian3.fromDegrees(-117.16, 32.71, 5000),
    });
    expect(complete).toHaveBeenCalled();
    expect(camera._currentFlight).toBeUndefined();
  });

  it("switches projections", function () {
    expect(camera.frustum).toBeInstanceOf(PerspectiveFrustum);
    camera.switchToOrthographicFrustum();
    expect(camera.frustum).toBeInstanceOf(OrthographicFrustum);
    camera.switchToPerspectiveFrustum();
    expect(camera.frustum).toBeInstanceOf(PerspectiveFrustum);
  });

  it("does not switch projection in 2D", function () {
    var ellipsoid = Ellipsoid.WGS84;
    var projection = new GeographicProjection(ellipsoid);
    var maxRadii = ellipsoid.maximumRadius;

    camera._mode = SceneMode.SCENE2D;
    camera._projection = projection;

    var frustum = new OrthographicOffCenterFrustum();
    frustum.right = maxRadii * Math.PI;
    frustum.left = -frustum.right;
    frustum.top =
      frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
    frustum.bottom = -frustum.top;
    frustum.near = 0.01 * maxRadii;
    frustum.far = 60.0 * maxRadii;
    camera.frustum = frustum;

    expect(camera.frustum).toBeInstanceOf(OrthographicOffCenterFrustum);
    camera.switchToOrthographicFrustum();
    expect(camera.frustum).toBeInstanceOf(OrthographicOffCenterFrustum);
    camera.switchToPerspectiveFrustum();
    expect(camera.frustum).toBeInstanceOf(OrthographicOffCenterFrustum);
  });

  it("normalizes WC members", function () {
    var transform = Matrix4.fromScale(new Cartesian3(2, 2, 2));
    camera.lookAtTransform(transform);
    expect(Cartesian3.magnitude(camera.directionWC)).toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON15
    );
    expect(Cartesian3.magnitude(camera.rightWC)).toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON15
    );
    expect(Cartesian3.magnitude(camera.upWC)).toEqualEpsilon(
      1.0,
      CesiumMath.EPSILON15
    );
  });

  it("get camera deltas", function () {
    camera._updateCameraChanged();
    expect(camera.positionWCDeltaMagnitude).toEqual(0);
    expect(camera.positionWCDeltaMagnitudeLastFrame).toEqual(0);

    camera.moveUp(moveAmount);

    camera._updateCameraChanged();
    expect(camera.positionWCDeltaMagnitude).toEqualEpsilon(
      moveAmount,
      CesiumMath.EPSILON10
    );
    expect(camera.positionWCDeltaMagnitudeLastFrame).toEqual(0);

    camera._updateCameraChanged();
    expect(camera.positionWCDeltaMagnitude).toEqual(0);
    expect(camera.positionWCDeltaMagnitudeLastFrame).toEqualEpsilon(
      moveAmount,
      CesiumMath.EPSILON10
    );
  });
});
