Help Translating WorldWind Functions to Cesium

I know this is a lot to ask, but I need help translating some WorldWind functions to Cesium equivalents. I know it’s a lot of code. My hope is that there may be an approach in Cesium that can simplify this.

We have software built on top of WorldWind in which we save parameters related to the current view. We can later retrieve those parameters resulting in 2 positions that we pass to a WorldWind method called setOrientation which orients the view to match the view that was saved. We are trying to use these same saved parameters (again that we ultimately convert to 2 positions) to set the view in a Cesium app. But in order to do this I find there is quite a bit of code in WorldWind that I’m struggling to reproduce in Cesium.

My hope is that there may be other methods in Cesium that would shortcut some of the WorldWind process. But I quickly get out of my depth when starting to deal with Matrices and Quaternions and Transforms.

Below I’ll paste what I believe is the pertinent WorldWind code (with error checking stripped out). And below that my attempt thus far to translate to Cesium code.

I’ve included the majority of the WorldWind code in order to hopefully give a full picture of what WorldWind is doing.

WorldWind code I want to translate to Cesium code…

public void setOrientation(Position eyePosition, Position centerPosition) {

Vec4 newEyePoint = globe.computePointFromPosition(eyePosition);
Vec4 newCenterPoint = globe.computePointFromPosition(centerPosition);

// If eye lat/lon != center lat/lon, then the surface normal at the center point will be a good value
// for the up direction.
Vec4 up = globe.computeSurfaceNormalAtPoint(newCenterPoint);

// Otherwise, estimate the up direction by using the *current* heading with the new center position.
Vec4 forward = newCenterPoint.subtract3(newEyePoint).normalize3();
if (forward.cross3(up).getLength3() < 0.001) {
    Matrix modelview = computeTransformMatrix(globe, eyePosition, heading, Angle.ZERO, Angle.ZERO);
    if (modelview != null) {
        Matrix modelviewInv = modelview.getInverse();
        if (modelviewInv != null) {
            up = Vec4.UNIT_Y.transformBy4(modelviewInv);
        }
    }
}

ViewState modelCoords = computeViewState(this.globe, newEyePoint, newCenterPoint, up);

setViewState(modelCoords);

}

public static Matrix computeTransformMatrix(Globe globe, Position position, Angle heading, Angle pitch, Angle roll) {

// To get a yaw-pitch-roll transform, do the view transform in reverse (i.e. roll-pitch-yaw)
Matrix transform = Matrix.IDENTITY;
transform = transform.multiply(Matrix.fromAxisAngle(roll, 0, 0, 1));
transform = transform.multiply(Matrix.fromAxisAngle(pitch, -1, 0, 0));
transform = transform.multiply(Matrix.fromAxisAngle(heading, 0, 0, 1));

transform = transform.multiply(computePositionTransform(globe, position));

return transform;

}

public static Matrix computePositionTransform(Globe globe, Position center) {

// The view eye position will be the same as the center position.
// This is only the case without any zoom, heading, and pitch.
Vec4 eyePoint = globe.computePointFromPosition(center);

// The view forward direction will be colinear with the
// geoid surface normal at the center position.
Vec4 normal = globe.computeSurfaceNormalAtLocation(center.getLatitude(), center.getLongitude());
Vec4 lookAtPoint = eyePoint.subtract3(normal);

// The up direction will be pointing towards the north pole.
Vec4 north = globe.computeNorthPointingTangentAtLocation(center.getLatitude(), center.getLongitude());

// Creates a viewing matrix looking from eyePoint towards lookAtPoint,
// with the given up direction. The forward, right, and up vectors
// contained in the matrix are guaranteed to be orthogonal. This means
// that the Matrix's up may not be equivalent to the specified up vector
// here (though it will point in the same general direction).
// In this case, the forward direction would not be affected.
return Matrix.fromViewLookAt(eyePoint, lookAtPoint, north);

}

