Rotating a CZML Model Using Quaternions

I’ve spent the past few days trying all I can think of to get the Cesium Airplane to rotate correctly around the x, y and z access. I’ve seen a lot of discussions about using Quaternions but haven’t been able to determine the exact steps to use for it. The closest I’ve been able to come is changing the roll and seeing the place rotate accordingly. If I add any other values to pitch or heading, the rotations get funny. Here is the code I am currently using. I determined the 50 and 339 were needed to get the plane level before I start doing any rotations to it.

var qx = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_X, degreesToRadians(50));

var qy = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Y, degreesToRadians(339 + roll));

var qz = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, degreesToRadians(0));

var conx = Cesium.Quaternion.conjugate(qx);

var qt = Cesium.Quaternion.multiple(qz, qy);

var cont = Cesium.Quaternion.conjugate(qt);

var n = Cesium.Quaternion.multiple(conx, cont);

var czmlData = { “id”:“Plane”,

“availability”:“2012-08-04T16:00:00Z/2012-08-04T17:04:54.9962195740191Z”,

“position”:{

“cartographicDegrees”:[lon, lat, alt]

},

“model” : {

“gltf” : ‘…/lib/models/Cesium_Air.json’, “scale” : 750.0

},

“scale”:1.0,

“show”:[{“boolean”:true}],

“orientation” : {

“epoch” : “2012-08-04T16:00:00Z”,

“interpolationAlgorithm” : “LINEAR”,

“interpolationDegree” : 1,

“unitQuaternion” : [0, n[‘x’], n[‘y’], n[‘z’], n[‘w’], 3600, n[‘x’], n[‘y’], n[‘z’], n[‘w’]]

}

};

Based on other questions and replies, I’ve tried numerous different variations of conjugating the quaternions and this is the only one that has been somewhat successful. Does anyone have any idea what I am doing wrong? Or suggestions on something new to try? Thanks.

Stephanie,

Were you able to make any progress on this issue with Quaternions? I’m also trying to rotate the cesium airplane so it follows a ‘heading’ property. I went with a different approach, using Matrix3 and Matrix 4 but i’m getting a lot of skew instead. I’ve successfully been able to rotate the airplane by 45 degrees. My code is mostly taken from the current 3D model sandcastle example. Hopefully it is helpful to you.

function createModel(url, height) {

height = Cesium.defaultValue(height, 0.0);

var modelMatrix = Cesium.Transforms.northEastDownToFixedFrame(Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height));

var rotateMatrix = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(45.0));

var rotation4 = Cesium.Matrix4.fromRotationTranslation(rotateMatrix,Cesium.Cartesian3.ZERO);

var rotatedModel = Cesium.Matrix4.multiply( modelMatrix,rotation4, modelMatrix);

scene.primitives.removeAll(); // Remove previous model

var model = scene.primitives.add(Cesium.Model.fromGltf({

url : url,

modelMatrix : rotatedModel,

minimumPixelSize : 128

}));

model.readyToRender.addEventListener(function(model) {

// Play and loop all animations at half-spead

model.activeAnimations.addAll({

speedup : 0.5,

loop : Cesium.ModelAnimationLoop.REPEAT

});

// Zoom to model

var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());

var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

var camera = scene.camera;

camera.transform = transform;

var controller = scene.screenSpaceCameraController;

var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);

controller.minimumZoomDistance = r * 0.5;

camera.lookAt(new Cesium.Cartesian3(r, r, r), Cesium.Cartesian3.ZERO, Cesium.Cartesian3.UNIT_Z);

});

}

``

Hi Mike,
No, I was not able to use Quaternions, I had to do the same thing you did. Here is my current code, I was unable to get pitch working correctly, but the following seems to do heading and roll correctly. I just updated to Cesium 1.0, not sure if you have yet, but I had to change two things to get it working (adding 180 to the roll and dividing the altitude by 3). Thanks for you help, and hopefully this can help you as well.

var scene = viewer.scene;

var primitives = scene.primitives;

var ellipsoid = viewer.scene.globe.ellipsoid;

var point = Cesium.Cartographic.fromDegrees(lon, lat, alt / 3);

var epoint = ellipsoid.cartographicToCartesian(point);

var heading = degreesToRadians((heading * -1));

var pitch = degreesToRadians(pitch);

var roll = degreesToRadians(roll + 180);

var rotMatrixY = Cesium.Matrix3.fromRotationY(roll);

var rotMatrixX = Cesium.Matrix3.fromRotationX(pitch);

var rotMatrixZ = Cesium.Matrix3.'fromRotationZ(heading);

var eastNorthUp = Cesium.Transforms.eastNorthUpToFixedFrame(epoint);

var p = new Cesium.Cartesian3(lon, lat, alt / 3);

var rotTransY = Cesium.Matrix4.fromRotationTranslation(rotMatrixY, p);

var rotTransX = Cesium.Matrix4.fromRotationTranslation(rotMatrixX, p);

var rotTransZ = Cesium.Matrix4.fromRotationTranslation(rotMatrixZ, p);

var z = new Cesium.Matrix4();

