Skip to content

Commit

Permalink
Add a new interplanetary transfer maneuver that lets the user choose the
Browse files Browse the repository at this point in the history
departure date and the transfer duration on a porkchop plot
  • Loading branch information
Meumeu committed Oct 2, 2014
1 parent 75769a1 commit 1039cd1
Show file tree
Hide file tree
Showing 10 changed files with 978 additions and 6 deletions.
10 changes: 8 additions & 2 deletions MechJeb2/DisplayModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,20 @@ public virtual GUILayoutOption[] WindowOptions()
return new GUILayoutOption[0];
}

protected virtual void WindowGUI(int windowID)
protected void WindowGUI(int windowID, bool draggable)
{
if (GUI.Button(new Rect(windowPos.width - 18, 2, 16, 16), ""))
{
enabled = false;
}

GUI.DragWindow();
if (draggable)
GUI.DragWindow();
}

protected virtual void WindowGUI(int windowID)
{
WindowGUI(windowID, true);
}

public virtual void DrawGUI(bool inEditor)
Expand Down
26 changes: 25 additions & 1 deletion MechJeb2/GuiUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ public interface IEditable
public class EditableDoubleMult : IEditable
{
[Persistent]
public double val;
public double _val;
public virtual double val
{
get { return _val; }
set
{
_val = value;
_text = (_val / multiplier).ToString();
}
}
public readonly double multiplier;

public bool parsed;
Expand Down Expand Up @@ -78,6 +87,16 @@ public EditableTime(double seconds)
_text = GuiUtils.TimeToDHMS(seconds);
}

public override double val
{
get { return _val; }
set
{
_val = value;
_text = GuiUtils.TimeToDHMS(_val);
}
}

public override string text
{
get { return _text; }
Expand Down Expand Up @@ -539,6 +558,7 @@ static ComboBox()

style = new GUIStyle(GUI.skin.window);
style.normal.background = background;
style.onNormal.background = background;
style.border.top = style.border.bottom;
style.padding.top = style.padding.bottom;
}
Expand Down Expand Up @@ -580,10 +600,14 @@ public static int Box(int selectedItem, string[] entries, object caller)
{
popupOwner = null;
selectedItem = ComboBox.selectedItem;
GUI.changed = true;
}

bool guiChanged = GUI.changed;
if (GUILayout.Button(entries[selectedItem]))
{
// We will set the changed status when we return from the menu instead
GUI.changed = guiChanged;
// Update the global state with the new items
popupOwner = caller;
popupActive = true;
Expand Down
2 changes: 2 additions & 0 deletions MechJeb2/Maneuver/Operation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public static Operation[] getAvailableOperations()
res.Sort((x,y) => x.getName().CompareTo(y.getName()));
return res.ToArray();
}

public virtual bool draggable { get { return true;}}
}
}

238 changes: 238 additions & 0 deletions MechJeb2/Maneuver/OperationAdvancedTransfer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
using System;
using UnityEngine;

