# Creating a camera flyover effect

Hi,

I looked through the forum and found some topics related but I feel like I don’t have a handle on whether this is possible or not.

I’d like to create a flyover by issuing a series of camera commands to move the camera from position A to B to C, etc. What I am trying to create is something similar to what you would see if you attached a camera to a drone and say had that drone fly at 100ft off the ground and follow the contours of a highway for instance at a specified heading and pitch (most importantly keeping the pitch constant). I tried to create it by chaining camera.flyto() commands, however, each flyto() command causes the camera to reposition itself looking down at the earth before ending in the specified destination and orientation.

What is the best way to achieve this effect? Is there a way to initiate each flyto() command to a set position, heading, pitch and roll (such as keeping it at what it is currently set) to create a smooth flight or is flyto() meant to always create an effect of moving away and then flying back in?

If not flyto, can I achieve the same desired effects through as series of Move commands, etc.? I like the flyto function as it allows me to set a duration of each flight. Can I control duration of a Move command?

Thanks

Joe

The camera commands are simply distances along or around vectors. If you want follow the contouring of the terrain you’re going to have to program all of that logic using those basic camera controls. In its current form flyTo won’t do what you ask.

Have you looked into CZML? You should be able to setup a object with the desired path then just make the camera’s position locked onto the object loaded.

Indeed, track an invisible object at 0 range using CZML might work well for setting up a camera route.

no, for now not looking to follow terrain, just simply fly at a certain height. But I was able to create a framework using camera move and setView commands that takes an array of ordered coordinates/camera angles. I then calculate the distance between each coordinate using EllipsoidGeodesic and issue camera move commands on some distance increment (say 1 meter) set to some time interval using SetInterval() until I reach that point, then do it again until I work through the array. I continuously update heading using EllipsoidGeodesic as it moves along as well as pitch and roll to create flight effects. Works pretty well, but I’ve run into a couple issues that are troubling me.

First, I want to return the camera to the position and view it had when the flight started after the flight is over. However, I can’t figure out to do that. How do I capture the camera’s initial coordinates? When I capture camera.position and use that as the destination in a flyTo command, it doesn’t work. Looking closer, it doesn’t appear that position is where it stores it’s current location as it doesn’t appear to change during the flight? How do I get the camera to return to it’s initial state before the flight began?

Also, as you mentioned, the move commands follow the vector, so since my pitch is downward, it will fly to the lowest point and then move along the surface after that point. Any suggestions on how to keep it at a certain height?

Perhaps the tracking suggestion might work. If I were to create object and lock the camera to look at that object at some fixed point and then simply update that object’s coordinates from my array, would that move the camera along? or do I need to update the target with a series of LookAt commands? Or is there a completely different way to track an object?!! This is all very new and very confusing to me but that means I just need to dig into it more. I’ll have to try that and see how that works.

thanks

Joe

Just another note. I want to be able to control the camera angles and such as it flies along, so tracking an object with a path might not work unless I can issue camera commands as it moves along. Still digging…

“I want to be able to control the camera angles and such as it flies along, so tracking an object with a path might not work unless I can issue camera commands as it moves along”

You can control the camera view when it tracks an object.

“I then calculate the distance between each coordinate using EllipsoidGeodesic and issue camera move commands on some distance increment…”

camera.move will move along a Cartesian straight line, while Geodesic is along a curve along the surface.

It’s tricky to calculate distance along the surface of an oblate ellipsoid.

-if you move along the Equator you move along a perfect sphere

-if you move along longitude its along a perfect ellipse

-Otherwise it’s some hybrid of a circle and ellipse, and how much is circle and how much is ellipse keeps changing!

Constant direction doesn’t equal constant heading unless it’s the equator or ellipse of longitude.

Instead of SetInterval() I’d just do a clock.onTick.addEventListener which executes every frame for smooth motion.

“I continuously update heading using EllipsoidGeodesic as it moves along as well as pitch and roll to create flight effects”

In Cesium I just use camera.rotate and move along a Great circle. Back when I used Google Earth API I had to continuously adjust heading as well to stay along a Great circle course.

“How do I capture the camera’s initial coordinates?”

camera.position yields relative to the current camera.transform, while camera.positionWC yields relative to Earth’s center no matter the current transform.

“Also, as you mentioned, the move commands follow the vector, so since my pitch is downward, it will fly to the lowest point and then move along the surface after that point. Any suggestions on how to keep it at a certain height?”

Moving