z = Cesium.Matrix4.multiply(eastNorthUp, rotTransZ, z);

var modelMatrix = new Cesium.Matrix4();

modelMatrix = Cesium.Matrix4.multiply(z, rotTransY, modelMatrix);

// Cesium.Matrix4.multiply(y, rotTransX, modelMatrix);

var fromGltf = Cesium.Model.fromGltf({ ‘url’ : ‘…/lib/models/Cesium_Air.gltf’, ‘modelMatrix’ : modelMatrix, ‘scale’ : 100.0});

Stephanie

Hi ho,

The 1.2 release includes many updates for models and quaternions. But I don’t know if this release also improved quaternions in CZML. Heck, I don’t even know if quaternions in CZML are different than those in Cesium. I suspect they are.

I’ve adapted some code for Sandcastle that uses CZML quaternions in conjunction with Sandbox’s Airplane gltf. The resulting air show is both cool and confounding. In the code I’ve noted the orientations I believe the model should have at each moment (assuming quaternions are x,y,z,w).

Are others seeing the same thing? Any thoughts? Cheers, erik

I’ve given up on this bit until better brains than mine can give me a little bunk-up, clue wise…

In your example, you seem to have ‘hand-written’ your czml ?

I’m using the Java version of the cesium-language-writer. One issue I’ve not fully investigated is the order of the quaternion parameters.

The Java utility uses the same order that I’ve managed to read, google-fu, and wiki-fu about with respect to quaternions: cesiumlanguagewriter.UnitQuaternion.UnitQuaternion(double w, double x, double y, double z)

The drawback is that I’m guessing that once the czml is written, it keeps that order, and does not change the parameters to the order that Javascript seems to require :- x,y,z, then w. I need to check this out…

The next thing is that I suspect that the initial/default position of the model has an effect on our subsequent ‘orientational’ rotations, and I guess that if we supply our own orientations, it’s not simply rotate x,y, or z, but they probably override the default value and therefore we will also need to take into account where our model is positioned on the planet surface - I’m working around -60 long, -52 lat …

You’re right that the UnitQuaternion constructor happens to take parameters in the order WXYZ, however, the actual JSON written by the czml-writer library is automatically written as XYZW, with no need to account for that yourself.

As far as I know, the orientation property only represents rotation, not translation. The ModelVisualizer internally handles combining orientation with position to produce the 4x4 model matrix.

Scott, are you sure about the XYZW thing, we were supposed to have changed that a while ago. If it’s still the case, it’s a bug and something we should address.

Cesium and CZML both use XYZW. Not sure which bug you’re thinking of.

We have also spent untold hours trying to figure out how to orient models! We've searched the forums and spent many hours on Google trying to figure something out.

It would be extremely helpful if someone could post an example of how to manipulate the heading, roll and pitch of a model... with examples of how to manipulate any one of these parameters individually as well as manipulating two or more of these parameters at the same time!

Below is a combination of the help we've found on the forums. We converted a bunch of models prior to the recent changes in the gltf converter. So some of the code here deals with the current orientation of our models. We are able to adjust one of the parameters successfully (for instance the heading), but we have not been able to adjust multiple parameters.

var tempMatrix = new Cesium.Matrix4();
    
var rotMatrixRoll = new Cesium.Matrix3();
Cesium.Matrix3.fromRotationY(degreesToRadians(0), rotMatrixRoll);
    
var rotMatrixPitch = new Cesium.Matrix3();
Cesium.Matrix3.fromRotationX(degreesToRadians(pos.PITCH), rotMatrixPitch);
    
var rotMatrixHdg = new Cesium.Matrix3();
Cesium.Matrix3.fromRotationZ(degreesToRadians(pos.HDG + 90), rotMatrixHdg);
    
// var currOrient = new Cesium.Matrix3();
// Cesium.Matrix3.multiply(rotMatrixPitch, rotMatrixHdg, currOrient);
//
// Cesium.Matrix4.fromRotationTranslation(currOrient,
// new Cesium.Cartesian3(0.0, 0.0, 0.0), tempMatrix);

Cesium.Matrix4.fromRotationTranslation(rotMatrixHdg, new Cesium.Cartesian3(0.0, 0.0, 0.0), tempMatrix);
                      
//Orient models that are rightside up, currently the models are upside down
// Cesium.Matrix4.multiply(Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(pos.LON,pos.LAT, pos.ALT * dblFEET_TO_METERS)),
    Cesium.Matrix4.multiply(Cesium.Transforms.northEastDownToFixedFrame(Cesium.Cartesian3.fromDegrees(pos.LON,pos.LAT, pos.ALT * dblFEET_TO_METERS)),
                            tempMatrix,
                            this.Model.modelMatrix);

Sorry Scott, I misread your post and was thinking you mean CZML files have one order and Cesium another, not the Quaternion class itself.

Hi,

Here is some code to rotate the model according to the heading, pitch, roll (also to position the model). It's more or less an adaptation of what I'm using so it should work. You can paste it into Sandcastle to see it live.
What I do is get the Quaternions for each Axis, multiply them (beware of the order you multiply them), then you just generate a new "modelMatrix" according to the Translation and Orientation that you calculated:

