Fixed crlf issue
[VesselSimulator.git] / KerbalEngineer / VesselSimulator / Simulation.cs
blob:a/KerbalEngineer/VesselSimulator/Simulation.cs -> blob:b/KerbalEngineer/VesselSimulator/Simulation.cs
// Kerbal Engineer Redux //
// Author: CYBUTEK // Kerbal Engineer Redux
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported //
// // Copyright (C) 2014 CYBUTEK
// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely //
// similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment // This program is free software: you can redistribute it and/or modify
// to how well the MuMech code works and the robustness of the simulation algorithem used. // it under the terms of the GNU 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 General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program. If not, see <http://www.gnu.org/licenses/>.
  //
   
  #region Using Directives
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
   
using UnityEngine; using UnityEngine;
   
  #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
  using CompoundParts;
  using Extensions;
  using Helpers;
   
public class Simulation public class Simulation
{ {
private List<Part> partList; private const double SECONDS_PER_DAY = 86400;
  private readonly Stopwatch _timer = new Stopwatch();
private List<PartSim> allParts; private List<EngineSim> activeEngines = new List<EngineSim>();
private List<PartSim> allFuelLines; private List<EngineSim> allEngines = new List<EngineSim>();
private HashSet<PartSim> drainingParts; private List<PartSim> allFuelLines = new List<PartSim>();
private List<EngineSim> allEngines; private List<PartSim> allParts = new List<PartSim>();
private List<EngineSim> activeEngines; private Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();
private HashSet<int> drainingResources; private double atmosphere;
  private int currentStage;
private int lastStage = 0; private double currentisp;
private int currentStage = 0; private bool doingCurrent;
private bool doingCurrent = false; private List<PartSim> dontStageParts = new List<PartSim>();
  List<List<PartSim>> dontStagePartsLists = new List<List<PartSim>>();
  private HashSet<PartSim> drainingParts = new HashSet<PartSim>();
  private HashSet<int> drainingResources = new HashSet<int>();
  private HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
  private double gravity;
   
  private int lastStage;
  private List<Part> partList = new List<Part>();
  private double simpleTotalThrust;
  private double stageStartMass;
  private Vector3d stageStartCom;
  private double stageTime;
  private double stepEndMass;
  private double stepStartMass;
  private double totalStageActualThrust;
  private double totalStageFlowRate;
  private double totalStageIspFlowRate;
  private double totalStageThrust;
  private ForceAccumulator totalStageThrustForce = new ForceAccumulator();
  private Vector3 vecActualThrust;
  private Vector3 vecStageDeltaV;
  private Vector3 vecThrust;
  private double mach;
  private float maxMach;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
  private WeightedVectorAverager vectorAverager = new WeightedVectorAverager();
private double stageTime = 0d;  
private Vector3 vecStageDeltaV;  
private double simpleTotalThrust = 0d;  
private double totalStageThrust = 0d;  
private double totalStageActualThrust = 0d;  
private Vector3 vecThrust;  
private Vector3 vecActualThrust;  
private double totalStageFlowRate = 0d;  
private double totalStageIspFlowRate = 0d;  
private double currentisp = 0d;  
private double stageStartMass = 0d;  
private double stepStartMass = 0d;  
private double stepEndMass = 0d;  
   
private double gravity = 0d;  
private double atmosphere = 0d;  
private double velocity = 0d;  
private Stopwatch _timer = new Stopwatch();  
private const double STD_GRAVITY = 9.81d;  
private const double SECONDS_PER_DAY = 86400d;  
   
public Simulation() public Simulation()
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
  {
MonoBehaviour.print("Simulation created"); MonoBehaviour.print("Simulation created");
  }
  }
   
  private double ShipMass
  {
  get
  {
  double mass = 0d;
   
  for (int i = 0; i < allParts.Count; ++i) {
  mass += allParts[i].GetMass();
  }
   
  return mass;
  }
  }
   
  private Vector3d ShipCom
  {
  get
  {
  vectorAverager.Reset();
   
  for (int i = 0; i < allParts.Count; ++i)
  {
  PartSim partSim = allParts[i];
  vectorAverager.Add(partSim.centerOfMass, partSim.GetMass());
  }
   
  return vectorAverager.Get();
  }
} }
   
// This function prepares the simulation by creating all the necessary data structures it will // This function prepares the simulation by creating all the necessary data structures it will
// need during the simulation. All required data is copied from the core game data structures // need during the simulation. All required data is copied from the core game data structures
// so that the simulation itself can be run in a background thread without having issues with // so that the simulation itself can be run in a background thread without having issues with
// the core game changing the data while the simulation is running. // the core game changing the data while the simulation is running.
public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0, double theVelocity = 0, bool dumpTree = false, bool vectoredThrust = false) public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0, double theMach = 0, bool dumpTree = false, bool vectoredThrust = false, bool fullThrust = false)
{ {
LogMsg log = null; LogMsg log = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("PrepareSimulation started"); log.buf.AppendLine("PrepareSimulation started");
dumpTree = true; dumpTree = true;
} }
this._timer.Start(); this._timer.Start();
   