Dot product camera.direction with nadir, scale that result with unitVectorNadir, subtract that result from camera.direction to get the direction your looking for. I’m glad you mentioned that, it just made me think of a better method than what I was doing previously, which was transforming to local ENU, setting .z to 0, then transforming back to Earth Fixed.

Be sure to check out this related article https://groups.google.com/forum/#!topic/cesium-dev/IQWJfs_rAEU

Thanks I’ll get on this and get back when I get it to work.

BTW I figured out why I had trouble capturing camera position. I needed to clone it! That’s what happens when aged brain meets complex problem over many hours. I usually have my epiphanies as I wake up and it’s all downhill from there!

Thanks for the help. I still have much to learn.

Joe

Hyper,

Thanks for the help. I’ve made a lot of progress. But as soon as you got to the last part about Moving, you went above my paygrade. I thought I was figuring it out but got lost on unitVectorNadir. Anyway, I thought the best thing at this point is to paste my code here and see if you can show me what you mean. It works very much like I intended except for keeping the camera constant at a certain height. There is an array of positions that are used as input to describe the path. I have height as a parameter for each position, so it would be nice for the camera to change as it moves along according to the values in the array as they change. I changed the roll in one section to “bank” the drone around a curve. This example should work in SandCastle. Please excuse any poor coding practices but feel free to improve the code if you wish. I move the camera with a moveForward command but I assume you would change that to a move(direction, amount) command based on the computed direction I’m having trouble understanding?

BTW I think this would make a nice addition to Cesium. You can call it Cesium.Drone and it takes a path as a parameter and then a .Start() function.

Thanks!

Joe

var viewer = new Cesium.Viewer(‘cesiumContainer’);

var VirtualTour = function(positions){

this.tourArray = ;

for (var i = 0; i < positions.cameraPositions.length; i++){

positions[i] = positions.cameraPositions[i];

positions[i].destination = Cesium.Cartesian3.fromDegrees(positions[i].longitude, positions[i].latitude, positions[i].height);

this.tourArray[positions[i].id] = positions[i];

}

}

this.tourExecuting = false;

this.nextPosition=1;

this.moveIncrement = 1;

this.timeInterval=10;

this.flyToWait = 6000;

this.setSpeedFactor = function(num){

this.moveIncrement = num*this.moveIncrement;

}

this.flyToRest = function(){

this.nextPosition++;

var thisTour = this;

setTimeout(function(){thisTour.flyToNext();},this.flyToWait);

}

this.flyToNext= function(){

var camera = viewer.scene.camera;

var geodesic = new Cesium.EllipsoidGeodesic();

var cart1 = Cesium.Cartographic.fromDegrees(this.tourArray[this.nextPosition-1].longitude,this.tourArray[this.nextPosition-1].latitude,this.tourArray[this.nextPosition-1].height);

var cart2 = Cesium.Cartographic.fromDegrees(this.tourArray[this.nextPosition].longitude,this.tourArray[this.nextPosition].latitude,this.tourArray[this.nextPosition].height);

geodesic.setEndPoints(cart1, cart2);

camera.setView({

});

var incGeodesic = new Cesium.EllipsoidGeodesic();

var distanceCovered = 0;

var thisTour = this;

this.flightListener = function() {

if (geodesic.surfaceDistance - distanceCovered < 1){

thisTour.nextPosition++;

if (thisTour.nextPosition === thisTour.tourArray.length){

thisTour.end();

}else{

cart1 = Cesium.Cartographic.fromDegrees(thisTour.tourArray[thisTour.nextPosition-1].longitude,thisTour.tourArray[thisTour.nextPosition-1].latitude,thisTour.tourArray[thisTour.nextPosition-1].height);

cart2 = Cesium.Cartographic.fromDegrees(thisTour.tourArray[thisTour.nextPosition].longitude,thisTour.tourArray[thisTour.nextPosition].latitude,thisTour.tourArray[thisTour.nextPosition].height);

geodesic.setEndPoints(cart1, cart2);

camera.setView({

pitch : thisTour.tourArray[thisTour.nextPosition].pitch,

roll : thisTour.tourArray[thisTour.nextPosition].roll,

});

distanceCovered = 0;

}

}else {

var currentPos = camera.positionCartographic;

incGeodesic.setEndPoints(currentPos, cart2);

camera.setView({

pitch : thisTour.tourArray[thisTour.nextPosition].pitch,

roll : thisTour.tourArray[thisTour.nextPosition].roll,

});

camera.moveForward(thisTour.moveIncrement);

distanceCovered = distanceCovered+thisTour.moveIncrement;

}

}

};