// Manipulate model ----->
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

var degreesToRadians = function(val) {
   return val*Math.PI/180;
}

var lon = -60;
var lat = -52;
var alt = 100;
var heading = 0;
var pitch = 0;
var roll = 0;

var primitives = scene.primitives;
var ellipsoid = viewer.scene.globe.ellipsoid;

var positionModel = function(model, lon, lat, alt, heading, pitch, roll) {
   var point = Cesium.Cartographic.fromDegrees(lon, lat, alt / 3);
   var epoint = ellipsoid.cartographicToCartesian(point);

   var heading = degreesToRadians((heading));
   var pitch = degreesToRadians(pitch);
   var roll = degreesToRadians(roll);

   var currentTranslation = new Cesium.Cartesian3();
   var currentRotation = new Cesium.Matrix3();

   var eastNorthUp = Cesium.Transforms.eastNorthUpToFixedFrame(epoint);
   var p = new Cesium.Cartesian3(lon, lat, alt);

   Cesium.Matrix4.getRotation(eastNorthUp, currentRotation);
   Cesium.Matrix4.getTranslation(eastNorthUp, currentTranslation);

   var headingQuaternion = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, -heading);
   var pitchQuaternion = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Y, -pitch);
   var rollQuaternion = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_X, -roll);

   var headingPitchQuaternion = Cesium.Quaternion.multiply(headingQuaternion, pitchQuaternion, new Cesium.Quaternion());
   var finalQuaternion = new Cesium.Quaternion();
   Cesium.Quaternion.multiply(headingPitchQuaternion, rollQuaternion, finalQuaternion);

   var rM = new Cesium.Matrix3();
   Cesium.Matrix3.fromQuaternion(finalQuaternion, rM);

   Cesium.Matrix3.multiply(currentRotation, rM, currentRotation);

   var modelMatrix = new Cesium.Matrix4();

   Cesium.Matrix4.fromRotationTranslation(
       currentRotation,
       currentTranslation,
       modelMatrix
   );

   model.modelMatrix = modelMatrix;
};

var positionCamera = function(model) {
   // Zoom to model
   var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
   var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
   var camera = scene.camera;
   camera.transform = transform;
   var controller = scene.screenSpaceCameraController;
   var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
   controller.minimumZoomDistance = r * 0.5;
   camera.lookAt(new Cesium.Cartesian3(r, r, r), Cesium.Cartesian3.ZERO, Cesium.Cartesian3.UNIT_Z);
};

var createModel = function(lon, lat, alt, heading, pitch, roll) {
   var model = scene.primitives.add(Cesium.Model.fromGltf({
       url : '../../SampleData/models/CesiumAir/Cesium_Air.gltf'
   }));

   model.readyToRender.addEventListener(function(model) {
       // Play and loop all animations at half-spead
       model.activeAnimations.addAll({
           speedup : 0.5,
           loop : Cesium.ModelAnimationLoop.REPEAT
       });

       viewer.clock.onTick.addEventListener(function() {
           positionModel(model, lon, lat, alt, heading, pitch, roll);
           positionCamera(model);
            
           //lon = lon+0.00001;
           //lat = lat+0.000005;
           //alt = alt+0.01;
           heading += 0.01;
           pitch += 0.05;
           roll += 0.1;
       });
   });
};

createModel(lon, lat, alt, heading, pitch, roll);
//<---- Manipulate model

Best,
André Santos

Andre,
Thank you very much for the example code! We will give this a try very soon to see if we can make this work.

Brian et al,

I unplugged for a week and missed a lot of discussion. A few notes:

1. Yes Brian, my CZML is 'hand-written.' I rank as solid amateur and sheepishly admit that I have a simple coding challenge compared to most visitors here. I am working on an animation of various army units in World War I. So I have hundreds of different models moving about assuming hundreds of different orientations. CZML seemed to be the best way to drive this animation. Please correct me if this is wrong-headed.

2. Kudos to André for posting his quaternion code. While examining it, I now see that a heading of 0 degrees with wings level and no pitch (therefore quaternions of 0,0,0,1 for x,y,z,w) corresponds with the airplane heading due east. I had assumed a heading of 0 meant due north. I eliminated the step increases in André's code to lock in the heading and this is clearly the result.

3. I am still getting confusing values for my quaternions. If I am going to stay in CZML for all of my model movements (as in the below example which can be pasted into Sandcastle), should I preprocess my quaternion calculations as André has done? Or is there a way to set ... "Orientation":{"unitQuaternion":["timeStamp",0,0,0,1, .... ]} to make the plane fly due east, wings level while maintaining altitude (see code below)?

Many thanks in advance for your thoughts. Cheers, erik

PS - See timeStamps at the bottom of this Sandcastle code for notes about my expected orientations:

André, just to let you know this post was really helpful for me getting to grips with manipulating a model.

One question, I found that if I don’t run positionCamera, the model does not appear. I am guessing this is due to the code adding the model to viewer.scene? The downside is that it seems to prevent the user from changing the camera/altering their view of the model. Is it possible to somehow uncouple the two things?

regards

Guy