Creating a camera flyover effect

2,615 views
Skip to first unread message

Joseph Barrus

unread,
Apr 29, 2015, 5:22:35 PM4/29/15
to cesiu...@googlegroups.com
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

Hyper Sonic

unread,
Apr 29, 2015, 10:58:47 PM4/29/15
to cesiu...@googlegroups.com
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.

Mike LP

unread,
Apr 30, 2015, 2:18:16 PM4/30/15
to cesiu...@googlegroups.com
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.  

Hyper Sonic

unread,
Apr 30, 2015, 2:22:43 PM4/30/15
to cesiu...@googlegroups.com
Indeed, track an invisible object at 0 range using CZML might work well for setting up a camera route.

Joseph Barrus

unread,
May 1, 2015, 6:53:10 PM5/1/15
to cesiu...@googlegroups.com
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

Joseph Barrus

unread,
May 1, 2015, 8:35:32 PM5/1/15
to cesiu...@googlegroups.com
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......

Hyper Sonic

unread,
May 2, 2015, 12:41:41 AM5/2/15
to cesiu...@googlegroups.com
"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

Joseph Barrus

unread,
May 2, 2015, 3:14:41 PM5/2/15
to cesiu...@googlegroups.com
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

Joseph Barrus

unread,
May 2, 2015, 5:35:57 PM5/2/15
to cesiu...@googlegroups.com
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 = [];
    this.load = function(positions){
        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);
            positions[i].heading = Cesium.Math.toRadians(positions[i].heading);
            positions[i].pitch = Cesium.Math.toRadians(positions[i].pitch);
            this.tourArray[positions[i].id] = positions[i];
        }
    }
    this.load(positions);    
    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({
                heading : geodesic.startHeading,
            });
            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({
                            heading : geodesic.startHeading,
                            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({
                        heading : incGeodesic.startHeading,
                        pitch : thisTour.tourArray[thisTour.nextPosition].pitch,
                        roll : thisTour.tourArray[thisTour.nextPosition].roll,                        
                    });                
                    camera.moveForward(thisTour.moveIncrement);
                    distanceCovered = distanceCovered+thisTour.moveIncrement;

                }
            }
            viewer.clock.onTick.addEventListener(thisTour.flightListener);        
            

    };
    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.heading = viewer.scene.camera.heading;
            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 : {
                    heading: this.tourArray[this.nextPosition].heading,
                    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 : {
                    heading: this.startPosition.heading,
                    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, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":2, 
        "name":"", 
        "longitude":-117.3492408, 
        "latitude":33.1782388, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":3, 
        "name":"", 
        "longitude":-117.3460436, 
        "latitude":33.1801246, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":4, 
        "name":"", 
        "longitude":-117.3431897, 
        "latitude":33.1810046, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":-0.1
    },
    {   "id":5, 
        "name":"", 
        "longitude":-117.3400569, 
        "latitude":33.1811662, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":6, 
        "name":"", 
        "longitude":-117.3364305, 
        "latitude":33.1811303, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":7, 
        "name":"", 
        "longitude":-117.3293495, 
        "latitude":33.1812021, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":8, 
        "name":"El Camino Real and SR78", 
        "longitude":-117.3269892, 
        "latitude":33.1816331, 
        "height":100.0, 
        "heading":50.0, 
        "pitch":-15.0, 
        "roll":0.0
    },
    {   "id":9, 
        "name":"", 
        "longitude":-117.3227835, 
        "latitude":33.1822258, 
        "height":100.0, 
        "heading":50.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();

Joseph Barrus

unread,
May 2, 2015, 6:57:38 PM5/2/15
to cesiu...@googlegroups.com
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 

Hyper Sonic

unread,
May 2, 2015, 11:18:55 PM5/2/15
to cesiu...@googlegroups.com
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
viewer.clock.onTick.addEventListener(function(clock)
{
        var position = entity.position.getValue(clock.currentTime);
        viewer.scene.camera.position = position.clone();
 
        viewer.scene.camera.setView({
        heading : 0,
        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();
                   
Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(currentPos, nadir);
                    CC3
.negate(nadir,nadir); //zenith to nadir
                   
var scalar = CC3.dot(camera.direction,nadir);
                    CC3
.multiplyByScalar(nadir,scalar,temp);
                    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.

Joseph Barrus

unread,
May 3, 2015, 6:01:31 PM5/3/15
to cesiu...@googlegroups.com
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

Hyper Sonic

unread,
May 3, 2015, 6:23:42 PM5/3/15
to cesiu...@googlegroups.com
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.)

Joseph Barrus

unread,
May 3, 2015, 8:17:39 PM5/3/15
to cesiu...@googlegroups.com
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 = [];
    this.load = function(positions){
        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);
            positions[i].heading = Cesium.Math.toRadians(positions[i].heading);
            positions[i].pitch = Cesium.Math.toRadians(positions[i].pitch);
            this.tourArray[positions[i].id] = positions[i];
        }
    }
    this.load(positions);    
    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({
                heading : geodesic.startHeading,
            });
            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);
                        var amount = geodesic.startHeading-camera.heading;
                        lookInc = amount/geodesic.surfaceDistance*thisTour.moveIncrement;

                        camera.setView({
                            //heading : geodesic.startHeading,
                            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({
                        //heading : incGeodesic.startHeading,
                        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();
                    Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(currentPos, nadir);
                    CC3.negate(nadir,nadir); //zenith to nadir
                    var scalar = CC3.dot(camera.direction,nadir);
                    CC3.multiplyByScalar(nadir,scalar,temp);
                    CC3.subtract(camera.direction,temp,temp); //remove downward component
                    viewer.scene.camera.move(temp,thisTour.moveIncrement);
                    }
                    
                    camera.moveUp(heightInc);
var positions = {"cameraPositions":[{"id":1,"name":"","longitude":-117.3506784,"latitude":33.1773947,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":2,"name":"","longitude":-117.34990600000002,"latitude":33.1778617,"height":30.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":3,"name":"","longitude":-117.34814640000002,"latitude":33.1789033,"height":40.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":4,"name":"","longitude":-117.3465586,"latitude":33.1798372,"height":50.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":5,"name":"","longitude":-117.3457861,"latitude":33.1801964,"height":60.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":6,"name":"","longitude":-117.34492780000001,"latitude":33.1805556,"height":70.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":7,"name":"","longitude":-117.3441124,"latitude":33.1808429,"height":80.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":8,"name":"","longitude":-117.34342580000002,"latitude":33.1810225,"height":90.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":9,"name":"","longitude":-117.34273909999999,"latitude":33.1810584,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":10,"name":"","longitude":-117.3418379,"latitude":33.1810584,"height":90.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":11,"name":"","longitude":-117.32986449999999,"latitude":33.1812021,"height":80.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":12,"name":"","longitude":-117.3293066,"latitude":33.1812021,"height":70.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":13,"name":"","longitude":-117.32883449999999,"latitude":33.1813099,"height":60.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":14,"name":"","longitude":-117.32836250000001,"latitude":33.1813458,"height":50.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":15,"name":"","longitude":-117.3279333,"latitude":33.1814535,"height":40.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":16,"name":"","longitude":-117.32741830000002,"latitude":33.1815254,"height":30.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":17,"name":"","longitude":-117.3252296,"latitude":33.1820282,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":18,"name":"","longitude":-117.3244143,"latitude":33.1822078,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":19,"name":"","longitude":-117.3238134,"latitude":33.1822437,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":20,"name":"","longitude":-117.32325550000002,"latitude":33.1822796,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":21,"name":"","longitude":-117.32274060000002,"latitude":33.1822437,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":22,"name":"","longitude":-117.3223114,"latitude":33.1821719,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":23,"name":"","longitude":-117.32188220000002,"latitude":33.1821,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":24,"name":"","longitude":-117.321496,"latitude":33.1819923,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":25,"name":"","longitude":-117.31677529999999,"latitude":33.1806274,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":26,"name":"","longitude":-117.31634620000001,"latitude":33.1805556,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":27,"name":"","longitude":-117.31595989999998,"latitude":33.1805197,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":28,"name":"","longitude":-117.31553079999999,"latitude":33.1804478,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":29,"name":"","longitude":-117.3109388,"latitude":33.1798013,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":30,"name":"","longitude":-117.31038090000001,"latitude":33.1797295,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":31,"name":"","longitude":-117.30986600000001,"latitude":33.1797295,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":32,"name":"","longitude":-117.309351,"latitude":33.1797295,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":33,"name":"","longitude":-117.3088789,"latitude":33.1798731,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":34,"name":"","longitude":-117.3084068,"latitude":33.1799091,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},{"id":35,"name":"","longitude":-117.30780600000001,"latitude":33.1800527,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0},]};

Joseph Barrus

unread,
May 3, 2015, 8:22:05 PM5/3/15
to cesiu...@googlegroups.com
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

Hyper Sonic

unread,
May 3, 2015, 8:37:22 PM5/3/15
to cesiu...@googlegroups.com
(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. 

Joseph Barrus

unread,
May 4, 2015, 1:56:21 AM5/4/15
to cesiu...@googlegroups.com
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

Joseph Barrus

unread,
May 4, 2015, 3:06:25 AM5/4/15
to cesiu...@googlegroups.com
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 
axisCartesian3The 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

Hyper Sonic

unread,
May 4, 2015, 3:23:45 AM5/4/15
to cesiu...@googlegroups.com
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

Joseph Barrus

unread,
May 4, 2015, 3:42:45 AM5/4/15
to cesiu...@googlegroups.com
I ended up making it work by setting roll directly in SetView().  

Thanks!
Joe

Joseph Barrus

unread,
May 4, 2015, 7:33:19 PM5/4/15
to cesiu...@googlegroups.com
Hyper,

Ok I need your help again.  I thought everything was great until I started creating more complex paths or changing pitch.  Then I realized my move wasn't working properly.  I think this is because it is a factor of the camera.direction.  For instance, if I face the camera downward at 90 degrees toward the earth, the camera does not move.  I think what I need is to incrementally rotate along the Great Circle between coordinates.  This way I can move the camera between points independently of how I set the camera heading, pitch and roll much like a camera on a satellite orbiting the earth.  But I just don't have a handle on all this spatial math yet and my brain is getting tired.  Perhaps you can give me another hand here.

I want to move from Point A to Point B where A and B are coordinates on the Earth surface.  Along the way I want to be able to control the camera to look left/right, up/down and move it up and down the normal.  I think I have a handle on how to implement the camera controls.  However, I am unable to figure out how to rotate the camera along the Great Circle incrementally between A and B.  As you have seen I have set my increments to be 1 meter or some multiple of that, so I think I need to compute an angle that is equal to an arc of that length as well as what axis on which to rotate.  The EllipsoidGeodesic gives me the geodesic I need but I can't figure out how to rotate along it.

I appreciate a little nudge here.

Thanks!
Joe

Hyper Sonic

unread,
May 5, 2015, 12:32:07 AM5/5/15
to cesiu...@googlegroups.com
I know I said this earlier but moving exactly along a EllipsoidGeodesic is complicated: along latitude it's circular, along longitude it's elliptical. Move diagonally and it's a hybrid of the two! As if that weren't enough: as you move diagonally along a constant direction, heading changes because constant direction != constant heading except for special cases (specifically along the equator and along longitudes.)

If such exactness isn't required moving along circular arcs works good enough (after all Earth's semi-minor axis is 99.66% the length of its semi-major axis.) Actually aircraft don't even fly along exact geodesics, they are affected by variations of the GeoID and atmospheric conditions. I'd start by converting both points A and B to Cartesian. Here's some code that should help. To change radius normalize camera.position and move along that vector. 

//declarations
var CC3=Cesium.Cartesian3;
var o=new CC3(0,0,0);
var oB=new CC3();var oBunit=new CC3();
var oA=new CC3();var oAunit=new CC3();
var rotater=new CC3();

//determine rotater, the axis you rotate around. +angle is from A to B
CC3
.subtract(B,o,oB);
CC3
.subtract(A,o,oA);
CC3
.normalize(oB,oBunit);
CC3
.normalize(oA,oAunit);
CC3
.cross(oAunit,oBunit,rotater);

//determine angle amount to rotate
var adj = CC3.dot(oBunit,oAunit);
var angle = Math.acos(adj);

//determine radius change from A to B
var magB=CC3.magnitude(oB);
var magA=CC3.magnitude(oA);
var change = magB-magA;

If instead of knowing points A and B you know point A and the partial circumference you want move.
revolutions = partial_circumference / 2*Math.PI*r
radians = revolutions *2*Math.PI
so
radians = partial_circumference / r

Joseph Barrus

unread,
May 5, 2015, 1:28:20 AM5/5/15
to cesiu...@googlegroups.com
Thanks I appreciate this!  I'll see if I can make sense of it. :)  But regarding moving along EllipsoidGeodesics, you you did say this.  But the paths I'm following are at most a few miles long with lots of variations, so I think approximating a circle will do just fine to simulate a drone flight.   I'll be sure to share the code when I'm finished.  

Thanks!
Joe


Hyper Sonic

unread,
May 5, 2015, 1:18:49 PM5/5/15
to cesiu...@googlegroups.com
Actually I think I may have had a misconception about geodesics. By definition a geodesic is: "of, relating to, or denoting the shortest possible line between two points on a sphere or other curved surface."

That does not necessarily means a line of constant direction. For example, walking around the base of a hill could be less distance than walking over the hill with a constant direction. Since the Earth is slighly bulged near the Equator it's shorter to walk slightly around that bulge than to walk directly over it. However that bulge is so slight there isn't that much difference.

You can see how slight in Google Earth, use the measuring tool to measure from anti-meridian/equator to prime-meridian/equator. The geodesic line updates as you move the cursor. Notice that it doesn't start curving around toward the poles until you get very close.

Joseph Barrus

unread,
May 5, 2015, 8:21:38 PM5/5/15
to cesiu...@googlegroups.com
Interesting.  Well, it works good enough for me.  :)  And as promised, below is my finished code (for now).  It works pretty well.  You can control, height, pitch, roll, yaw or zoom as you move along the path and each transition will be smooth.  The level of control and the speed of each transition can be managed by the number and distance of established points along the path since each transition is designed to use the entire time between points to make the transition.  I have a couple small issues I'll eventually get to.  Zoom can throw me off the path, I assume because Zoom actually moves the position of the camera.  I also had some problem with Yaw and it might be when combined with pitch.  Somehow after applying the Yaw it gets the camera out of whack (like it's roll stays skewed).  Not sure what the issue is there.  I created a beach run for a demo below.  I threw in arbitrary and crude camera commands to show them off.  It's hard since I don't have a way to relate the points I exported from Google Earth to it's physical location after the fact so I can go in and add camera commands.  Right now it's trial and error.  Maybe soon I'll build my own editor to so that I can visually add camera commands at each point.  :)

One question I do have.  I tried the Terrain Provider below (currently commented out), but it's minimu height is higher than the default provider.  Can I set that to be lower?  Would it be viewer.scene.screenSpaceCameraController.minimumZoomDistance like you mentioned previously (about when I was scraping along the surface).?

Below is the code. I wrote a little routine that will convert the path string copied out of the Google Earth KML file into my custom objects for input.  I pasted that routine first.  You can just paste that into Sandcastle too and copy the output from the console.

Enjoy!
Joe


var input = "-117.3544594443053,33.19032879482207,0 -117.3529133632621,33.18967647907626,0 -117.3523860774533,33.188296775106,0 -117.352980795485,33.18733232134633,0 -117.3545952133418,33.18728432028313,0 -117.355771525032,33.1886704537332,0 -117.3557786891534,33.18994254100041,0 -117.3544594443053,33.19032879482207,0";

var firstA = input.split(" ");
var secondA = [];
for (var i = 0; i < firstA.length; i++){
    secondA[i] = firstA[i].split(",");
}
var cameraPositions = '{"cameraPositions":[';
for (var i = 1; i < secondA.length+1; i++){
     cameraPositions = cameraPositions+'{"id":'+i+',"name":"","longitude":'+secondA[i-1][0]+',"latitude":'+secondA[i-1][1]+',"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},'
}
cameraPositions = cameraPositions+"]}";
console.log(cameraPositions);


____________________________________________________________________________________________________


var widget = new Cesium.CesiumWidget('cesiumContainer', {
//    terrainProvider : new Cesium.CesiumTerrainProvider({
//        url : '//assets.agi.com/stk-terrain/world'
//    })
});

var VirtualTour = function(positions){
    var CC3=Cesium.Cartesian3;    
    this.tourArray = [];
    this.load = function(positions){
        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);
            positions[i].heading = Cesium.Math.toRadians(positions[i].heading);
            positions[i].pitch = Cesium.Math.toRadians(positions[i].pitch);
            positions[i].roll = Cesium.Math.toRadians(positions[i].roll);
            positions[i].yaw = Cesium.Math.toRadians(positions[i].yaw);            
            this.tourArray[positions[i].id] = positions[i];
        }
    }
    this.load(positions);    
    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 = widget.scene.camera;
            var heightInc = 0;
            var cameraHeadingInc = 0;
            var lookUpInc = 0;   
            var rollLeftInc = 0;   
            var rollCompleted = 0;
            var angleInc = 0;
            var radiusChangeInc = 0;
            var zoomInc = 0;
            var yawInc = 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({
                heading : geodesic.startHeading,
            });
            var incGeodesic = new Cesium.EllipsoidGeodesic();
            var distanceCovered = 0;
            var thisTour = this;
            var rotater=new CC3();
            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);
                        var cameraHeadingAmount = Cesium.Math.zeroToTwoPi(geodesic.endHeading)-camera.heading;
                        if (cameraHeadingAmount < 0 && (-cameraHeadingAmount) > Cesium.Math.PI ){cameraHeadingAmount = Cesium.Math.PI*2-camera.heading+geodesic.endHeading};
                        if (cameraHeadingAmount > Cesium.Math.PI ){cameraHeadingAmount = -(camera.heading-geodesic.endHeading)};
                        
                        cameraHeadingInc = cameraHeadingAmount/geodesic.surfaceDistance*thisTour.moveIncrement;
                        lookUpInc = (thisTour.tourArray[thisTour.nextPosition].pitch - thisTour.tourArray[thisTour.nextPosition-1].pitch)/geodesic.surfaceDistance*thisTour.moveIncrement;
                        rollLeftInc = (thisTour.tourArray[thisTour.nextPosition].roll - thisTour.tourArray[thisTour.nextPosition-1].roll)/geodesic.surfaceDistance*thisTour.moveIncrement;
                        zoomInc = (thisTour.tourArray[thisTour.nextPosition].zoom - thisTour.tourArray[thisTour.nextPosition-1].zoom)/geodesic.surfaceDistance*thisTour.moveIncrement;
                        yawInc = (thisTour.tourArray[thisTour.nextPosition].yaw - thisTour.tourArray[thisTour.nextPosition-1].yaw)/geodesic.surfaceDistance*thisTour.moveIncrement;
                        console.log ("from "+(thisTour.nextPosition-1)+" to "+thisTour.nextPosition);

                        console.log("cam "+camera.heading);
                        console.log("geo "+geodesic.endHeading);
                        console.log(cameraHeadingAmount);
                        
                        camera.setView({
                            //heading : geodesic.startHeading,
                            //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;
                        
                        //declarations

                        var o=new CC3(0,0,0);
                        var oB=new CC3();var oBunit=new CC3();
                        var oA=new CC3();var oAunit=new CC3();
                        var A = CC3.fromDegrees(thisTour.tourArray[thisTour.nextPosition].longitude,thisTour.tourArray[thisTour.nextPosition].latitude,thisTour.tourArray[thisTour.nextPosition].height);
                        var B = CC3.fromDegrees(thisTour.tourArray[thisTour.nextPosition-1].longitude,thisTour.tourArray[thisTour.nextPosition-1].latitude,thisTour.tourArray[thisTour.nextPosition-1].height);

                        //determine rotater, the axis you rotate around. +angle is from A to B
                        CC3.subtract(B,o,oB);
                        CC3.subtract(A,o,oA);
                        CC3.normalize(oB,oBunit);
                        CC3.normalize(oA,oAunit);
                        CC3.cross(oAunit,oBunit,rotater);
                        //console.log(rotater);
                        
                        //determine angle amount to rotate
                        var adj = CC3.dot(oBunit,oAunit);
                        var angle = Math.acos(adj);
                        angleInc = angle/geodesic.surfaceDistance*thisTour.moveIncrement;

                        //determine radius change from A to B
                        var magB=CC3.magnitude(oB);
                        var magA=CC3.magnitude(oA); 
                        var radiusChange = magB-magA;
                        radiusChangeInc = radiusChange/geodesic.surfaceDistance*thisTour.moveIncrement;
                    }
                }else {
                    //declarations

                    var o=new CC3(0,0,0);
                    var oB=new CC3();var oBunit=new CC3();
                    var oA=new CC3();var oAunit=new CC3();
                    var A = CC3.fromDegrees(thisTour.tourArray[thisTour.nextPosition].longitude,thisTour.tourArray[thisTour.nextPosition].latitude,thisTour.tourArray[thisTour.nextPosition].height);
                    var B = CC3.fromDegrees(thisTour.tourArray[thisTour.nextPosition-1].longitude,thisTour.tourArray[thisTour.nextPosition-1].latitude,thisTour.tourArray[thisTour.nextPosition-1].height);

                    //determine rotater, the axis you rotate around. +angle is from A to B
                    CC3.subtract(B,o,oB);
                    CC3.subtract(A,o,oA);
                    CC3.normalize(oB,oBunit);
                    CC3.normalize(oA,oAunit);
                    CC3.cross(oAunit,oBunit,rotater);
                    //console.log(rotater);

                    //determine angle amount to rotate
                    var adj = CC3.dot(oBunit,oAunit);
                    var angle = Math.acos(adj);
                    angleInc = angle/geodesic.surfaceDistance*thisTour.moveIncrement;

                    //determine radius change from A to B
                    var magB=CC3.magnitude(oB);
                    var magA=CC3.magnitude(oA); 
                    var radiusChange = magB-magA;
                    radiusChangeInc = radiusChange/geodesic.surfaceDistance*thisTour.moveIncrement;
 
                    var currentPos = camera.positionCartographic;
                    incGeodesic.setEndPoints(currentPos, cart2);
                    camera.look(Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(currentPos),cameraHeadingInc);
                    camera.lookUp(lookUpInc);
                    camera.twistRight(rollLeftInc);
                    camera.zoomIn(zoomInc);
                    camera.lookRight(yawInc);
                    
                    camera.setView({
                        //positionCartographic: geodesic.interpolateUsingSurfaceDistance(distanceCovered),
                        //heading : incGeodesic.startHeading,
                        //pitch : thisTour.tourArray[thisTour.nextPosition].pitch,
                        //roll : rollCompleted,                        
                    });   
                    /*
                    if(0)
                    {camera.moveForward(thisTour.moveIncrement);}
                    else
                    {
                    //remove vertical component from camera.direction
                    var nadir = new CC3();var temp= new CC3();
                    Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(currentPos, nadir);
                    CC3.negate(nadir,nadir); //zenith to nadir
                    var scalar = CC3.dot(camera.direction,nadir);
                    CC3.multiplyByScalar(nadir,scalar,temp);
                    CC3.subtract(camera.direction,temp,temp); //remove downward component
                    //widget.scene.camera.move(temp,thisTour.moveIncrement);
                    }
                    */
                    //camera.rotate(rotater, thisTour.moveIncrement/6371000+radiusChangeInc+heightInc);
                    camera.rotate(rotater, angleInc);
                    camera.move(Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(currentPos),heightInc);
                    distanceCovered = distanceCovered+thisTour.moveIncrement;

                }
            }
            widget.clock.onTick.addEventListener(thisTour.flightListener);        
            

    };
    this.start = function(){
        try {
            if (this.tourExecuting) return; 
            this.tourExecuting = true;
            this.nextPosition=1;
            this.startPosition = {};
            this.startPosition.destination = Cesium.Cartesian3.clone(widget.scene.camera.positionWC);
            this.startPosition.heading = widget.scene.camera.heading;
            this.startPosition.pitch = widget.scene.camera.pitch;
            this.startPosition.roll = widget.scene.camera.roll;            
            widget.scene.camera.flyTo({
                destination : this.tourArray[this.nextPosition].destination,
                duration : 5,
                complete : this.flyToRest.call(this),            
                orientation : {
                    heading: this.tourArray[this.nextPosition].heading,
                    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;
            widget.clock.onTick.removeEventListener(this.flightListener);              
            widget.scene.camera.flyTo({
                destination : this.startPosition.destination,
 
                duration : 5,
         
                orientation : {
                    heading: this.startPosition.heading,
                    pitch: this.startPosition.pitch,
                    roll: this.startPosition.roll,
                }              
            });              
     
        }catch(err) {
            this.tourExecuting = false;
        }
    }
    
}

function tourExecuting(){
    return sanMarcosTour.tourExecuting;
}


var positions = {"cameraPositions":[{"id":1,"name":"","longitude":-117.2574805017832,"latitude":32.93612414370479,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":2,"name":"","longitude":-117.2576608676146,"latitude":32.93831739821949,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":3,"name":"","longitude":-117.2579841712574,"latitude":32.93894148374235,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":4,"name":"","longitude":-117.2587435114121,"latitude":32.93942140275828,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":5,"name":"","longitude":-117.2594873755863,"latitude":32.93970836908177,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":6,"name":"","longitude":-117.2602644248514,"latitude":32.93969835583901,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":7,"name":"","longitude":-117.2610725917547,"latitude":32.94043843876157,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":8,"name":"","longitude":-117.2615265613306,"latitude":32.93757776020794,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":9,"name":"","longitude":-117.2597804456261,"latitude":32.9256440520405,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":10,"name":"","longitude":-117.2589789942027,"latitude":32.91494184362212,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":11,"name":"","longitude":-117.2562562275032,"latitude":32.9068053290119,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":12,"name":"","longitude":-117.2545063327262,"latitude":32.89179637715441,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":13,"name":"","longitude":-117.2540908126949,"latitude":32.88399252006997,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":14,"name":"","longitude":-117.2527851342648,"latitude":32.87966014623343,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":15,"name":"","longitude":-117.2522264417479,"latitude":32.87624928505539,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":16,"name":"","longitude":-117.2525859543191,"latitude":32.87336449763869,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":17,"name":"","longitude":-117.2540523482096,"latitude":32.8700631337669,"height":20.0,"heading":0.0,"pitch":0.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":18,"name":"","longitude":-117.2557263760174,"latitude":32.86552883427716,"height":100.0,"heading":0.0,"pitch":0.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":19,"name":"","longitude":-117.2576105498436,"latitude":32.86067776364092,"height":100.0,"heading":0.0,"pitch":0.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":20,"name":"","longitude":-117.2599242985757,"latitude":32.85651551476757,"height":20.0,"heading":0.0,"pitch":-90.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":21,"name":"","longitude":-117.2610728775735,"latitude":32.85413848711738,"height":100.0,"heading":0.0,"pitch":-90.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":22,"name":"","longitude":-117.2633041330431,"latitude":32.85193036086147,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":23,"name":"","longitude":-117.2655343611839,"latitude":32.85000759593054,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":24,"name":"","longitude":-117.2668491792967,"latitude":32.84963570717962,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":300.0},{"id":25,"name":"","longitude":-117.2670609902502,"latitude":32.84959368232232,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":300.0},{"id":26,"name":"","longitude":-117.2681280546488,"latitude":32.84961904027797,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":27,"name":"","longitude":-117.2693640026944,"latitude":32.85035478385458,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":28,"name":"","longitude":-117.2710194890279,"latitude":32.85125839505423,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":29,"name":"","longitude":-117.2712825206309,"latitude":32.8512955715919,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":30,"name":"","longitude":-117.2728362009425,"latitude":32.85168314049089,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":31,"name":"","longitude":-117.273182333766,"latitude":32.85177104885528,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":32,"name":"","longitude":-117.2745246645412,"latitude":32.85198971273201,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":33,"name":"","longitude":-117.2762007546314,"latitude":32.85131409915105,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":34,"name":"","longitude":-117.2764130556624,"latitude":32.85111987434836,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":35,"name":"","longitude":-117.2765769772855,"latitude":32.85094470039187,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":36,"name":"","longitude":-117.2775449716605,"latitude":32.85008884850251,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":37,"name":"","longitude":-117.2786863025526,"latitude":32.85143051498085,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":38,"name":"","longitude":-117.2791629329039,"latitude":32.85493397515227,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":39,"name":"","longitude":-117.2770596815736,"latitude":32.85615390154368,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":40,"name":"","longitude":-117.2744351014736,"latitude":32.85643090461426,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":41,"name":"","longitude":-117.2695653884658,"latitude":32.85501932547913,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":42,"name":"","longitude":-117.2693627142943,"latitude":32.85365262887459,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":43,"name":"","longitude":-117.2696617865192,"latitude":32.85311481938188,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":44,"name":"","longitude":-117.2700505997411,"latitude":32.85285553151987,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":45,"name":"","longitude":-117.2707043799224,"latitude":32.85289457277322,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":46,"name":"","longitude":-117.2711066376687,"latitude":32.85257085617489,"height":100.0,"heading":0.0,"pitch":-15.0,"roll":20.0,"yaw":30.0,"zoom":0.0},{"id":47,"name":"","longitude":-117.2714153210521,"latitude":32.85224386194295,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":48,"name":"","longitude":-117.2715787043067,"latitude":32.85206142828558,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":49,"name":"","longitude":-117.2716442232234,"latitude":32.85196335317858,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":50,"name":"","longitude":-117.2716812635401,"latitude":32.85189833237033,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":51,"name":"","longitude":-117.2717126269207,"latitude":32.85183396244378,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},{"id":52,"name":"","longitude":-117.2718703225404,"latitude":32.85154592095168,"height":20.0,"heading":0.0,"pitch":-15.0,"roll":0.0,"yaw":0.0,"zoom":0.0},]};

Hyper Sonic

unread,
May 6, 2015, 8:47:56 AM5/6/15
to cesiu...@googlegroups.com
It's progressing very nicely! I'm surprised my code snippets actually worked, I never tested them. I'm not sure what you plan to do with zoom, unless you mean FOV zoom?

Some observations:
A geodesic in the air will be longer than it's 'shadow' on the ground. As far as I can tell Cesium only calculates geodesic along the ground (height zero.) Since you move along circular arcs I'm not sure how useful the geodesic is. Circular arc distance is very easy to calculate at any radius. If the radius continuously changes on the circular arc you'd need to measure spiral distance for better accuracy.However for now I'd just replace geodesic with circular arc distance.

length = radians * radius
with an average radius
var radius = (magA+magB)/2;

You're also going need to know heading at the end of the arc:

//determine ab in terms of Earth Fixed
var Wab = new CC3;
CC3.subtract(B,A,Wab);
CC3.normalize(Wab,Wab);

//calc ENU at point B in terms of Earth Fixed
var Wup = new CC3();var Weast = new CC3();var Wnorth = new CC3();
Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(B,Wup);
CC3.cross({x:0,y:0,z:1},Wup,Weast);
CC3.cross(Wup,Weast,Wnorth); 

//determine ab in terms of local ENU
var Lab=new CC3();
Lab.x=CC3.dot(Wab,Weast);
Lab.y=CC3.dot(Wab,Wnorth);
Lab.z=CC3.dot(Wab,Wup);

//determine heading
var heading = Math.atan2(Lab.x,Lab.y);//math: y b4 x, heading: x b4 y

Joseph Barrus

unread,
May 6, 2015, 1:41:02 PM5/6/15
to cesiu...@googlegroups.com
Thanks!  It is looking pretty good and I appreciate your help.  Your snippets work great out of the box!  :)  My code can eventually for a good framework for refinement.  While I wrote this for a specific project, I might pull it out into it's own for reuse in other projects.  I'll give your circle code a shot.

This looks even better when I add a TerrainProvider.  I used '//assets.agi.com/stk-terrain/world' as I've seen in some examples.  However, it seems the minimum height is higher when I set the camera position height to 0.0.  Is there a way to set the minimum lower?  Or I can adjust the heights in the path if TerrainProvider is in place but I still haven't figured out how to determine the minimum height yet.  I'm guessing it is some sort of average elevation?   It would be nice to be able to fly over hills and down into valleys, etc.  Ideally I could simply add the current elevation data to the specified path heights at each position.  I'll have to dig into the documentation or fourms to see how to get the elevation at a current position but maybe you can point it out if you already know.

Thanks!
Joe

Joseph Barrus

unread,
May 6, 2015, 4:17:47 PM5/6/15
to cesiu...@googlegroups.com
Hyper,

Nevermind about how to get the position elevation from the terrain provider.  I figured that out from the Sandcastle terrain example.  Looks nice!  
But I still don't know how to adjust to a lower minimum?  The elevations returned by the TerrainProvider seem to be relative the the current minimum as I'm -34 meters for positions over the ocean.

Joe

Hyper Sonic

unread,
May 6, 2015, 6:08:04 PM5/6/15
to cesiu...@googlegroups.com
Height from viewer.scene.globe.getHeight is the terrain height at that lon/lat in meters from the reference ellipsoid.
Height from viewer.scene.camera.positionCartographic.height is the camera height in meters from the reference ellipsoid.

Be sure to check out https://groups.google.com/d/msg/cesium-dev/oT04Ly2pwMw/04DfVZ6ipGQJ for info regarding ellipsoid and MSL.

BTW where the surf meets the turf along the San Diego coast the terrain is typically 30-40 meters below the reference ellipsoid. Of course where the surf meets the turf along any ocean coastline is 0 meters sea level, but that's not what Cesium is measuring. I usually set viewer.scene.screenSpaceCameraController.minimumZoomDistance = 2 allowing the camera to be within 2 meters of the terrain.

Also non-mountainous tiles can disappear when the camera sinks to negative heights.

Joseph Barrus

unread,
May 7, 2015, 12:34:48 AM5/7/15
to cesiu...@googlegroups.com
That did it.  When I set the minimumZoomDistance to 0 without a terrain provider and fly along the beach, my camera height tends to float just above zero in the single digits.  When I add the terrain provider, the height goes up about 350 meters on average.  So the TP seems to be adding about 350 meters to minimum flight height.  So by setting the minimumZoomDistance to -300, I can get it to fly just a few meters above the surface and roughly follow the contours of the terrain if I set my points on my path at the various high and low points.

Thanks!
Joe 

Hyper Sonic

unread,
May 7, 2015, 1:19:18 AM5/7/15
to cesiu...@googlegroups.com
minimumZoomDistance is always in relation to the terrain surface, -300 actually allows you to bury the camera 300 meters under the surface! You can view the WGS84 Ellipsoid as a kind of terrain provider where the terrain exactly matches the ellipsoid. 

Be sure to not confuse viewer.scene.camera.positionCartographic.height with height above terrain. Camera height above terrain is actually viewer.scene.camera.positionCartographic.height minus viewer.scene.globe.getHeight at the camera position.

Yes with WGS84 Ellipsoid viewer.scene.camera.positionCartographic.height happens to also equal height above terrain simply because the terrain is always zero distance from the ellipsoid.

patmcc...@gmail.com

unread,
May 25, 2016, 12:03:36 PM5/25/16
to cesium-dev
Amazing work guys. I pasted it in and it works for me too!
Reply all
Reply to author
Forward
0 new messages