////////////////////////////////////////////////////////////////////
// Copyright © 2009 Carl Johansen
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more
// details ().
////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Windows.Media.Media3D;
namespace GravityDemo
{
class SolarSystemDemo : INewtonianSystemDemo
{
private DisplaySystem _system;
public IList Bodies
{
get { return _system.Bodies; }
}
public IList Guidelines
{
get { return _system.Guidelines; }
}
///
/// The scale factor to be applied to positions in the system.
///
///
/// It seems that WPF has problems with 3D coordinates greater than about 2^24
/// (approx 1.6e7). Apply ScaleFactor to the coordinates in SolarSystemDemo
/// in order to avoid presenting WPF with large coordinate values.
///
public double ScaleFactor { get; private set; }
public float RealSecondsPerMillisecond { get; set; }
private SignWatcher _launchWindowWatcher = new SignWatcher();
private DisplayBody _earth, _mars, _sun, _satellite;
private EllipticalOrbit _earthOrbit, _marsOrbit;
private double _marsHeadstartRequired;
private bool _satelliteLaunched;
private bool _includeHohmannSatellite;
public SolarSystemDemo(DisplayBody sun,
DisplayBody mercury,
DisplayBody venus,
DisplayBody earth,
DisplayBody mars,
DisplayBody satellite,
EllipticalOrbit earthOrbit,
EllipticalOrbit marsOrbit,
DisplayLine[] guidelines,
float realSecondsPerMillisecond,
bool includeHohmannSatellite)
{
_includeHohmannSatellite = includeHohmannSatellite;
_system = new DisplaySystem();
_system.AddBody(sun);
_system.AddBody(mercury);
_system.AddBody(venus);
_system.AddBody(earth);
_system.AddBody(mars);
_sun = sun;
_earth = earth;
_mars = mars;
_earthOrbit = earthOrbit;
_marsOrbit = marsOrbit;
if (_includeHohmannSatellite)
{
_system.AddBody(satellite);
_satellite = satellite;
}
if (guidelines != null)
{
foreach (DisplayLine line in guidelines)
Guidelines.Add(line);
}
ScaleFactor = 1e7 / (marsOrbit.SemiMajorAxis * 2);
RealSecondsPerMillisecond = realSecondsPerMillisecond;
InitializeScene(earthOrbit, marsOrbit, sun.Body);
}
private void InitializeScene(EllipticalOrbit earthOrbit, EllipticalOrbit marsOrbit, Body sun)
{
double satSemiMajorAxis = (earthOrbit.SemiMajorAxis + marsOrbit.SemiMajorAxis) / 2;
double satC = (marsOrbit.SemiMajorAxis - earthOrbit.SemiMajorAxis) / 2;
double satEcc = satC / satSemiMajorAxis;
if (_includeHohmannSatellite)
{
//Get an orbit for the satellite so that we can find out its period. We need this
// to calculate the launch window.
EllipticalOrbit satOrbit = new EllipticalOrbit
{
CentralBody = sun,
SemiMajorAxis = satSemiMajorAxis,
Eccentricity = satEcc,
};
_marsHeadstartRequired = Math.PI * (1 - satOrbit.Period / marsOrbit.Period);
_satelliteLaunched = false;
}
}
public void UpdateScene(float elapsedSeconds)
{
if (_includeHohmannSatellite && !_satelliteLaunched && ReachedMarsLaunchWindow())
{
InsertSatellite(_earthOrbit, _marsOrbit, _earth.Body, _sun.Body, _satellite);
_satelliteLaunched = true;
}
_system.UpdateScene(elapsedSeconds);
}
private bool ReachedMarsLaunchWindow()
{
double earthAngle = _earth.Body.GetXZAngle(_sun.Body);
double marsAngle = _mars.Body.GetXZAngle(_sun.Body);
if (marsAngle < earthAngle)
marsAngle += 2.0 * Math.PI; //always consider Mars to be ahead of Earth
//We're looking for HeadstartRequired = CurrentHeadstart, but we need to cope with floating-point
// inaccuracies. The best way to do this is to look for a sign change in (HeadstartRequired - CurrentHeadstart)
double planetAngularSeparation = marsAngle - earthAngle;
return _launchWindowWatcher.SignChangeInDifference(_marsHeadstartRequired, planetAngularSeparation);
}
private void InsertSatellite(EllipticalOrbit startingBodyOrbit,
EllipticalOrbit targetBodyOrbit,
Body startingBody,
Body centralBody,
DisplayBody satellite)
{
double satPerihelion = startingBodyOrbit.SemiMajorAxis;
double satAphelion = targetBodyOrbit.SemiMajorAxis;
//Calculate how much extra velocity the satellite needs (in addition to what it gets
// from the Earth) in order to get to Mars (from http://en.wikipedia.org/wiki/Hohmann_transfer_orbit).
double satDeltaV = Math.Sqrt(centralBody.GM / satPerihelion) *
((Math.Sqrt(2 * satAphelion / (satAphelion + satPerihelion)) - 1));
//Calculate the direction and size of the Earth's current velocity.
double earthVelocityMagnitude = startingBody.Velocity.Length;
Vector3D earthVelocityNormalized = startingBody.Velocity;
earthVelocityNormalized.Normalize();
//Initial satellite velocity is Earth velocity plus satellite's delta-v (set in the direction in
// which the Earth is currently moving).
Vector3D satVelocity = Vector3D.Multiply(earthVelocityMagnitude + satDeltaV, earthVelocityNormalized);
//This is the formula for the Earth's "sphere of influence" radius (from http://en.wikipedia.org/wiki/Orbital_mechanics).
//Our satellite will magically materialize at this point (saves having to calculate how to
// get it there from the Earth!). From this distance we can more or less ignore Earth's gravitational influence.
double earthSphereOfInfluenceRadius = startingBody.Radius + (startingBodyOrbit.SemiMajorAxis * Math.Pow((startingBody.Mass / centralBody.Mass), 2F / 5));
satellite.Body.MoveTo(startingBody.Position + Vector3D.Multiply(earthSphereOfInfluenceRadius, earthVelocityNormalized));
satellite.Body.SetVelocity(satVelocity);
satellite.Body.Enabled = true;
satellite.LeaveTrail = true;
}
}
}