Heading, Pitch, Roll

One of the most frequently requested features for Cesium is the ability to easily set the heading, pitch, and roll for the camera and primitives. Starting with version 1.6, a couple of functions have been added to make it easier.

For anyone who is unfamiliar with heading, pitch and roll angles:

  • Heading is the rotation from the local north direction where a positive angle is increasing eastward.

  • Pitch is the rotation from the local east-north plane. Positive pitch angles are above the plane. Negative pitch angles are below the plane.

  • Roll is the first rotation applied about the local east axis.

The camera position, heading, pitch and roll can be set with Camera.setView. The position can be either an instance of Cartesian3 or Cartographic. This can be used in all three scene modes, however, in 2D the pitch and roll are ignored because the camera must be looking straight down at the map though it can still be rotated about the view direction by setting the heading. For example, setting the view with a cartesian position looks like:

camera.setView({

position : new Cesium.Cartesian3(x, y, z),

heading : headingAngle,

pitch : pitchAngle,

roll : rollAngle

});

An example setting the position using a cartographic:

camera.setView({

positionCartographic : new Cesium.Cartographic(longitude, latitude, height),

heading : headingAngle,

pitch : pitchAngle,

roll : rollAngle

});

I think the most common use case would be to set the camera position looking straight down at the Earth with the heading oriented to north:

camera.setView({

position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height),

heading : 0.0,

pitch : -Cesium.Math.PI_OVER_TWO,

roll : 0.0

});

If both position and positionCartographic are provided, then position will be used. All of the parameters are optional. The default values for any undefined parameters are the current camera position, heading, pitch, and roll. For example, if you wish to just change the heading while the pitch, roll and position remain the same:

camera.setView({

heading : 0.0

});

We also added Transforms.headingPitchRollToFixedFrame. This creates a model matrix from a position, heading, pitch and roll. An example:

var origin = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);

var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll);

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

url : ‘path/to/model’,

modelMatrix : modelMatrix

}));

The heading, pitch and roll angles are all computed in the local east-north-up frame at the position. If heading, pitch, and roll are all zero, this is equivalent to Transforms.eastNorthUpToFixedFrame.

Dan

What will be the best way to combine this with this into a flyTo call?

This is awesome news :slight_smile:

We are making a platform for data acquisition with planes, drones ect. I am almost sure at this point that the roll,pitch and heading is something that is more understandable for our devs and users.

Conventional aircraft axes are (the thing most familiar to me):

x - out the nose

y - out the right wing

z - down

Rotations in order are:

yaw (psi) - positive right-hand rotation about the z-axis (nose right)

pitch (theta) - positive right-hand rotation about the y-axis (nose up)

roll (phi) - positive right-hand rotation about the x-axis (right wing down)

I’m sure I’m not telling you something you don’t already know but it sounds like you are proposing something quite different. It will undoutedly be a big improvement over the current situation but I’m wondering how you arrived at that particular reference frame?

I usually associate

+x with right wing (lateral)

+y with nose (longitudinal)

+z with up (vertical)

Many game sims seem to use the following conventions

When looking from (0,0,0) to (1,1,1)

+x is down-right

+y is down-left

+z is up

When looking from (0,0,0) to (1,0,0)

CCW is positive, pitching nose down, pitching tail up

When looking from (0,0,0) to (0,1,0)

CCW is positive, rolling left wing down, rolling right wing up

When looking from (0,0,0) to (0,0,1)

CCW is positive, nose yaw right, tail yaw left

It’s a shame that Navigation conventions didn’t adopt Math’s conventions

In Math +x is 0deg heading, +y is +90deg heading.

In Navigation +x is +90deg heading, +y is 0deg heading.

Math functions expect Math’s convention, such as atan2.

"…I’m wondering how you arrived at that particular reference frame? "

I realize you weren’t asking me, but the conventions are very similar to Google Earth

Cesium Heading = Google Earth’s Heading (both 0 North, positive N to E)

Cesium Pitch = Google Earth’s tilt - 90 (both positive away from Earth. Cesium wants signed, to be like latitude)

Cesium roll = Google Earth’s roll (both CCW positive)

Other than the sign of roll direction it appears to be the same as what you’ve described.

