Skip to content

Commit

Permalink
Merge pull request #904 from lamont-granquist/lcg/ascent-guidance
Browse files Browse the repository at this point in the history
Ascent Guidance overhaul and closed loop guidance
  • Loading branch information
sarbian committed Jul 17, 2017
2 parents cb91da7 + fa4d544 commit 446a642
Show file tree
Hide file tree
Showing 16 changed files with 2,045 additions and 616 deletions.
53 changes: 35 additions & 18 deletions MechJeb2/FuelFlowSimulation.cs
Expand Up @@ -15,23 +15,26 @@ public class FuelFlowSimulation
public int simStage; //the simulated rocket's current stage
readonly List<FuelNode> nodes = new List<FuelNode>(); //a list of FuelNodes representing all the parts of the ship
readonly Dictionary<Part, FuelNode> nodeLookup = new Dictionary<Part, FuelNode>();
readonly Dictionary<FuelNode, Part> partLookup = new Dictionary<FuelNode, Part>();

private double KpaToAtmospheres;

//Takes a list of parts so that the simulation can be run in the editor as well as the flight scene
public void Init(List<Part> parts, bool dVLinearThrust)
{
KpaToAtmospheres = PhysicsGlobals.KpaToAtmospheres;

// Create FuelNodes corresponding to each Part
nodes.Clear();
nodeLookup.Clear();
partLookup.Clear();

for (int index = 0; index < parts.Count; index++)
{
Part part = parts[index];
FuelNode node = FuelNode.Borrow(part, dVLinearThrust);
nodeLookup[part] = node;
partLookup[node] = part;
nodes.Add(node);
}
// Determine when each part will be decoupled
Expand All @@ -46,7 +49,7 @@ public void Init(List<Part> parts, bool dVLinearThrust)
}

simStage = StageManager.LastStage + 1;