// Store the parameters in members for ease of access in other functions // Store the parameters in members for ease of access in other functions
this.partList = parts; this.partList = parts;
this.gravity = theGravity; this.gravity = theGravity;
this.atmosphere = theAtmosphere; this.atmosphere = theAtmosphere;
this.velocity = theVelocity; this.mach = theMach;
this.lastStage = Staging.lastStage; this.lastStage = Staging.lastStage;
//MonoBehaviour.print("lastStage = " + lastStage); //MonoBehaviour.print("lastStage = " + lastStage);
   
// Create the lists for our simulation parts // Clear the lists for our simulation parts
this.allParts = new List<PartSim>(); allParts.Clear();
this.allFuelLines = new List<PartSim>(); allFuelLines.Clear();
this.drainingParts = new HashSet<PartSim>(); drainingParts.Clear();
this.allEngines = new List<EngineSim>(); allEngines.Clear();
this.activeEngines = new List<EngineSim>(); activeEngines.Clear();
this.drainingResources = new HashSet<int>(); drainingResources.Clear();
   
  PartSim.ReleaseAll();
  EngineSim.ReleaseAll();
  AttachNodeSim.ReleaseAll();
   
// A dictionary for fast lookup of Part->PartSim during the preparation phase // A dictionary for fast lookup of Part->PartSim during the preparation phase
Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>(); partSimLookup.Clear();
   
if (this.partList.Count > 0 && this.partList[0].vessel != null) if (this.partList.Count > 0 && this.partList[0].vessel != null)
{ {
this.vesselName = this.partList[0].vessel.vesselName; this.vesselName = this.partList[0].vessel.vesselName;
this.vesselType = this.partList[0].vessel.vesselType; this.vesselType = this.partList[0].vessel.vesselType;
} }
   
// First we create a PartSim for each Part (giving each a unique id) // First we create a PartSim for each Part (giving each a unique id)
int partId = 1; int partId = 1;
foreach (Part part in this.partList) for (int i = 0; i < partList.Count; ++i)
{ {
  Part part = partList[i];
   
// If the part is already in the lookup dictionary then log it and skip to the next part // If the part is already in the lookup dictionary then log it and skip to the next part
if (partSimLookup.ContainsKey(part)) if (partSimLookup.ContainsKey(part))
{ {
if (log != null) if (log != null)
  {
log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once"); log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once");
  }
continue; continue;
} }
   
// Create the PartSim // Create the PartSim
PartSim partSim = new PartSim(part, partId, this.atmosphere, log); PartSim partSim = PartSim.GetPoolObject().Initialise(part, partId, this.atmosphere, log);
   
// Add it to the Part lookup dictionary and the necessary lists // Add it to the Part lookup dictionary and the necessary lists
partSimLookup.Add(part, partSim); partSimLookup.Add(part, partSim);
this.allParts.Add(partSim); this.allParts.Add(partSim);
if (partSim.isFuelLine) if (partSim.isFuelLine)
  {
this.allFuelLines.Add(partSim); this.allFuelLines.Add(partSim);
  }
if (partSim.isEngine) if (partSim.isEngine)
partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.velocity, vectoredThrust, log); {
  partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.mach, vectoredThrust, fullThrust, log);
  }
   
partId++; partId++;
  }
   
  for (int i = 0; i < allEngines.Count; ++i)
  {
  maxMach = Mathf.Max(maxMach, allEngines[i].maxMach);
} }
   
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
// Now that all the PartSims have been created we can do any set up that needs access to other parts // Now that all the PartSims have been created we can do any set up that needs access to other parts
// First we set up all the parent links // First we set up all the parent links
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
partSim.SetupParent(partSimLookup, log); partSim.SetupParent(partSimLookup, log);
} }
   
// Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
foreach (PartSim partSim in this.allFuelLines) for (int i = 0; i < allFuelLines.Count; ++i)
{ {
if ((partSim.part as FuelLine).target != null) PartSim partSim = allFuelLines[i];
   
  CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>();
  if (fuelLine.target != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue((partSim.part as FuelLine).target, out targetSim)) if (partSimLookup.TryGetValue(fuelLine.target, out targetSim))
{ {
if (log != null) if (log != null)
  {
log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);
  }
   
targetSim.fuelTargets.Add(partSim.parent); targetSim.fuelTargets.Add(partSim.parent);
} }
else else
{ {
if (log != null) if (log != null)
  {
log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")"); log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")");
  }
} }
} }
else else
{ {
if (log != null) if (log != null)
  {
log.buf.AppendLine("Fuel line target is null"); log.buf.AppendLine("Fuel line target is null");
  }
} }
} }
} }
   
