When working in Unity, placing features on a map in the correct geographic coordinates requires accuracy and precision in order to match real world locations. This can be tricky, so we’ve created a geo coordinates data structure to match Unity World Space with the real world. Here is a preview of what is shipping in our latest Mapbox Unity SDK release.
Unity’s coordinate system math is based on 32 bit floats, which causes floating point precision issues when working at a global scale. For example, a longitude in San Francisco, -122.450475, can round to -122.450478 as a 32 bit float. That error might not matter in some contexts, but in terms of longitude it’s enough to put a mailbox in the middle of the street instead of on a sidewalk. The rounding problems can increase as transforms (such as rotation and motion) build on each other. To solve the problem, we implemented a Vector2 struct with 64 bit floats, called Vector2d. This means that within the Mapbox Unity SDK, geo coordinates are stored and manipulated as 64 bit values to maintain precision.
Relative positioning reduces precision loss
Now that we have more precise storage, we can safely convert WGS84 longitude and latitude (real-world GPS coordinates) into Unity World Space. For the conversion, we use Spherical Mercator, EPSG:3857. That’s the projection from longitude and latitude into planar coordinates, which can easily be positioned in Unity on a plane.
At this point, another problem appears. Our Spherical Mercator coordinates are safely stored as 64 bit floats, but we will lose that precision when we try to put the values into a Unity GameObject’s transform, which is a 32 bit float Vector2 class.
To resolve this, we use relative positioning. The center of the very first tile loaded is moved to the Unity World Space origin, then used as a reference for every other tile and object that we create to make a map. For example, if the tile containing City Hall is at (4548003.18297, -13627499.15079), and another point is at (4549226.17542, -13630556.63193), we can consider the second point as being at its distance (x and y difference) from City Hall: (-1222.99245, 3057.48114). That relative value converts to a 32 bit float with much less loss of precision, because standard floating point numbers are more precise at values nearer 1.
Rendering continent-sized areas
But this raises another question: What if the initial tile itself is too big for the float range? For example, if you render the entire continent of North America in zoom level 1, your tile size will be (20037508.34279, 20037508.34279), and you’ll start having problems if, let’s say, you want to place a cube on each corner of this tile.
A solution we use in our demos is to simply scale down the entire world. Scaling by a factor of 1/1000, for example, gives you a reasonably sized world to play with. However, this will result in some loss of precision: it’s a trade-off. Use the size of the features (such as buildings or terrain) that you want to include in your project to find a good scale for it. You can alter the scale factor dynamically by using the TileSize property in the MapController in the demos.
Before and after switching to double data type.
Download our latest SDK to use these new features and let us know what you think of the code on GitHub repo.