this.start = function(){

try {

if (this.tourExecuting) return;

this.tourExecuting = true;

this.nextPosition=1;

this.startPosition = {};

this.startPosition.destination = Cesium.Cartesian3.clone(viewer.scene.camera.positionWC);

this.startPosition.pitch = viewer.scene.camera.pitch;

this.startPosition.roll = viewer.scene.camera.roll;

viewer.scene.camera.flyTo({

destination : this.tourArray[this.nextPosition].destination,

duration : 5,

complete : this.flyToRest.call(this),

orientation : {

pitch: this.tourArray[this.nextPosition].pitch,

roll: this.tourArray[this.nextPosition].roll,

}

});

}catch(err) {

this.tourExecuting = false;

}

};

this.end = function(){

try {

this.nextPosition=0;

this.tourExecuting=false;

viewer.clock.onTick.removeEventListener(this.flightListener);

viewer.scene.camera.flyTo({

destination : this.startPosition.destination,

duration : 5,

orientation : {

pitch: this.startPosition.pitch,

roll: this.startPosition.roll,

}

});

}catch(err) {

this.tourExecuting = false;

}

}

}

function tourExecuting(){

return sanMarcosTour.tourExecuting;

}

var positions = {“cameraPositions”:[

{ “id”:1,

“name”:“SR78 Begin”,

“longitude”:-117.3519015,

“latitude”:33.1766404,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:2,

“name”:"",

“longitude”:-117.3492408,

“latitude”:33.1782388,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:3,

“name”:"",

“longitude”:-117.3460436,

“latitude”:33.1801246,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:4,

“name”:"",

“longitude”:-117.3431897,

“latitude”:33.1810046,

“height”:100.0,

“pitch”:-15.0,

“roll”:-0.1

},

{ “id”:5,

“name”:"",

“longitude”:-117.3400569,

“latitude”:33.1811662,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:6,

“name”:"",

“longitude”:-117.3364305,

“latitude”:33.1811303,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:7,

“name”:"",

“longitude”:-117.3293495,

“latitude”:33.1812021,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:8,

“name”:“El Camino Real and SR78”,

“longitude”:-117.3269892,

“latitude”:33.1816331,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

},

{ “id”:9,

“name”:"",

“longitude”:-117.3227835,

“latitude”:33.1822258,

“height”:100.0,

“pitch”:-15.0,

“roll”:0.0

}

]}

var sanMarcosTour = new VirtualTour(positions);

/*

var sanMarcosTour = {};

jQuery.getJSON( “resources/SanMarcosTour.json”, function( positions ) {

sanMarcosTour = new VirtualTour(positions);

});

*/

function SanMarcosTour(){

if (tourExecuting()) return;

sanMarcosTour.setSpeedFactor(2);

sanMarcosTour.start();

}

SanMarcosTour();

Also, you will notice that as the camera approaches a point where it needs to make a turn, it is very jumpy. Is there a way you know of to smooth out the camera view rotation? I’ve tried putting camera.lookRight in a loop and increment in parts but that doesn’t seem to work. What I would want is some sort of ease function as it transitions from one heading to another. Is there a function in Cesium or just Javascript that might help?

thanks

It seems that this would be perfect http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Interpolation.html&label=Showcases
enter the following at the bottom to the linked SandCastle example

{
var position = entity.position.getValue(clock.currentTime);
viewer.scene.camera.position = position.clone();

``````    viewer.scene.camera.setView({
pitch : 0,
roll : 0,
});
``````

});

``

Select LaGrange interpolation. Don’t select view aircraft as that will interfere with your camera control scheme.

You can get heading/pitch that the aircraft is traveling&heading toward using I think VelocityOrientationProperty getValue http://cesiumjs.org/Cesium/Build/Documentation/VelocityOrientationProperty.html

Which gives a Quaternion which you can somehow change to heading/pitch http://cesiumjs.org/Cesium/Build/Documentation/Quaternion.html

Your example program is good. Camera.moveForward is just a wrapper function for camera.move https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Camera.js#L1079

Since you’re pitched down you’re scraping 20 meters above the ground which is determined by viewer.scene.screenSpaceCameraController.minimumZoomDistance

You can remove the downward component with the following

``````                if(0)
{camera.moveForward(thisTour.moveIncrement);}
else
{
//remove vertical component from camera.direction
var CC3=Cesium.Cartesian3;
var nadir = new CC3();var temp= new CC3();
CC3.subtract(camera.direction,temp,temp); //remove downward component
viewer.scene.camera.move(temp,thisTour.moveIncrement);
}
``````

