Position Models with another as reference (moving from Google Earth to Cesiumjs)

On my attempt to port my project (youbeq) from Google Earth to Cesiumjs I've encountered some difficulties that are preventing me from moving forward.

You can check out a part of what is already running on Cesium: http://dev.youbeq.com/Apps/Sandcastle/gallery/FollowModel.html

If you check that example you can see that I have a vehicle you can control around the world with different components "attached" to it, like the wheels.
Unfortunately I can't seem to find the correct way to position them according to the position of the vehicle (lat, lng, alt, heading, tilt, roll), and in the example, since the wheels are spinning, the roll affects the other transformations in a bad way.

What I was doing in GE was constructing a Quaternion with the heading, tilt and roll of the vehicle:

// code snippet ----->

// helper object ---->
Quaternion = function (x, y, z, w) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;

    this.mul = function (q) {
        var _x = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y;
        var _y = this.w * q.y + this.y * q.w + this.z * q.x - this.x * q.z;
        var _z = this.w * q.z + this.z * q.w + this.x * q.y - this.y * q.x;
        var _w = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z;
        return Quaternion(_x, _y, _z, _w)
    };

    this.get_eulerAngles = function () {
        var h;
        var p;
        var b;
        var sp = -2 * (this.z * this.y - this.w * this.x);
        if (Math.abs(sp) > .998) {
            p = Math.PI / 2 * sp;
            h = Math.atan2(-this.x * this.y + this.w * this.z, .5 - this.z * this.z - this.y * this.y);
            b = 0
        } else {
            p = Math.asin(sp);
            h = Math.atan2(this.x * this.y + this.w * this.z, .5 - this.x * this.x - this.z * this.z);
            b = Math.atan2(this.x * this.z + this.w * this.y, .5 - this.x * this.x - this.y * this.y)
        }
        return EulerAngles(h, p, b)
    };
};
Quaternion.euler = function (heading, pitch, bank) {
    var hq = Quaternion.angleAxis(heading, new Vector3(0, 0, 1));
    var pq = Quaternion.angleAxis(pitch, new Vector3(1, 0, 0));
    var bq = Quaternion.angleAxis(bank, new Vector3(0, 1, 0));
    return bq.mul(pq).mul(hq)
};
Quaternion.angleAxis = function (radian, axis) {
    var h = radian / 2;
    var s = Math.sin(h);
    return Quaternion(s * axis.x, s * axis.y, s * axis.z, Math.cos(h))
};
//<----- helper object

var qVehicle = Quaternion.euler(heading, tilt, roll);
//<----- code snippet

Then I would find the new LatLngAlt of that component according to the offset in the xyz axis and the orientation multiplying the heading, tilt and roll of the component with the Quaternion of the vehicle:

// code snippet ----->

//helper function ---->
getLatLngAlt = function (x, y, z) {
        var p = new Vector3(x, y, z);
        var m = new Matrix3x3;
        m.setRotationY(-vehicleRoll.position); // vehicleRoll contains the current roll value for the vehicle
        p.multiply(m);
        m.setRotationX(-vehicleTilt.position); // vehicleTilt contains the current tilt value for the vehicle
        p.multiply(m);
        var angle = -Math.atan2(p.y, p.x) + 1.570796326794897;
        var length = lengthVector(p.x, p.y); // returns the vector length
        var latLng = vehiclePosition.createOffset(getVehicleHeading() + angle, length); // vehiclePosition contains the current LatLngAlt of the vehicle. CreateOffset will compute the new location according to the direction (heading) and distance (length)
        var alt = 0;

        alt = vehicleAltitude.position + p.z; // vehicleAltitude contains the current altitude value for the vehicle

        return new LatLngAlt(latLng.lat(), latLng.lng(), alt);
};
//<------- helper function

var latLngAlt = getLatLngAlt(x, y, z);

var q1 = Quaternion.angleAxis(wheelHeading, new Math3D.geometry.Vector3(0, 0, 1));
var q2 = Quaternion.angleAxis(wheelTilt+wheelSpeedForward, new Vector3(1, 0, 0));
var e = q2.mul(q1).mul(qVehicle).get_eulerAngles();

var pitch = e.pitch;
var bank = e.bank;
var heading = e.heading;
//<----- code snippet

With this I could position the components and they kept "attached" to the main body. In the Google Earth Plugin everything works by setting LatLngAlt and the heading, tilt and roll.
In Cesium I attempted to accomplish the same with no success, when I try to generate a rotation matrix from the computed Quaternion and apply it to the Model Matrix all hell breaks lose:

// code snippet ----->

//helper function ---->
var euler = function (heading, pitch, bank) {
    var hq = new Cesium.Quaternion();
    Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(0, 0, 1), heading, hq);
    var pq = new Cesium.Quaternion();
    Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(1, 0, 0), pitch, pq);
    var bq = new Cesium.Quaternion();
    Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(0, 1, 0), bank, bq);

    var bpq = new Cesium.Quaternion();
    Cesium.Quaternion.multiply(bq, pq, bpq);
    var bphq = new Cesium.Quaternion();
    Cesium.Quaternion.multiply(bpq, hq, bphq);
    return bphq;
};
//<------- helper function

var qVehicle = euler(heading, tilt, roll);

var q1 = new Cesium.Quaternion();
Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(0, 0, 1), wheelHeading, q1);
var q2 = new Cesium.Quaternion();
Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(1, 0, 0), wheelTilt+wheelSpeedForward, q2);
var q21 = new Cesium.Quaternion();
Cesium.Quaternion.multiply(q2, q1, q21);
var q21v = new Cesium.Quaternion();
Cesium.Quaternion.multiply(q21, qVehicle, q21v);

