Gravity Sim Job System - Video 02 - The GravityForceJob

YouTube Video #2 - Gravity Series

This C# Job is the longest (100 lines) of all the jobs mostly because of the comment and the force calculation. The forces applied to any given body (meaning object) is either a repulsion force due to a collision, like a bounce, or it is the force of gravity.

This is the first stage in a data computation pipeline. This stage computes the sum of all forces on each object based on current position and mass. The sum of forces are used in the next stage of the pipeline - see the next video for that.

Concept

The force for gravity is calculated using the Classical Physics formula. The Unity.Mathematics float3 value type has a lot of nice features and, with the Burst Compiler, will use SIMD (Single Instruction Multiple Data) operations to calculate the square of the X, Y and Z parts of a distance vector.

The force magnitude is calculated and a unit vector (vector of magnitude of 1 but with the direction on object relative to the other) to calculate a force vector.

But it is not enough to calculate one force vector, we want to compute the influence of all the other objects on the current object - and cycle that through all objects.

Code

Here is the code for the GravityForceJob which is the first stage of our data pipeline.

GravityForceJob.cs

using Unity.Burst;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;

/// <summary>
/// Computes acceleration upon a set of bodies based on mass and distance
/// for Newtonian gravity but also applies a repulsion force when the distance
/// is very small.
/// 
/// for N bodies, compare each against the other
/// 
///    0 1 2 3 4 5 ... N [Current Object]
///  0 - X X X X X
///  1 X - X X X X
///  2 X X - X X X
///  3 X X X - X X
///  4 X X X X - X
///  5 X X X X X -
///  ...
///  N
///  [Others] 
/// 
/// </summary>
[BurstCompile]
public struct GravityForceJob : IJob
{
    const float REPULSION_BASE_FORCE = 3f;

    [ReadOnly] public NativeArray<GravityComponent> Bodies;
    public NativeArray<float3> Forces;
    public float RepulsionThreshold;
    public float GravityStrength;

    public static JobHandle Begin(
        NativeArray<GravityComponent> bodies,
        NativeArray<float3> forces,
        float repulsionRadius,
        float gravityStrength
    )
    {
        var job = new GravityForceJob()
        {
            Bodies = bodies,
            Forces = forces,
            RepulsionThreshold = repulsionRadius,
            GravityStrength = gravityStrength
        };
        return IJobExtensions.Schedule(job);
    }

    public void Execute()
    {
        ZeroOutForces();
        ComputeForces();
    }

    private void ComputeForces()
    {
        for (int current = 0; current < Bodies.Length; current++)
        {
            for (int other = 0; other < Bodies.Length; other++)
            {
                if (current < other)
                {
                    CalculateForce(current, other);
                }
            }
        }
    }

    private void CalculateForce(int current, int other)
    {
        float3 distance = (Bodies[current].Position - Bodies[other].Position);
        float3 distSqr = distance * distance;
        float distanceSquared = distSqr.x + distSqr.y + distSqr.z;

        float force = distanceSquared < (RepulsionThreshold * RepulsionThreshold)
            ? RepulsionForce(distanceSquared)
            : GravityForce(current, other, distanceSquared);

        float3 forceVectorCurrentToOther = force * UnitVector(
            Bodies[current].Position,
            Bodies[other].Position,
            distanceSquared
        );
        Forces[current] = Forces[current] + forceVectorCurrentToOther;
        Forces[other] = Forces[other] - forceVectorCurrentToOther;
    }

    private float3 UnitVector(float3 from, float3 to, float distanceSquared)
        => (to - from) / math.sqrt(distanceSquared);

    private float GravityForce(int current, int other, float distanceSquared)
        => GravityStrength * Bodies[current].Mass * Bodies[other].Mass / distanceSquared;

    private float RepulsionForce(float distanceSquared)
        => REPULSION_BASE_FORCE / (1 + distanceSquared);
    private void ZeroOutForces()
    {
        for (int index = 0; index < Forces.Length; index++)
        {
            Forces[index] = float3.zero;
        }
    }
}