//////////////////////////////////////////////////////////////////// // 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; } } }