``

You could either use Cesium built-in pathing, or continue to refine a custom version.

Yeah that’s cool. I’ll have to look at interpolation as an option, but your last piece of code really helped. I appreciate it. Do you have any suggestions for my last concern?

"Also, you will notice that as the camera approaches a point where it needs to make a turn, it is very jumpy. Is there a way you know of to smooth out the camera view rotation? I’ve tried putting camera.lookRight in a loop and increment in parts but that doesn’t seem to work. What I would want is some sort of ease function as it transitions from one heading to another. Is there a function in Cesium or just Javascript that might help?

"

Thanks

Joe

Glad I could help! Do you always want to face the direction you’re traveling in with gradual turning? If so you’re going to have to determine a curved path to travel in, which Cesium seems to have solved with the LaGrange interpolation (Hermite has a bit of a turn snap at the sample points, but not as bad as linear.)

Actually I just figured it out! Since my program already breaks the move commands down to small increments between points, I simply commented out the heading property of the setView command and issued an incremental lookRight command so that by the time it completes turn it is heading in the right direction. I did the same thing with a moveUp command so I could have the drone change height while still moving along a plane parallel to the surface. It is a really smooth transition. I don’t know if mathematically I’m off in my heading but it looks pretty good and is good enough in my book! LOL. Paste this into SanCastle and have a look for yourself.

var viewer = new Cesium.Viewer(‘cesiumContainer’);

var VirtualTour = function(positions){

this.tourArray = ;

for (var i = 0; i < positions.cameraPositions.length; i++){

positions[i] = positions.cameraPositions[i];

positions[i].destination = Cesium.Cartesian3.fromDegrees(positions[i].longitude, positions[i].latitude, positions[i].height);

this.tourArray[positions[i].id] = positions[i];

}

}

this.tourExecuting = false;

this.nextPosition=1;

this.moveIncrement = 1;

this.timeInterval=10;

this.flyToWait = 6000;

this.setSpeedFactor = function(num){

this.moveIncrement = num*this.moveIncrement;

}

this.flyToRest = function(){

this.nextPosition++;

var thisTour = this;

setTimeout(function(){thisTour.flyToNext();},this.flyToWait);

}

this.flyToNext= function(){

var camera = viewer.scene.camera;

var heightInc = 0;

var lookInc = 0;

var geodesic = new Cesium.EllipsoidGeodesic();

var cart1 = Cesium.Cartographic.fromDegrees(this.tourArray[this.nextPosition-1].longitude,this.tourArray[this.nextPosition-1].latitude,this.tourArray[this.nextPosition-1].height);

var cart2 = Cesium.Cartographic.fromDegrees(this.tourArray[this.nextPosition].longitude,this.tourArray[this.nextPosition].latitude,this.tourArray[this.nextPosition].height);

geodesic.setEndPoints(cart1, cart2);

camera.setView({

});

var incGeodesic = new Cesium.EllipsoidGeodesic();

var distanceCovered = 0;

var thisTour = this;

this.flightListener = function() {

if (geodesic.surfaceDistance - distanceCovered < 1){

thisTour.nextPosition++;

if (thisTour.nextPosition === thisTour.tourArray.length){

thisTour.end();

}else{

cart1 = Cesium.Cartographic.fromDegrees(thisTour.tourArray[thisTour.nextPosition-1].longitude,thisTour.tourArray[thisTour.nextPosition-1].latitude,thisTour.tourArray[thisTour.nextPosition-1].height);

cart2 = Cesium.Cartographic.fromDegrees(thisTour.tourArray[thisTour.nextPosition].longitude,thisTour.tourArray[thisTour.nextPosition].latitude,thisTour.tourArray[thisTour.nextPosition].height);

geodesic.setEndPoints(cart1, cart2);

lookInc = amount/geodesic.surfaceDistance*thisTour.moveIncrement;

camera.setView({

pitch : thisTour.tourArray[thisTour.nextPosition].pitch,

roll : thisTour.tourArray[thisTour.nextPosition].roll,

});

distanceCovered = 0;

heightInc = (thisTour.tourArray[thisTour.nextPosition].height - thisTour.tourArray[thisTour.nextPosition-1].height)/geodesic.surfaceDistance*thisTour.moveIncrement

}

}else {

var currentPos = camera.positionCartographic;

incGeodesic.setEndPoints(currentPos, cart2);

camera.lookRight(lookInc);

camera.setView({

pitch : thisTour.tourArray[thisTour.nextPosition].pitch,

roll : thisTour.tourArray[thisTour.nextPosition].roll,

});

if(0)

{camera.moveForward(thisTour.moveIncrement);}

else

{

//remove vertical component from camera.direction

var CC3=Cesium.Cartesian3;

var nadir = new CC3();var temp= new CC3();

CC3.subtract(camera.direction,temp,temp); //remove downward component

viewer.scene.camera.move(temp,thisTour.moveIncrement);

}

camera.moveUp(heightInc);

distanceCovered = distanceCovered+thisTour.moveIncrement;

}

}

};