//MonoBehaviour.print("SetupAttachNodes and count stages"); //MonoBehaviour.print("SetupAttachNodes and count stages");
foreach (PartSim partSim in this.allParts) for (int i = 0; i < allParts.Count; ++i)
{ {
  PartSim partSim = allParts[i];
   
partSim.SetupAttachNodes(partSimLookup, log); partSim.SetupAttachNodes(partSimLookup, log);
if (partSim.decoupledInStage >= this.lastStage) if (partSim.decoupledInStage >= this.lastStage)
  {
this.lastStage = partSim.decoupledInStage + 1; this.lastStage = partSim.decoupledInStage + 1;
  }
} }
   
// And finally release the Part references from all the PartSims // And finally release the Part references from all the PartSims
//MonoBehaviour.print("ReleaseParts"); //MonoBehaviour.print("ReleaseParts");
foreach (PartSim partSim in this.allParts) for (int i = 0; i < allParts.Count; ++i)
partSim.ReleasePart(); {
  allParts[i].ReleasePart();
  }
   
// And dereference the core's part list // And dereference the core's part list
this.partList = null; this.partList = null;
   
this._timer.Stop(); this._timer.Stop();
if (log != null) if (log != null)
{ {
log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms"); log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms");
log.Flush(); log.Flush();
} }
   
if (dumpTree) if (dumpTree)
  {
this.Dump(); this.Dump();
  }
   
return true; return true;
} }
   
   
// This function runs the simulation and returns a newly created array of Stage objects // This function runs the simulation and returns a newly created array of Stage objects
public Stage[] RunSimulation() public Stage[] RunSimulation()
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
  {
MonoBehaviour.print("RunSimulation started"); MonoBehaviour.print("RunSimulation started");
  }
   
this._timer.Start(); this._timer.Start();
   
  LogMsg log = null;
  if (SimManager.logOutput)
  {
  log = new LogMsg();
  }
   
// Start with the last stage to simulate // Start with the last stage to simulate
// (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage) // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage)
this.currentStage = this.lastStage; this.currentStage = this.lastStage;
   
LogMsg log = null;  
if (SimManager.logOutput)  
log = new LogMsg();  
   
// Work out which engines would be active if just doing the staging and if this is different to the // Work out which engines would be active if just doing the staging and if this is different to the
// currently active engines then generate an extra stage // currently active engines then generate an extra stage
// Loop through all the engines // Loop through all the engines
foreach (EngineSim engine in this.allEngines) bool anyActive = false;
{ for (int i = 0; i < allEngines.Count; ++i)
  {
  EngineSim engine = allEngines[i];
   
if (log != null) if (log != null)
  {
log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId); log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId);
  }
bool bActive = engine.isActive; bool bActive = engine.isActive;
bool bStage = (engine.partSim.inverseStage >= this.currentStage); bool bStage = (engine.partSim.inverseStage >= this.currentStage);
if (log != null) if (log != null)
  {
log.buf.AppendLine("bActive = " + bActive + " bStage = " + bStage); log.buf.AppendLine("bActive = " + bActive + " bStage = " + bStage);
  }
if (HighLogic.LoadedSceneIsFlight) if (HighLogic.LoadedSceneIsFlight)
{ {
  if (bActive)
  {
  anyActive = true;
  }
if (bActive != bStage) if (bActive != bStage)
{ {
// If the active state is different to the state due to staging // If the active state is different to the state due to staging
if (log != null) if (log != null)
  {
log.buf.AppendLine("Need to do current active engines first"); log.buf.AppendLine("Need to do current active engines first");
  }
   
this.doingCurrent = true; this.doingCurrent = true;
this.currentStage++;  
break;  
} }
} }
else else
{ {
if (bStage) if (bStage)
{ {
if (log != null) if (log != null)
  {
log.buf.AppendLine("Marking as active"); log.buf.AppendLine("Marking as active");
  }
   
engine.isActive = true; engine.isActive = true;
} }
} }
} }
   
  // If we need to do current because of difference in engine activation and there actually are active engines
  // then we do the extra stage otherwise activate the next stage and don't treat it as current
  if (this.doingCurrent && anyActive)
  {
  this.currentStage++;
  }
  else
  {
  this.ActivateStage();
  this.doingCurrent = false;
  }
   
  // Create a list of lists of PartSims that prevent decoupling
  BuildDontStageLists(log);
   
if (log != null) if (log != null)
  {
log.Flush(); log.Flush();
  }
   
// Create the array of stages that will be returned // Create the array of stages that will be returned
Stage[] stages = new Stage[this.currentStage + 1]; Stage[] stages = new Stage[this.currentStage + 1];
   
   
// Loop through the stages // Loop through the stages
while (this.currentStage >= 0) while (this.currentStage >= 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Simulating stage " + this.currentStage); log.buf.AppendLine("Simulating stage " + this.currentStage);
log.buf.AppendLine("ShipMass = " + this.ShipMass); log.buf.AppendLine("ShipMass = " + this.ShipMass);
log.Flush(); log.Flush();
this._timer.Reset(); this._timer.Reset();
this._timer.Start(); this._timer.Start();
} }
   
// Update active engines and resource drains // Update active engines and resource drains
this.UpdateResourceDrains(); this.UpdateResourceDrains();
   
