Help Understanding 3-D Coordinate Systems

Could someone point me to some practical resources for learning about the Unity coordinate system… and specifically related to Cesium for Unity?

We’ve been working with CesiumJS since it was in beta. And we’re investigating the possibilities of using Cesium for Unity. But we have no experience with 3-D coordinate systems. And I’d like to find some good resources to study and gain a working understanding.

I’ve read all the Getting Started information on Cesium’s website along with “Cesium for Unity: Placing Objects on the Globe”. I’ve read through every post in the Cesium for Unity forum related to positioning and rotation. I’ve also read through many articles about position and rotation in Unity. But with absolutely no background or experience with 3-D coordinate systems I can’t put the pieces together so far.

We’d like to fly aircraft models along their radar flight paths in Cesium for Unity. At this point we’ve imported a single aircraft fbx model. We’ve created a prefab using that model which we use to create multiple instances of the aircraft. We’ve been able to load the radar track data and can move the aircraft with the following approach…

    public IEnumerator FlightPlayback(Flight flt)
    {
        GameObject objectToMove = flt.gameObject;
        List<TrackPoint> lstTPs = flt.pts;

        //Debug.Log("FlightPlayback started for OpId: " + flt.oper_id + " trackStart: " + tracksStart.ToString());

        for (int i = 1; i < lstTPs.Count; i++)
        {
            TrackPoint tp = lstTPs[i];

            // Get the speed
            float deltaSec = (tp.deltaSec - lstTPs[i - 1].deltaSec) / SpeedMultiplier;
            
            float elapsedTime = 0;

            while (elapsedTime < deltaSec)
            {

                // Get start for the trackpoint
                double3 start = lstTPs[i - 1].xyz;

                // Get end for the trackpoint
                double3 tarPos = lstTPs[i].xyz;

                var lerpPos = math.lerp(start, tarPos, (elapsedTime / deltaSec));
                var ECEFStart = CesiumWgs84Ellipsoid.LongitudeLatitudeHeightToEarthCenteredEarthFixed(start);
                var ECEFTarget = CesiumWgs84Ellipsoid.LongitudeLatitudeHeightToEarthCenteredEarthFixed(lerpPos);
                objectToMove.GetComponent<CesiumGlobeAnchor>().positionGlobeFixed = ECEFTarget;

                //var newPosLatLon = _map.WorldToGeoPosition(objectToMove.transform.localPosition);
                //locPos = _map.GeoToWorldPosition(newPosLatLon, true);
                //locVec = _map.WorldToGeoPosition(locPos);


                // Rotate to look at the next point
                //objectToMove.transform.rotation = Quaternion.LookRotation(tarPos - start);
                Vector3 v3ECEFTarget = new Vector3();
                v3ECEFTarget.x = (float)ECEFTarget.x;
                v3ECEFTarget.y = (float)ECEFTarget.y;
                v3ECEFTarget.z = (float)ECEFTarget.z;

                Vector3 v3ECEFStart = new Vector3();
                v3ECEFStart.x = (float)ECEFStart.x;
                v3ECEFStart.y = (float)ECEFStart.y;
                v3ECEFStart.z = (float)ECEFStart.z;

                //float3 normalizedSource = (float3)math.normalize(ECEFStart);
                //float3 normalizedDestination = (float3)math.normalize(ECEFTarget);
                ////Quaternion flyQuat = Quaternion.FromToRotation(normalizedSource, normalizedDestination);
                //Quaternion flyQuat = Quaternion.FromToRotation(v3ECEFStart, v3ECEFTarget);
                //Debug.Log(flyQuat);

                //Vector3 directionToTarget = v3ECEFTarget - v3ECEFStart;
                //Vector3 currentDirection = transform.forward;
                //float maxTurnSpeed = 60f; // degrees per second
                //Vector3 resultingDirection = Vector3.RotateTowards(currentDirection, directionToTarget, maxTurnSpeed * Mathf.Deg2Rad * Time.deltaTime, 1f);
                //transform.rotation = Quaternion.LookRotation(resultingDirection);


                Vector3 dir = v3ECEFTarget - v3ECEFStart;
                float angle = Mathf.Atan2(dir.x, dir.y) * Mathf.Rad2Deg;
                //objectToMove.GetComponent<CesiumGlobeAnchor>().transform.rotation = Quaternion.Euler(0, angle + 180.0f, 0);


                ////var q = Quaternion.identity;
                //var q = Quaternion.LookRotation(v3ECEFTarget - v3ECEFStart);
                ////q.y = 0;
                ////objectToMove.GetComponent<CesiumGlobeAnchor>().rotationEastUpNorth = flyQuat;
                //objectToMove.GetComponent<CesiumGlobeAnchor>().rotationGlobeFixed = q;

                //Vector3 angles = this.transform.eulerAngles;
                //angles.z = 0.0f;
                //objectToMove.GetComponent<CesiumGlobeAnchor>().transform.eulerAngles = angles;

                // Interpolate rotation in the EUN frame. The local EUN rotation will
                // be transformed to the appropriate world rotation as we fly.
                objectToMove.GetComponent<CesiumGlobeAnchor>().rotationEastUpNorth = Quaternion.Slerp(
                    objectToMove.GetComponent<CesiumGlobeAnchor>().rotationEastUpNorth,
                    Quaternion.Euler(0, (angle - 225), 0),
                    (float)(elapsedTime / deltaSec)
                );


                elapsedTime += Time.deltaTime;


                yield return new WaitForEndOfFrame();
            }

        }

Each track point is held in a double3 ‘xyz’ as:

tp.xyz = new double3 { x = tp.lon, y = tp.lat, z = tp.alt * Constants.FEET_TO_METERS };

(By the way we are using the CesiumGeoreference and Cesium Globe Anchors.)

But we don’t have the first clue how to manipulate the heading, pitch, roll.

I left all of the rotation/transformation attempts in the code I posted. As you can see we have no idea what we’re doing and have basically been shooting in the dark hoping something will begin to make sense.

I’m not so much looking for someone to write the rotation code for us (although we certainly would love to see a good example of how to do what we’re trying to do). Instead, we’d love help with a couple of things…

  1. Can you point us to a practical resource or resources to understand and use the 3-D coordinate systems required for Cesium for Unity?
  2. Any thoughts about how the flight data is stored/structured would also be welcome. Is the double3 a practical approach? Or would we benefit from structuring the data differently? If so, what would be a better approach and why?
  3. Is the method we’re using to move the models reasonable? Or should we used a different approach for moving the models?

We’re open to all input as this is a new world for us… and we want to gain a solid, working understanding about these topics as we begin to experiment and develop in this environment.

Thanks for any help!

So this is how I’m handling heading, where anchor is a reference to the CesiumGlobalAnchor for the aircraft gameobject. Should be easy to adapt for pitch and roll I figure.

 var q = new Quaternion();
 q.eulerAngles = new Vector3(0, hdg-90, 0);

 anchor.rotationEastUpNorth = q;

The best way to learn about the Unity coordinate system is from the Unity documentation. This page is a good place to start:

For the Cesium for Unity coordinate system, the comments on the CesiumGlobeAnchor and CesiumGeoreference components may help. But mostly it’s pretty straightforward: The CesiumGeoreference makes it so that the Unity world coordinates are centered on the georeference origin you’ve specified and Unity’s +X axis points East, its +Y axis points Up, and its +Z axis points North.

But setting the position from longitude/latitude/height should be really simple if you’re using a CesiumGlobeAnchor:

anchor.SetPositionLongitudeLatitudeHeight(longitudeDegrees, latitudeDegrees, heightAboveWGS84EllipsoidMeters);

Setting the orientation from heading/pitch/roll is a little more complicated, because we have to be clear about what exactly heading, pitch, and roll are. But essentially, we’ll convert that sequence of rotations into a quaternion, and then set the rotationEastUpNorth property.

Unity has two quaternion classes. One is UnityEngine.Quaternion, and the other is Unity.Mathematics.quaternion. The rotationEastUpNorth property takes the latter, but there’s also an implicit conversion from the former, so basically you can use whichever you prefer.

Now, “East-Up-North” literally means that the X axis points East, the Y axis points Up, and the Z axis points North. So we need to create a quaternion that rotates from the given heading-pitch-roll to that coordinate system. Quaternion.Euler, as mentioned by @paulgee32, is a good place to start. The documentation says:

Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis; applied in that order.

If you look at the CesiumGlobeAnchor component in the Editor, the East-Up-North rotation value is shown in the UI using Euler angles. So you can play around with these and see how they relate to your understanding of the heading-pitch roll.

So I have to make some assumptions here. Let’s say your model has +X forward, +Y up, and +Z left. And heading is the angle from North toward East. Positive Pitch makes the nose point up. Positive Roll is a bank left. If I have that all right, you shoud be able to set your angles like this:

anchor.rotationEastUpNorth = Quaternion.Euler(roll, heading - 90, pitch);

If any of the details of that are wrong, you’ll have to adjust accordingly. The best way is to add your model to the scene with a globe anchor and manually adjust the Rotation East-Up-North rotation in the UI to understand how it relates to your heading-pitch-roll.

1 Like

Hi @paulgee32, thank you for the example.

@Kevin_Ring thank you for taking the time to provide all the detailed explanations and examples. This is very helpful! We’ll spend time studying the links you provided and digesting the information.