Warning
Warning: Unity Simulation is deprecated as of December 2023, and is no longer available.
Working with Coordinate Spaces
In Unity, the X axis points right, Y up, and Z forward. Other simulation systems, on the other hand, use various coordinate spaces: For example, ROS uses a coordinate space where X points forward, Y left, and Z up - this is commonly called the "FLU" coordinate space (forward, left, up), whereas the Unity coordinate space would be "RUF" (right, up, forward).
The Unity.Simulation.Foundation namespace contains code to make it easier to work with these various coordinate spaces - letting you be explicit about what coordinates a given value is in at compile time, and managing the conversions for you.
Message conversions:
The main position messages (geometry_msgs/Point, geometry_msgs/Point32 and geometry_msgs/Vector3) can be converted to and from Unity Vector3s like this:
PointMsg ToMessageExample(Vector3 position)
{
return position.To<FLU>();
}
Vector3 ToUnityExample(PointMsg message)
{
return message.From<FLU>();
}
Similarly, the geometry_msgs/Quaternion message can be converted to and from a Unity Quaternion.
QuaternionMsg myQuaternionMsg = myGameObject.transform.rotation.To<FLU>();
Quaternion myUnityQuaternion = myQuaternionMsg.From<FLU>();
Hence, writing 3d data into a message can often be as simple as writing:
ImuMsg msg = new ImuMsg()
{
linear_acceleration = acceleration.To<FLU>();
orientation = rigidbody.transform.rotation.To<FLU>();
angular_velocity = rigidbody.angularVelocity.To<FLU>();
}
connection.Publish("imu", msg);
Unity's standard Transform class also has a To<C>()
extension method that returns a TransformMsg. In other words, creating a TransformMsg typically looks like this:
TransformMsg msg = myGameObject.transform.To<FLU>();
Internal details
Some more detail about how this works: The core feature here is the two generic structs, Vector3<C>
and Quaternion<C>
. The type parameter C indicates the coordinate frame you're working in - either FLU, FRD, RUF, or perhaps one of the more exotic frames such as NED (north, east, down) or ENU (east, north, up), used in aviation.
These are fully-fledged Vector3 and Quaternion classes, so if you want, you can work with them directly to perform geometric operations in an arbitrary coordinate space. (Note, it's a compile time error to combine mismatched types - for example, you can't add a Vector3<FLU>
to a Vector3<RUF>
.)
These are the types returned by the To<FLU>()
calls above. Vector3<C>
also has implicit conversions to PointMsg, Point32Msg, and Vector3Msg, which is how this one call can be used to convert to all three data types.
Converting between frames:
If, for example, you need to convert an object's position into the FLU coordinate frame, you might explicitly create a Vector3
Vector3<FLU> fluPos = obj.transform.position.To<FLU>();
An explicit cast, or calling the constructor, would also produce the same result.
Vector3<FLU> fluPos2 = (Vector3<FLU>)obj.transform.position;
Vector3<FLU> fluPos3 = new Vector3<FLU>(obj.transform.position);
To convert back, just access the "toUnity" property on the vector.
Vector3 unityPos = fluPos.toUnity;
The same functions and properties apply for converting Quaternions.
Converting incoming messages
You can also convert Point, Point32 and Vector3 messages back into Unity coordinates. If you know that a PointMsg is in coordinate space C, you can convert it into a Unity Vector3 in the unity coordinate space by writing From<C>
. For example:
void SubscriberCallback(PointMsg p)
{
transform.position = p.From<FLU>();
}
Or, if you want to leave it in the FLU coordinate space for now, you can write:
Vector3<FLU> fluPos = p.As<FLU>();
(Note, the As function does NOT do any coordinate conversion. It simply assumes the point is in the FLU coordinate frame already, and transfers it into an appropriate container.)
And again, the same goes for converting a Quaternion message into a Unity Quaternion or Quaternion<C>
.
Geographical Coordinates
The geographical coordinate systems NED and ENU are more complicated than the rest: besides the obvious "north" direction, they also provide a convention for which direction is "forward", which is not even consistent between them - In NED, forward (i.e. yaw 0) is north, whereas in ENU forward is east.
Think of it this way: NED coordinates are just FRD (forward, right, down) coordinates, with the added convention that the root coordinate frame's forward direction is called "north". Similarly, ENU coordinates are just FLU (forward, left, up) coordinates, with the added convention that the root coordinate frame's forward direction is called "east".
Because of this inconsistency, there's no universal convention that we could establish for which direction is north in Unity. Instead, we're forced to make a distinction between local and world rotations.
To correctly convert a world position or rotation to or from NED or ENU coordinates, you should use the standard NED or ENU coordinate space - for example, To<NED>()
. This conversion will respect what direction you have set as North. By default, the Unity Z axis points north, but this is a configuration option that you can change in the Simulation/Settings menu.
If you have a local rotation represented in NED or ENU coordinates, you should convert it using the coordinate space NEDLocal (a synonym for FRD) or ENULocal (a synonym for FLU): for example, To<NEDLocal>()
. These conversions are not appropriate for handling world rotations, because they don't respect the north direction, only what direction is forward (i.e. the Unity Z axis). In other words, with NEDLocal and ENULocal, an identity quaternion is still identity. This is not necessarily true for NED or ENU.
If you only care about NED coordinates, you can avoid having to deal with this distinction by setting the Z axis direction to North (the default) - this will make the NED coordinate space equal to NEDLocal (FRD). Similarly, if you only care about ENU coordinates, setting the Z axis direction to East will make ENU equal to ENULocal (FLU).