// Create the Stage object for this stage // Create the Stage object for this stage
Stage stage = new Stage(); Stage stage = new Stage();
   
this.stageTime = 0d; this.stageTime = 0d;
this.vecStageDeltaV = Vector3.zero; this.vecStageDeltaV = Vector3.zero;
   
this.stageStartMass = this.ShipMass; this.stageStartMass = this.ShipMass;
  this.stageStartCom = this.ShipCom;
   
this.stepStartMass = this.stageStartMass; this.stepStartMass = this.stageStartMass;
this.stepEndMass = 0; this.stepEndMass = 0;
   
this.CalculateThrustAndISP(); this.CalculateThrustAndISP();
   
// Store various things in the Stage object // Store various things in the Stage object
stage.thrust = this.totalStageThrust; stage.thrust = this.totalStageThrust;
//MonoBehaviour.print("stage.thrust = " + stage.thrust); //MonoBehaviour.print("stage.thrust = " + stage.thrust);
stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity); stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity);
stage.maxThrustToWeight = stage.thrustToWeight; stage.maxThrustToWeight = stage.thrustToWeight;
//MonoBehaviour.print("StageMass = " + stageStartMass); //MonoBehaviour.print("StageMass = " + stageStartMass);
//MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight); //MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);
stage.actualThrust = this.totalStageActualThrust; stage.actualThrust = this.totalStageActualThrust;
stage.actualThrustToWeight = this.totalStageActualThrust / (this.stageStartMass * this.gravity); stage.actualThrustToWeight = this.totalStageActualThrust / (this.stageStartMass * this.gravity);
   
