Seeing drift when converting from inertial to fixed coordinates

1. A concise explanation of the problem you're experiencing.

I have am trying to manually position the camera along the vector formed between an entity and the sun. The camera should look through the entity towards the sun. I get all the positions in inertial coordinates and then transform to fixed-frame so the camera will be positioned correctly. You will notice in my example that the camera appears to drift over time and then "snap" back to the correct position (click "view sun" and then speed up the clock to see the effect better). The odd thing is if you click 'view aircraft' you will notice I have a line that is pointing from the entity to the sun that does not show this weird drift / reset effect

2. A minimal code example. If you've found a bug, this helps us reproduce and repair it.

var viewer = new Cesium.Viewer('cesiumContainer', {
    infoBox: false, //Disable InfoBox widget
    selectionIndicator: false, //Disable selection indicator
    shouldAnimate: true, // Enable animations
    terrainProvider: Cesium.createWorldTerrain()
});

//Enable lighting based on sun/moon positions
viewer.scene.globe.enableLighting = true;

//Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;

//Set the random number seed for consistent results.
Cesium.Math.setRandomNumberSeed(3);

//set labels for sun
var sunPositionProperty = new Cesium.CallbackProperty(function(time, result){
    Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(time, result);
     return Cesium.PositionProperty.convertToReferenceFrame(
        time,
        result,
        Cesium.ReferenceFrame.INERTIAL,
        Cesium.ReferenceFrame.FIXED,
        result);
}, false);

viewer.entities.add({
    position : sunPositionProperty,
    label : {
        text : 'Sun'
    }
});

var lineP1 = Cesium.Cartesian3.fromDegrees(-75, 35);
var lineP2 = Cesium.Cartesian3.fromDegrees(-175, 35);
var line_positions = [lineP1, lineP2];

function line_callback(time, result){
    var sun_pos = {};
    var curr_pos = {};
    entity.position.getValue(time, lineP1);
    sunPositionProperty.getValue(time, sun_pos);
    Cesium.Cartesian3.subtract(sun_pos, lineP1, curr_pos);
    Cesium.Cartesian3.normalize(curr_pos, curr_pos);
    Cesium.Cartesian3.multiplyByScalar(curr_pos, 30, curr_pos);
    Cesium.Cartesian3.add(curr_pos, lineP1, lineP2);
    return line_positions;
}

var lineEntity = viewer.entities.add({
    polyline : {
        positions : new Cesium.CallbackProperty(line_callback, false),
        width : 5,
        material : new Cesium.PolylineArrowMaterialProperty(Cesium.Color.YELLOW),
    }
});

//Compute the entity position property.
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 10000000);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);

var entity = viewer.entities.add({
    name : '../../../../Apps/SampleData/models/CesiumAir/Cesium_Air.glb',
    position : position,
    orientation : orientation,
    model : {

        uri : '../../../../Apps/SampleData/models/CesiumAir/Cesium_Air.glb',
        minimumPixelSize : 128,
        maximumScale : 20000
    }
});

var VIEW_TYPE = 0;
viewer.trackedEntity = entity;

Sandcastle.addToolbarButton('View Sun', function() {
    viewer.trackedEntity = undefined;
    lineEntity.show = false;
    VIEW_TYPE = 2;
});

//Add button to track the entity as it moves

Sandcastle.addToolbarButton('View Aircraft', function() {
    VIEW_TYPE = 3;
    viewer.trackedEntity = entity;
    lineEntity.show = true;

});

viewer.scene.sun.glowFactor = 25;
viewer.clock.onTick.addEventListener(function(clock)

{
    if(VIEW_TYPE != 2)
        return;
    const Vec3 = Cesium.Cartesian3;

    var currPos = new Vec3();
    var sunPos = new Vec3();
    var sunDir= new Vec3();
    var offset = new Vec3();

    var currPosInert = new Vec3();
    var sunPosInert = new Vec3();
    var sunDirInert = new Vec3();
    var offsetInert = new Vec3();

    var result = new Vec3();
    
    //first method get sun position in inertial frame
    Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(clock.currentTime, sunPosInert);

    //get entity / airplane position in inertial frame
    entity.position.getValueInReferenceFrame(clock.currentTime, Cesium.ReferenceFrame.INERTIAL, currPosInert);
     entity.position.getValueInReferenceFrame(clock.currentTime, Cesium.ReferenceFrame.FIXED, currPos);
    
    //create the directional vector pointing to the sun from the entity / airplane
    Vec3.subtract(sunPosInert, currPosInert, sunDirInert);
    Vec3.normalize(sunDirInert, sunDirInert);
    
    //scale the directional vector to create an offset for viewing (behind airplane)
    Vec3.multiplyByScalar(sunDirInert, -300, offsetInert);
    
    Vec3.add(currPosInert, offsetInert, currPosInert);
    
    var icrfToFixedMat = new Cesium.Matrix3();
    Cesium.Transforms.computeIcrfToFixedMatrix(clock.currentTime, icrfToFixedMat);
    if(typeof icrfToFixedMat != 'undefined') {
        Cesium.Matrix3.multiplyByVector(icrfToFixedMat, currPosInert, currPos);
        Cesium.Matrix3.multiplyByVector(icrfToFixedMat, sunDirInert, sunDirInert);
        viewer.camera.position = currPos;
        viewer.camera.direction = sunDirInert;
    }
    
});

3. Context. Why do you need to do this? We might know a better way to accomplish your goal.