@Berwyn The flyTo parameters have changed as well. You can have a heading, pitch and roll. I’ll add another post with the details.

@Matt As Hyper Sonic said, the convention should be the same except the roll is in the opposite direction. Heading and yaw angles are different. From what I understand (and correct me if I’m wrong), heading is the rotation from north, where positive rotation is eastward, and is the direction the aircraft is travelling while yaw is the rotation about z from that heading.

@Hyper Sonic and @Matt The reason we chose this convention is because its similar to what Google Earth and World Wind use.

Over the past two weeks there has been more improvements to the camera API. Specifically, flyTo and lookAt have changed. The changes to flyTo are as follows:

  • The destination parameter can now be either a Cartesian3 position or a Rectangle.
  • The direction and up parameters have been deprecated.
  • There is a new orientation parameter which can either be heading/pitch/roll or direction/up. The heading/pitch/roll angles are defined the same as for setView and direction/up are orthonormal unit vectors.
  • flyToRectangle has been deprecated. You can now use flyTo.
    Here are some examples:

//Fly to a position with a top-down view

viewer.camera.flyTo({

destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)

});

// Fly to a Rectangle with a top-down view

viewer.camera.flyTo({

destination : Cesium.Rectangle.fromDegrees(west, south, east, north)

});

// Fly to a position with an orientation using unit vectors.

viewer.camera.flyTo({

destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),

orientation : {

direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),

up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)

}

});

// Fly to a position with an orientation using heading, pitch and roll.

viewer.camera.flyTo({

destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),

orientation : {

heading : Cesium.Math.toRadians(175.0),

pitch : Cesium.Math.toRadians(-35.0),

roll : 0.0

}

});

The eye, target and up parameters to lookAt have been deprecated. There is now only a target and an offset. The offset can be either an instance of Cartesian3 or an instance of HeadingPitchRange. The heading and pitch angles are defined the same way as for setView. The range is the distance from the target in meters. The Cartesian3 offset is defined in the east-north-up frame centered at the target. The x-axis is east, the y-axis is north, and up is the geodetic normal. The camera up direction will always be aligned with the local up direction. Here’s some examples:

// Using a cartesian offset

var center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0);

viewer.camera.lookAt(center, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));

// Using a HeadingPitchRange offset

var center = Cartesian3.fromDegrees(-72.0, 40.0);

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

var pitch = Cesium.Math.toRadians(-20.0);

var range = 5000.0;

viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range));

This sets the camera reference frame to be the local east-north-up frame at the target. So after calling lookAt the default left mouse click would rotate around the target. To set it back to the default, call camera.lookAtTransform(Matrix4.IDENTITY). This will reset the camera to be in the reference frame centered at the earth. The lookAtTransform function is a little more advanced. Its parameters are a 4x4 matrix that defined a reference frame and a Cartesian3/HeadingPitchRange offset. For example, you would use this to view a satellite in orbit or the Earth in the inertial frame. The offset is optional and is defined in the given frame. If the offset is undefined, it sets the camera reference frame and transforms the position and orientation to be in that new frame.

Regards,

Dan

Fantastic, looking forward to leveraging flyto with heading pithc and roll

“The camera up direction will always be aligned with the local up direction.”

So instead of supplying an up vector as in the old lookAt, the up-vector in the east-north-up frame is now always the ‘supplied vector’.

Right vector is ‘direction vector’ cross ‘supplied vector’. (looking from right vector to origin: direction vector to supplied vector is CCW)

Up vector is then ‘right vector’ cross ‘direction vector’ (looking from up vector to origin: right vector to direction vector is CCW)

So roll is always 0. If anyone wanted a non-zero roll they could simply perform a camera.look around the camera.direction vector afterward.

“So after calling lookAt the default left mouse click would rotate around the target.”

So not only does it perform a one-time lookAt, it also changes the transform for future moves? I presume this is done from this._setTransform(transform);

“To set it back to the default, call camera.lookAtTransform(Matrix4.IDENTITY).”

Looking at the identity matrix:

x vector column = (1,0,0)

y vector column = (0,1,0)

z vector column = (0,0,1)

position column = (0,0,0)

The last component (w) of each column I suppose is used for Quaternions? W for rotation vectors are 0, and 1 for the position vector.