var currentTranslation = new Cesium.Cartesian3();
var position = Cesium.Cartesian3.fromDegrees(lng, lat, alt, scene.globe.ellipsoid, new Cesium.Cartesian3());
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
Cesium.Matrix4.getTranslation(modelMatrix, currentTranslation);

var currentRotation = new Cesium.Matrix3.fromQuaternion(q21v);

Cesium.Matrix4.fromRotationTranslation(
                currentRotation,
                currentTranslation,
                model.modelMatrix
);
//<----- code snippet

and if I apply heading, tilt and roll to the current model matrix rotation I have the issue of the roll messing with the heading and tilt:

// code snippet ----->

//helper function ---->
var rotateMatrix = function(mat, h, t, r){
    var mh, mt;

    // Heading
    if (h === 0) {
        mh = Cesium.Matrix3.IDENTITY;
    } else {
        mh = Cesium.Matrix3.fromRotationZ(-h);
    }

    // Tilt
    if (t !== 0) {
        mt = Cesium.Matrix3.fromRotationX(-t);
    } else {
        // TBD
        mt = Cesium.Matrix3.IDENTITY;
    }

    // Taken from https://github.com/AnalyticalGraphicsInc/cesium/issues/1133
    // perform these two rotations so that we can determine the direction vector for roll
    Cesium.Matrix3.multiply(mat, mh, mat);
    Cesium.Matrix3.multiply(mat, mt, mat);

    // Roll (-180 < 0 < 180)
    // Roll around the calculated direction axis
    if (r !== 0) {
        var right = new Cesium.Cartesian3();
        Cesium.Matrix3.getColumn(mat, 0, right);
        var up = new Cesium.Cartesian3();
        Cesium.Matrix3.getColumn(mat, 1, up);
        var direction = new Cesium.Cartesian3();
        Cesium.Matrix3.getColumn(mat, 2, direction);

        var rad = r;
        var mr = Cesium.Matrix3.fromQuaternion(Cesium.Quaternion.fromAxisAngle(direction, rad));

        Cesium.Matrix3.multiplyByVector(mr, right, right);
        Cesium.Matrix3.multiplyByVector(mr, up, up);
        Cesium.Matrix3.multiplyByVector(mr, direction, direction);

        Cesium.Matrix3.setColumn(mat, 0, right, mat);
        Cesium.Matrix3.setColumn(mat, 1, up, mat);
        Cesium.Matrix3.setColumn(mat, 2, direction, mat);
    }

    return mat;
};
//<------- helper function

var currentTranslation = new Cesium.Cartesian3();
var position = Cesium.Cartesian3.fromDegrees(lng, lat, alt, scene.globe.ellipsoid, new Cesium.Cartesian3());
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
Cesium.Matrix4.getTranslation(modelMatrix, currentTranslation);

var currentRotation = new Cesium.Matrix3();
Cesium.Matrix4.getRotation(initialModelMatrix, currentRotation);

// Apply rotation to model
currentRotation = rotateMatrix(currentRotation, heading, tilt, roll);

Cesium.Matrix4.fromRotationTranslation(
                currentRotation,
                currentTranslation,
                model.modelMatrix
);
//<----- code snippet

If anyone can give me some help in finding the correct way to accomplish this type of movement it would be greatly appreciated.

Is your model a hierarchy of meshes? If it is, like the sample ground vehicle in the Sandcastle, you can change the individual model matrices of the nodes of the model. The model will do the work of making sure everything is placed correctly within the hierarchy when it updates before the next render.

I inserted a working Sandcastle example below that rotates and turns the wheels. Use the ‘w’/‘s’ keys to increase/decrease the speed. Use the ‘a’/‘d’ keys to turn the wheels left and right.

In the onTick event, I update the quaternions used to rotate/turn the wheels, get the nodes of the model for the wheels, and set the model matrices using the quaternions, existing translation and no scale.

Code sample:

Cesium Demo @import url(../templates/bucket.css);

Loading...

No, they are actually different models that I position on each tick. Unfortunately in GE using that kind of approach (hierarchy of meshes) wouldn't help because there is none of the magic that Cesium has.

But your approach works and makes sense to me so I'm gonna adapt my code. Thanks for the help!

By the way, is there a way to hide those nodes? Let's say I have the landing gear of an airplane, when we take off they retract and are inside the model so they don't need to be rendered.

Even though I'm going to use your solution there may be a need to position a model with another as a reference, the first not being part of the hierarchy of meshes of the second, how would I go about finding the right translation and orientation?

Thanks again for your help.

Terça-feira, 16 de Setembro de 2014 12:26:12 UTC+1, andre....@inovmapping.com escreveu:

There is no way to hide only the nodes of a model. I submitted issue #2135 to add the feature.

To position one model with another as a reference, you can multiply it by the reference model’s model-matrix. For example, something like:

var translationOrientation = Cesium.Matrix4.fromTranslationQuaternionRotationScale(translation, quaternion, scale);

var modelMatrix = Cesium.Matrix4.multiply(referenceModel.modelMatrix, translationOrientation, new Cesium.Matrix4());

var model = Cesium.Model.fromGltf({

url : ‘/path/to/model’,

modelMatrix : modelMatrix

});

Cool, thanks for that.
That makes sense, a solution along those lines came to mind after I asked that.

Thanks for help.

Quinta-feira, 18 de Setembro de 2014 17:45:57 UTC+1, Daniel Bagnell escreveu:

Andre,

We just added per-node show/hide in #2352. This will most likely make Cesium 1.5 at the start of January. Feel free to use the branch in the meantime. Let me know if you have any issues with this in youbeQ.

Patrick