Orbital physics - Maintaining a circular orbit

Hi everyone!

I’m currently working on a 2-body orbital physics simulation and came across a problem involving the orbital physics:

I am using Newtons 2nd Law (F = -G * M *m / r^2) to calculate the gravitational forces for each planet (currently only one for simpler testing, but works with multiple). Then I apply them to the planet’s rigidbodies using the standard AddForce(…) method in FixedUpdate().

float r = Vector3.Distance(star.Position, planet.Position);
float totalForce = -(Constants.G * star.Mass * planet.Mass) / (r * r);
Vector3 force = (planet.Position - star.Position).normalized * totalForce;
planet.GetComponent<Rigidbody>().AddForce(force);

This works fine, producing the expected results.

My problem surfaces here:
I am trying to calculate the initial velocity required to keep the planets on a circular orbit by using the Vis-Viva equation:
v = sqrt(G * M * (2/r - 1/a))
(derived from setting centripetal force equal to gravitational force)

Simplified for a circular orbit:
v = sqrt(G * M / r).
Using this equation my planet is too slow to keep a circular orbit though.

Note: Position is just a simplification for transform.position. Mass a simplification for GetComponent().mass

Now: The highest velocity allowing for any orbit is the escape velocity v_e = v * sqrt(2).

After a bit of trying I figured out that I need a velocity of sqrt(2) * v for a circular orbit. Basically the escape velocity.
My escape velocity is scaled by sqrt(2) too, making it sqrt(4 * G * M / r).
If I use these scaled values everything works as intended. I also tested with different masses, radii, gravitational constants, etc. Same results.
Also: All parameters are scaled to reasonable values, so no rounding errors from too large or too small numbers. But that shouldn’t affect the result itself if the equations are right.
For completeness: star.Mass = 1000, planet.Mass = 1, r = 10 (star is at 0,0,0; planet at 10,0,0), G = 1

The initial velocity is set in the Start() method once.

float initV = Mathf.Sqrt(2f * Constants.G * star.Mass / planet.Position.magnitude);
float escapeV = Mathf.Sqrt(4f * Constants.G * star.Mass / planet.Position.magnitude);
planet.GetComponent<Rigidbody>().velocity += new Vector3(0, initV, 0);

I know I’m currently orbiting in the x-y plane, so no error there :wink:

Question: Why is it scaled by sqrt(2)? I could just scale it but I want to get it right. Anyone have an idea? I am relatively new to Unity so I’m not sure if it’s maybe something with Unity’s physics engine that creates that problem.
It’s also a possibility that I’m just stupid and overlooked something obvious :slight_smile:

Thanks in advance,
gartoks

EDIT:
OK, it MUST be something with Unity. I calculated the eccentricity from the initial position and velocity. With my scaled version (which is actually the escape velocity) I get e = 1, with the velocity as it is supposed to be I get 0, and 0 is the correct answer.
Formula used btw: e = ((v^2 - G * M / r)*r - (r * v) * v) / (G * M)
(Bold letters are vectors, same letter not-bold is the magnitude of that vector)

Hi,

Your use of vis-visa is fine and as you have determined there must be something else going on.

I created the following script and attached it to a RigidBody with a mass of one. (I used the raw position with star assumed at (0,0,0) and G=1 to save myself some typing.)

public class UnityPlanet : MonoBehaviour {

    // orbits around a "star" at the origin with fixed mass
    public float starMass = 1000f;

    // Use this for initialization
    void Start () {
        float initV = Mathf.Sqrt(starMass / transform.position.magnitude);
        GetComponent<Rigidbody>().velocity = new Vector3(0, initV, 0);
    }
  
    // Update is called once per frame
    void FixedUpdate () {
        float r = Vector3.Magnitude(transform.position);
        float totalForce = -(starMass) / (r * r);
        Vector3 force = (transform.position).normalized * totalForce;
        GetComponent<Rigidbody>().AddForce(force);
    }
}

and I get the elliptical orbit you are seeing. (Edited)

This kinda thing is something I have been thinking about a LOT, since I am a week away from submitting a Gravity Physics asset that does elliptical orbits for bodies (showing the orbit in the scene view first), gravitational particles and a ton of other cool gravitational stuff. I’ll announce it on nbodyphysics.com and the Asset Forum once it has been released.

EDIT: (Blush) Now that I add an object at (0,0,0) I can see the orbit is actually NOT a circle, but the ellipse you are seeing. As an experiment I put my Gravity Engine asset in the same scene. With the same initial velocity (0,10,0) it DOES move in a circle. This means that the Unity engine physics has gone astray somewhere. I tried all four of the ForceMode settings for AddForce but nothing resolved it.