I noticed in the examples that latitude / longitude are given for target, but not height. Target’s z seems to be used in Transforms.eastNorthUpToFixedFrame function.

var center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0);

I suppose this will default center.z to 0, center.z being geodetic height from reference ellipsoid?

In Transforms.eastNorthUpToFixedFrame, is the origin parameter lat,lon,height or x,y,z, or either?

Looking at the new code for Cesium 1.6 it would appear that roll is now CW positive to be like Heading which is also CW positive.

Of course rotation direction depends on the point of view. I believe these two points of view are fairly common for most people:

-For roll : CW from the point of view of the camera origin looking toward the end point of the camera direction vector.

-For heading/yaw: CW from the point of view of the end point of the ENU up vector looking toward the camera origin.

I believe in Mathematics I believe the convention is CCW is positive when looking from the end point of a rotation vector toward it’s origin, however the Navigation heading convention would be difficult to change.

Hello, I follow the Hyper Sonic’s note about the orientation of heading and pitch. In mathematics, the heading should be counter clock wise oriented when looking from the end of the rotation vector (third axis of the local reference frame or “up” in East North Up local reference frame).
Maybe in cesium that was a decision to use the opposite?

Is there in Cesium any function like HeadingPitchRangefromOffset(offsetWC) which returns HPR for a given offset in the given local coordinate system. It should be an inverse procedure to : Cesium.Camera.offsetFromHeadingPitchRange(heading, pitch, range).

The last function is used by Camera.prototype.lookAt = function(target, offset) where the offset is defined in the local target coordinates, therefore HPR are also local target parameters.

That offset vector can be transformed. to camera system - and I would be happy to observe HPR there - where i suppose that only range is the same , and HP diofferent , which contain a different up versor

I am asking about that - because I am afraid that in the lookAt function mode there disappear some camera parameters as undefined - i found camera.positionWC and camera directionWC and pitch undefined

I have one more question ( motivated bt updates of frustum after each lookAT ):

How to find range parameter from the frustum variables and camera invariants ( invariant in flyTo and lookAt procedures )

W dniu środa, 17 stycznia 2018 00:06:24 UTC+1 użytkownik Tadeusz Słupski napisał:

I think that I found the hprFromOffset function. I used the code verification() - added below - to check that it is really the inverse function - and found the the output equals the input up to 6 decimal digits. Perhaps the simplicity of the formula will help to understand better what the HPR is.

   var hprFromOffset = function (offset) {
   var r = Cesium.Cartesian3.magnitude(offset);
   return {heading: Math.atan2(-offset.x, -offset.y), pitch: Math.asin(-offset.z / r), range: r};
}

var verification = function () {
  var heading = 2.3456789;// put any number (in radians ) with the module smaller than PI
  var pitch = 1.2345678;// put any number (in radians ) with the module smaller than PI /2
  var range = 1000;
  var offset;

  offset = offsetFromHeadingPitchRange(heading, pitch, range);
  console.log('   offset = ' + offset);
  var hpr = hprFromOffset(offset);
  console.log(' input heading = ' + heading);
  console.log('output heading = ' + hpr.heading);
  console.log('   input pitch = ' + pitch);
  console.log(' output  pitch = ' + hpr.pitch);
}
verification();

I did not tried to understand the Cesium code of:


function offsetFromHeadingPitchRange(heading, pitch, range) {
pitch = CesiumMath.clamp(pitch, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
heading = CesiumMath.zeroToTwoPi(heading) - CesiumMath.PI_OVER_TWO;

var pitchQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, scratchLookAtHeadingPitchRangeQuaternion1);
var headingQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, scratchLookAtHeadingPitchRangeQuaternion2);
var rotQuat = Quaternion.multiply(headingQuat, pitchQuat, headingQuat);
var rotMatrix = Matrix3.fromQuaternion(rotQuat, scratchHeadingPitchRangeMatrix3);

var offset = Cartesian3.clone(Cartesian3.UNIT_X, scratchLookAtHeadingPitchRangeOffset);
Matrix3.multiplyByVector(rotMatrix, offset, offset);
Cartesian3.negate(offset, offset);
Cartesian3.multiplyByScalar(offset, range, offset);
return offset;
}