In my actual application the position of the aircraft entity will be set by data sent from a realtime source. I need to manually position the camera to ensure it stays synced up with the aircraft and the sun.

4. The Cesium version you're using, your operating system and browser. Latest, MacOSX, Chrome

Thanks for sharing a full code example! This looked quite bizarre, especially when commenting out the camera position setting and just watching what it looks like when the direction is changing.

The view distorts, and you can actually see tiles that are missing because they are “outside” the frustum. I showed this to Dan who pointed out that if you just change the direction, and not the camera.up and camera.right, the axes are no longer orthonormal, which will distort the frustum like that.

So you’ll have to set the camera up and right as well.

Let me know if it works! This looks like a really cool demo, and I know it’s a common use case that others will find useful.

Thanks Omar,

It does seem the other vectors need to be recalc’d to keep camera matrix ortho-normalized. I ended up using the method lookAtTransform instead. Below is a working example:

var viewer = new Cesium.Viewer(‘cesiumContainer’, {

infoBox: false, //Disable InfoBox widget

selectionIndicator: false, //Disable selection indicator

shouldAnimate: true, // Enable animations

terrainProvider: Cesium.createWorldTerrain()

});

//Enable lighting based on sun/moon positions

viewer.scene.globe.enableLighting = true;

//Enable depth testing so things behind the terrain disappear.

viewer.scene.globe.depthTestAgainstTerrain = true;

//Set the random number seed for consistent results.

Cesium.Math.setRandomNumberSeed(3);

//set labels for sun

var sunPositionProperty = new Cesium.CallbackProperty(function(time, result){

Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(time, result);

return Cesium.PositionProperty.convertToReferenceFrame(

time,

result,

Cesium.ReferenceFrame.INERTIAL,

Cesium.ReferenceFrame.FIXED,

result);

}, false);

var sunEntity = viewer.entities.add({

position : sunPositionProperty,

label : {

text : ‘Sun’

}

});

var lineP1 = Cesium.Cartesian3.fromDegrees(-75, 35);

var lineP2 = Cesium.Cartesian3.fromDegrees(-175, 35);

var line_positions = [lineP1, lineP2];

function line_callback(time, result){

var sun_pos = {};

var curr_pos = {};

entity.position.getValue(time, lineP1);

sunPositionProperty.getValue(time, sun_pos);

Cesium.Cartesian3.subtract(sun_pos, lineP1, curr_pos);

Cesium.Cartesian3.normalize(curr_pos, curr_pos);

Cesium.Cartesian3.multiplyByScalar(curr_pos, 30, curr_pos);

Cesium.Cartesian3.add(curr_pos, lineP1, lineP2);

return line_positions;

}

var lineEntity = viewer.entities.add({

polyline : {

positions : new Cesium.CallbackProperty(line_callback, false),

width : 5,

material : new Cesium.PolylineArrowMaterialProperty(Cesium.Color.YELLOW),

}

});

//Compute the entity position property.

var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 10000000);

var heading = Cesium.Math.toRadians(135);

var pitch = 0;

var roll = 0;

var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);

var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);

var entity = viewer.entities.add({

name : ‘…/…/…/…/Apps/SampleData/models/CesiumAir/Cesium_Air.glb’,

position : position,

orientation : orientation,

model : {

uri : ‘…/…/…/…/Apps/SampleData/models/CesiumAir/Cesium_Air.glb’,

minimumPixelSize : 128,

maximumScale : 20000

}

});

var VIEW_TYPE = 0;

viewer.trackedEntity = entity;

//viewer.scene.screenSpaceCameraController.enableInputs = false;

Sandcastle.addToolbarButton(‘View Sun’, function() {

viewer.trackedEntity = undefined;

lineEntity.show = false;

VIEW_TYPE = 2;

});

//Add button to track the entity as it moves

Sandcastle.addToolbarButton(‘View Aircraft’, function() {

VIEW_TYPE = 3;

viewer.trackedEntity = entity;

lineEntity.show = true;

});

viewer.scene.sun.glowFactor = 25;

viewer.clock.onTick.addEventListener(function(clock)

{

if(VIEW_TYPE != 2)

return;

// alias cartesian3 to make life easier

const Vec3 = Cesium.Cartesian3;

var currPos = new Vec3();

var sunPos = new Vec3();

var sunDir= new Vec3();

sunEntity.position.getValue(clock.currentTime, sunPos);

entity.position.getValueInReferenceFrame(clock.currentTime, Cesium.ReferenceFrame.FIXED, currPos);

//create the directional vector pointing to the sun from the entity / airplane

Vec3.subtract(sunPos, currPos, sunDir);

Vec3.normalize(sunDir, sunDir);

Vec3.multiplyByScalar(sunDir, -100, sunDir);

var modelMat = new Cesium.Matrix4();

var modelInvMat = new Cesium.Matrix4();

entity.computeModelMatrix(clock.currentTime, modelMat);

Cesium.Matrix4.inverse(modelMat,modelInvMat);

Cesium.Matrix4.multiplyByPointAsVector(modelInvMat, sunDir, sunDir);

viewer.camera.lookAtTransform(modelMat, sunDir);

});

Awesome, thanks for sharing Richard!

I had some trouble copying the code into Sandcastle because the forum was inserting “quoted text” in the middle of it for some reason. You can click “Share” in Sandcastle to get a link to share. Here’s the example on Sandcastle that can be shared.