// Calculate the cost and mass of this stage // calculate torque and associates
foreach (PartSim partSim in this.allParts) stage.maxThrustTorque = this.totalStageThrustForce.TorqueAt(this.stageStartCom).magnitude;
{  
  // torque divided by thrust. imagine that all engines are at the end of a lever that tries to turn the ship.
  // this numerical value, in meters, would represent the length of that lever.
  double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust;
   
  // how far away are the engines from the CoM, actually?
  double thrustDistance = (this.stageStartCom - this.totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude;
   
  // the combination of the above two values gives an approximation of the offset angle.
  double sinThrustOffsetAngle = 0;
  if (thrustDistance > 1e-7) {
  sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance;
  if (sinThrustOffsetAngle > 1) {
  sinThrustOffsetAngle = 1;
  }
  }
   
  stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI;
   
  // Calculate the cost and mass of this stage and add all engines and tanks that are decoupled
  // in the next stage to the dontStageParts list
  for (int i = 0; i < allParts.Count; ++i)
  {
  PartSim partSim = allParts[i];
   
if (partSim.decoupledInStage == this.currentStage - 1) if (partSim.decoupledInStage == this.currentStage - 1)
{ {
stage.cost += partSim.cost; stage.cost += partSim.cost;
stage.mass += partSim.GetStartMass(); stage.mass += partSim.GetStartMass();
} }
} }
   
  this.dontStageParts = dontStagePartsLists[this.currentStage];
   
if (log != null) if (log != null)
MonoBehaviour.print("Stage setup took " + this._timer.ElapsedMilliseconds + "ms"); {
  log.buf.AppendLine("Stage setup took " + this._timer.ElapsedMilliseconds + "ms");
   
  if (this.dontStageParts.Count > 0)
  {
  log.buf.AppendLine("Parts preventing staging:");
  foreach (PartSim partSim in this.dontStageParts)
  {
  partSim.DumpPartToBuffer(log.buf, "");
  }
  }
  else
  {
  log.buf.AppendLine("No parts preventing staging");
  }
   
  log.Flush();
  }
   
// Now we will loop until we are allowed to stage // Now we will loop until we are allowed to stage
int loopCounter = 0; int loopCounter = 0;
while (!this.AllowedToStage()) while (!this.AllowedToStage())
{ {
loopCounter++; loopCounter++;
//MonoBehaviour.print("loop = " + loopCounter); //MonoBehaviour.print("loop = " + loopCounter);
   
// Calculate how long each draining tank will take to drain and run for the minimum time // Calculate how long each draining tank will take to drain and run for the minimum time
double resourceDrainTime = double.MaxValue; double resourceDrainTime = double.MaxValue;
PartSim partMinDrain = null; PartSim partMinDrain = null;
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
{ {
double time = partSim.TimeToDrainResource(); double time = partSim.TimeToDrainResource();
if (time < resourceDrainTime) if (time < resourceDrainTime)
{ {
resourceDrainTime = time; resourceDrainTime = time;
partMinDrain = partSim; partMinDrain = partSim;
} }
} }
   
if (log != null) if (log != null)
  {
MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")"); MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
  }
   
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
  {
partSim.DrainResources(resourceDrainTime); partSim.DrainResources(resourceDrainTime);
  }
   
// Get the mass after draining // Get the mass after draining
this.stepEndMass = this.ShipMass; this.stepEndMass = this.ShipMass;
this.stageTime += resourceDrainTime; this.stageTime += resourceDrainTime;
   
double stepEndTWR = this.totalStageThrust / (this.stepEndMass * this.gravity); double stepEndTWR = this.totalStageThrust / (this.stepEndMass * this.gravity);
//MonoBehaviour.print("After drain mass = " + stepEndMass); //MonoBehaviour.print("After drain mass = " + stepEndMass);
//MonoBehaviour.print("currentThrust = " + totalStageThrust); //MonoBehaviour.print("currentThrust = " + totalStageThrust);
//MonoBehaviour.print("currentTWR = " + stepEndTWR); //MonoBehaviour.print("currentTWR = " + stepEndTWR);
if (stepEndTWR > stage.maxThrustToWeight) if (stepEndTWR > stage.maxThrustToWeight)
  {
stage.maxThrustToWeight = stepEndTWR; stage.maxThrustToWeight = stepEndTWR;
  }
   
//MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight); //MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight);
   
// If we have drained anything and the masses make sense then add this step's deltaV to the stage total // If we have drained anything and the masses make sense then add this step's deltaV to the stage total
if (resourceDrainTime > 0d && this.stepStartMass > this.stepEndMass && this.stepStartMass > 0d && this.stepEndMass > 0d) if (resourceDrainTime > 0d && this.stepStartMass > this.stepEndMass && this.stepStartMass > 0d && this.stepEndMass > 0d)
this.vecStageDeltaV += this.vecThrust * (float)((this.currentisp * STD_GRAVITY * Math.Log(this.stepStartMass / this.stepEndMass)) / this.simpleTotalThrust); {
  this.vecStageDeltaV += this.vecThrust * (float)((this.currentisp * Units.GRAVITY * Math.Log(this.stepStartMass / this.stepEndMass)) / this.simpleTotalThrust);
  }
   
// Update the active engines and resource drains for the next step // Update the active engines and resource drains for the next step
this.UpdateResourceDrains(); this.UpdateResourceDrains();
   
// Recalculate the current thrust and isp for the next step // Recalculate the current thrust and isp for the next step
this.CalculateThrustAndISP(); this.CalculateThrustAndISP();
   
// Check if we actually changed anything // Check if we actually changed anything
if (this.stepStartMass == this.stepEndMass) if (this.stepStartMass == this.stepEndMass)
{ {
//MonoBehaviour.print("No change in mass"); //MonoBehaviour.print("No change in mass");
break; break;
} }
   
// Check to stop rampant looping // Check to stop rampant looping
if (loopCounter == 1000) if (loopCounter == 1000)
{ {
MonoBehaviour.print("exceeded loop count"); MonoBehaviour.print("exceeded loop count");
MonoBehaviour.print("stageStartMass = " + this.stageStartMass); MonoBehaviour.print("stageStartMass = " + this.stageStartMass);
MonoBehaviour.print("stepStartMass = " + this.stepStartMass); MonoBehaviour.print("stepStartMass = " + this.stepStartMass);
MonoBehaviour.print("StepEndMass = " + this.stepEndMass); MonoBehaviour.print("StepEndMass = " + this.stepEndMass);
  Logger.Log("exceeded loop count");
  Logger.Log("stageStartMass = " + this.stageStartMass);
  Logger.Log("stepStartMass = " + this.stepStartMass);
  Logger.Log("StepEndMass = " + this.stepEndMass);
break; break;
} }
   
// The next step starts at the mass this one ended at // The next step starts at the mass this one ended at
this.stepStartMass = this.stepEndMass; this.stepStartMass = this.stepEndMass;
} }
   
// Store more values in the Stage object and stick it in the array // Store more values in the Stage object and stick it in the array
   
// Store the magnitude of the deltaV vector // Store the magnitude of the deltaV vector
stage.deltaV = this.vecStageDeltaV.magnitude; stage.deltaV = this.vecStageDeltaV.magnitude;
  stage.resourceMass = this.stageStartMass - this.stepEndMass;
   
// Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around) // Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around)
// Note: If the mass doesn't change then this is a divide by zero // Note: If the mass doesn't change then this is a divide by zero
if (this.stageStartMass != this.stepStartMass) if (this.stageStartMass != this.stepStartMass)
stage.isp = stage.deltaV / (STD_GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass)); {
  stage.isp = stage.deltaV / (Units.GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass));
  }
else else
  {
stage.isp = 0; stage.isp = 0;
  }
   
// Zero stage time if more than a day (this should be moved into the window code) // Zero stage time if more than a day (this should be moved into the window code)
stage.time = (this.stageTime < SECONDS_PER_DAY) ? this.stageTime : 0d; stage.time = (this.stageTime < SECONDS_PER_DAY) ? this.stageTime : 0d;
stage.number = this.doingCurrent ? -1 : this.currentStage; // Set the stage number to -1 if doing current engines stage.number = this.doingCurrent ? -1 : this.currentStage; // Set the stage number to -1 if doing current engines
  stage.totalPartCount = this.allParts.Count;
  stage.maxMach = maxMach;
stages[this.currentStage] = stage; stages[this.currentStage] = stage;
   
