ForceAccumulator is now resettable.
ForceAccumulator is now resettable.

// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
   
namespace KerbalEngineer namespace KerbalEngineer
{ {
// a (force, application point) tuple // a (force, application point) tuple
public class AppliedForce public class AppliedForce
{ {
public Vector3d vector; public Vector3d vector;
public Vector3d applicationPoint; public Vector3d applicationPoint;
   
public AppliedForce(Vector3d vector, Vector3d applicationPoint) { public AppliedForce(Vector3d vector, Vector3d applicationPoint) {
this.vector = vector; this.vector = vector;
this.applicationPoint = applicationPoint; this.applicationPoint = applicationPoint;
} }
} }
   
// This class was mostly adapted from FARCenterQuery, part of FAR, by ferram4, GPLv3 // This class was mostly adapted from FARCenterQuery, part of FAR, by ferram4, GPLv3
// https://github.com/ferram4/Ferram-Aerospace-Research/blob/master/FerramAerospaceResearch/FARCenterQuery.cs // https://github.com/ferram4/Ferram-Aerospace-Research/blob/master/FerramAerospaceResearch/FARCenterQuery.cs
// Also see https://en.wikipedia.org/wiki/Resultant_force // Also see https://en.wikipedia.org/wiki/Resultant_force
   
// It accumulates forces and their points of applications, and provides methods for // It accumulates forces and their points of applications, and provides methods for
// calculating the effective torque at any position, as well as the minimum-torque net force application point. // calculating the effective torque at any position, as well as the minimum-torque net force application point.
// //
// The latter is a non-trivial issue; there is a 1-dimensional line of physically-equivalent solutions parallel // The latter is a non-trivial issue; there is a 1-dimensional line of physically-equivalent solutions parallel
// to the resulting force vector; the solution closest to the weighted average of force positions is chosen. // to the resulting force vector; the solution closest to the weighted average of force positions is chosen.
// In the case of non-parallel forces, there usually is an infinite number of such lines, all of which have // In the case of non-parallel forces, there usually is an infinite number of such lines, all of which have
// some amount of residual torque. The line with the least amount of residual torque is chosen. // some amount of residual torque. The line with the least amount of residual torque is chosen.
public class ForceAccumulator public class ForceAccumulator
{ {
// Total force. // Total force.
private Vector3d totalForce = Vector3d.zero; private Vector3d totalForce = Vector3d.zero;
// Torque needed to compensate if force were applied at origin. // Torque needed to compensate if force were applied at origin.
private Vector3d totalZeroOriginTorque = Vector3d.zero; private Vector3d totalZeroOriginTorque = Vector3d.zero;
   
// Weighted average of force application points. // Weighted average of force application points.
private WeightedVectorAverager avgApplicationPoint = new WeightedVectorAverager(); private WeightedVectorAverager avgApplicationPoint = new WeightedVectorAverager();
   
// Feed an force to the accumulator. // Feed an force to the accumulator.
public void AddForce(Vector3d applicationPoint, Vector3d force) public void AddForce(Vector3d applicationPoint, Vector3d force)
{ {
totalForce += force; totalForce += force;
totalZeroOriginTorque += Vector3d.Cross(applicationPoint, force); totalZeroOriginTorque += Vector3d.Cross(applicationPoint, force);
avgApplicationPoint.Add(applicationPoint, force.magnitude); avgApplicationPoint.Add(applicationPoint, force.magnitude);
} }
   
public Vector3d GetAverageForceApplicationPoint() { public Vector3d GetAverageForceApplicationPoint() {
return avgApplicationPoint.Get(); return avgApplicationPoint.Get();
} }
   
public void AddForce(AppliedForce force) { public void AddForce(AppliedForce force) {
AddForce(force.applicationPoint, force.vector); AddForce(force.applicationPoint, force.vector);
} }
   
// Residual torque for given force application point. // Residual torque for given force application point.
public Vector3d TorqueAt(Vector3d origin) public Vector3d TorqueAt(Vector3d origin)
{ {
return totalZeroOriginTorque - Vector3d.Cross(origin, totalForce); return totalZeroOriginTorque - Vector3d.Cross(origin, totalForce);
} }
   
// Total force vector. // Total force vector.
public Vector3d GetTotalForce() public Vector3d GetTotalForce()
{ {
return totalForce; return totalForce;
} }
   
// Returns the minimum-residual-torque force application point that is closest to origin. // Returns the minimum-residual-torque force application point that is closest to origin.
// Note that TorqueAt(GetMinTorquePos()) is always parallel to totalForce. // Note that TorqueAt(GetMinTorquePos()) is always parallel to totalForce.
public Vector3d GetMinTorqueForceApplicationPoint(Vector3d origin) public Vector3d GetMinTorqueForceApplicationPoint(Vector3d origin)
{ {
double fmag = totalForce.sqrMagnitude; double fmag = totalForce.sqrMagnitude;
if (fmag <= 0) { if (fmag <= 0) {
return origin; return origin;
} }
   
return origin + Vector3d.Cross(totalForce, TorqueAt(origin)) / fmag; return origin + Vector3d.Cross(totalForce, TorqueAt(origin)) / fmag;
} }
   
public Vector3d GetMinTorqueForceApplicationPoint() public Vector3d GetMinTorqueForceApplicationPoint()
{ {
return GetMinTorqueForceApplicationPoint(avgApplicationPoint.Get()); return GetMinTorqueForceApplicationPoint(avgApplicationPoint.Get());
} }
   
  public void Reset()
  {
  totalForce = Vector3d.zero;
  totalZeroOriginTorque = Vector3d.zero;
  avgApplicationPoint.Reset();
  }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
#region Using Directives #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 #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using CompoundParts; using CompoundParts;
using Extensions; using Extensions;
using Helpers; using Helpers;
   
public class Simulation public class Simulation
{ {
private const double SECONDS_PER_DAY = 86400; private const double SECONDS_PER_DAY = 86400;
private readonly Stopwatch _timer = new Stopwatch(); private readonly Stopwatch _timer = new Stopwatch();
private List<EngineSim> activeEngines = new List<EngineSim>(); private List<EngineSim> activeEngines = new List<EngineSim>();
private List<EngineSim> allEngines = new List<EngineSim>(); private List<EngineSim> allEngines = new List<EngineSim>();
private List<PartSim> allFuelLines = new List<PartSim>(); private List<PartSim> allFuelLines = new List<PartSim>();
private List<PartSim> allParts = new List<PartSim>(); private List<PartSim> allParts = new List<PartSim>();
private Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>(); private Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();
private double atmosphere; private double atmosphere;
private int currentStage; private int currentStage;
private double currentisp; private double currentisp;
private bool doingCurrent; private bool doingCurrent;
private List<PartSim> dontStageParts = new List<PartSim>(); private List<PartSim> dontStageParts = new List<PartSim>();
List<List<PartSim>> dontStagePartsLists = new List<List<PartSim>>(); List<List<PartSim>> dontStagePartsLists = new List<List<PartSim>>();
private HashSet<PartSim> drainingParts = new HashSet<PartSim>(); private HashSet<PartSim> drainingParts = new HashSet<PartSim>();
private HashSet<int> drainingResources = new HashSet<int>(); private HashSet<int> drainingResources = new HashSet<int>();
private HashSet<PartSim> decoupledParts = new HashSet<PartSim>(); private HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
private double gravity; private double gravity;
   
private int lastStage; private int lastStage;
private List<Part> partList = new List<Part>(); private List<Part> partList = new List<Part>();
private double simpleTotalThrust; private double simpleTotalThrust;
private double stageStartMass; private double stageStartMass;
private Vector3d stageStartCom; private Vector3d stageStartCom;
private double stageTime; private double stageTime;
private double stepEndMass; private double stepEndMass;
private double stepStartMass; private double stepStartMass;
private double totalStageActualThrust; private double totalStageActualThrust;
private double totalStageFlowRate; private double totalStageFlowRate;
private double totalStageIspFlowRate; private double totalStageIspFlowRate;
private double totalStageThrust; private double totalStageThrust;
private ForceAccumulator totalStageThrustForce; private ForceAccumulator totalStageThrustForce = new ForceAccumulator();
private Vector3 vecActualThrust; private Vector3 vecActualThrust;
private Vector3 vecStageDeltaV; private Vector3 vecStageDeltaV;
private Vector3 vecThrust; private Vector3 vecThrust;
private double mach; private double mach;
private float maxMach; private float maxMach;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
private WeightedVectorAverager vectorAverager = new WeightedVectorAverager(); private WeightedVectorAverager vectorAverager = new WeightedVectorAverager();
   
public Simulation() public Simulation()
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Simulation created"); MonoBehaviour.print("Simulation created");
} }
} }
   
private double ShipMass private double ShipMass
{ {
get get
{ {
double mass = 0d; double mass = 0d;
   
for (int i = 0; i < allParts.Count; ++i) { for (int i = 0; i < allParts.Count; ++i) {
mass += allParts[i].GetMass(); mass += allParts[i].GetMass();
} }
   
return mass; return mass;
} }
} }
   
private Vector3d ShipCom private Vector3d ShipCom
{ {
get get
{ {
vectorAverager.Reset(); vectorAverager.Reset();
   
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
PartSim partSim = allParts[i]; PartSim partSim = allParts[i];
vectorAverager.Add(partSim.centerOfMass, partSim.GetMass()); vectorAverager.Add(partSim.centerOfMass, partSim.GetMass());
} }
   
return vectorAverager.Get(); 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 theMach = 0, bool dumpTree = false, bool vectoredThrust = false, bool fullThrust = 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.mach = theMach; this.mach = theMach;
this.lastStage = Staging.lastStage; this.lastStage = Staging.lastStage;
//MonoBehaviour.print("lastStage = " + lastStage); //MonoBehaviour.print("lastStage = " + lastStage);
   
// Clear the lists for our simulation parts // Clear the lists for our simulation parts
allParts.Clear(); allParts.Clear();
allFuelLines.Clear(); allFuelLines.Clear();
drainingParts.Clear(); drainingParts.Clear();
allEngines.Clear(); allEngines.Clear();
activeEngines.Clear(); activeEngines.Clear();
drainingResources.Clear(); drainingResources.Clear();
   
PartSim.ReleaseAll(); PartSim.ReleaseAll();
EngineSim.ReleaseAll(); EngineSim.ReleaseAll();
AttachNodeSim.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
partSimLookup.Clear(); 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;
for (int i = 0; i < partList.Count; ++i) for (int i = 0; i < partList.Count; ++i)
{ {
Part part = partList[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 = PartSim.GetPoolObject().Initialise(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.mach, vectoredThrust, fullThrust, log); partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.mach, vectoredThrust, fullThrust, log);
} }
   
partId++; partId++;
} }
   
for (int i = 0; i < allEngines.Count; ++i) for (int i = 0; i < allEngines.Count; ++i)
{ {
maxMach = Mathf.Max(maxMach, allEngines[i].maxMach); 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)
{ {
for (int i = 0; i < allFuelLines.Count; ++i) for (int i = 0; i < allFuelLines.Count; ++i)
{ {
PartSim partSim = allFuelLines[i]; PartSim partSim = allFuelLines[i];
   
CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>(); CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>();
if (fuelLine.target != null) if (fuelLine.target != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(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");
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
PartSim partSim = allParts[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");
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
allParts[i].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; LogMsg log = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); 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;
   
// 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
bool anyActive = false; bool anyActive = false;
for (int i = 0; i < allEngines.Count; ++i) for (int i = 0; i < allEngines.Count; ++i)
{ {
EngineSim engine = allEngines[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) if (bActive)
{ {
anyActive = true; 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;
} }
} }
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 // 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 // then we do the extra stage otherwise activate the next stage and don't treat it as current
if (this.doingCurrent && anyActive) if (this.doingCurrent && anyActive)
{ {
this.currentStage++; this.currentStage++;
} }
else else
{ {
this.ActivateStage(); this.ActivateStage();
this.doingCurrent = false; this.doingCurrent = false;
} }
   
// Create a list of lists of PartSims that prevent decoupling // Create a list of lists of PartSims that prevent decoupling
BuildDontStageLists(log); 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.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 torque and associates // calculate torque and associates
stage.maxThrustTorque = this.totalStageThrustForce.TorqueAt(this.stageStartCom).magnitude; 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. // 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. // this numerical value, in meters, would represent the length of that lever.
double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust; double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust;
   
// how far away are the engines from the CoM, actually? // how far away are the engines from the CoM, actually?
double thrustDistance = (this.stageStartCom - this.totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude; double thrustDistance = (this.stageStartCom - this.totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude;
   
// the combination of the above two values gives an approximation of the offset angle. // the combination of the above two values gives an approximation of the offset angle.
double sinThrustOffsetAngle = 0; double sinThrustOffsetAngle = 0;
if (thrustDistance > 1e-7) { if (thrustDistance > 1e-7) {
sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance; sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance;
if (sinThrustOffsetAngle > 1) { if (sinThrustOffsetAngle > 1) {
sinThrustOffsetAngle = 1; sinThrustOffsetAngle = 1;
} }
} }
   
stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI; 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 // 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 // in the next stage to the dontStageParts list
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
PartSim partSim = allParts[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]; this.dontStageParts = dontStagePartsLists[this.currentStage];
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Stage setup took " + this._timer.ElapsedMilliseconds + "ms"); log.buf.AppendLine("Stage setup took " + this._timer.ElapsedMilliseconds + "ms");
   
if (this.dontStageParts.Count > 0) if (this.dontStageParts.Count > 0)
{ {
log.buf.AppendLine("Parts preventing staging:"); log.buf.AppendLine("Parts preventing staging:");
foreach (PartSim partSim in this.dontStageParts) foreach (PartSim partSim in this.dontStageParts)
{ {
partSim.DumpPartToBuffer(log.buf, ""); partSim.DumpPartToBuffer(log.buf, "");
} }
} }
else else
{ {
log.buf.AppendLine("No parts preventing staging"); log.buf.AppendLine("No parts preventing staging");
} }
   
log.Flush(); 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 * Units.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("exceeded loop count");
Logger.Log("stageStartMass = " + this.stageStartMass); Logger.Log("stageStartMass = " + this.stageStartMass);
Logger.Log("stepStartMass = " + this.stepStartMass); Logger.Log("stepStartMass = " + this.stepStartMass);
Logger.Log("StepEndMass = " + this.stepEndMass); 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; 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 / (Units.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.totalPartCount = this.allParts.Count;
stage.maxMach = maxMach; 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; 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) private void BuildDontStageLists(LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Creating list with capacity of " + (this.currentStage + 1)); log.buf.AppendLine("Creating list with capacity of " + (this.currentStage + 1));
} }
   
dontStagePartsLists.Clear(); dontStagePartsLists.Clear();
for (int i = 0; i <= this.currentStage; i++) for (int i = 0; i <= this.currentStage; i++)
{ {
if (i < dontStagePartsLists.Count) if (i < dontStagePartsLists.Count)
{ {
dontStagePartsLists[i].Clear(); dontStagePartsLists[i].Clear();
} }
else else
{ {
dontStagePartsLists.Add(new List<PartSim>()); dontStagePartsLists.Add(new List<PartSim>());
} }
} }
   
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
PartSim partSim = allParts[i]; PartSim partSim = allParts[i];
   
if (partSim.isEngine || !partSim.Resources.Empty) if (partSim.isEngine || !partSim.Resources.Empty)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(partSim.name + ":" + partSim.partId + " is engine or tank, decoupled = " + partSim.decoupledInStage); log.buf.AppendLine(partSim.name + ":" + partSim.partId + " is engine or tank, decoupled = " + partSim.decoupledInStage);
} }
   
if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > this.currentStage - 1) if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > this.currentStage - 1)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("decoupledInStage out of range"); log.buf.AppendLine("decoupledInStage out of range");
} }
} }
else else
{ {
dontStagePartsLists[partSim.decoupledInStage + 1].Add(partSim); dontStagePartsLists[partSim.decoupledInStage + 1].Add(partSim);
} }
} }
} }
   