namespace MuMech
{
public class OperationAdvancedTransfer : Operation
{
enum Mode
{
LimitedTime,
Porkchop
}
static string[] modeNames = {"Limited time", "Porkchop selection"};
public override string getName() { return "advanced transfer to another planet";}

double minDepartureTime;
double minTransferTime;
double maxDepartureTime;
double maxTransferTime;

public EditableTime maxArrivalTime = new EditableTime();

const double minSamplingStep = 12 * 3600;

private Mode selectionMode = Mode.Porkchop;
int windowWidth;

TransferCalculator worker;
private PlotArea plot;

bool _draggable = true;
public override bool draggable { get { return _draggable;}}

const int porkchop_Height = 200;
GUIStyle progressStyle;

public OperationAdvancedTransfer()
{
progressStyle = new GUIStyle();
progressStyle.font = GuiUtils.skin.font;
progressStyle.fontSize = GuiUtils.skin.label.fontSize;
progressStyle.fontStyle = GuiUtils.skin.label.fontStyle;
progressStyle.normal.textColor = GuiUtils.skin.label.normal.textColor;
}

public void CheckPreconditions(Orbit o, MechJebModuleTargetController target)
{
if (o.eccentricity >= 1 || o.ApR >= o.referenceBody.sphereOfInfluence)
throw new Exception("initial orbit must not be hyperbolic");

if (!target.NormalTargetExists)
throw new Exception("must select a target for the interplanetary transfer.");

if (o.referenceBody.referenceBody == null)
throw new Exception("doesn't make sense to plot an interplanetary transfer from an orbit around " + o.referenceBody.theName + ".");

if (o.referenceBody.referenceBody != target.TargetOrbit.referenceBody)
{
if (o.referenceBody == target.TargetOrbit.referenceBody)
throw new Exception("use regular Hohmann transfer function to intercept another body orbiting " + o.referenceBody.theName + ".");
throw new Exception("an interplanetary transfer from within " + o.referenceBody.theName + "'s sphere of influence must target a body that orbits " + o.referenceBody.theName + "'s parent, " + o.referenceBody.referenceBody.theName + ".");
}
}

static double SafeDepartureTime (Orbit o, double universalTime)
{
return universalTime + 600 + o.period;
}

void ComputeStuff(Orbit o, double universalTime, MechJebModuleTargetController target)
{
errorMessage = "";
try
{
CheckPreconditions(o, target);
}
catch(Exception e)
{
errorMessage = e.Message;
return;
}

if (worker != null)
worker.stop = true;
plot = null;

switch (selectionMode)
{
case Mode.LimitedTime:
// We could end up asking for parameters in the past, take a safe 10 min margin
worker = new TransferCalculator (o, target.TargetOrbit, SafeDepartureTime(o, universalTime), maxArrivalTime, minSamplingStep);
break;
case Mode.Porkchop:
worker = new AllGraphTransferCalculator(o, target.TargetOrbit, minDepartureTime, maxDepartureTime, minTransferTime, maxTransferTime, windowWidth, porkchop_Height);
break;
}
}

void ComputeTimes(Orbit o, Orbit destination, double universalTime)
{
if (destination == null)
return;

double synodic_period = o.referenceBody.orbit.SynodicPeriod(destination);
double hohmann_transfer_time = OrbitUtil.GetTransferTime(o.referenceBody.orbit, destination);

minDepartureTime = SafeDepartureTime(o, universalTime);
minTransferTime = 3600;

maxDepartureTime = minDepartureTime + synodic_period * 1.5;
maxTransferTime = hohmann_transfer_time * 1.5;
maxArrivalTime.val = (synodic_period + hohmann_transfer_time) * 1.5;
}

private void DoPorkchopGui(Orbit o, double universalTime, MechJebModuleTargetController target)
{
if (worker == null)
return;
string dv = " - ";
string departure = " - ";
string duration = " - ";
if (worker.Finished && worker.computed.GetLength(1) == porkchop_Height)
{
if (plot == null && Event.current.type == EventType.Layout)
{
plot = new PlotArea(
worker.minDepartureTime,
worker.maxDepartureTime,
worker.minTransferTime,
worker.maxTransferTime,
new Porkchop(worker.computed).texture,
(xmin, xmax, ymin, ymax) => {
minDepartureTime = Math.Max(xmin, SafeDepartureTime(o, universalTime));
maxDepartureTime = xmax;
minTransferTime = Math.Max(ymin, 3600);
maxTransferTime = ymax;
GUI.changed = true;
});
plot.selectedPoint = new int[]{worker.bestDate, worker.bestDuration};
}
}
if (plot != null)
{
var point = plot.selectedPoint;
if (plot.hoveredPoint != null)
point = plot.hoveredPoint;

var p = worker.computed[point[0], point[1]];
if (p != null)
{
dv = MuUtils.ToSI(p.dV.magnitude) + "m/s";
departure = GuiUtils.TimeToDHMS(worker.DateFromIndex(point[0]) - Planetarium.GetUniversalTime());
duration = GuiUtils.TimeToDHMS(worker.DurationFromIndex(point[1]));
}
plot.DoGUI();
if (! plot.draggable) _draggable = false;
}
else
{
GUILayout.Box("Computing: " + worker.Progress + "%", progressStyle, new GUILayoutOption[] {
GUILayout.Width(windowWidth),
GUILayout.Height(porkchop_Height)});
}
GUILayout.BeginHorizontal();
GUILayout.Label("ΔV: " + dv);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Reset", GuiUtils.yellowOnHover))
ComputeTimes(o, target.TargetOrbit, universalTime);
GUILayout.EndHorizontal();
GUILayout.Label("departure in " +departure);
GUILayout.Label("transit duration " + duration);
}

public override void DoParametersGUI(Orbit o, double universalTime, MechJebModuleTargetController target)
{
_draggable = true;
if (worker != null && !target.NormalTargetExists && Event.current.type == EventType.Layout)
{
worker.stop = true;
worker = null;
plot = null;
}

selectionMode = (Mode) GuiUtils.ComboBox.Box((int) selectionMode, modeNames, this);
if (Event.current.type == EventType.Repaint)
windowWidth = (int)GUILayoutUtility.GetLastRect().width;

switch (selectionMode)
{
case Mode.LimitedTime:
GuiUtils.SimpleTextBox("Max arrival time", maxArrivalTime);
if (worker != null && !worker.Finished)
GuiUtils.SimpleLabel("Computing: " + worker.Progress + "%");
break;
case Mode.Porkchop:
DoPorkchopGui(o, universalTime, target);
break;
}

if (worker == null || worker.destinationOrbit != target.TargetOrbit || worker.originOrbit != o)
ComputeTimes(o, target.TargetOrbit, universalTime);

if (GUI.changed || worker == null || worker.destinationOrbit != target.TargetOrbit || worker.originOrbit != o)
ComputeStuff(o, universalTime, target);
}

public override ManeuverParameters MakeNodeImpl(Orbit o, double UT, MechJebModuleTargetController target)
{
// Check preconditions
CheckPreconditions(o, target);
// Check if computation is finished
if (worker != null && !worker.Finished)
throw new Exception("Computation not finished");
if (worker == null)
{
ComputeStuff(o, UT, target);
throw new Exception("Started computation");
}

if (worker.result == null)
{
throw new Exception("Computation failed");
}
if (selectionMode == Mode.Porkchop)
{
if (plot == null || plot.selectedPoint == null)
throw new Exception("Invalid point selected.");
return TransferCalculator.OptimizeEjection(
worker.computed[plot.selectedPoint[0], plot.selectedPoint[1]],
o, worker.destinationOrbit,
worker.DateFromIndex(plot.selectedPoint[0]) + worker.DurationFromIndex(plot.selectedPoint[1]));
}

return worker.OptimizedResult;
}
}
}

Loading

0 comments on commit 1039cd1

Please sign in to comment.