public static ViewState computeViewState(Globe globe, Vec4 eyePoint, Vec4 centerPoint, Vec4 up) {
if (up == null) {
up = ViewUtil.getUpVector(globe, centerPoint);
}

Matrix modelview = Matrix.fromViewLookAt(eyePoint, centerPoint, up);
return computeModelCoordinates(globe, modelview, centerPoint, eyePoint);

}

public static Vec4 getUpVector(Globe globe, Vec4 lookAtPoint) {
return globe.computeSurfaceNormalAtPoint(lookAtPoint);
}

public static ViewState computeModelCoordinates(Globe globe, Matrix modelTransform, Vec4 centerPoint, Vec4 eyePoint) {

// Compute the center position.
Position centerPos = globe.computePositionFromPoint(centerPoint);
// Compute the center position transform.
Matrix centerTransform = computePositionTransform(globe, centerPos);
Matrix centerTransformInv = centerTransform.getInverse();

// Compute the heading-pitch-zoom transform.
Matrix hpzTransform = modelTransform.multiply(centerTransformInv);
// Extract the heading, pitch, and zoom values from the transform.
Angle heading = computeHeading(hpzTransform);
Angle pitch = computePitch(hpzTransform);
if (heading == null || pitch == null) {
    return null;
}
Position viewPosition = globe.computePositionFromPoint(eyePoint);
return new ViewState(viewPosition, heading, pitch, Angle.ZERO);

}

public static Angle computeHeading(Matrix headingPitchZoomTransform) {
return headingPitchZoomTransform.getRotationZ();
}

public static Angle computePitch(Matrix transform) {
Angle a = transform.getRotationX();
if (a != null) {
a = a.multiply(-1.0);
}
return a;
}

public static Angle computeRoll(Matrix transform) {
return transform.getRotationY();
}

protected void setViewState(ViewState modelCoords) {
if (modelCoords != null) {
if (modelCoords.getPosition() != null) {
eyePosition = normalizedEyePosition(modelCoords.getPosition());
}
if (modelCoords.getHeading() != null) {
heading = normalizedHeading(modelCoords.getHeading());
}
if (modelCoords.getPitch() != null) {
pitch = normalizedPitch(modelCoords.getPitch());
}
if (modelCoords.getRoll() != null) {
roll = normalizedRoll(modelCoords.getRoll());
}
}
}

public static Position normalizedEyePosition(Position unnormalizedPosition){
if (unnormalizedPosition == null)
{
String message = Logging.getMessage(“nullValue.PositionIsNull”);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}

return new Position(
    Angle.normalizedLatitude(unnormalizedPosition.getLatitude()),
    Angle.normalizedLongitude(unnormalizedPosition.getLongitude()),
    unnormalizedPosition.getElevation());

}

public static Angle normalizedHeading(Angle unnormalizedHeading){
if (unnormalizedHeading == null)
{
String message = Logging.getMessage(“nullValue.AngleIsNull”);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}

double degrees = unnormalizedHeading.degrees;
double heading = degrees % 360;
return Angle.fromDegrees(heading > 180 ? heading - 360 : (heading < -180 ? 360 + heading : heading));

}

public static Angle normalizedPitch(Angle unnormalizedPitch){
if (unnormalizedPitch == null)
{
String message = Logging.getMessage(“nullValue.AngleIsNull”);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}

// Normalize pitch to the range [-180, 180].
double degrees = unnormalizedPitch.degrees;
double pitch = degrees % 360;
return Angle.fromDegrees(pitch > 180 ? pitch - 360 : (pitch < -180 ? 360 + pitch : pitch));

}

public static Angle normalizedRoll(Angle unnormalizedRoll){
if (unnormalizedRoll == null)
{
String message = Logging.getMessage(“nullValue.AngleIsNull”);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}

double degrees = unnormalizedRoll.degrees;
double roll = degrees % 360;
return Angle.fromDegrees(roll > 180 ? roll - 360 : (roll < -180 ? 360 + roll : roll));

}

``

Here’s what I have so far in my attempt to translate to Cesium…

