Simulating the solar system #1

Simulating the solar system #1

S

Last time we left off having figured out what the physics equations behind everything will look like. Now it is time to get to coding. First off, I like to keep my code separated into small classes/components each doing one very specific thing. It is suprising how many complex behaviours can be broken down into these very simplistic components.

Let’s make sure we have a way to reference constant values from the real world. For this I created a static class where I store a few constants the code for which you can find below. It’s pretty straight forward and will make things easier down the line.

namespace HercDotTech.Data
{
    public static class PhysicsConstants
    {
        /// <summary>
        /// Gravitational contant [m * 1/kg * 1/s^2]
        /// </summary>
        public const double GRAVITY = 6.67430e-11;

        /// <summary>
        /// Plank constant [m^2 * kg / s]
        /// </summary>
        public const double PLANCK = 6.62607004e-34;

        /// <summary>
        /// Speed of light in a vacuum [m / s] 
        /// </summary>
        public const double LIGHTSPEED = 2.9979e7;

        /// <summary>
        /// Astronomical unit [m]
        /// </summary>
        public const double AU = 1.50e11;
    }
}

As you might have noticed, I like using namespaces to keep my classes segregated between layers and functionality. Moreover, it is always a good idea to decouple the data classes from your application ones. What I mean is that I’ll have a different layer where all the data is stored and then a second one on top of it that Unity will use to render things. This approach will make it easier in the future to change where this data is coming from as well as its precision level without requiring me to compile my own version of unity that uses doubles instead of floats for its physics.
I won’t go too much into detail about the performance difference between float16, float32 and float64 (doubles) when performing different calculations but the main thing you need to be aware of is that doubles are considerably slower than singles when it comes to calculations. However, in certain scenarios, such as this game, the accuract provided by the extra bits is necesary and as such the performance hit is acceptable.

Since I want all the planets, stars, moons and solar systems to live in the same space and have a singular spatial position which will allow me to do some really cool things later on, I will have to rely on doubles for some positions. Another trick I will use is storing the celestial bodies positions relative to the solar system position and in different units. By example, the celestial body position within the solar system will be in SI unit (meters) whilst the solar system position in the galaxy will be in AU (Astronomical Units). Given that the distance from the Sun to Pluto is approximately 6 billion kilometers and I’d like the positions stored in meters you can see how a float is too small. Technically, it isn’t too small, it is just going to provide very little accuracy at that scale and as such it would be impossible to add small movements to it.
We’re getting a bit sidetracked here so let’s get back into it. We need a class that is very similar to the Vector3 one that Unity provides but relies on double precision properties. I believe that we should focus on building new things rather than reinventing the wheel over and over again, especially when others have done it before. Now, due to licensing, I have built my own class but you can find one that will work just fine here: https://github.com/sldsmkd/vector3d .

With that sorted it is time to define the basic component of our solar system, the solar system class. It is pretty straight forward. At this point all it needs is a name, position and list of celestial bodies which, moving forward, I’ll call orbitals.

using System;
using System.Collections.Generic;

namespace HercDotTech.Data
{
    [Serializable]
    public class SolarSystem
    {
        /// <summary>
        ///  The name of the solar system
        /// </summary>
        public readonly string Name;

        /// <summary>
        /// Position of the solar system [AU]
        /// </summary>
        public DoubleVector3 Position { get; private set; }

        /// <summary>
        /// List of orbitals in the system
        /// </summary>
        public List<Orbital> Orbitals { get; private set; }

        public SolarSystem(string name, DoubleVector3 position)
        {
            Name = name;
            Position = position;
            Orbitals = new List<Orbital>();
        }

        public void AddOrbital(Orbital orbital)
        {
            Orbitals.Add(orbital);
        }

        public Orbital GetOrbital(int index)
        {
            return Orbitals[index];
        }
    }
}

Finally, the orbital class. You’ll note that so far neither of these classes knows how to calculate gravitational forces or apply them. Although the orbital does store its position and can update its velocity and position dependent on eachother, it doesn’t have a concept of what that velocity represents or how it is calculated.
All of that will be handled in the SolarSystemUpdater that we will be dealing with next time. Since these classes are completely independent from Unity’s types and thread safe, we can run the updates in a different thread, separate from the one running Unity’s physics. This gives us full control over how often the planets get updated and at which point during the frame time.

Below you can find the code that provides all the functionality an Orbital needs to be aware of right now.

using System;
using System.Numerics;

namespace HercDotTech.Data
{
    [Serializable]
    public class Orbital
    {
        /// <summary>
        ///  The name of the orbital
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// The radius of the orbital [m]
        /// </summary>
        public double Radius { get; private set; }

        /// <summary>
        /// The surface gravity of the orbital [kg * m / s^2]
        /// </summary>
        public double SurfaceGravity { get; private set; }

        /// <summary>
        /// The mass of the orbital [kg]
        /// </summary>
        public double Mass { get; private set; }

        /// <summary>
        /// Whether the orbital can move or not
        /// </summary>
        public bool Immovable { get; private set; }

        /// <summary>
        /// Position of the orbital [m]
        /// </summary>
        public DoubleVector3 Position { get; private set; }

        /// <summary>
        /// Position of the orbital [m/s]
        /// </summary>
        public DoubleVector3 Velocity { get; private set; }

        /// <summary>
        /// Rotation of the orbital
        /// </summary>
        public Quaternion Rotation { get; private set; }

        /// <summary>
        /// Full constructor
        /// </summary>
        /// <param name="name"></param>
        /// <param name="radius"></param>
        /// <param name="surfaceGravity"></param>
        /// <param name="mass"></param>
        /// <param name="immovable"></param>
        /// <param name="position"></param>
        /// <param name="velocity"></param>
        /// <param name="rotation"></param>
        public Orbital(
             string name,
             double radius,
             double surfaceGravity,
             double mass,
             bool immovable,
             DoubleVector3 position,
             DoubleVector3 velocity,
             Quaternion rotation
        )
        {
            Name = name;
            Radius = radius;
            SurfaceGravity = surfaceGravity;
            Mass = mass;
            Immovable = immovable;
            Position = position;
            Velocity = velocity;
            Rotation = rotation;
        }

        /// <summary>
        /// Sets the velocity of the orbital
        /// </summary>
        /// <param name="velocity"></param>
        public void SetVelocity(DoubleVector3 velocity)
        {
            Velocity = velocity;
        }

        /// <summary>
        /// Adds the given velocity to the exiting one
        /// </summary>
        /// <param name="velocity"></param>
        public void AddVelocity(DoubleVector3 velocity)
        {
            Velocity += velocity;
        }

        /// <summary>
        /// Adds the given force to the velocity
        /// </summary>
        /// <param name="force"></param>
        public void AddForce(DoubleVector3 force)
        {
            Velocity += force / Mass;
        }

        /// <summary>
        /// Updates the orbital's position based on its current one and its velocity
        /// </summary>
        public void UpdatePosition()
        {
            Position += Velocity;
        }
    }
}

In part two of this stage we will create some components to make Unity able to use our newly created data classes. See you soon!