Scaling

“Space is big. You just won’t believe how vastly, hugely, mindbogglingly big it is. I mean, you may think it’s a long way down the road to the chemist’s, but that’s just peanuts to space.”

― Douglas Adams, The Hitchhiker’s Guide to the Galaxy

Scaling a space game is tricky. Spacecraft are game objects that are measured in meters, planets in thousands of kilometers and orbits in millions of kilometers. Unity starts to complain when objects are located beyond 100,000 units from the origin and this imposes a length scale in the rendering of the scene. Additional complications arise in the specification of values for the gravitational physics. We want to be able to map real-world data into values that can be integrated by the physics engine without a loss of precision. To accomplish this all N-body simulations will scale the distances, velocities and masses to attempt to avoid numerical issues.

Given the constraints of Unity position limits and the requirements of numerical precision, it is not possible to use one set of values for position, velocity and time in the Unity scene and the physics engine. In addition it is useful when creating space simulations to be able to provide positions and velocities in “real-world” units (e.g. m, km,AU) and these units may not be suitable for either the scene or physics representations.

As a result we have three “spaces”:

  1. World space (positions designated in real-world units [e.g. AU, km, m] or dimensionless units)

  2. Scene/Display space

  3. Physics space

GE2 tries to make the scaling issues as painless as possible by allowing the designer to specify a typical:

  • orbit size in world units

  • orbit period in game seconds

The GSController component allows the scale parameters to be set via the inspector values:

  • Orbit Scale: size of a typical orbit in the scene in world units

  • Orbit Mass Scale: the mass of the body around which the typical orbit is defined (generally the heaviest object in the scene)

  • Game Sec Per Orbit: the game time desired for one orbit a body at the orbit scale around the orbit mass

GSController uses this information to configure the scaling in the GravityEngine (GE) class it instantiates. The GE API uses world units but internally values are converted into units that are more suitable for numerical integration. The code for this is found in GBUnits.GEScaler.

GravityEngine Internal Units

This section is optional and is provided for those who like to delve into the details.

The N-body simulation in GE models gravity using Newton’s force law and good ‘ol \(F= m a\). These equations describe the inter-related changes in velocity and position of all bodies as a set of differential equations and these are then integrated numerically using one of several standard techniques. It would be a shame not to show these off, so for the record the equations for an N-body gravitational system are:

(1)\[\begin{equation} \ddot{r}_j = \sum_{k=0,k\ne j}^{N-1}\frac{G m_k (r_k-r_j)}{|r_j-r_k|^3}, j=0,...,N-1 \end{equation}\]

where \(r_j\) is the three dimensional position vector of the jth body in Cartesian coordinates, \(m_j\) is the mass of the mth body and G is Newton’s gravitational constant.

Note

Wait, \(r^3\)? The Physics 101 formula for gravity was \(F= G m_1 m_2/r^2\). What’s with the \(r^3\)? The answer is that the equation above is in vector form and the direction of the force is the \(r\) unit vector which is in the direction \((r_k-r_j)\) but is normalized by dividing by its magnitude. This gives us an extra \(|(r_k-r_j)|\) in the denominator in addition to the two from \(r^2=|(r_j-r_k)|^2\).

The goal in the numerical evolution of differential equations is to maintain accuracy by a suitable choice of time step size and ensuring the position and velocity values do not get to large or small. In gravitational N-body systems this is done by re-scaling the system so that:

  • the typical length value for positions is 1.0

  • the typical mass value is 1.0

  • the value of Newton’s constant (normally 6.674E-11 in SI units) is 1 (because who wants to multiple by E-11 everywhere?)

At first this appears to be a lot to ask and it is a bit surprising that all these goals can be achieved. I always need to go back to basics to remind myself so here’s how it works:

The force equation for gravity in one dimension (which is all we need to check the units) is:

(2)\[\begin{equation} m a = G\frac{m M}{r^2} \end{equation}\]

where a is the acceleration, m the mass of one body, M the mass of the other, r the distance between them.

Expressing this in terms of type of units (L=length, T=time, M=mass) and recalling \(a\) has units of \(L/T^2\), rearranging for \(G\) we get:

(3)\[\begin{equation} G = \frac{M L^3}{T^2} \end{equation}\]

This equation has four variables. We can scale three of them as specified above (L=1, M=1, G=1). By doing so we are then constrained to use a time scale which is defined as \(T_{scaled} = T * \sqrt{(G_{si} M_{scale})/L_{scale}^3}\).

For reference, the transform functions for L, V, T and M are provided in the table below. The class GBUnits.GEScaler handles this in GE2.

Scale From

Scale To

Length

Velocity

Time

Mass

world

GE

\(1/L_o\)

\(\sqrt{L_o/(G_u M_o)}\)

\(\sqrt{(G_u M_o)/L_o^3}\)

\(1/M_o\)

GE

world

\(L_o\)

\(\sqrt{(G_u M_o)/L_o}\)

\(\sqrt{L_o^3/(G_u M_o)}\)

\(M_o\)

Note: \(L_o\) is the orbital scale in world units and \(M_o\) is the mass scale. \(G_u\) is the value of Newton’s constant in the selected world units. e.g. \(G_{SI}\)=6.67430E-11 \(m^3/(kg^2 s^2)\).

Time Scaling: Unity <-> GE2

The scaling in the physics core \((L=1, M=1, G=1)\) described required that we “mess with” the time scale. An object orbiting at \(L=1\) will complete an orbit after \(t=2 \pi\) time units. How does this related to the world time? How does this relate to the Unity time?

The GECore creates a class GEScalar that handles this. To get the time scaling to world time the method GEScaler.ScaleTimeGEToWorld() can be used. This allows the world time (e.g. in SI_km, SI, AU this will be world seconds). An alternative is to get the world time directly via GECore.TimeWorld() (internal physics time can be retreived with GECore.TimeGE()).

Unity time (Time.time) typically evolves at the same rate as user time (e.g. the time a clock on the wall keeps). [The only exception is if the Player setting are altered to change the Time.timeScale value]. How does this time relate to the GE2 world time? This is typically managed by the settings in GSController:

  • Time Scale = WORLD and World Sec per Game Sec

  • Time Scale = PER_ORBIT and Game Sec Per Orbit

A GSController in WORLD scale determines the increment in Unity time since the last GECore evolution and then calls for evolution by that value multiplied by the value of World Sec per Game Sec. In PER_ORBIT mode the value of the world time per game sec is determined by assuming one orbit at a radius corresponding to the Orbit Scale around a body with mass of Orbit Mass Scale is required to take a specfied amount of Unity time. The value can be retreived with: GSController.WorldTimePerGameSec().