function setOrientation(eyePoint, centerPoint, heading) {

// If eye lat/lon != center lat/lon, then the surface normal at the center point will be a good value
// for the up direction.
var up = scene.globe.ellipsoid.geodeticSurfaceNormal(centerPoint);

// Otherwise, estimate the up direction by using the *current* heading with the new center position.
var forward = Cesium.Cartesian3.subtract(eyePoint, centerPoint, new Cesium.Cartesian3());
forward = Cesium.Cartesian3.normalize(forward, forward);
var cross = Cesium.Cartesian3.cross(forward, up, new Cesium.Cartesian3());
cross = getLength3(cross);
if (cross < 0.001) {
    var modelMatrix = Cesium.Transforms.headingPitchRollQuaternion(eyePoint, heading, 0.0, 0.0);   // Was this the right thing to do or do I need to duplicate the computeTransformMatrix function?
    if (modelMatrix) {
        var modelMatrixInv = Cesium.Quaternion.inverse(modelMatrix, new Cesium.Quaternion());
        if (modelMatrixInv) {
            up = Vec4.UNIT_Y.transformBy4(modelMatrixInv);                                        // What's equivalent in Cesium?
        }
    }
}

var modelCoords = computeViewState(globe, newEyePoint, newCenterPoint, up);

setViewState(modelCoords);

}

function getLength3(objCartesian3) {
return Math.sqrt(getLengthSquared3(objCartesian3));
}

function getLengthSquared3(objCartesian3) {
return (objCartesian3.x * objCartesian3.x)
+ (objCartesian3.y * objCartesian3.y)
+ (objCartesian3.z * objCartesian3.z);
}

function computeViewState(globe, eyePoint, centerPoint, up) {
if (typeof up === “undefined”)
up = getUpVector(centerPoint);
}

var modelMatrix = Matrix.fromViewLookAt(eyePoint, centerPoint, up);                                 // What's equivalent in Cesium?
return computeModelCoordinates(globe, modelMatrix, centerPoint, eyePoint);

}

function getUpVector(lookAtPoint) {
return scene.globe.ellipsoid.geodeticSurfaceNormal(lookAtPoint);
}

function computeModelCoordinates(globe, modelMatrix, centerPoint, eyePoint) {

// Compute the center position.
var centerPos = scene.globe.ellipsoid.cartesianToCartographic(centerPoint);

// Compute the center position transform.
var centerTransformMatrix = computePositionTransform(globe, centerPos);
var centerTransformMatrixInv = Cesium.Quaternion.inverse(centerTransformMatrix, new Cesium.Quaternion());

// Compute the heading-pitch-zoom transform.
var hpzTransformMatrix = modelMatrix.multiply(centerTransformMatrixInv);                            // What's equivalent in Cesium?
// Extract the heading, pitch, and zoom values from the transform.

var headingAngle = computeHeading(hpzTransformMatrix);
var pitchAngle = computePitch(hpzTransformMatrix);
if (headingAngle == null || pitchAngle == null) {
    return null;
}

var viewPosition = scene.globe.ellipsoid.cartesianToCartographic(eyePoint);
return new ViewState(viewPosition, headingAngle, pitchAngle, Angle.ZERO);

}

function computePositionTransform(globe, center) {

// The view eye position will be the same as the center position.
// This is only the case without any zoom, heading, and pitch.
var eyePoint = scene.globe.ellipsoid.cartographicToCartesian(center);

// The view forward direction will be colinear with the
// geoid surface normal at the center position.
var normal = scene.globe.ellipsoid.geodeticSurfaceNormal(eyePoint);
var lookAtPoint = eyePoint.subtract3(normal);

// The up direction will be pointing towards the north pole.
var north = globe.computeNorthPointingTangentAtLocation(center.getLatitude(), center.getLongitude());   // What's equivalent in Cesium?

// Creates a viewing matrix looking from eyePoint towards lookAtPoint,
// with the given up direction. The forward, right, and up vectors
// contained in the matrix are guaranteed to be orthogonal. This means
// that the Matrix's up may not be equivalent to the specified up vector
// here (though it will point in the same general direction).
// In this case, the forward direction would not be affected.
return Matrix.fromViewLookAt(eyePoint, lookAtPoint, north);                                         // What's equivalent in Cesium?

}

