VOID_Data: Working on multi-stage burn time estimation.
VOID_Data: Working on multi-stage burn time estimation.

--- /dev/null
+++ b/Tools/VOID_StageExtensions.cs
@@ -1,1 +1,38 @@
+// VOID © 2015 toadicus
+//
+// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
+// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 
+using KerbalEngineer.VesselSimulator;
+using KSP;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace VOID
+{
+	public static class VOID_StageExtensions
+	{
+		public static double NominalThrust(this Stage stage)
+		{
+			if (stage.actualThrust == 0d)
+			{
+				return stage.thrust;
+			}
+			else
+			{
+				return stage.actualThrust;
+			}
+		}
+
+		public static double MassFlow(this Stage stage)
+		{
+			double stageIsp = VOID_Data.Core.LastStage.isp;
+			double stageThrust = stage.NominalThrust();
+
+			return stageThrust / (stageIsp * VOID_Data.KerbinGee);
+		}
+	}
+}
+
+

--- a/Tools/VOID_Tools.cs
+++ b/Tools/VOID_Tools.cs
@@ -316,6 +316,7 @@
 		}
 		#endregion
 
+		#region WINDOW_UTILS
 		private static Dictionary<int, GUI.WindowFunction> functionCache;
 		public static UnityEngine.GUI.WindowFunction GetWindowHandler(Action<int> func)
 		{
@@ -377,7 +378,9 @@
 				}
 			}
 		}
-
+		#endregion
+
+		#region TIME_UTILS
 		/// <summary>
 		/// Formats the interval given in seconds as a human-friendly
 		/// time period in [[[[years, ]days, ]hours, ]minutes, and ]seconds.
@@ -545,6 +548,7 @@
 
 			public UnpackedTime() : this(0, 0, 0, 0, 0d) {}
 		}
+		#endregion
 
 		public static string UppercaseFirst(string s)
 		{

--- a/VOID.csproj
+++ b/VOID.csproj
@@ -113,6 +113,7 @@
     <Compile Include="API\Attributes\VOID_GameModesAttribute.cs" />
     <Compile Include="VOID_ConfigWindow.cs" />
     <Compile Include="Tools\VOID_Localization.cs" />
+    <Compile Include="Tools\VOID_StageExtensions.cs" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -38,6 +38,7 @@
 	public static class VOID_Data
 	{
 		private static Dictionary<int, IVOID_DataValue> dataValues = new Dictionary<int, IVOID_DataValue>();
+
 		public static Dictionary<int, IVOID_DataValue> DataValues
 		{
 			get
@@ -380,19 +381,7 @@
 						return double.NaN;
 					}
 
-					double stageIsp = Core.LastStage.isp;
-					double stageThrust = stageNominalThrust;
-
-					Tools.PostDebugMessage(typeof(VOID_Data), "calculating stageMassFlow from:\n" +
-						"\tstageIsp: {0}\n" +
-						"\tstageThrust: {1}\n" +
-						"\tKerbinGee: {2}\n",
-						stageIsp,
-						stageThrust,
-						KerbinGee
-					);
-
-					return stageThrust / (stageIsp * KerbinGee);
+					return Core.LastStage.MassFlow();
 				},
 				"Mg/s"
 			);
@@ -407,14 +396,7 @@
 						return double.NaN;
 					}
 
-					if (Core.LastStage.actualThrust == 0d)
-					{
-						return Core.LastStage.thrust;
-					}
-					else
-					{
-						return Core.LastStage.actualThrust;
-					}
+					return Core.LastStage.NominalThrust();
 				},
 				"kN"
 			);
@@ -481,7 +463,7 @@
 					double maxThrust = Core.LastStage.thrust;
 					double mass = Core.LastStage.totalMass;
 					double gravity = (VOIDCore.Constant_G * Core.Vessel.mainBody.Mass) /
-					               (Core.Vessel.mainBody.Radius * Core.Vessel.mainBody.Radius);
+					                 (Core.Vessel.mainBody.Radius * Core.Vessel.mainBody.Radius);
 					double weight = mass * gravity;
 
 					return maxThrust / weight;
@@ -564,13 +546,13 @@
 					thrustDir = vesselTransform.InverseTransformDirection(thrustDir);
 
 					Vector3d thrustOffset = VectorTools.PointDistanceToLine(
-						                      thrustPos, thrustDir.normalized, Core.Vessel.findLocalCenterOfMass());
+						                        thrustPos, thrustDir.normalized, Core.Vessel.findLocalCenterOfMass());
 
 					Tools.PostDebugMessage(typeof(VOID_Data), "vesselThrustOffset:\n" +
-						"\tthrustPos: {0}\n" +
-						"\tthrustDir: {1}\n" +
-						"\tthrustOffset: {2}\n" +
-						"\tvessel.CoM: {3}",
+					"\tthrustPos: {0}\n" +
+					"\tthrustDir: {1}\n" +
+					"\tthrustOffset: {2}\n" +
+					"\tvessel.CoM: {3}",
 						thrustPos,
 						thrustDir.normalized,
 						thrustOffset,
@@ -610,9 +592,9 @@
 								propellantList = engineModule.propellants;
 							}
 							else if (part.tryGetFirstModuleOfType<ModuleEnginesFX>(out enginesFXModule))
