5

For a game I am making, I am trying to calculate the position of an orbiting object around one or more bodies. I have successfully implemented this gravity simulation by calculating the force, then the acceleration, and then the velocity of the object, but the problem with this implementation is that the discretization of time (because the gravity is calculated during the update of each frame) causes instability of the orbit.

Therefore, I am looking for (a) function(s) of time that tells me the position of the orbiting object, therefore avoiding the granularity of (what I have come to understand as) the Euler integration. Any thoughts? The stuff I have read on here has been too complex to understand, so try to keep it at as low a level as you possibly can.

Edit: A parallel to what I am asking for is something similar to what is done in Kerbal Space Program, where the orbit is calculated ahead of time, except my game is in two dimensions.

Qmechanic
  • 220,844
ben
  • 153

2 Answers2

4

For two bodies this is relatively easy as the equations of motion describe a conic (an ellipse for a closed orbit, a hyperbola for an "open" orbit). You can use the vis viva equation to get the parameters of the orbit (semi major axis etc) from the given initial conditions, and the rest follows.

For an ellipse, you can also express the position as a function of time as

$$v_x = a \sin \omega t\\ v_y = b \cos \omega t$$

where your challenge is to find $a$, $b$ and $\omega$. For this you would like to know the velocity and distance at the furthest point, but you can in principle solve this for any place along the orbit.

