////////////////////////////////////////////////////////////////////
/// EllipticalOrbitingBody.cs
/// © 2005 Carl Johansen
///
/// Represents a body in elliptical orbit about a massive central body
////////////////////////////////////////////////////////////////////
using System;
using System.Drawing;
namespace CarlInc.Demos.Hohmann
{
///
/// Represents a body in elliptical orbit about a massive central body
///
public class EllipticalOrbitingBody : OrbitingBody
{
private double semiMinorAxis; // also known as "b"
private double eccentricity; // also known as "e"
private double c; // there doesn't seem to be a name for the distance from the centre to a focus - it's just called "c"
private double periapsisDistance; // distance from central body to orbiting body at closest point (needed when drawing the orbit ellipse)
public override double SemiMajorAxis { get { return base.SemiMajorAxis; }
set
{
base.SemiMajorAxis = value;
RecalcDerivedQuantities(); // update e, b and periapsis distance - they depend on a and c
}
}
public double PeriapsisDistance { get { return periapsisDistance ; } }
///
/// Half the distance between the foci of the orbit ellipse
///
public double C
{
get
{ return c ; }
set
{
c = value;
RecalcDerivedQuantities(); // update e, b and periapsis distance - they depend on a and c
}
}
public EllipticalOrbitingBody(double semiMajorAxis, double c)
{
SemiMajorAxis = semiMajorAxis;
C = c;
RecalcDerivedQuantities();
base.Init();
}
///
/// The angle between the body and the sun (in radians).
///
public override double GetTrueAnomaly()
{
// True Anomaly in this context is the angle between the body and the sun.
// For elliptical orbits, it's a bit tricky. The percentage of the period completed is still a key input, but we also need
// to apply Kepler's equation (based on the eccentricity) to ensure that we sweep out equal areas in equal times.
// This equation is transcendental (ie can't be solved algebraically)
// so we either have to use an approximating equation or solve by a numeric method. My implementation uses
// Newton-Raphson iteration to get an excellent approximate answer (usually in 2 or 3 iterations).
double ecc = eccentricity;
double M = base.GetTrueAnomaly(); // the base implementation returns the "mean anomaly", which
// assumes a circular orbit. The rest of our work involves correcting
// this to get the true "eccentric" anomaly
double E, E_new, E_old = M + (ecc / 2);
const double epsilon = 0.0001;
double bodyAngle;
//Solve [ 0 = E - e sin(E) - M ] for E using Newton-Raphson: Xn+1 = Xn - [ f(Xn) / f'(Xn) ]
// E = Eccentric Anomaly, M = Mean Anomaly
do
{
E_new = E_old - (E_old - ecc * Math.Sin(E_old) - M) / (1 - ecc * Math.Cos(E_old));
E_old = E_new;
} while(Math.Abs(E_old - E_new) > epsilon);
E = E_new;
//Solve cos(bodyAngle) = ( cos(E) - e ) / (1 - e cos(E) ) to get the body's angle with the Sun
bodyAngle = Math.Acos( (Math.Cos(E) - ecc)/(1 - ecc * Math.Cos(E)) );
// Arccos returns a value between 0 and pi, but when M > pi (ie past halfway point) we
// take (2pi - angle) to get the solution that lies between pi and 2pi
int T = OrbitalPeriod, t = GetDaysElapsed() % T;
if(t > T / 2) bodyAngle = 2.0F * Math.PI - bodyAngle;
return bodyAngle;
}
public override double CurrentRadius(double bodyOffsetAngle)
{
// for ellipses there is a formula linking radius from a focus to angle and eccentricity:
// r = a(1 - e^2) / (1 + e cos(theta))
return (SemiMajorAxis * (1 - eccentricity * eccentricity)) / ( 1 + eccentricity * Math.Cos(bodyOffsetAngle));
}
///
/// Returns a rectangle that bounds the orbit ellipse.
///
public override RectangleF OrbitBoundingRectUnrotated
{
get
{
return new RectangleF((float) (-1 * semiMinorAxis),
(float) (-1 * periapsisDistance),
(float) (2 * semiMinorAxis),
(float) (2 * SemiMajorAxis));
}
}
private void RecalcDerivedQuantities()
{
// eccentricity is simply the ratio of c to a (showing how fat/thin the ellipse is)
eccentricity = C / SemiMajorAxis;
// There is a simple formula linking a, b, and e for any ellipse: b^2 = a^2 * (1 - e^2)
semiMinorAxis = SemiMajorAxis * Math.Sqrt(1 - eccentricity * eccentricity);
// The periapsis distance is given by a(1 - e), or a - c
periapsisDistance = SemiMajorAxis - C;
}
}
}