-								{
-									propellantList = enginesFXModule.propellants;
-								}
+							{
+								propellantList = enginesFXModule.propellants;
+							}
 
 							if (propellantList != null)
 							{
@@ -701,8 +683,8 @@
 				{
 
 					if (Core.Vessel == null ||
-					  Planetarium.fetch == null ||
-					  Core.Vessel.mainBody != Planetarium.fetch.Home)
+					    Planetarium.fetch == null ||
+					    Core.Vessel.mainBody != Planetarium.fetch.Home)
 					{
 						return double.NaN;
 					}
@@ -955,14 +937,12 @@
 				"Total Burn Time",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDeltaV.Value);
 				},
 				"s"
 			);
@@ -972,14 +952,12 @@
 				"Burn Time Remaining",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDVRemaining == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDVRemaining.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDVRemaining.Value);
 				},
 				"s"
 			);
@@ -989,14 +967,12 @@
 				"Half Burn Time",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDeltaV.Value / 2d);
 				},
 				"s"
 			);
@@ -1088,7 +1064,7 @@
 				delegate()
 				{
 					double orbitRadius = Core.Vessel.mainBody.Radius +
-					                   Core.Vessel.mainBody.GetAltitude(Core.Vessel.findWorldCenterOfMass());
+					                     Core.Vessel.mainBody.GetAltitude(Core.Vessel.findWorldCenterOfMass());
 					return (VOIDCore.Constant_G * Core.Vessel.mainBody.Mass) /
 					(orbitRadius * orbitRadius);
 				},
@@ -1157,9 +1133,9 @@
 				{
 					double trueAnomalyAscNode = 360d - argumentPeriapsis;
 					double dTAscNode = Core.Vessel.orbit.GetDTforTrueAnomaly(
-						trueAnomalyAscNode * Mathf.Deg2Rad,
-						Core.Vessel.orbit.period
-					);
+						                   trueAnomalyAscNode * Mathf.Deg2Rad,
+						                   Core.Vessel.orbit.period
+					                   );
 
 					dTAscNode %= Core.Vessel.orbit.period;
 
@@ -1179,9 +1155,9 @@
 				{
 					double trueAnomalyAscNode = 180d - argumentPeriapsis;
 					double dTDescNode = Core.Vessel.orbit.GetDTforTrueAnomaly(
-						trueAnomalyAscNode * Mathf.Deg2Rad,
-						Core.Vessel.orbit.period
-					);
+						                    trueAnomalyAscNode * Mathf.Deg2Rad,
+						                    Core.Vessel.orbit.period
+					                    );
 
 					dTDescNode %= Core.Vessel.orbit.period;
 
@@ -1198,7 +1174,7 @@
 			new VOID_DoubleValue(
 				"Local Sidereal Longitude",
 				new Func<double>(() => VOID_Tools.FixDegreeDomain(
-						Core.Vessel.longitude + Core.Vessel.orbit.referenceBody.rotationAngle)),
+					Core.Vessel.longitude + Core.Vessel.orbit.referenceBody.rotationAngle)),
 				"°"
 			);
 
@@ -1244,10 +1220,10 @@
 		private static double burnTime(double deltaV, double initialMass, double massFlow, double thrust)
 		{
 			Tools.PostDebugMessage(typeof(VOID_Data), "calculating burnTime from:\n" +
-				"\tdeltaV: {0}\n" +
-				"\tinitialMass: {1}\n" +
-				"\tmassFlow: {2}\n" +
-				"\tthrust: {3}\n",
+			"\tdeltaV: {0}\n" +
+			"\tinitialMass: {1}\n" +
+			"\tmassFlow: {2}\n" +
+			"\tthrust: {3}\n",
 				deltaV,
 				initialMass,
 				massFlow,
@@ -1255,6 +1231,43 @@
 			);
 			return initialMass / massFlow * (1d - Math.Exp(-deltaV * massFlow / thrust));
 		}
+
+		private static double dVfromBurnTime(double time, double initialMass, double massFlow, double thrust)
+		{
+			return -thrust / massFlow * Math.Log(1d - time * massFlow / initialMass);
+		}
+
+		private static double realVesselBurnTime(double deltaV)
+		{
+			if (Core.Stages == null || Core.Stages.Length < 1)
+			{
+				return double.NaN;
+			}
+
+			double burntime = 0d;
+			double dVRemaining = deltaV;
+
+			int stageIdx = Core.Stages.Length - 1;
+
+			while (dVRemaining > double.Epsilon)
+			{
+				if (stageIdx < 1)
+				{
+					return double.PositiveInfinity;
+				}
+
+				Stage stage = Core.Stages[stageIdx];
+
+				double stageDVUsed = Math.Min(stage.deltaV, dVRemaining);
+
+				burntime += burnTime(stageDVUsed, stage.totalMass, stage.MassFlow(), stage.NominalThrust());
+				dVRemaining -= stageDVUsed;
+
+				stageIdx--;
+			}
+
+			return burntime;
+		}
 	}
 }