// Add a fake stage if we are beyond the first one
// Mostly useful for the Node Executor who use the last stage info
// and fail to get proper info when the ship was never staged and
Expand Down Expand Up @@ -112,6 +115,14 @@ private Stats SimulateStage(float throttle, double staticPressure, double atmDen
stats.deltaTime = 0;
stats.deltaV = 0;

// track active engines to "fingerprint" this stage
// (could improve by adding fuel tanks being drained and thereby support drop-tanks)
stats.parts = new List<Part>();
var engines = FindActiveEngines().value;
for(int i = 0; i < engines.Count; i++) {
stats.parts.Add(partLookup[engines[i]]);
}

const int maxSteps = 100;
int step;
for (step = 0; step < maxSteps; step++)
Expand Down Expand Up @@ -367,6 +378,8 @@ public struct Stats
public double StartTWR(double geeASL) { return startMass > 0 ? startThrust / (9.80665 * geeASL * startMass) : 0; }
public double MaxTWR(double geeASL) { return maxAccel / (9.80665 * geeASL); }

public List<Part> parts;

//Computes the deltaV from the other fields. Only valid when the thrust is constant over the time interval represented.
public void ComputeTimeStepDeltaV()
{
Expand All @@ -392,6 +405,7 @@ public Stats Append(Stats s)
maxAccel = Math.Max(this.maxAccel, s.maxAccel),
deltaTime = this.deltaTime + (s.deltaTime < float.MaxValue && !double.IsInfinity(s.deltaTime) ? s.deltaTime : 0),
deltaV = this.deltaV + s.deltaV,
parts = this.parts,
isp = this.startMass == s.endMass ? 0 : (this.deltaV + s.deltaV) / (9.80665f * Math.Log(this.startMass / s.endMass))
};
}
Expand All @@ -402,7 +416,7 @@ public Stats Append(Stats s)
public class FuelNode
{
readonly DefaultableDictionary<int, double> resources = new DefaultableDictionary<int, double>(0); //the resources contained in the part
readonly KeyableDictionary<int, double> resourceConsumptions = new KeyableDictionary<int, double>(); //the resources this part consumes per unit time when active at full throttle
readonly KeyableDictionary<int, double> resourceConsumptions = new KeyableDictionary<int, double>(); //the resources this part consumes per unit time when active at full throttle
readonly DefaultableDictionary<int, double> resourceDrains = new DefaultableDictionary<int, double>(0); //the resources being drained from this part per unit time at the current simulation time
readonly DefaultableDictionary<int, bool> freeResources = new DefaultableDictionary<int, bool>(false); //the resources that are "free" and assumed to be infinite like IntakeAir

Expand All @@ -422,9 +436,9 @@ public class FuelNode
KeyableDictionary<int, float> propellantRatios = new KeyableDictionary<int, float>(); //ratios of propellants used by this engine
KeyableDictionary<int, ResourceFlowMode> propellantFlows = new KeyableDictionary<int, ResourceFlowMode>(); //flow modes of propellants since the engine can override them
float propellantSumRatioTimesDensity; //a number used in computing propellant consumption rates

readonly List<FuelNode> crossfeedSources = new List<FuelNode>();

float maxFuelFlow = 0; //max fuel flow of this part
float minFuelFlow = 0; //min fuel flow of this part

Expand Down Expand Up @@ -485,16 +499,16 @@ private void Init(Part part, bool dVLinearThrust)

propellantRatios.Clear();
propellantFlows.Clear();

crossfeedSources.Clear();

isEngine = false;

dryMass = 0;
modulesStagedMass = 0;

decoupledInStage = int.MinValue;

modulesUnstagedMass = 0;
if (!part.IsLaunchClamp())
{
Expand All @@ -505,7 +519,7 @@ private void Init(Part part, bool dVLinearThrust)
modulesStagedMass = part.GetModuleMassNoAlloc((float) dryMass, ModifierStagingSituation.STAGED);

float currentModulesMass = part.GetModuleMassNoAlloc((float) dryMass, ModifierStagingSituation.CURRENT);

// if it was manually staged
if (currentModulesMass == modulesStagedMass)
{
Expand Down Expand Up @@ -599,7 +613,7 @@ private void Init(Part part, bool dVLinearThrust)
// Some brilliant engine mod seems to consider that FuelFlow is not something they should properly initialize
if (minFuelFlow == 0 && engine.minThrust > 0)
{
maxFuelFlow = engine.minThrust / (engine.atmosphereCurve.Evaluate(0f) * engine.g);
minFuelFlow = engine.minThrust / (engine.atmosphereCurve.Evaluate(0f) * engine.g);
}
if (maxFuelFlow == 0 && engine.maxThrust > 0)
{
Expand All @@ -616,6 +630,9 @@ private void Init(Part part, bool dVLinearThrust)
velCurve = new FloatCurve(engine.velCurve.Curve.keys);

propellantSumRatioTimesDensity = engine.propellants.Slinq().Select(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)).Sum();
float ratio = propellantSumRatioTimesDensity / engine.propellants.Slinq().Where(prop => !prop.ignoreForIsp).Select(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)).Sum();
maxFuelFlow *= ratio;
minFuelFlow *= ratio;
propellantRatios.Clear();
propellantFlows.Clear();
var dics = new Tuple<KeyableDictionary<int, float>, KeyableDictionary<int, ResourceFlowMode>>(propellantRatios, propellantFlows);
Expand Down Expand Up @@ -644,7 +661,7 @@ public void AssignDecoupledInStage(Part p, Dictionary<Part, FuelNode> nodeLookup
for (int i = 0; i < p.Modules.Count; i++)
{
PartModule m = p.Modules[i];

ModuleDecouple mDecouple = m as ModuleDecouple;
if (mDecouple != null)
{
Expand Down Expand Up @@ -701,14 +718,14 @@ public void AssignDecoupledInStage(Part p, Dictionary<Part, FuelNode> nodeLookup
//print("AssignDecoupledInStage ModuleDecouple " + p.partInfo.name + "(" + p.inverseStage + ") decoupling " + attach.attachedPart + "(" + attach.attachedPart.inverseStage + "). not the parent " + decoupledInStage);
// The part we decouple is dropped when we decouple
nodeLookup[attach.attachedPart].AssignDecoupledInStage(attach.attachedPart, nodeLookup, p.inverseStage);

}
}
}
break; // Hopefully no one made part with multiple decoupler modules ?
}
}

ModuleAnchoredDecoupler mAnchoredDecoupler = m as ModuleAnchoredDecoupler;
if (mAnchoredDecoupler != null)
{
Expand All @@ -723,7 +740,7 @@ public void AssignDecoupledInStage(Part p, Dictionary<Part, FuelNode> nodeLookup
{
attach = p.srfAttachNode;
}

if (attach != null && attach.attachedPart != null)
{
if (attach.attachedPart == p.parent)
Expand Down Expand Up @@ -807,7 +824,7 @@ public void AssignDecoupledInStage(Part p, Dictionary<Part, FuelNode> nodeLookup
decoupledInStage = parentDecoupledInStage;
//print("AssignDecoupledInStage " + p.partInfo.name + "(" + p.inverseStage + ")" + decoupledInStage);
}

isSepratron = isEngine && (inverseStage == decoupledInStage);

for (int i = 0; i < p.children.Count; i++)
Expand Down Expand Up @@ -857,7 +874,7 @@ public void AddCrossfeedSouces(HashSet<Part> parts, Dictionary<Part, FuelNode> n
}
}
}

//call this when a node no longer exists, so that this node knows that it's no longer a valid source
public void RemoveSourceNode(FuelNode n)
{
Expand Down Expand Up @@ -943,7 +960,7 @@ public bool CanDrawNeededResources(List<FuelNode> vessel)
//check if we contain the needed resource:
if (resources[type] < resourceRequestRemainingThreshold) return false;
break;

case ResourceFlowMode.ALL_VESSEL:
case ResourceFlowMode.ALL_VESSEL_BALANCE:
case ResourceFlowMode.STAGE_PRIORITY_FLOW:
Expand Down Expand Up @@ -1004,7 +1021,7 @@ public void AssignResourceDrainRates(List<FuelNode> vessel)
case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE:
AssignFuelDrainRateStagePriorityFlow(type, amount, true, vessel);
break;

case ResourceFlowMode.STAGE_STACK_FLOW:
case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE:
case ResourceFlowMode.STACK_PRIORITY_SEARCH:
Expand Down
13 changes: 6 additions & 7 deletions MechJeb2/MechJebCore.cs
Expand Up @@ -82,7 +82,7 @@ public bool DeactivateControl

[KSPField(isPersistant = false)]
public bool eduMode = false;

public bool rssMode { get { return settings.rssMode; } }

[KSPAction("Orbit Prograde")]
Expand Down Expand Up @@ -306,7 +306,7 @@ private void SetTranslatronSpeed(float speed, bool relative = false)

public bool someModuleAreLocked = false; // True if any module was locked by the R&D system

//Returns whether the vessel we've registered OnFlyByWire with is the correct one.
//Returns whether the vessel we've registered OnFlyByWire with is the correct one.
//If it isn't the correct one, fixes it before returning false
bool CheckControlledVessel()
{
Expand Down Expand Up @@ -461,7 +461,7 @@ public override void OnStart(PartModule.StartState state)
ready = true;
if (state == PartModule.StartState.None) return; //don't do anything when we start up in the loading screen

//OnLoad doesn't get called for parts created in editor, so do that manually so
//OnLoad doesn't get called for parts created in editor, so do that manually so
//that we can load global settings.
//However, if you press ctrl-Z, a new PartModule object gets created, on which the
//game DOES call OnLoad, and then OnStart. So before calling OnLoad from OnStart,
Expand Down Expand Up @@ -676,7 +676,7 @@ public void Update()
Profiler.BeginSample("OnMenuUpdate");
GetComputerModule<MechJebModuleMenu>().OnMenuUpdate(); // Allow the menu movement, even while in Editor
Profiler.EndSample();

if (vessel == null)
{
Profiler.EndSample();
Expand Down Expand Up @@ -708,7 +708,7 @@ void LoadComputerModules()
{
try
{
foreach (var module in (from t in ass.GetTypes() where t.IsSubclassOf(typeof(ComputerModule)) select t).ToList())
foreach (var module in (from t in ass.GetTypes() where t.IsSubclassOf(typeof(ComputerModule)) && !t.IsAbstract select t).ToList())
{
moduleRegistry.Add(module);
}
Expand Down Expand Up @@ -911,7 +911,7 @@ public override void OnSave(ConfigNode sfsNode)
// Only Masters can save
if (this != vessel.GetMasterMechJeb()) return;

//KSP calls OnSave *before* OnLoad when the first command pod is created in the editor.
//KSP calls OnSave *before* OnLoad when the first command pod is created in the editor.
//Defend against saving empty settings.
if (unorderedComputerModules.Count == 0) return;

Expand Down Expand Up @@ -1190,4 +1190,3 @@ public new static void print(object message)
}
}
}

0 comments on commit 446a642

Please sign in to comment.