// Now activate the next stage // Now activate the next stage
this.currentStage--; this.currentStage--;
this.doingCurrent = false; this.doingCurrent = false;
   
if (log != null) if (log != null)
{ {
// Log how long the stage took // Log how long the stage took
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms");
stage.Dump(); stage.Dump();
this._timer.Reset(); this._timer.Reset();
this._timer.Start(); this._timer.Start();
} }
   
// Activate the next stage // Activate the next stage
this.ActivateStage(); this.ActivateStage();
   
if (log != null) if (log != null)
{ {
// Log how long it took to activate // Log how long it took to activate
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms");
} }
} }
   
// Now we add up the various total fields in the stages // Now we add up the various total fields in the stages
for (int i = 0; i < stages.Length; i++) for (int i = 0; i < stages.Length; i++)
{ {
// For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above // For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above
for (int j = i; j >= 0; j--) for (int j = i; j >= 0; j--)
{ {
stages[i].totalCost += stages[j].cost; stages[i].totalCost += stages[j].cost;
stages[i].totalMass += stages[j].mass; stages[i].totalMass += stages[j].mass;
stages[i].totalDeltaV += stages[j].deltaV; stages[i].totalDeltaV += stages[j].deltaV;
stages[i].totalTime += stages[j].time; stages[i].totalTime += stages[j].time;
  stages[i].partCount = i > 0 ? stages[i].totalPartCount - stages[i - 1].totalPartCount : stages[i].totalPartCount;
} }
// We also total up the deltaV for stage and all stages below // We also total up the deltaV for stage and all stages below
for (int j = i; j < stages.Length; j++) for (int j = i; j < stages.Length; j++)
{ {
stages[i].inverseTotalDeltaV += stages[j].deltaV; stages[i].inverseTotalDeltaV += stages[j].deltaV;
} }
   
// Zero the total time if the value will be huge (24 hours?) to avoid the display going weird // Zero the total time if the value will be huge (24 hours?) to avoid the display going weird
// (this should be moved into the window code) // (this should be moved into the window code)
if (stages[i].totalTime > SECONDS_PER_DAY) if (stages[i].totalTime > SECONDS_PER_DAY)
  {
stages[i].totalTime = 0d; stages[i].totalTime = 0d;
  }
} }
   
if (log != null) if (log != null)
{ {
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms");
} }
   
return stages; return stages;
} }
   
  private void BuildDontStageLists(LogMsg log)
  {
  if (log != null)
  {
  log.buf.AppendLine("Creating list with capacity of " + (this.currentStage + 1));
  }
   
  dontStagePartsLists.Clear();
  for (int i = 0; i <= this.currentStage; i++)
  {
  if (i < dontStagePartsLists.Count)
  {
  dontStagePartsLists[i].Clear();
  }
  else
  {
  dontStagePartsLists.Add(new List<PartSim>());
  }
  }
   
  for (int i = 0; i < allParts.Count; ++i)
  {
  PartSim partSim = allParts[i];
   
  if (partSim.isEngine || !partSim.Resources.Empty)
  {
  if (log != null)
  {
  log.buf.AppendLine(partSim.name + ":" + partSim.partId + " is engine or tank, decoupled = " + partSim.decoupledInStage);
  }
   
  if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > this.currentStage - 1)
  {
  if (log != null)
  {
  log.buf.AppendLine("decoupledInStage out of range");
  }
  }
  else
  {
  dontStagePartsLists[partSim.decoupledInStage + 1].Add(partSim);
  }
  }
  }
   
  for (int i = 1; i <= this.lastStage; i++)
  {
  if (dontStagePartsLists[i].Count == 0)
  {
  dontStagePartsLists[i] = dontStagePartsLists[i - 1];
  }
  }
  }
   
// This function simply rebuilds the active engines by testing the isActive flag of all the engines // This function simply rebuilds the active engines by testing the isActive flag of all the engines
private void UpdateActiveEngines() private void UpdateActiveEngines()
{ {
this.activeEngines.Clear(); this.activeEngines.Clear();
foreach (EngineSim engine in this.allEngines) for (int i = 0; i < allEngines.Count; ++i)
{ {
  EngineSim engine = allEngines[i];
   
if (engine.isActive) if (engine.isActive)
  {
this.activeEngines.Add(engine); this.activeEngines.Add(engine);
  }
} }
} }
   
