Seeking clarification on entity, camera, and scene updating

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

I have an entity I would like to update in realtime from a websocket. In order to simulate this I created an entity and update its position in the onTick event handler. I want a camera to track this entity and I use the position and rotation of the entity to make calls to lookAtTransform. When I do this it seems the camera and entity updates are out of sync somehow even though they are both computed in the same handler. If I pause the handler (just return from the function). I see the camera match up with the entity how I would expect. I want to know why this is and the exact update order that occurs during scene rendering. Currently it seems that camera updates happen first then the entity updates and then rendering occurs. I suspect this because if I put my entity update logic inside of postUpdate and my camera logic in preUpdate, the two stay in sync as I would expect. The sandcastle demo below shows the working method with the original method commented out at the bottom.

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);

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;

viewer.scene.sun.glowFactor = 25;
// disable the default event handlers
viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
viewer.scene.screenSpaceCameraController.enableLook = false;

var startMousePosition;
var mousePosition;
var flags = {
    looking : false,
    zooming: false
};

var canvas = viewer.canvas;
var handler = new Cesium.ScreenSpaceEventHandler(canvas);
var cameraRot = new Cesium.Cartesian2(0,0);
var cameraZoom = -100;
handler.setInputAction(function(movement) {
    flags.looking = true;
    mousePosition = startMousePosition = Cesium.Cartesian3.clone(movement.position);
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

handler.setInputAction(function(movement) {
    mousePosition = movement.endPosition;
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;

    // Coordinate (0.0, 0.0) will be where the mouse was clicked.
    if(flags.looking == true) {
        var x = (mousePosition.x - movement.startPosition.x) / width;
        var y = (mousePosition.y - movement.startPosition.y) / height;
        var lookFactor = Math.abs(cameraZoom)*0.15;
        cameraRot.x += x* lookFactor;
        cameraRot.y += y* lookFactor;
    }
    
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

handler.setInputAction(function(position) {
    flags.looking = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

handler.setInputAction(function(movement) {
    cameraZoom += movement;
    if(cameraZoom > 0 && cameraZoom + movement == 0)
        cameraZoom = -1;
    if(cameraZoom < 0 && cameraZoom + movement == 0)
        cameraZoom = 1;
}, Cesium.ScreenSpaceEventType.WHEEL);

var paused = false;
Sandcastle.addToolbarButton('Pause', function() {
    paused = !paused;
});

viewer.scene.postUpdate.addEventListener(function(scene)
{
    if(paused)
      return;
    
    var entPos = new Cesium.Cartesian3(0,0,0);
    entity.position.getValue(viewer.clock.currentTime, entPos);
    Cesium.Cartesian3.add(entPos, new Cesium.Cartesian3(1050,0,0), entPos);
    entity.position.setValue(entPos);
    //console.log("entity pos: "+entPos);
  
    let modelMat = new Cesium.Matrix4();
    let dir = new Cesium.Cartesian3(0,0,cameraZoom);
    entity.computeModelMatrix(viewer.clock.currentTime, modelMat);
});

viewer.scene.preUpdate.addEventListener(function(scene)
{

    let modelMat = new Cesium.Matrix4();
    let dir = new Cesium.Cartesian3(0,0,cameraZoom);
    entity.computeModelMatrix(viewer.clock.currentTime, modelMat);

    var calcPos = new Cesium.Cartesian3(0,0,0);
    Cesium.Matrix4.getTranslation(modelMat, calcPos);
    //console.log("camera pos: "+calcPos);
    
    viewer.camera.lookAtTransform(modelMat, dir);
    viewer.camera.rotateLeft(cameraRot.x );
    viewer.camera.rotateDown(cameraRot.y);
});

/* This doesn't work...why??
viewer.clock.onTick.addEventListener(function(clock) {
    if(paused)
      return;
    
    var entPos = new Cesium.Cartesian3(0,0,0);
    entity.position.getValue(viewer.clock.currentTime, entPos);
    Cesium.Cartesian3.add(entPos, new Cesium.Cartesian3(1050,0,0), entPos);
    entity.position.setValue(entPos);
    //console.log("entity pos: "+entPos);
  
    let modelMat = new Cesium.Matrix4();
    let dir = new Cesium.Cartesian3(0,0,cameraZoom);
    entity.computeModelMatrix(viewer.clock.currentTime, modelMat);

    var calcPos = new Cesium.Cartesian3(0,0,0);
    Cesium.Matrix4.getTranslation(modelMat, calcPos);
    //console.log("camera pos: "+calcPos);
    
    viewer.camera.lookAtTransform(modelMat, dir);
    viewer.camera.rotateLeft(cameraRot.x );
    viewer.camera.rotateDown(cameraRot.y);
});
*/

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

I need this to work both for czml and realtime updates for an application that must be able to handle streamed realtime data and also historical playback for after-action review.

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

Hey Richard,

Sorry for the slow response. A lot of us were out at FOSS4G (where I ran into Syrus from Prominent Edge!)

You are correct about the order of updates being the issue here. You can find Cesium’s render loop here:

https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Widgets/CesiumWidget/CesiumWidget.js#L690-L698

initializeFrame will update the camera: https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Scene.js#L3115-L3131

Then the onTick handler will run, then all primitives (and entities) will be updated. So I think what we’re seeing here is you set the camera’s position to the entity, but the camera doesn’t reflect this position until the next frame, when the entity would have moved, so they’re always out of sync.

Let me know if that helps!

Hey thanks for the information Omar!

Sorry for not responding to this earlier as well. Glad you got to meet Syrus too haha!