function computeHeading(headingPitchZoomTransform) {
return headingPitchZoomTransform.getRotationZ(); // What’s equivalent in Cesium?
}

function computePitch(Matrix transform) {
Angle a = transform.getRotationX(); // What’s equivalent in Cesium?
if (a != null) {
a = a.multiply(-1.0); // What’s equivalent in Cesium?
}
return a;
}

function computeRoll(Matrix transform) {
return transform.getRotationY(); // What’s equivalent in Cesium?
}

function setViewState(ViewState modelCoords) {
if (modelCoords != null) {
if (modelCoords.getPosition() != null) {
eyePosition = normalizedEyePosition(modelCoords.getPosition());
}
if (modelCoords.getHeading() != null) {
heading = normalizedHeading(modelCoords.getHeading());
}
if (modelCoords.getPitch() != null) {
pitch = normalizedPitch(modelCoords.getPitch());
}
if (modelCoords.getRoll() != null) {
roll = normalizedRoll(modelCoords.getRoll());
}
}
}

``

If anyone is willing to take the time to provide any instructions/education/help it would be greatly appreciated!

As an aside… I would love to understand these concepts (Matrices, Quaternions, Transforms). If anyone could point me to a good educational source that would help with practical understanding I would appreciate that as well.

Thanks,

Rob

Hello,

var viewer = new Cesium.Viewer(‘cesiumContainer’);
var camera = viewer.camera;
var view = {
position: camera.positionWC.clone(),
heading: camera.heading,
pitch: camera.pitch,
roll: camera.roll
};

Sandcastle.addToolbarButton(‘Save View’, function() {
view.position = camera.positionWC.clone();
view.heading = camera.heading;
view.pitch = camera.pitch;
view.roll = camera.roll;
});

Sandcastle.addToolbarButton(‘Restore View’, function() {
camera.setView({
destination: view.position,
orientation: {
heading: view.heading,
pitch: view.pitch,
roll: view.roll
}
});
});

``

If I misunderstood what you wanted to do, let me know =)

Best,

Hannah

Hi Hannah,

Thank you for the response. But I understand how to save a view in Cesium and restore it later.

What I’m needing to do is to use parameters that have been saved by a WorldWind application that define a “look at” position and an “eye position”. I then need to use those 2 positions and replicate WorldWind’s “setOrientation” function to set the view using those 2 positions so that the view in Cesium matches the view in WorldWind.

I have tried to simply use the positions themselves to set the Cesium view directly. But the result is not the same as the view in WorldWind. That’s when I started digging into the WorldWind code and found that the 2 positions were being passed to WorldWind’s “setOrientation” function. I then started walking through the “setOrientation” function and found all of the steps WorldWind goes through to use those 2 positions to set the view.

I need help with converting the WorldWind code to Cesium equivalent. Especially the places where I noted “// What’s equivalent in Cesium?” or “// Was this the right thing to do or do I need to duplicate the computeTransformMatrix function?” in the code I posted.

Thanks again for responding. I’m hoping someone can help me with finding equivalent Cesium functions so that I can translate this set of WorldWind functions into Cesium funtions.

Rob

Ah okay. Sorry, this is something I don’t know as much about.
I would guess you’d be able to use the Camera.lookAt function but it sounds like you’ve tried that.

I’m not sure if this would be helpful, but we have a bunch of helper functions on Matrix4 for creating transforms.

Sorry I can’t be of more help. Hopefully someone else will have a better idea

Best,

Hannah

I appreciate the help. I completely understand.