private void CalculateThrustAndISP() private void CalculateThrustAndISP()
{ {
// Reset all the values // Reset all the values
this.vecThrust = Vector3.zero; this.vecThrust = Vector3.zero;
this.vecActualThrust = Vector3.zero; this.vecActualThrust = Vector3.zero;
this.simpleTotalThrust = 0d; this.simpleTotalThrust = 0d;
this.totalStageThrust = 0d; this.totalStageThrust = 0d;
this.totalStageActualThrust = 0d; this.totalStageActualThrust = 0d;
this.totalStageFlowRate = 0d; this.totalStageFlowRate = 0d;
this.totalStageIspFlowRate = 0d; this.totalStageIspFlowRate = 0d;
  this.totalStageThrustForce.Reset();
   
// Loop through all the active engines totalling the thrust, actual thrust and mass flow rates // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates
// The thrust is totalled as vectors // The thrust is totalled as vectors
foreach (EngineSim engine in this.activeEngines) for (int i = 0; i < activeEngines.Count; ++i)
{ {
  EngineSim engine = activeEngines[i];
   
this.simpleTotalThrust += engine.thrust; this.simpleTotalThrust += engine.thrust;
this.vecThrust += ((float)engine.thrust * engine.thrustVec); this.vecThrust += ((float)engine.thrust * engine.thrustVec);
this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec); this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec);
   
this.totalStageFlowRate += engine.ResourceConsumptions.Mass; this.totalStageFlowRate += engine.ResourceConsumptions.Mass;
this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp; this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
   
  for (int j = 0; j < engine.appliedForces.Count; ++j)
  {
  this.totalStageThrustForce.AddForce(engine.appliedForces[j]);
  }
} }
   
//MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + " magnitude = " + vecThrust.magnitude); //MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + " magnitude = " + vecThrust.magnitude);
   
this.totalStageThrust = this.vecThrust.magnitude; this.totalStageThrust = this.vecThrust.magnitude;
this.totalStageActualThrust = this.vecActualThrust.magnitude; this.totalStageActualThrust = this.vecActualThrust.magnitude;
   
// Calculate the effective isp at this point // Calculate the effective isp at this point
if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d) if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d)
  {
this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate; this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate;
  }
else else
  {
this.currentisp = 0; this.currentisp = 0;
  }
} }
   
// This function does all the hard work of working out which engines are burning, which tanks are being drained // This function does all the hard work of working out which engines are burning, which tanks are being drained
// and setting the drain rates // and setting the drain rates
private void UpdateResourceDrains() private void UpdateResourceDrains()
{ {
// Update the active engines // Update the active engines
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
// Empty the draining resources set // Empty the draining resources set
this.drainingResources.Clear(); this.drainingResources.Clear();
   
// Reset the resource drains of all draining parts // Reset the resource drains of all draining parts
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
  {
partSim.ResourceDrains.Reset(); partSim.ResourceDrains.Reset();
  }
   
// Empty the draining parts set // Empty the draining parts set
this.drainingParts.Clear(); this.drainingParts.Clear();
   
// Loop through all the active engine modules // Loop through all the active engine modules
foreach (EngineSim engine in this.activeEngines) for (int i = 0; i < activeEngines.Count; ++i)
{ {
  EngineSim engine = activeEngines[i];
   
// Set the resource drains for this engine // Set the resource drains for this engine
if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts)) if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))
{ {
// If it is active then add the consumed resource types to the set // If it is active then add the consumed resource types to the set
foreach (int type in engine.ResourceConsumptions.Types) for (int j = 0; j < engine.ResourceConsumptions.Types.Count; ++j)
this.drainingResources.Add(type); {
  drainingResources.Add(engine.ResourceConsumptions.Types[j]);
  }
} }
} }
   
// Update the active engines again to remove any engines that have no fuel supply // Update the active engines again to remove any engines that have no fuel supply
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
StringBuilder buffer = new StringBuilder(1024); StringBuilder buffer = new StringBuilder(1024);
buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count); buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count);
int i = 0; int i = 0;
foreach (EngineSim engine in this.activeEngines) foreach (EngineSim engine in this.activeEngines)
  {
engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":"); engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");
  }
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
} }
   
// This function works out if it is time to stage // This function works out if it is time to stage
private bool AllowedToStage() private bool AllowedToStage()
{ {
StringBuilder buffer = null; StringBuilder buffer = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer = new StringBuilder(1024); buffer = new StringBuilder(1024);
buffer.AppendLine("AllowedToStage"); buffer.AppendLine("AllowedToStage");
buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage); buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage);
} }
   
if (this.activeEngines.Count == 0) if (this.activeEngines.Count > 0)
{ {
if (SimManager.logOutput) for (int i = 0; i < dontStageParts.Count; ++i)
{ {
buffer.AppendLine("No active engines => true"); PartSim partSim = dontStageParts[i];
MonoBehaviour.print(buffer);  
} if (SimManager.logOutput)
  {
return true; partSim.DumpPartToBuffer(buffer, "Testing: ");
} }
  //buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");
bool partDecoupled = false;  
bool engineDecoupled = false; if (!partSim.isSepratron && !partSim.EmptyOf(this.drainingResources))
   
foreach (PartSim partSim in this.allParts)  
{  
//partSim.DumpPartToBuffer(buffer, "Testing: ", allParts);  
//buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");  
if (partSim.decoupledInStage == (this.currentStage - 1) && (!partSim.isSepratron || partSim.decoupledInStage < partSim.inverseStage))  
{  
partDecoupled = true;  
   
if (!partSim.Resources.EmptyOf(this.drainingResources))  
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: "); partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
   
return false; return false;
} }
   
if (partSim.isEngine) if (partSim.isEngine)
{ {
foreach (EngineSim engine in this.activeEngines) for (int j = 0; j < activeEngines.Count; ++j)
{ {
  EngineSim engine = activeEngines[j];
   
if (engine.partSim == partSim) if (engine.partSim == partSim)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: "); partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
} }
  }
engineDecoupled = true; }
} }
}  
} if (this.currentStage == 0 && this.doingCurrent)
   