this.start = function(){

try {

if (this.tourExecuting) return;

this.tourExecuting = true;

this.nextPosition=1;

this.startPosition = {};

this.startPosition.destination = Cesium.Cartesian3.clone(viewer.scene.camera.positionWC);

this.startPosition.pitch = viewer.scene.camera.pitch;

this.startPosition.roll = viewer.scene.camera.roll;

viewer.scene.camera.flyTo({

destination : this.tourArray[this.nextPosition].destination,

duration : 5,

complete : this.flyToRest.call(this),

orientation : {

pitch: this.tourArray[this.nextPosition].pitch,

roll: this.tourArray[this.nextPosition].roll,

}

});

}catch(err) {

this.tourExecuting = false;

}

};

this.end = function(){

try {

this.nextPosition=0;

this.tourExecuting=false;

viewer.clock.onTick.removeEventListener(this.flightListener);

viewer.scene.camera.flyTo({

destination : this.startPosition.destination,

duration : 5,

orientation : {

pitch: this.startPosition.pitch,

roll: this.startPosition.roll,

}

});

}catch(err) {

this.tourExecuting = false;

}

}

}

function tourExecuting(){

return sanMarcosTour.tourExecuting;

}

var sanMarcosTour = new VirtualTour(positions);

/*

var sanMarcosTour = {};

jQuery.getJSON( “resources/SanMarcosTour.json”, function( positions ) {

sanMarcosTour = new VirtualTour(positions);

});

*/

function SanMarcosTour(){

if (tourExecuting()) return;

sanMarcosTour.setSpeedFactor(2);

sanMarcosTour.start();

}

SanMarcosTour();

Also, I am getting my coordinates from Google Maps Create Map feature by drawing a line along a path and then exporting the KML. I then have a routine that will convert that string of coordinates into my custom path structures. Do you know of any other online tool that would be better to draw a path and get coordinates especially if I could get finer grained points along my path?

thanks!

Joe

(BTW I had to press show quoted text, copy / paste, then delete where it says hide selected test once in SandCastle)

Looks very good! Eventually I plan to add a recording module to my Space Navigator plugin which would be a good source of fine grained 3D paths. But Google Earth works great for creating a 2D path quickly. Select create path, hold left mouse and draw a path, then save as KML (plain text.)

I could be wrong, but shouldn’t geodesic.startHeading be geodesic.endHeading ? Though for short distances they would be virtually identical.

I’m not sure. You are more versed than I am but I think you are probably right. For 1 meter distances they are virtually identical but endHeading will surely get you closer to the endpoint. Thanks
I’ll give GE a shot. Should be able to get an even smoother flight. I’m going to apply same technique to pitch and roll and now I’ll have a real flying drone! This will work fine until you get your recording module done.

thanks

Joe

Ok one more question. I want to apply the same technique to pitch and roll. I was fine with pitch but am uncertain how to implement roll. I thought it might be the rotate (axis, angle) function.
It says that the axis is

`axis`
Cartesian3
The axis to rotate around given in world coordinates.

But I am uncertain how to represent the the axis I need for roll in world coordinates. Can you enlighten? Is rotate the right method?

Thanks

Joe

camera.look rotates the camera’s rotation matrix around it’s own origin.
camera.rotate does the same thing but it also rotates the the camera’s position around the camera’s point of reference, usually the Earth’s center

I use camera.rotate to go around Great Circles.

//6DOF movements are very easy in Cesium

camera.move(camera.direction,amount); //move longitudinal

camera.move(camera.right,amount); //move horizontal

camera.move(camera.up,amount); //move vertical

camera.look(camera.direction,amount); //roll

camera.look(camera.right,amount); //pitch

camera.look(camera.up,amount); //yaw

I ended up making it work by setting roll directly in SetView().

Thanks!

Joe