For example, for a satellite in elliptical orbit above a massive planet (mass $M$), for given initial velocity $v$ in the radial direction, at a distance $r$ from the center of the planet, we can know the following (see http://en.wikipedia.org/wiki/Vis-viva_equation):

$$v^2 = GM\left(\frac{2}{r}-\frac{1}{a}\right)$$

In this expression, $a$ is the semimajor axis. We can calculate it from the given initial conditions,

$$\frac{1}{a} = \frac{2}{r}-\frac{v^2}{2GM}$$

Now we need $b$; again, following the wikipedia page quoted above, and for the conditions given (v perpendicular to radial vector at distance r, so we know angular momentum of the orbit $L=mvr$), we can write

$$b = vr\sqrt{\frac{a}{GM}}$$

With $a$ and $b$ calculated, we just need to find $\omega$ which follows again from the initial conditions, since we know

$$\omega = \frac{v}{r}$$

at the initial position. And with $a$, $b$ and $\omega$ known you can substitute into the above equations.

Incidentally it is more practical, when you have more than one gravitational object, to do the integration "properly". I recommend using a fourth-order Runge-Kutta integration, which is quite stable for this kind of problem. You can see http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf for some ideas on how to do this - it's a bit hard to follow. I actually wrote a piece of code that did this in Visual Basic a little while ago - it was actually taking into account both gravity and atmospheric drag. I reproduce it here without warranty...

Option Explicit

Const gm = 9.8 * 6300000# * 6300000# ' approximate value of GM on earth surface
                                     ' the # signifies a double precision "integer"
Const C_d = 0.5 * 0.03 * 0.03 * 0.2  ' drag coefficient for particular object
Const R_e = 6300000#                 ' approximate radius of earth
Const mass = 25                      ' mass of object
Const k1 = 12000#                    ' constants used to fit density of atmosphere
Const k2 = 22000#                    ' again, approximate!

Function rho(h)   
  ' return density as a function of height
  If h < 0 Then rho = 1.2 Else rho = 1.225 * Exp(-(h / k1 + (h / k2) ^ (3 / 2)))
End Function

Sub accel(x, y, vx, vy, ByRef ax, ByRef ay)
' compute acceleration of object at location x,y with velocity vx, vy
' return values in ax, ay
  Dim r As Double, v2 As Double, v As Double, r3 As Double, h As Double
  Dim density
  r = Sqr(x * x + y * y) ' sqrt() in most languages... this is VBA
  v2 = vx * vx + vy * vy
  v = Sqr(v2)
  r3 = 1# / r ^ 3
  density = rho(r - R_e)
  ' don't need the second term if you don't care about drag!
  ax = -gm * x * r3 - vx ^ 3 * C_d * density / (v * mass)
  ay = -gm * y * r3 - vy ^ 3 * C_d * density / (v * mass)
End Sub

Function rk(ByRef x, ByRef y, ByRef vx, ByRef vy, dt)
' implement one Runge-Kutta fourth order stop
' this requires calculating the acceleration at verious locations
' for a single "step" in the algorithm
  Dim ax As Double, ay As Double
  Dim kx1, kx2, kx3, kx4, ky1, ky2, ky3, ky4, lx1, lx2, lx3, lx4, ly1, ly2, ly3, ly4, dt2
  ' get acceleration at initial point
  accel x, y, vx, vy, ax, ay
  ' half time step:
  dt2 = dt / 2
  kx1 = dt2 * ax
  ky1 = dt2 * ay
  lx1 = dt2 * vx
  ly1 = dt2 * vy
  ' get acceleration at new location
  accel x + lx1, y + ly1, vx + kx1, vy + ky1, ax, ay
  kx2 = dt2 * ax
  ky2 = dt2 * ay
  lx2 = dt2 * (vx + kx1)
  ly2 = dt2 * (vy + ky1)
  ' get acceleration at half way point:
  accel x + lx2, y + ly2, vx + kx2, vy + ky2, ax, ay
  kx3 = dt * ax
  ky3 = dt * ay
  lx3 = dt * (vx + kx2)
  ly3 = dt * (vy + ky2)
  ' compute acceleration for third combination of position and velocity:
  accel x + lx3, y + ly3, vx + kx3, vy + ky3, ax, ay
  kx4 = dt2 * ax
  ky4 = dt2 * ay
  lx4 = dt2 * (vx + kx3)
  ly4 = dt2 * (vy + ky3)
  ' compute best approximation to new x, y, vx, vy
  x = x + (lx1 + 2# * lx2 + lx3 + lx4) / 3#
  y = y + (ly1 + 2# * ly2 + ly3 + ly4) / 3#
  vx = vx + (kx1 + 2# * kx2 + kx3 + kx4) / 3#
  vy = vy + (ky1 + 2# * ky2 + ky3 + ky4) / 3#

End Function

Calling the RK function multiple times you can now track the orbit, and you will find it is quite stable. You can easily add multiple massive objects (just update the acceleration formula) and it will still work (although orbits will become chaotic).

Floris
  • 119,981
0

If you're absolutely determined to use a parametric equation, then see https://www.mathopenref.com/coordparamellipse.html

Otherwise, there are many different integration methods. Pick and choose between them, based on your needs. Below is C++ code for symplectic integration, Euler integration, and Runge-Kutta integration.

// https://en.wikipedia.org/wiki/Symplectic_integrator
// Also known as Verlet integration
void proceed_symplectic2(custom_math::vector_3 &pos, custom_math::vector_3 &vel, const custom_math::vector_3 &ang_vel)
{
    static const double c[2] = { 0, 1 };
    static const double d[2] = { 0.5, 0.5 };

// pos += vel * c[0] * dt; // first element c[0] is always 0 vel += acceleration(pos, vel, ang_vel) * d[0] * dt;

pos += vel * c[1] * dt;
vel += acceleration(pos, vel, ang_vel) * d[1] * dt;

}

// https://en.wikipedia.org/wiki/Symplectic_integrator void proceed_symplectic4(custom_math::vector_3 &pos, custom_math::vector_3 &vel, const custom_math::vector_3 &ang_vel) { static double const cr2 = pow(2.0, 1.0 / 3.0);

static const double c[4] =
{
    1.0 / (2.0*(2.0 - cr2)),
    (1.0 - cr2) / (2.0*(2.0 - cr2)),
    (1.0 - cr2) / (2.0*(2.0 - cr2)),
    1.0 / (2.0*(2.0 - cr2))
};

static const double d[4] =
{
    1.0 / (2.0 - cr2),
    -cr2 / (2.0 - cr2),
    1.0 / (2.0 - cr2),
    0.0
};

pos += vel * c[0] * dt;
vel += acceleration(pos, vel, ang_vel) * d[0] * dt;

pos += vel * c[1] * dt;
vel += acceleration(pos, vel, ang_vel) * d[1] * dt;

pos += vel * c[2] * dt;
vel += acceleration(pos, vel, ang_vel) * d[2] * dt;

pos += vel * c[3] * dt;

// vel += acceleration(pos, vel, ang_vel) * d[3] * dt; // last element d[3] is always 0 }

void proceed_Euler(custom_math::vector_3 &pos, custom_math::vector_3 &vel, const custom_math::vector_3 &ang_vel) { vel += acceleration(pos, vel, ang_vel) * dt; pos += vel * dt; }

inline void proceed_RK2(custom_math::vector_3 &pos, custom_math::vector_3 &vel, const custom_math::vector_3 &ang_vel) { custom_math::vector_3 k1_velocity = vel; custom_math::vector_3 k1_acceleration = acceleration(pos, k1_velocity, ang_vel); custom_math::vector_3 k2_velocity = vel + k1_acceleration * dt0.5; custom_math::vector_3 k2_acceleration = acceleration(pos + k1_velocity dt*0.5, k2_velocity, ang_vel);

vel += k2_acceleration * dt;
pos += k2_velocity * dt;

}

void proceed_RK4(custom_math::vector_3 &pos, custom_math::vector_3 &vel, const custom_math::vector_3 &ang_vel) { static const double one_sixth = 1.0 / 6.0;

custom_math::vector_3 k1_velocity = vel;
custom_math::vector_3 k1_acceleration = acceleration(pos, k1_velocity, ang_vel);
custom_math::vector_3 k2_velocity = vel + k1_acceleration * dt*0.5;
custom_math::vector_3 k2_acceleration = acceleration(pos + k1_velocity * dt*0.5, k2_velocity, ang_vel);
custom_math::vector_3 k3_velocity = vel + k2_acceleration * dt*0.5;
custom_math::vector_3 k3_acceleration = acceleration(pos + k2_velocity * dt*0.5, k3_velocity, ang_vel);
custom_math::vector_3 k4_velocity = vel + k3_acceleration * dt;
custom_math::vector_3 k4_acceleration = acceleration(pos + k3_velocity * dt, k4_velocity, ang_vel);

vel += (k1_acceleration + (k2_acceleration + k3_acceleration)*2.0 + k4_acceleration)*one_sixth*dt;
pos += (k1_velocity + (k2_velocity + k3_velocity)*2.0 + k4_velocity)*one_sixth*dt;

}

... where ...

custom_math::vector_3 acceleration(const custom_math::vector_3 &pos, const custom_math::vector_3 &vel, custom_math::vector_3 &ang_vel)
{
    custom_math::vector_3 grav_dir = sun_pos - pos;
float distance = grav_dir.length();

grav_dir.normalize();
custom_math::vector_3 accel = grav_dir * (G*sun_mass / pow(distance, 2.0));

return accel;

}