Cheers,

Peter

Thanks for your reply!

It’s interesting that I’m not the only one having this problem.

I tried to make the rigidbody kinematic and implement my own, simple physics for this one (apply force for veloctiy, velocity for position, nothing fancy), and I get the same, wrong, result.

I also had a physicist friend of mine look over the code and he couldn’t find anything either.

Could you figure out why it works with your Gravity Engine and maybe give a hint? That would be wonderful!

Cheers,
gartoks

I tried two other things:

  • apply the force in start() as well
  • add the velocity in the first fixed update
    Neither had an impact.

I do not understand why the Unity physics behaves in this way.

My gravity engine does the acceleration and velocity calculations each FixedUpdate cycle and then updates the positions of the bodies.

A “pro tip” for this is not to use the obvious physics update (e.g. Vnew = Vold + a * dt, Xnew = Xold + v*dt), which is known as Euler’s method, but rather use an algorithm that is reversible and conserves energy. Gravity Engine offers Leapfrog (see Wikipedia:Leapfrog Integration) as well as a variable time-step Hermite method. There is also a special purpose integrator for three body solutions.

I have started to get the tutorials ready for asset submission. You can see them at https://www.youtube.com/channel/UCxH9ldb8ULCO_B7_hZwIPvw.

The asset “Gravity Engine” is now available in the asset store:

I know this thread is quite old but as its the only one I could find through Google I would just like to comment that at least in 5.5.1f1, gartoks method works for a circular orbit so the physics issue must have been fixed in an update.

Here is my “Force Push On Start” script that has a circularization toggle in the inspector. I use this in Orbital Dogfight to create an asteroid ring.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ForcePushOnStart : MonoBehaviour {
    public float forceAmount = 1000;
    public Vector3 pushVec;
    private Rigidbody objectToPush;
    public float _G = 6674f; // increased gravitational constant
    public float _M = 100000f; // temp number : will be replaced by Dominant Gravity Source's Mass
    public float _r = 10000f; // temp number : will be replaced by distance between mass centers
    public float _F;

    public bool _Circularizing = false; // set this to TRUE in the inspector to create a circular orbit
    private GravityObject m_GO; // this is a public class that keeps track of the dominant gravity source / mass // etc for a given object

    public bool randomRotation = false; // can ignore the random raotation functions for this
    private float xseed;
    private float yseed;
    private float zfeed;

    // Use this for initialization
    void Start () {

        m_GO = GetComponent<GravityObject>();
        objectToPush = gameObject.GetComponent<Rigidbody>();

        StartCoroutine("WaitingForInfo");
    }

    private void StartPush()
    {
        objectToPush.mass = objectToPush.mass * transform.localScale.x;
        Vector3 gravity = m_GO.m_DominantGravitySource.transform.position - transform.position;
        _M = m_GO.m_DominantGravitySource.GetComponent<Rigidbody>().mass; //the Mass attracting
        Vector3 pushDirection = Vector3.ProjectOnPlane(transform.forward, -gravity.normalized).normalized; //gets the correct direction to push for a circular orbit

        _r = gravity.magnitude;
        //Debug.Log("Has Radius");

        //Debug.Log("Has Gravity Direction");

        pushVec = pushDirection;

        if (_Circularizing)
        {
            GetCircularizingForce();
        }
        else
        {
            pushVec = pushVec * forceAmount;
            objectToPush.velocity = pushVec;
        }

        if (randomRotation) // this is can be ignored ..
        {
            xseed = Random.Range(2f, 89f);
            yseed = Random.Range(2f, 89f);
            zfeed = Random.Range(2f, 89f);
            transform.rotation = Quaternion.Euler(xseed, yseed, zfeed);
        }
    }

    private IEnumerator WaitingForInfo() //get the dominant gravity source : this is a two body equation
    {
        yield return new WaitUntil(() => m_GO.m_DominantGravitySource != null); // get the largest mass exerting force on this

        StartPush();
    }

    void GetCircularizingForce()
    {
        _F = Mathf.Sqrt((_G * _M) / _r); // here is the trick .. only the larger Mass's value is included in the equation
        pushVec = pushVec * _F;
        objectToPush.velocity = pushVec;
    }
}

and some more orbit play

asteroid sinusoidal waveform collapse (dont ask) each asteroid is in a circular orbit