for (int i = 1; i <= this.lastStage; i++) for (int i = 1; i <= this.lastStage; i++)
{ {
if (dontStagePartsLists[i].Count == 0) if (dontStagePartsLists[i].Count == 0)
{ {
dontStagePartsLists[i] = dontStagePartsLists[i - 1]; 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();
for (int i = 0; i < allEngines.Count; ++i) for (int i = 0; i < allEngines.Count; ++i)
{ {
EngineSim engine = allEngines[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 = new ForceAccumulator(); 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
for (int i = 0; i < activeEngines.Count; ++i) for (int i = 0; i < activeEngines.Count; ++i)
{ {
EngineSim engine = activeEngines[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) for (int j = 0; j < engine.appliedForces.Count; ++j)
{ {
this.totalStageThrustForce.AddForce(engine.appliedForces[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
for (int i = 0; i < activeEngines.Count; ++i) for (int i = 0; i < activeEngines.Count; ++i)
{ {
EngineSim engine = activeEngines[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
for (int j = 0; j < engine.ResourceConsumptions.Types.Count; ++j) for (int j = 0; j < engine.ResourceConsumptions.Types.Count; ++j)
{ {
drainingResources.Add(engine.ResourceConsumptions.Types[j]); 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)
{ {
for (int i = 0; i < dontStageParts.Count; ++i) for (int i = 0; i < dontStageParts.Count; ++i)
{ {
PartSim partSim = dontStageParts[i]; PartSim partSim = dontStageParts[i];
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Testing: "); partSim.DumpPartToBuffer(buffer, "Testing: ");
} }
//buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false"); //buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");
   
if (!partSim.isSepratron && !partSim.EmptyOf(this.drainingResources)) if (!partSim.isSepratron && !partSim.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)
{ {
for (int j = 0; j < activeEngines.Count; ++j) for (int j = 0; j < activeEngines.Count; ++j)
{ {
EngineSim engine = activeEngines[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;
} }
} }
} }
} }
} }
   
if (this.currentStage == 0 && this.doingCurrent) if (this.currentStage == 0 && this.doingCurrent)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("Current stage == 0 && doingCurrent => false"); buffer.AppendLine("Current stage == 0 && doingCurrent => false");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("Returning true"); buffer.AppendLine("Returning true");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return true; 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
decoupledParts.Clear(); decoupledParts.Clear();
for (int i = 0; i < allParts.Count; ++i) for (int i = 0; i < allParts.Count; ++i)
{ {
PartSim partSim = allParts[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
for (int i = 0; i < allParts.Count; ++i) { 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
allParts[i].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
for (int i = 0; i < allEngines.Count; ++i) for (int i = 0; i < allEngines.Count; ++i)
{ {
EngineSim engine = allEngines[i]; EngineSim engine = allEngines[i];
if (engine.partSim.inverseStage == this.currentStage) if (engine.partSim.inverseStage == this.currentStage)
{ {
engine.isActive = true; engine.isActive = true;
} }
} }
} }
   
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);
} }
} }
} }