Interpolation of angles/headings in Cesium

When a entity in a CZML Cesium billboard has two headings in the timeline, for example one 3 minutes ago at 45º and one now at 320º, Cesium will interpolate the values in a way that the billboard does a very large rotation from 45º to 320º instead of a short rotation from 45º to -40º.

This probably happens because Cesium interpolates all values in the same way, without awareness of the value represents.

A solution for this in case of values that represent angles would to detect changes larger than 180º between two values, and in that case convert one of the source/dest values to the equivalent negative angle (or an angle > 360º in some cases).

Is this something that we can do ourselves without modifying Cesium in any way?

Absolutely. SampledProperty works with any value that implements the Packable interface and there is also an additional PackableForInterpolation interface when further control is needed (for example, Quaternion can’t be directly interpolated so it uses PackableForInterpolation in order to interpolate using axis and angle. I believe what you want to do is actually kind of tricky if you wanted it to work across all interpolation types. However, if all you care about is linear interpolation (which is the default) that should be easy enough. Basically, create a new object, called Angle and have it implement Packable and PackableForInterpolation. Then you can detect the result in unpackInterpolationResult and use the ratio to have the rotation go in the opposite direction. Here’s some shell code for you and an example for using it. Just replace the TODO with the code to computes the alternate result from start and end values.

//A value that always interpolates the shortest distances between 2 angles.

var Angle = {

packedLength : 1,

packedInterpolationLength : 1,

pack : function(value, array, startingIndex) {

    startingIndex = Cesium.defaultValue(startingIndex, 0);

    array[startingIndex] = value;

},

unpack : function(array, startingIndex, result) {

    startingIndex = Cesium.defaultValue(startingIndex, 0);

    return array[startingIndex];

},

convertPackedArrayForInterpolation : function(packedArray, startingIndex, lastIndex, result) {

    for (var i = 0, len = lastIndex - startingIndex + 1; i < len; i++) {

        result[i] = packedArray[startingIndex + i];

    }

},

unpackInterpolationResult : function(value, sourceArray, firstIndex, lastIndex, result) {

    var start = sourceArray[firstIndex];

    var end = sourceArray[lastIndex];

    var difference = Math.abs(end - start);

    if(difference > Math.PI / 2) {

        //TODO convert value to rotation in opposite direction.

        return value;

    }

    return value;

}

};

//Example

var property = new Cesium.SampledProperty(Angle);

var now = Cesium.JulianDate.now();

var nowPlus1 = Cesium.JulianDate.addSeconds(now, 1, new Cesium.JulianDate());

var nowPlus2 = Cesium.JulianDate.addSeconds(now, 2, new Cesium.JulianDate());

property.addSample(now, Cesium.Math.toRadians(45));

property.addSample(nowPlus1, Cesium.Math.toRadians(320));

console.log(property.getValue(nowPlus1));

Another solution that just occurred to me as I was about to send this would be to change the implementation of convertPackedArrayForInterpolation to store the shortest difference between values to fill the result array, and then you would simply add the result value toe the start value in unpackInterpolationResult. The benefit of the second solution is that it would work with all interpolation types. It would actually be easy to implement. If you go this second route, let us know and we’d be happy to take a pull request into Cesium for it.

Thanks, that is very useful code!

I’m just a bit lost, how to combine that with processPacketData of a CMZL source?
Because we want to do this custom interpolation for rotation values of billboards that come from a CMLZ generated in our server.

Update: I tried altering Cesium to allow custom types in processPacketData method (it was throwing an exception if the type was not one of the types Cesium knows, inside one big switch).

The code now runs without errors, however the billboards now longer have rotations at all (probably because now the rotation type is Angle instead of Number…)

So, what is really the proper solution for this that also supports CZML?
Note that we’re using both Cesium CZML types (billboard, etc) but also our own custom types/primitives which also have rotations (via DataSources processors)

Another update:

I modified Cesium by adding a new Angle type, which is now used by the Billboards.

A pull request was submitted here, it is very small, so in case anyone from the Cesium team has some free time, take a look:
https://github.com/AnalyticalGraphicsInc/cesium/pull/2666