Merge branch 'master' into dev-EditorTWR
Merge branch 'master' into dev-EditorTWR

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("0.11.0.*")]
+[assembly: AssemblyVersion("0.12.0.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/VOID.csproj
+++ b/VOID.csproj
@@ -9,6 +9,7 @@
     <RootNamespace>VOID</RootNamespace>
     <AssemblyName>VOID</AssemblyName>
     <CodePage>65001</CodePage>
+    <UseMSBuildEngine>False</UseMSBuildEngine>
     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ReleaseVersion>0.11</ReleaseVersion>
@@ -94,6 +95,7 @@
     <Compile Include="VOID_Localization.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="VOID_HUDAdvanced.cs" />
+    <Compile Include="VOID_EditorTWR.cs" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>

--- a/VOID_CBInfoBrowser.cs
+++ b/VOID_CBInfoBrowser.cs
@@ -274,14 +274,14 @@
 
 			GUILayout.Label((body.Radius / 1000).ToString("##,#") + "km", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
-			GUILayout.Label(((Math.Pow((body.Radius), 2) * 4 * Math.PI) / 1000).ToString("0.00e+00") + "km²", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label((((body.Radius * body.Radius) * 4 * Math.PI) / 1000).ToString("0.00e+00") + "km²", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
 			// divide by 1000 to convert m to km
-			GUILayout.Label((((4d / 3) * Math.PI * Math.Pow(body.Radius, 3)) / 1000).ToString("0.00e+00") + "km³", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label((((4d / 3) * Math.PI * (body.Radius * body.Radius * body.Radius)) / 1000).ToString("0.00e+00") + "km³", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
 			GUILayout.Label(body.Mass.ToString("0.00e+00") + "kg", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
-			double p = body.Mass / (Math.Pow(body.Radius, 3) * (4d / 3) * Math.PI);
+			double p = body.Mass / ((body.Radius * body.Radius * body.Radius) * (4d / 3) * Math.PI);
 
 			GUILayout.Label(p.ToString("##,#") + "kg/m³", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
@@ -299,7 +299,7 @@
 
 			GUILayout.Label(num_art_sats.ToString(), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 
-			double g_ASL = (VOID_Core.Constant_G * body.Mass) / Math.Pow(body.Radius, 2);
+			double g_ASL = (VOID_Core.Constant_G * body.Mass) / (body.Radius * body.Radius);
 
 			GUILayout.Label(Tools.MuMech_ToSI(g_ASL) + "m/s²", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
 

--- a/VOID_Core.cs
+++ b/VOID_Core.cs
@@ -81,7 +81,7 @@
 		 * Fields
 		 * */
 		protected string VoidName = "VOID";
-		protected string VoidVersion = "0.11.0";
+		protected string VoidVersion;
 
 		protected bool _factoryReset = false;
 
@@ -480,8 +480,9 @@
 
 			if (this.vessel != null && this.vesselSimActive)
 			{
+				double radius = VOID_Core.Instance.vessel.Radius();
 				SimManager.Gravity = VOID_Core.Instance.vessel.mainBody.gravParameter /
-					Math.Pow(VOID_Core.Instance.vessel.Radius(), 2);
+					(radius * radius);
 				SimManager.TryStartSimulation();
 			}
 			else if (!this.vesselSimActive)
@@ -991,6 +992,10 @@
 		{
 			this._Name = "VOID Core";
 
+			System.Version version = this.GetType().Assembly.GetName().Version;
+
+			this.VoidVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.MajorRevision);
+
 			this._Active.value = true;
 
 			this._skinName = this.defaultSkin;

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -234,7 +234,7 @@
 			line += vessel.flightIntegrator.getExternalTemperature().ToString("F2") + ";";
 			//gravity
 			double r_vessel = vessel.mainBody.Radius + vessel.mainBody.GetAltitude(vessel.findWorldCenterOfMass());
-			double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / Math.Pow(r_vessel, 2);
+			double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / (r_vessel * r_vessel);
 			line += g_vessel.ToString("F3") + ";";
 			//atm density
 			line += (vessel.atmDensity * 1000).ToString("F3") + ";";

file:b/VOID_EditorTWR.cs (new)
--- /dev/null
+++ b/VOID_EditorTWR.cs
@@ -1,1 +1,187 @@
+// VOID © 2014 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 KSP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public class VOID_EditorTWR : VOID_WindowModule, IVOID_EditorModule
+	{
+		private Dictionary<string, double> bodyGeeValues;
+		private List<CelestialBody> sortedBodyList;
+
+		public VOID_EditorTWR() : base()
+		{
+			this._Name = "IP Thrust-to-Weight Ratios";
+		}
+
+		public override void ModuleWindow(int _)
+		{
+			if (Event.current.type != EventType.Layout)
+			{
+				return;
+			}
+
+			Engineer.VesselSimulator.SimManager.RequestSimulation();
+
+			GUILayout.Label(
+				this._Name,
+				VOID_EditorCore.Instance.LabelStyles["center_bold"],
+				GUILayout.ExpandWidth(true));
+
+			if (bodyGeeValues == null)
+			{
+				if (FlightGlobals.Bodies != null && FlightGlobals.Bodies.Count > 0)
+				{
+					this.bodyGeeValues = new Dictionary<string, double>();
+
+					foreach (CelestialBody body in FlightGlobals.Bodies)
+					{
+						this.bodyGeeValues[body.name] = body.GeeASL;
+					}
+
+					this.sortedBodyList = new List<CelestialBody>(FlightGlobals.Bodies);
+					this.sortedBodyList.Sort(new CBListComparer());
+					this.sortedBodyList.Reverse();
+
+					Debug.Log(string.Format("sortedBodyList: {0}", string.Join("\n\t", this.sortedBodyList.Select(b => b.bodyName).ToArray())));
+				}
+				else
+				{
+					GUILayout.BeginVertical();
+					GUILayout.BeginHorizontal();
+					GUILayout.Label("Unavailable.");
+					GUILayout.EndHorizontal();
+					GUILayout.EndVertical();
+				}
+			}
+			else
+			{
+				GUILayout.BeginVertical();
+
+				foreach (CelestialBody body in this.sortedBodyList)
+				{
+					Tools.PostDebugMessage(this, "Doing label for {0}", body.bodyName);
+					GUILayout.BeginHorizontal();
+
+					GUILayout.Label(body.bodyName);
+					GUILayout.FlexibleSpace();
+					GUILayout.Label(
+						(VOID_Data.currThrustWeight.Value / this.bodyGeeValues[body.bodyName]).ToString("0.0##"),
+						GUILayout.ExpandWidth(true)
+					);
+
+					GUILayout.EndHorizontal();
+				}
+
+				GUILayout.EndVertical();
+			}
+
+			GUI.DragWindow();
+		}
+	}
+
+	public class CBListComparer : IComparer<CelestialBody>
+	{
+		public int Compare(CelestialBody bodyA, CelestialBody bodyB)
+		{
+			Tools.PostDebugMessage(this, "got bodyA: {0} & bodyB: {1}", bodyA, bodyB);
+
+			if (bodyA == null && bodyB == null)
+			{
+				Tools.PostDebugMessage(this, "both bodies are null, returning 0");
+				return 0;
+			}
+			if (bodyA == null)
+			{
+				Tools.PostDebugMessage(this, "bodyA is null, returning -1");
+				return -1;
+			}
+			if (bodyB == null)
+			{
+				Tools.PostDebugMessage(this, "bodyB is null, returning 1");
+				return 1;
+			}
+
+			Tools.PostDebugMessage(this, "bodies are not null, carrying on");
+
+			if (object.ReferenceEquals(bodyA, bodyB))
+			{
+				Tools.PostDebugMessage(this, "bodies are equal, returning 0");
+				return 0;
+			}
+
+			Tools.PostDebugMessage(this, "bodies are not equal, carrying on");
+
+			if (bodyA.orbitDriver == null)
+			{
+				Tools.PostDebugMessage(this, "bodyA.orbit is null (bodyA is the sun, returning 1");
+				return 1;
+			}
+			if (bodyB.orbitDriver == null)
+			{
+				Tools.PostDebugMessage(this, "bodyB.orbit is null (bodyB is the sun, returning -1");
+				return -1;
+			}
+
+			Tools.PostDebugMessage(this, "orbits are not null, carrying on");
+
+			if (bodyA.orbit.referenceBody == bodyB.orbit.referenceBody)
+			{
+				Tools.PostDebugMessage(this, "bodies share a parent, comparing SMAs");
+				return -bodyA.orbit.semiMajorAxis.CompareTo(bodyB.orbit.semiMajorAxis);
+			}
+
+			Tools.PostDebugMessage(this, "orbits do not share a parent, carrying on");
+
+			if (bodyA.hasAncestor(bodyB))
+			{
+				Tools.PostDebugMessage(this, "bodyA is a moon or sub-moon of bodyB, returning -1");
+				return -1;
+			}
+			if (bodyB.hasAncestor(bodyA))
+			{
+				Tools.PostDebugMessage(this, "bodyA is a moon or sub-moon of bodyB, returning 1");
+				return 1;
+			}
+
+			Tools.PostDebugMessage(this, "bodies do not have an obvious relationship, searching for one");
+
+			if (VOID_Tools.NearestRelatedParents(ref bodyA, ref bodyB))
+			{
+				Tools.PostDebugMessage(this, "good relation {0} and {1}, comparing", bodyA.bodyName, bodyB.bodyName);
+				return this.Compare(bodyA, bodyB);
+			}
+
+			Tools.PostDebugMessage(this, "bad relation {0} and {1}, giving up", bodyA.bodyName, bodyB.bodyName);
+
+			return 0;
+		}
+	}
+
+	public static partial class VOID_Data
+	{
+		public static readonly VOID_DoubleValue nominalThrustWeight = new VOID_DoubleValue(
+			"Thrust-to-Weight Ratio",
+			delegate()
+		{
+			if (HighLogic.LoadedSceneIsEditor || currThrustWeight.Value == 0d)
+			{
+				return maxThrustWeight.Value;
+			}
+
+			return currThrustWeight.Value;
+		},
+			""
+		);
+	}
+}
+
+

--- a/VOID_HUDAdvanced.cs
+++ b/VOID_HUDAdvanced.cs
@@ -134,7 +134,7 @@
 					VOID_Data.vesselAngularVelocity.ToSIString(2)
 				);
 
-				if (VOID_Data.stageNominalThrust != 0)
+				if (VOID_Data.stageNominalThrust != 0d)
 				{
 					leftHUD.AppendFormat(
 						string.Intern("Thrust Offset: {0}\n"),
@@ -239,7 +239,7 @@
 				this.leftHUDPos.value = GUI.Window(
 					VOID_Core.Instance.windowID,
 					this.leftHUDPos,
-					this.leftHUDWindow,
+					VOID_Tools.GetWindowHandler(this.leftHUDWindow),
 					GUIContent.none,
 					GUIStyle.none
 				);
@@ -249,7 +249,7 @@
 					this.rightHUDPos.value = GUI.Window(
 						VOID_Core.Instance.windowID,
 						this.rightHUDPos,
-						this.rightHUDWindow,
+						VOID_Tools.GetWindowHandler(this.rightHUDWindow),
 						GUIContent.none,
 						GUIStyle.none
 					);
@@ -351,8 +351,13 @@
 				thrustDir /= thrust;
 			}
 
+			Transform vesselTransform = core.vessel.transform;
+
+			thrustPos = vesselTransform.InverseTransformPoint(thrustPos);
+			thrustDir = vesselTransform.InverseTransformDirection(thrustDir);
+
 			Vector3d thrustOffset = VectorTools.PointDistanceToLine(
-				thrustPos, thrustDir.normalized, core.vessel.findWorldCenterOfMass());
+				thrustPos, thrustDir.normalized, core.vessel.findLocalCenterOfMass());
 
 			Tools.PostDebugMessage(typeof(VOID_Data), "vesselThrustOffset:\n" +
 				"\tthrustPos: {0}\n" +
@@ -584,6 +589,11 @@
 			}
 
 			double interval = (node.UT - currentNodeBurnDuration) - Planetarium.GetUniversalTime();
+
+			if (double.IsNaN(interval))
+			{
+				return string.Intern("NaN");
+			}
 
 			int sign = Math.Sign(interval);
 			interval = Math.Abs(interval);

--- a/VOID_Module.cs
+++ b/VOID_Module.cs
@@ -214,42 +214,7 @@
 		protected float defWidth = 250f;
 		protected float defHeight = 50f;
 
-		public virtual void ModuleWindow(int _)
-		{
-//			if (VOID_Core.Instance.updateTimer - this.lastUpdate > VOID_Core.Instance.updatePeriod) {
-//				foreach (var fieldinfo in this.GetType().GetFields(
-//					BindingFlags.Instance |
-//					BindingFlags.NonPublic |
-//					BindingFlags.Public |
-//					BindingFlags.FlattenHierarchy
-//				))
-//				{
-//					object field = null;
-//
-//					try
-//					{
-//						field = fieldinfo.GetValue (this);
-//					}
-//					catch (NullReferenceException) {
-//						Tools.PostDebugMessage(string.Format(
-//							"{0}: caught NullReferenceException, could not get value for field {1}.",
-//							this.GetType().Name,
-//							fieldinfo.Name
-//						));
-//					}
-//
-//					if (field == null) {
-//						continue;
-//					}
-//
-//					if (typeof(IVOID_DataValue).IsAssignableFrom (field.GetType ())) {
-//						(field as IVOID_DataValue).Refresh ();
-//					}
-//				}
-//
-//				this.lastUpdate = VOID_Core.Instance.updateTimer;
-//			}
-		}
+		public abstract void ModuleWindow(int _);
 
 		public override void DrawGUI()
 		{

--- a/VOID_Orbital.cs
+++ b/VOID_Orbital.cs
@@ -52,8 +52,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			int idx = 0;
 
             GUILayout.BeginVertical();
@@ -186,7 +184,7 @@
 			double orbitRadius = VOID_Core.Instance.vessel.mainBody.Radius +
 				VOID_Core.Instance.vessel.mainBody.GetAltitude(VOID_Core.Instance.vessel.findWorldCenterOfMass());
 			return (VOID_Core.Constant_G * VOID_Core.Instance.vessel.mainBody.Mass) /
-				Math.Pow(orbitRadius, 2);
+				(orbitRadius * orbitRadius);
 		},
 			"m/s²"
 		);

--- a/VOID_SurfAtmo.cs
+++ b/VOID_SurfAtmo.cs
@@ -49,8 +49,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			int idx = 0;
 
 			GUILayout.BeginVertical();

--- a/VOID_Tools.cs
+++ b/VOID_Tools.cs
@@ -28,12 +28,71 @@
 
 using KSP;
 using System;
+using System.Collections.Generic;
 using UnityEngine;
 
 namespace VOID
 {
 	public static partial class VOID_Tools
 	{
+		#region CelestialBody Utilities
+		public static bool hasAncestor(this CelestialBody bodyA, CelestialBody bodyB)
+		{
+			if (bodyA == null || bodyB == null)
+			{
+				return false;
+			}
+
+			while (bodyA.orbitDriver != null)
+			{
+				if (bodyA.orbit.referenceBody == bodyB)
+				{
+					return true;
+				}
+
+				bodyA = bodyA.orbit.referenceBody;
+			} 
+
+			return false;
+		}
+
+		public static bool NearestRelatedParents(ref CelestialBody bodyA, ref CelestialBody bodyB)
+		{
+			if (bodyA == null || bodyB == null || bodyA.orbitDriver == null || bodyB.orbitDriver == null)
+			{
+				throw new ArgumentException(string.Concat(
+					"CelestialBody::FindRelatedParents: ",
+					"Neither body may be null, and both bodies must have orbits."
+				));
+			}
+
+			CelestialBody a, b;
+
+			a = bodyA;
+
+			while (bodyA.orbitDriver != null)
+			{
+				b = bodyB;
+
+				while (b.orbitDriver != null)
+				{
+					if (a.orbit.referenceBody == b.orbit.referenceBody)
+					{
+						bodyA = a;
+						bodyB = b;
+						return true;
+					}
+
+					b = b.orbit.referenceBody;
+				}
+
+				a = a.orbit.referenceBody;
+			}
+
+			return false;
+		}
+		#endregion
+
 		#region VESSEL_EXTENSIONS_SCIENCE
 		public static CBAttributeMap.MapAttribute GetBiome(this Vessel vessel)
 		{
@@ -256,6 +315,39 @@
 		}
 		#endregion
 
+		private static Dictionary<int, GUI.WindowFunction> functionCache;
+		public static UnityEngine.GUI.WindowFunction GetWindowHandler(Action<int> func)
+		{
+			if (functionCache == null)
+			{
+				functionCache = new Dictionary<int, GUI.WindowFunction>();
+			}
+
+			int hashCode = func.GetHashCode();
+
+			if (!functionCache.ContainsKey(hashCode))
+			{
+				functionCache[hashCode] = delegate (int id)
+				{
+					try
+					{
+						func(id);
+					}
+					catch (ArgumentException ex)
+					{
+						Debug.LogWarning(
+							string.Format("[{0}]: ArgumentException caught during window call.", func.Target.GetType().Name)
+						);
+						#if DEBUG
+						Debug.LogException(ex);
+						#endif
+					}
+				};
+			}
+
+			return functionCache[hashCode];
+		}
+
 		/// <summary>
 		/// Converts the interval given in seconds to a human-friendly
 		/// time period in [years], [days], hours, minutes, and seconds.
@@ -345,8 +437,14 @@
 		//transfer angles
 		public static double Nivvy_CalcTransferPhaseAngle(double r_current, double r_target, double grav_param)
 		{
-			double T_target = (2 * Math.PI) * Math.Sqrt(Math.Pow((r_target / 1000), 3) / (grav_param / 1000000000));
-			double T_transfer = (2 * Math.PI) * Math.Sqrt(Math.Pow((((r_target / 1000) + (r_current / 1000)) / 2), 3) / (grav_param / 1000000000));
+			r_target /= 1000;
+			r_current /= 1000;
+			grav_param /= 1000000000;
+
+			double midpoint = (r_target + r_current) / 2;
+
+			double T_target = (2 * Math.PI) * Math.Sqrt((r_target * r_target * r_target) / grav_param);
+			double T_transfer = (2 * Math.PI) * Math.Sqrt((midpoint * midpoint * midpoint) / grav_param);
 			return 360 * (0.5 - (T_transfer / (2 * T_target)));
 		}
 
@@ -385,7 +483,7 @@
             r = r1*r2
             return math.sqrt(bar / r)
             */
-			double foo = r2 * Math.Pow(v, 2) - 2 * mu;
+			double foo = r2 * (v * v) - 2 * mu;
 			double bar = r1 * foo + (2 * r2 * mu);
 			double r = r1 * r2;
 			return Math.Sqrt(bar / r);
@@ -407,9 +505,9 @@
             return 180 - degrees
             */
 			double epsilon, h, ee, theta, degrees;
-			epsilon = (Math.Pow(v, 2) / 2) - (mu / r);
+			epsilon = ((v * v) / 2) - (mu / r);
 			h = r * v * Math.Sin(angle);
-			ee = Math.Sqrt(1 + ((2 * epsilon * Math.Pow(h, 2)) / Math.Pow(mu, 2)));
+			ee = Math.Sqrt(1 + ((2 * epsilon * (h * h)) / (mu * mu)));
 			theta = Math.Acos(1.0 / ee);
 			degrees = theta * (180.0 / Math.PI);
 			return 180 - degrees;

--- a/VOID_VesselInfo.cs
+++ b/VOID_VesselInfo.cs
@@ -48,8 +48,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
 			{
 				SimManager.RequestSimulation();
@@ -68,7 +66,7 @@
 
 			VOID_Data.totalMass.DoGUIHorizontal ("F3");
 
-			VOID_Data.resourceMass.DoGUIHorizontal ("F3");
+			VOID_Data.comboResourceMass.DoGUIHorizontal ();
 
 			VOID_Data.stageDeltaV.DoGUIHorizontal (3, false);
 
@@ -127,9 +125,31 @@
 					return double.NaN;
 				}
 
-				return SimManager.LastStage.totalBaseMass;
+				return SimManager.LastStage.totalMass - SimManager.LastStage.totalBaseMass;
 			},
 			"tons"
+		);
+
+		public static readonly VOID_DoubleValue stageResourceMass = new VOID_DoubleValue(
+			"Resource Mass (Current Stage)",
+			delegate()
+			{
+				if (SimManager.LastStage == null)
+				{
+					return double.NaN;
+				}
+
+				return SimManager.LastStage.mass - SimManager.LastStage.baseMass;
+			},
+			"tons"
+		);
+
+		public static readonly VOID_StrValue comboResourceMass = new VOID_StrValue(
+			"Resource Mass (curr / total)",
+			delegate()
+		{
+			return string.Format("{0} / {1}", stageResourceMass.ValueUnitString(), resourceMass.ValueUnitString());
+		}
 		);
 
 		public static readonly VOID_DoubleValue stageDeltaV = new VOID_DoubleValue(
@@ -231,7 +251,7 @@
 				double maxThrust = SimManager.LastStage.thrust;
 				double mass = SimManager.LastStage.totalMass;
 				double gravity = (VOID_Core.Constant_G * VOID_Core.Instance.vessel.mainBody.Mass) /
-					Math.Pow(VOID_Core.Instance.vessel.mainBody.Radius, 2);
+				(VOID_Core.Instance.vessel.mainBody.Radius * VOID_Core.Instance.vessel.mainBody.Radius);
 				double weight = mass * gravity;
 
 				return maxThrust / weight;