I’m out of my depth when it comes to transforms, matrices, quaternions, etc. And since I don’t fully understand what WorldWind’s code is doing and since the terminology is different between WorldWind’s functions and Cesium’s functions I don’t know how to find functions in Cesium that do the same thing WorldWind is doing. I’m sure Cesium has equivalent capabilities. I just don’t know how to make the translation.

Hopefully someone can just help me find functions/methods in Cesium that do the same thing the WorldWind functions are doing where I put the “// What’s equivalent in Cesium?” notes in the code I posted.

Thanks again for responding!

Rob

If you have the position of the eye and the position of what you’re looking at, have you tried this:

var offset = Cartesian3.subtract(eyePosition, lookAtPosition, new Cartesian3());

camera.lookAt(lookAtPosition, offset);

From a quick look at the Worldwind code, the behavior should be the same.

Hi Dan,

Thank you so much for the response! I was hoping that there might be an approach in Cesium that would simplify this.

I gave your code a try, and I got interesting results. The view’s lookAt position seems to be correct. But the eyePosition is at ground level (when it should be up around 40 degrees) and the heading is off by about 90 degrees. I think this may be due to x,y,z axis definitions being different between WorldWind and Cesium.

The other strange thing that happened when I tried your code is that my mouse handling code seemed to change. We have overridden default Cesium behavior so that the left button on the mouse only pans the view while the right button provides heading and tilt movement. After I make the camera.lookAt call it appears that default Cesium mouse behavior is restored.

Any suggestions on either of these issues are appreciated. Thanks again for your help!

Rob

The view parameters that we save include the heading, tilt and view distance. So I switched to using the camera.lookAt call that takes a HeadingPitchRange for the offset. This came close to replicating the view in WorldWind.

However, strangely I need to subtract 75 degrees from both the heading and the tilt to get the view to match what i see in WorldWind.

Regarding the screenSpaceCameraController… I tried just resetting the tilt and zoom event types after the camera.lookAt call, but this did not correct the problem. The tilt event mapping to right_drag has been lost. Any ideas on why this happens or how to fix it?

The default behavior for Camera.lookAt changes the camera reference frame to be centered on the object being looked at. You can revert it by calling camera.lookAtTransform(Matrix4.IDENTITY) which will keep the camera position and orientation, but changes its reference frame to be the center of the earth.

Hi Dan,

Thanks for that. Calling camera.lookAtTransform(Matrix4.IDENTITY) did put the reference frame back to the center of the earth!

However, I still lose my mouse right_drag behavior. Below is the code that I use to set up the screenSpaceCameraController on startup. But calling this function after the camera.lookAt call does not return my right_drag behavior.

function setupCameraController() {

// Following 2 lines are a HACK to prevent crash on iOS
// when first action after load and .setView or .flyTo
// is a zoom
scene.screenSpaceCameraController._aggregator._currentMousePosition.x = 1;
scene.screenSpaceCameraController._aggregator._currentMousePosition.y = 1;        


scene.screenSpaceCameraController.tiltEventTypes = [Cesium.CameraEventType.RIGHT_DRAG, Cesium.CameraEventType.PINCH];
scene.screenSpaceCameraController.zoomEventTypes = [Cesium.CameraEventType.WHEEL, Cesium.CameraEventType.PINCH];
scene.screenSpaceCameraController.enableLook = false;    


// Cause zoom to center of screen instead of mouse location
if ( !isMobile.any() ) {
    scene.screenSpaceCameraController.enableZoom = false;  
    cesiumViewer.screenSpaceEventHandler.setInputAction(function(amount){
        amount = Cesium.Math.sign(amount) * scene.camera.positionCartographic.height / Math.log(scene.camera.positionCartographic.height);
        scene.camera.zoomIn(amount * 1.5);

// showMessage(parseInt(cesiumViewer.camera.positionCartographic.height));
}, Cesium.ScreenSpaceEventType.WHEEL);
}

}

``

Thanks again for your help!

Dan,

My mistake! I didn’t reference the Matrix4.IDENTITY call correctly. When I fixed that it fixed the right drag issue.

Thank you very much for your help!