if (!partDecoupled)  
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("No engine decoupled but something is => false"); buffer.AppendLine("Current stage == 0 && doingCurrent => false");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
   
if (this.currentStage > 0 && !this.doingCurrent)  
{  
if (SimManager.logOutput)  
{  
buffer.AppendLine("Current stage > 0 && !doingCurrent => true");  
MonoBehaviour.print(buffer);  
}  
return true;  
}  
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("Returning false"); buffer.AppendLine("Returning true");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return true;
} }
   
// This function activates the next stage // This function activates the next stage
// currentStage must be updated before calling this function // currentStage must be updated before calling this function
private void ActivateStage() private void ActivateStage()
{ {
// Build a set of all the parts that will be decoupled // Build a set of all the parts that will be decoupled
HashSet<PartSim> decoupledParts = new HashSet<PartSim>(); decoupledParts.Clear();
foreach (PartSim partSim in this.allParts) for (int i = 0; i < allParts.Count; ++i)
{ {
  PartSim partSim = allParts[i];
   
if (partSim.decoupledInStage >= this.currentStage) if (partSim.decoupledInStage >= this.currentStage)
  {
decoupledParts.Add(partSim); decoupledParts.Add(partSim);
  }
} }
   
foreach (PartSim partSim in decoupledParts) foreach (PartSim partSim in decoupledParts)
{ {
// Remove it from the all parts list // Remove it from the all parts list
this.allParts.Remove(partSim); this.allParts.Remove(partSim);
if (partSim.isEngine) if (partSim.isEngine)
{ {
// If it is an engine then loop through all the engine modules and remove all the ones from this engine part // If it is an engine then loop through all the engine modules and remove all the ones from this engine part
for (int i = this.allEngines.Count - 1; i >= 0; i--) for (int i = this.allEngines.Count - 1; i >= 0; i--)
{ {
if (this.allEngines[i].partSim == partSim) if (this.allEngines[i].partSim == partSim)
  {
this.allEngines.RemoveAt(i); this.allEngines.RemoveAt(i);
  }
} }
} }
// If it is a fuel line then remove it from the list of all fuel lines // If it is a fuel line then remove it from the list of all fuel lines
if (partSim.isFuelLine) if (partSim.isFuelLine)
  {
this.allFuelLines.Remove(partSim); this.allFuelLines.Remove(partSim);
  }
} }
   
// Loop through all the (remaining) parts // Loop through all the (remaining) parts
foreach (PartSim partSim in this.allParts) for (int i = 0; i < allParts.Count; ++i) {
{  
// Ask the part to remove all the parts that are decoupled // Ask the part to remove all the parts that are decoupled
partSim.RemoveAttachedParts(decoupledParts); allParts[i].RemoveAttachedParts(decoupledParts);
} }
   
// Now we loop through all the engines and activate those that are ignited in this stage // Now we loop through all the engines and activate those that are ignited in this stage
foreach(EngineSim engine in this.allEngines) for (int i = 0; i < allEngines.Count; ++i)
{ {
  EngineSim engine = allEngines[i];
if (engine.partSim.inverseStage == this.currentStage) if (engine.partSim.inverseStage == this.currentStage)
  {
engine.isActive = true; engine.isActive = true;
} }
}  
   
private double ShipStartMass  
{  
get  
{  
double mass = 0d;  
   
foreach (PartSim partSim in this.allParts)  
{  
mass += partSim.GetStartMass();  
}  
   
return mass;  
}  
}  
   
private double ShipMass  
{  
get  
{  
double mass = 0d;  
   
foreach (PartSim partSim in this.allParts)  
{  
mass += partSim.GetMass();  
}  
   
return mass;  
} }
} }
   
public void Dump() public void Dump()
{ {
StringBuilder buffer = new StringBuilder(1024); StringBuilder buffer = new StringBuilder(1024);
buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count); buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count);
   
// Output a nice tree view of the rocket // Output a nice tree view of the rocket
if (this.allParts.Count > 0) if (this.allParts.Count > 0)
{ {
PartSim root = this.allParts[0]; PartSim root = this.allParts[0];
while (root.parent != null) while (root.parent != null)
  {
root = root.parent; root = root.parent;
  }
   
if (root.hasVessel) if (root.hasVessel)
  {
buffer.AppendFormat("vesselName = '{0}' vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType)); buffer.AppendFormat("vesselName = '{0}' vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType));
  }
   
root.DumpPartToBuffer(buffer, "", this.allParts); root.DumpPartToBuffer(buffer, "", this.allParts);
} }
   
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
} }
} }