defineSuite([ 'Scene/CameraFlightPath', 'Core/Cartesian3', 'Core/Cartographic', 'Core/Math', 'Core/OrthographicOffCenterFrustum', 'Scene/SceneMode', 'Specs/createScene' ], function( CameraFlightPath, Cartesian3, Cartographic, CesiumMath, OrthographicOffCenterFrustum, SceneMode, createScene) { 'use strict'; var scene; beforeEach(function() { scene = createScene(); }); afterEach(function() { scene.destroyForSpecs(); }); function createOrthographicFrustum() { var current = scene.camera.frustum; var f = new OrthographicOffCenterFrustum(); f.near = current.near; f.far = current.far; var tanTheta = Math.tan(0.5 * current.fovy); f.top = f.near * tanTheta; f.bottom = -f.top; f.right = current.aspectRatio * f.top; f.left = -f.right; return f; } it('create animation throws without a scene', function() { expect(function() { CameraFlightPath.createTween(undefined, { destination : new Cartesian3(1e9, 1e9, 1e9) }); }).toThrowDeveloperError(); }); it('create animation throws without a destination', function() { expect(function() { CameraFlightPath.createTween(scene, {}); }).toThrowDeveloperError(); }); it('creates an animation', function() { var destination = new Cartesian3(1e9, 1e9, 1e9); var duration = 5.0; var complete = function() { }; var cancel = function() { }; var flight = CameraFlightPath.createTween(scene, { destination : destination, duration : duration, complete : complete, cancel: cancel }); expect(flight.duration).toEqual(duration); expect(typeof flight.complete).toEqual('function'); expect(typeof flight.cancel).toEqual('function'); expect(typeof flight.update).toEqual('function'); expect(flight.startObject).toBeDefined(); expect(flight.stopObject).toBeDefined(); expect(flight.easingFunction).toBeDefined(); }); it('creates an animation in 3d', function() { var camera = scene.camera; var startPosition = Cartesian3.clone(camera.position); var startHeading = camera.heading; var startPitch = camera.pitch; var startRoll = camera.roll; var endPosition = Cartesian3.negate(startPosition, new Cartesian3()); var endHeading = CesiumMath.toRadians(20.0); var endPitch = CesiumMath.toRadians(-45.0); var endRoll = CesiumMath.TWO_PI; var duration = 5.0; var flight = CameraFlightPath.createTween(scene, { destination : endPosition, heading : endHeading, pitch : endPitch, roll : endRoll, duration : duration }); flight.update({ time : 0.0 }); expect(camera.position).toEqualEpsilon(startPosition, CesiumMath.EPSILON12); expect(camera.heading).toEqualEpsilon(startHeading, CesiumMath.EPSILON12); expect(camera.pitch).toEqualEpsilon(startPitch, CesiumMath.EPSILON12); expect(camera.roll).toEqualEpsilon(startRoll, CesiumMath.EPSILON12); flight.update({ time : duration }); expect(camera.position).toEqualEpsilon(endPosition, CesiumMath.EPSILON12); expect(camera.heading).toEqualEpsilon(endHeading, CesiumMath.EPSILON12); expect(camera.pitch).toEqualEpsilon(endPitch, CesiumMath.EPSILON12); expect(camera.roll).toEqualEpsilon(endRoll, CesiumMath.EPSILON12); }); it('creates an animation in Columbus view', function() { scene._mode = SceneMode.COLUMBUS_VIEW; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); var startPosition = Cartesian3.clone(camera.position); var projection = scene.mapProjection; var destination = Cartesian3.add(startPosition, new Cartesian3(-6e5 * Math.PI, 6e5 * CesiumMath.PI_OVER_FOUR, 100.0), new Cartesian3()); var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(destination)); var duration = 5.0; var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration }); flight.update({ time : 0.0 }); expect(camera.position).toEqualEpsilon(startPosition, CesiumMath.EPSILON12); flight.update({ time : duration }); expect(camera.position).toEqualEpsilon(destination, CesiumMath.EPSILON4); }); it('creates an animation in 2D', function() { scene._mode = SceneMode.SCENE2D; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); camera.frustum = createOrthographicFrustum(); var startHeight = camera.frustum.right - camera.frustum.left; var startPosition = Cartesian3.clone(camera.position); var projection = scene.mapProjection; var destination = Cartesian3.add(startPosition, new Cartesian3(-6e6 * Math.PI, 6e6 * CesiumMath.PI_OVER_FOUR, 100.0), new Cartesian3()); var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(destination)); var duration = 5.0; var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration }); flight.update({ time : 0.0 }); expect(camera.position).toEqualEpsilon(startPosition, CesiumMath.EPSILON12); expect(camera.frustum.right - camera.frustum.left).toEqualEpsilon(startHeight, CesiumMath.EPSILON7); flight.update({ time : duration }); expect(camera.position.x).toEqualEpsilon(destination.x, CesiumMath.EPSILON7); expect(camera.position.y).toEqualEpsilon(destination.y, CesiumMath.EPSILON7); expect(camera.position.z).toEqualEpsilon(startPosition.z, CesiumMath.EPSILON7); expect(camera.frustum.right - camera.frustum.left).toEqualEpsilon(destination.z, CesiumMath.EPSILON7); }); it('creates a path where the start and end points only differ in height', function() { var camera = scene.camera; var start = Cartesian3.clone(camera.position); var mag = Cartesian3.magnitude(start); var end = Cartesian3.multiplyByScalar(Cartesian3.normalize(start, new Cartesian3()), mag - 1000000.0, new Cartesian3()); var duration = 3.0; var flight = CameraFlightPath.createTween(scene, { destination : end, duration : duration }); flight.update({ time : 0.0 }); expect(camera.position).toEqualEpsilon(start, CesiumMath.EPSILON12); flight.update({ time : duration }); expect(camera.position).toEqualEpsilon(end, CesiumMath.EPSILON12); }); it('does not create a path to the same point', function() { var camera = scene.camera; camera.position = new Cartesian3(7000000.0, 0.0, 0.0); var startPosition = Cartesian3.clone(camera.position); var startHeading= camera.heading; var startPitch = camera.pitch; var startRoll = camera.roll; var duration = 3.0; var flight = CameraFlightPath.createTween(scene, { destination : startPosition, heading : startHeading, pitch : startPitch, roll: startRoll, duration : duration }); expect(flight.duration).toEqual(0); expect(camera.position).toEqual(startPosition); expect(camera.heading).toEqual(startHeading); expect(camera.pitch).toEqual(startPitch); expect(camera.roll).toEqual(startRoll); }); it('creates an animation with 0 duration', function() { var destination = new Cartesian3(1e9, 1e9, 1e9); var duration = 0.0; var complete = function() { return true; }; var flight = CameraFlightPath.createTween(scene, { destination : destination, duration : duration, complete : complete }); expect(flight.duration).toEqual(duration); expect(flight.complete).not.toEqual(complete); expect(flight.update).toBeUndefined(); expect(scene.camera.position).not.toEqual(destination); flight.complete(); expect(scene.camera.position).toEqualEpsilon(destination, CesiumMath.EPSILON14); }); it('duration is 0 when destination is the same as camera position in 2D', function() { scene._mode = SceneMode.SCENE2D; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); camera.frustum = createOrthographicFrustum(); camera.update(scene.mode); var frustum = camera.frustum; var destination = Cartesian3.clone(camera.position); destination.z = Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom); var projection = scene.mapProjection; var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(destination)); var flight = CameraFlightPath.createTween(scene, { destination : endPosition }); expect(flight.duration).toEqual(0.0); }); it('duration is 0 when destination is the same as camera position in 3D', function() { scene._mode = SceneMode.SCENE3D; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.setView({ orientation: { heading: 0, pitch: -CesiumMath.PI_OVER_TWO, roll: 0 } }); camera.frustum = createOrthographicFrustum(); var flight = CameraFlightPath.createTween(scene, { destination : camera.position }); expect(flight.duration).toEqual(0.0); }); it('duration is 0 when destination is the same as camera position in CV', function() { scene._mode = SceneMode.COLUMBUS_VIEW; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.setView({ orientation: { heading: 0, pitch: -CesiumMath.PI_OVER_TWO, roll: 0 } }); var projection = scene.mapProjection; var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(camera.position)); var flight = CameraFlightPath.createTween(scene, { destination : endPosition }); expect(flight.duration).toEqual(0.0); }); it('creates an animation in 2D 0 duration', function() { scene._mode = SceneMode.SCENE2D; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); camera.frustum = createOrthographicFrustum(); camera.update(scene.mode); var startPosition = Cartesian3.clone(camera.position); var projection = scene.mapProjection; var destination = Cartesian3.add(startPosition, new Cartesian3(-6e5 * Math.PI, 6e5 * CesiumMath.PI_OVER_FOUR, 100.0), new Cartesian3()); var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(destination)); var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 0.0 }); expect(typeof flight.complete).toEqual('function'); flight.complete(); expect(camera.position.x).toEqualEpsilon(destination.x, CesiumMath.EPSILON7); expect(camera.position.y).toEqualEpsilon(destination.y, CesiumMath.EPSILON7); expect(camera.frustum.right - camera.frustum.left).toEqualEpsilon(destination.z, CesiumMath.EPSILON7); }); it('creates an animation in Columbus view 0 duration', function() { scene._mode = SceneMode.COLUMBUS_VIEW; var camera = scene.camera; camera.position = new Cartesian3(0.0, 0.0, 1000.0); camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); var startPosition = Cartesian3.clone(camera.position); var projection = scene.mapProjection; var destination = Cartesian3.add(startPosition, new Cartesian3(-6e6 * Math.PI, 6e6 * CesiumMath.PI_OVER_FOUR, 100.0), new Cartesian3()); var endPosition = projection.ellipsoid.cartographicToCartesian(projection.unproject(destination)); var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 0.0 }); expect(typeof flight.complete).toEqual('function'); flight.complete(); expect(camera.position).toEqualEpsilon(destination, CesiumMath.EPSILON8); }); it('creates an animation in 3d 0 duration', function() { var camera = scene.camera; var startPosition = Cartesian3.clone(camera.position); var endPosition = Cartesian3.negate(startPosition, new Cartesian3()); var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 0.0 }); expect(typeof flight.complete).toEqual('function'); flight.complete(); expect(camera.position).toEqualEpsilon(endPosition, CesiumMath.EPSILON12); }); it('creates animation to hit flyOverLongitude', function() { var camera = scene.camera; var projection = scene.mapProjection; var position = new Cartographic(); camera.position = Cartesian3.fromDegrees(10.0, 45.0, 1000.0); var endPosition = Cartesian3.fromDegrees(20.0, 45.0, 1000.0); var overLonFlight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 1.0, flyOverLongitude: CesiumMath.toRadians(0.0) }); var directFlight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 1.0 }); expect(typeof overLonFlight.update).toEqual('function'); expect(typeof directFlight.update).toEqual('function'); overLonFlight.update({ time : 0.3 }); projection.ellipsoid.cartesianToCartographic(camera.position, position); var lon = CesiumMath.toDegrees(position.longitude); expect(lon).toBeLessThan(10.0); directFlight.update({ time : 0.3 }); projection.ellipsoid.cartesianToCartographic(camera.position, position); lon = CesiumMath.toDegrees(position.longitude); expect(lon).toBeGreaterThan(10.0); expect(lon).toBeLessThan(20.0); }); it('uses flyOverLongitudeWeight', function() { var camera = scene.camera; var projection = scene.mapProjection; var position = new Cartographic(); camera.position = Cartesian3.fromDegrees(10.0, 45.0, 1000.0); var endPosition = Cartesian3.fromDegrees(50.0, 45.0, 1000.0); var overLonFlightSmallWeight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 1.0, flyOverLongitude: CesiumMath.toRadians(0.0), flyOverLongitudeWeight: 2 }); var overLonFlightBigWeight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : 1.0, flyOverLongitude: CesiumMath.toRadians(0.0), flyOverLongitudeWeight: 20 }); overLonFlightBigWeight.update({ time : 0.3 }); projection.ellipsoid.cartesianToCartographic(camera.position, position); var lon = CesiumMath.toDegrees(position.longitude); expect(lon).toBeLessThan(10.0); overLonFlightSmallWeight.update({ time : 0.3 }); projection.ellipsoid.cartesianToCartographic(camera.position, position); lon = CesiumMath.toDegrees(position.longitude); expect(lon).toBeGreaterThan(10.0); expect(lon).toBeLessThan(50.0); }); it('adjust pitch if camera flyes higher than pitchAdjustHeight', function(){ var camera = scene.camera; var duration = 5.0; camera.setView({ destination : Cartesian3.fromDegrees(-20.0, 0.0, 1000.0), orientation: { heading : CesiumMath.toRadians(0.0), pitch : CesiumMath.toRadians(-15.0), roll : 0.0 } }); var startPitch = camera.pitch; var endPitch = CesiumMath.toRadians(-45.0); var flight = CameraFlightPath.createTween(scene, { destination : Cartesian3.fromDegrees(60.0, 0.0, 2000.0), pitch : endPitch, duration : duration, pitchAdjustHeight: 2000 }); flight.update({ time: 0.0 }); expect(camera.pitch).toEqualEpsilon(startPitch, CesiumMath.EPSILON6); flight.update({ time : duration }); expect(camera.pitch).toEqualEpsilon(endPitch, CesiumMath.EPSILON6); flight.update({ time : duration / 2.0 }); expect(camera.pitch).toEqualEpsilon(-CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON4); }); it('animation with flyOverLongitude is smooth over two pi', function() { var camera = scene.camera; var duration = 100.0; var projection = scene.mapProjection; var position = new Cartographic(); var startLonDegrees = 10.0; var endLonDegrees = 20.0; camera.position = Cartesian3.fromDegrees(startLonDegrees, 45.0, 1000.0); var endPosition = Cartesian3.fromDegrees(endLonDegrees, 45.0, 1000.0); var outsideTwoPiFlight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration, flyOverLongitude: CesiumMath.toRadians(0.0) }); var prevLon = startLonDegrees; var crossedDateChangesLine = 0; for(var t = 1; t < duration; t++) { outsideTwoPiFlight.update({ time: t }); projection.ellipsoid.cartesianToCartographic(camera.position, position); var lon = CesiumMath.toDegrees(position.longitude); var d = lon - prevLon; if (d > 0) { expect(prevLon).toBeLessThan(-90); crossedDateChangesLine ++; d -= 360; } prevLon = lon; expect(d).toBeLessThan(0); } expect(crossedDateChangesLine).toEqual(1); }); it('animation with flyOverLongitude is smooth', function() { var camera = scene.camera; var duration = 100.0; var projection = scene.mapProjection; var position = new Cartographic(); var startLonDegrees = -100.0; var endLonDegrees = 100.0; camera.position = Cartesian3.fromDegrees(startLonDegrees, 45.0, 1000.0); var endPosition = Cartesian3.fromDegrees(endLonDegrees, 45.0, 1000.0); var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration, flyOverLongitude: CesiumMath.toRadians(0.0) }); var prevLon = startLonDegrees; for(var t = 1; t < duration; t++) { flight.update({ time: t }); projection.ellipsoid.cartesianToCartographic(camera.position, position); var lon = CesiumMath.toDegrees(position.longitude); var d = lon - prevLon; prevLon = lon; expect(d).toBeGreaterThan(0); } }); it('does not go above the maximum height', function() { var camera = scene.camera; var startPosition = Cartesian3.fromDegrees(0.0, 0.0, 1000.0); var endPosition = Cartesian3.fromDegrees(10.0, 0.0, 1000.0); var duration = 5.0; camera.setView({ destination : startPosition }); var flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration }); var maximumHeight = Number.NEGATIVE_INFINITY; var i; for (i = 0; i <= duration; ++i) { flight.update({ time : i }); maximumHeight = Math.max(maximumHeight, camera.positionCartographic.height); } maximumHeight *= 0.5; camera.setView({ destination : startPosition }); flight = CameraFlightPath.createTween(scene, { destination : endPosition, duration : duration, maximumHeight : maximumHeight }); for (i = 0; i <= duration; ++i) { flight.update({ time : i }); expect(camera.positionCartographic.height).toBeLessThan(maximumHeight); } }); }, 'WebGL');