diff --git a/Source/ROLib/DebugTools/DebugDrawer.cs b/Source/ROLib/DebugTools/DebugDrawer.cs
new file mode 100644
index 0000000..a34462b
--- /dev/null
+++ b/Source/ROLib/DebugTools/DebugDrawer.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Conditional = System.Diagnostics.ConditionalAttribute;
+
+namespace ROLib
+{
+ ///
+ /// Borrowed from Sarbian's DebugStuff
+ ///
+ [KSPAddon(KSPAddon.Startup.Instantly, true)]
+ class DebugDrawer : MonoBehaviour
+ {
+ private static readonly List lines = new List();
+ private static readonly List points = new List();
+ private static readonly List transforms = new List();
+ public Material lineMaterial;
+
+ private struct Line
+ {
+ public readonly Vector3 start;
+ public readonly Vector3 end;
+ public readonly Color color;
+
+ public Line(Vector3 start, Vector3 end, Color color)
+ {
+ this.start = start;
+ this.end = end;
+ this.color = color;
+ }
+ }
+
+ private struct Point
+ {
+ public readonly Vector3 pos;
+ public readonly Color color;
+
+ public Point(Vector3 pos, Color color)
+ {
+ this.pos = pos;
+ this.color = color;
+ }
+ }
+
+ private struct Trans
+ {
+ public readonly Vector3 pos;
+ public readonly Vector3 up;
+ public readonly Vector3 right;
+ public readonly Vector3 forward;
+
+ public Trans(Vector3 pos, Vector3 up, Vector3 right, Vector3 forward)
+ {
+ this.pos = pos;
+ this.up = up;
+ this.right = right;
+ this.forward = forward;
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void DebugLine(Vector3 start, Vector3 end, Color col)
+ {
+ lines.Add(new Line(start, end, col));
+ }
+
+ [Conditional("DEBUG")]
+ public static void DebugPoint(Vector3 start, Color col)
+ {
+ points.Add(new Point(start, col));
+ }
+
+ [Conditional("DEBUG")]
+ public static void DebugTransforms(Transform t)
+ {
+ transforms.Add(new Trans(t.position, t.up, t.right, t.forward));
+ }
+
+ [Conditional("DEBUG")]
+ private void Start()
+ {
+ DontDestroyOnLoad(this);
+ if (!lineMaterial)
+ {
+ Shader shader = Shader.Find("Hidden/Internal-Colored");
+ lineMaterial = new Material(shader);
+ lineMaterial.hideFlags = HideFlags.HideAndDontSave;
+ lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
+ lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
+ lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
+ lineMaterial.SetInt("_ZWrite", 0);
+ lineMaterial.SetInt("_ZWrite", (int)UnityEngine.Rendering.CompareFunction.Always);
+ }
+ StartCoroutine("EndOfFrameDrawing");
+ }
+
+ private IEnumerator EndOfFrameDrawing()
+ {
+ Debug.Log("DebugDrawer starting");
+ while (true)
+ {
+ yield return new WaitForEndOfFrame();
+
+ Camera cam = GetActiveCam();
+
+ if (cam == null) continue;
+
+ try
+ {
+ transform.position = Vector3.zero;
+
+ GL.PushMatrix();
+ lineMaterial.SetPass(0);
+
+ // In a modern Unity we would use cam.projectionMatrix.decomposeProjection to get the decomposed matrix
+ // and Matrix4x4.Frustum(FrustumPlanes frustumPlanes) to get a new one
+
+ // Change the far clip plane of the projection matrix
+ Matrix4x4 projectionMatrix = Matrix4x4.Perspective(cam.fieldOfView, cam.aspect, cam.nearClipPlane, float.MaxValue);
+ GL.LoadProjectionMatrix(projectionMatrix);
+ GL.MultMatrix(cam.worldToCameraMatrix);
+ //GL.Viewport(new Rect(0, 0, Screen.width, Screen.height));
+
+ GL.Begin(GL.LINES);
+
+ for (int i = 0; i < lines.Count; i++)
+ {
+ Line line = lines[i];
+ DrawLine(line.start, line.end, line.color);
+ }
+
+ for (int i = 0; i < points.Count; i++)
+ {
+ Point point = points[i];
+ DrawPoint(point.pos, point.color);
+ }
+
+ for (int i = 0; i < transforms.Count; i++)
+ {
+ Trans t = transforms[i];
+ DrawTransform(t.pos, t.up, t.right, t.forward);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.Log("EndOfFrameDrawing Exception" + e);
+ }
+ finally
+ {
+ GL.End();
+ GL.PopMatrix();
+
+ lines.Clear();
+ points.Clear();
+ transforms.Clear();
+ }
+ }
+ }
+
+ private static Camera GetActiveCam()
+ {
+ if (!HighLogic.fetch)
+ return Camera.main;
+
+ if (HighLogic.LoadedSceneIsEditor && EditorLogic.fetch)
+ return EditorLogic.fetch.editorCamera;
+
+ if (HighLogic.LoadedSceneIsFlight && PlanetariumCamera.fetch && FlightCamera.fetch)
+ return MapView.MapIsEnabled ? PlanetariumCamera.Camera : FlightCamera.fetch.mainCamera;
+
+ return Camera.main;
+ }
+
+ private static void DrawLine(Vector3 origin, Vector3 destination, Color color)
+ {
+ GL.Color(color);
+ GL.Vertex(origin);
+ GL.Vertex(destination);
+ }
+
+ private static void DrawRay(Vector3 origin, Vector3 direction, Color color)
+ {
+ GL.Color(color);
+ GL.Vertex(origin);
+ GL.Vertex(origin + direction);
+ }
+
+ private static void DrawTransform(Vector3 position, Vector3 up, Vector3 right, Vector3 forward, float scale = 1.0f)
+ {
+ DrawRay(position, up * scale, Color.green);
+ DrawRay(position, right * scale, Color.red);
+ DrawRay(position, forward * scale, Color.blue);
+ }
+
+ private static void DrawPoint(Vector3 position, Color color, float scale = 1.0f)
+ {
+ DrawRay(position + Vector3.up * (scale * 0.5f), -Vector3.up * scale, color);
+ DrawRay(position + Vector3.right * (scale * 0.5f), -Vector3.right * scale, color);
+ DrawRay(position + Vector3.forward * (scale * 0.5f), -Vector3.forward * scale, color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ROLib/DebugTools/DrawTools.cs b/Source/ROLib/DebugTools/DrawTools.cs
new file mode 100644
index 0000000..c1ef628
--- /dev/null
+++ b/Source/ROLib/DebugTools/DrawTools.cs
@@ -0,0 +1,479 @@
+using System.Reflection;
+using UnityEngine;
+
+namespace ROLib
+{
+ ///
+ /// Borrowed from Sarbian's DebugStuff
+ ///
+ public static class DrawTools
+ {
+ private static Material _material;
+
+ private static int glDepth = 0;
+
+ private static Material material
+ {
+ get
+ {
+ if (_material == null) _material = new Material(Shader.Find("Hidden/Internal-Colored"));
+ return _material;
+ }
+ }
+
+ // Ok that's cheap but I did not want to add a bunch
+ // of try catch to make sure the GL calls ends.
+ public static void NewFrame()
+ {
+ if (glDepth > 0)
+ MonoBehaviour.print(glDepth);
+ glDepth = 0;
+ }
+
+ private static void GLStart()
+ {
+ if (glDepth == 0)
+ {
+ GL.PushMatrix();
+ material.SetPass(0);
+ GL.LoadPixelMatrix();
+ GL.Begin(GL.LINES);
+ }
+ glDepth++;
+ }
+
+ private static void GLEnd()
+ {
+ glDepth--;
+
+ if (glDepth == 0)
+ {
+ GL.End();
+ GL.PopMatrix();
+ }
+ }
+
+
+ private static Camera GetActiveCam()
+ {
+ Camera cam;
+ if (HighLogic.LoadedSceneIsEditor)
+ cam = EditorLogic.fetch.editorCamera;
+ else if (HighLogic.LoadedSceneIsFlight)
+ cam = MapView.MapIsEnabled ? PlanetariumCamera.Camera : FlightCamera.fetch.mainCamera;
+ else
+ cam = Camera.main;
+ return cam;
+ }
+
+ private static Vector3 Tangent(Vector3 normal)
+ {
+ Vector3 tangent = Vector3.Cross(normal, Vector3.right);
+ if (tangent.sqrMagnitude <= float.Epsilon)
+ tangent = Vector3.Cross(normal, Vector3.up);
+ return tangent;
+ }
+
+ private static void DrawLine(Vector3 origin, Vector3 destination, Color color)
+ {
+ Camera cam = GetActiveCam();
+
+ Vector3 screenPoint1 = cam.WorldToScreenPoint(origin);
+ Vector3 screenPoint2 = cam.WorldToScreenPoint(destination);
+
+ GL.Color(color);
+ GL.Vertex3(screenPoint1.x, screenPoint1.y, 0);
+ GL.Vertex3(screenPoint2.x, screenPoint2.y, 0);
+ }
+
+ private static void DrawRay(Vector3 origin, Vector3 direction, Color color)
+ {
+ Camera cam = GetActiveCam();
+
+ Vector3 screenPoint1 = cam.WorldToScreenPoint(origin);
+ Vector3 screenPoint2 = cam.WorldToScreenPoint(origin + direction);
+
+ GL.Color(color);
+ GL.Vertex3(screenPoint1.x, screenPoint1.y, 0);
+ GL.Vertex3(screenPoint2.x, screenPoint2.y, 0);
+ }
+
+ public static void DrawTransform(Transform t, float scale = 1.0f)
+ {
+ GLStart();
+
+ DrawRay(t.position, t.up * scale, Color.green);
+ DrawRay(t.position, t.right * scale, Color.red);
+ DrawRay(t.position, t.forward * scale, Color.blue);
+
+ GLEnd();
+ }
+ public static void DrawPoint(Vector3 position, Color color, float scale = 1.0f)
+ {
+ GLStart();
+ GL.Color(color);
+
+ DrawRay(position + Vector3.up * (scale * 0.5f), -Vector3.up * scale, color);
+ DrawRay(position + Vector3.right * (scale * 0.5f), -Vector3.right * scale, color);
+ DrawRay(position + Vector3.forward * (scale * 0.5f), -Vector3.forward * scale, color);
+
+ GLEnd();
+ }
+
+ public static void DrawArrow(Vector3 position, Vector3 direction, Color color)
+ {
+ GLStart();
+ GL.Color(color);
+
+ DrawRay(position, direction, color);
+
+ GLEnd();
+
+ DrawCone(position + direction, -direction * 0.333f, color, 15);
+ }
+
+ public static void DrawCone(Vector3 position, Vector3 direction, Color color, float angle = 45)
+ {
+ float length = direction.magnitude;
+
+ Vector3 forward = direction;
+ Vector3 up = Tangent(forward).normalized;
+ Vector3 right = Vector3.Cross(forward, up).normalized;
+
+ float radius = length * Mathf.Tan(Mathf.Deg2Rad * angle);
+
+ GLStart();
+ GL.Color(color);
+
+ DrawRay(position, direction + radius * up, color);
+ DrawRay(position, direction - radius * up, color);
+ DrawRay(position, direction + radius * right, color);
+ DrawRay(position, direction - radius * right, color);
+
+ GLEnd();
+
+ DrawCircle(position + forward, direction, color, radius);
+ DrawCircle(position + forward * 0.5f, direction, color, radius * 0.5f);
+ }
+
+ public static void DrawLocalMesh(Transform transform, Mesh mesh, Color color)
+ {
+ if (mesh == null || mesh.triangles == null || mesh.vertices == null)
+ return;
+ int[] triangles = mesh.triangles;
+ Vector3[] vertices = mesh.vertices;
+ GLStart();
+ GL.Color(color);
+
+ for (int i = 0; i < triangles.Length; i += 3)
+ {
+ Vector3 p1 = transform.TransformPoint(vertices[triangles[i]]);
+ Vector3 p2 = transform.TransformPoint(vertices[triangles[i + 1]]);
+ Vector3 p3 = transform.TransformPoint(vertices[triangles[i + 2]]);
+ DrawLine(p1, p2, color);
+ DrawLine(p2, p3, color);
+ DrawLine(p3, p1, color);
+ }
+
+ GLEnd();
+ }
+
+ public static void DrawBounds(Bounds bounds, Color color)
+ {
+ Vector3 center = bounds.center;
+
+ float x = bounds.extents.x;
+ float y = bounds.extents.y;
+ float z = bounds.extents.z;
+
+ Vector3 topa = center + new Vector3(x, y, z);
+ Vector3 topb = center + new Vector3(x, y, -z);
+ Vector3 topc = center + new Vector3(-x, y, z);
+ Vector3 topd = center + new Vector3(-x, y, -z);
+
+ Vector3 bota = center + new Vector3(x, -y, z);
+ Vector3 botb = center + new Vector3(x, -y, -z);
+ Vector3 botc = center + new Vector3(-x, -y, z);
+ Vector3 botd = center + new Vector3(-x, -y, -z);
+
+ GLStart();
+ GL.Color(color);
+
+ // Top
+ DrawLine(topa, topc, color);
+ DrawLine(topa, topb, color);
+ DrawLine(topc, topd, color);
+ DrawLine(topb, topd, color);
+
+ // Sides
+ DrawLine(topa, bota, color);
+ DrawLine(topb, botb, color);
+ DrawLine(topc, botc, color);
+ DrawLine(topd, botd, color);
+
+ // Bottom
+ DrawLine(bota, botc, color);
+ DrawLine(bota, botb, color);
+ DrawLine(botc, botd, color);
+ DrawLine(botd, botb, color);
+
+ GLEnd();
+ }
+
+ public static void DrawRectTransform(RectTransform rectTransform, Canvas canvas, Color color)
+ {
+
+ Vector3[] corners = new Vector3[4];
+ Vector3[] screenCorners = new Vector3[2];
+
+ rectTransform.GetWorldCorners(corners);
+
+ if (canvas.renderMode == RenderMode.ScreenSpaceCamera || canvas.renderMode == RenderMode.WorldSpace)
+ {
+ screenCorners[0] = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, corners[1]);
+ screenCorners[1] = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, corners[3]);
+ }
+ else
+ {
+ screenCorners[0] = RectTransformUtility.WorldToScreenPoint(null, corners[1]);
+ screenCorners[1] = RectTransformUtility.WorldToScreenPoint(null, corners[3]);
+ }
+
+ GLStart();
+ GL.Color(color);
+
+ GL.Vertex3(screenCorners[0].x, screenCorners[0].y, 0);
+ GL.Vertex3(screenCorners[0].x, screenCorners[1].y, 0);
+
+ GL.Vertex3(screenCorners[0].x, screenCorners[1].y, 0);
+ GL.Vertex3(screenCorners[1].x, screenCorners[1].y, 0);
+
+ GL.Vertex3(screenCorners[1].x, screenCorners[1].y, 0);
+ GL.Vertex3(screenCorners[1].x, screenCorners[0].y, 0);
+
+ GL.Vertex3(screenCorners[1].x, screenCorners[0].y, 0);
+ GL.Vertex3(screenCorners[0].x, screenCorners[0].y, 0);
+
+ GLEnd();
+ }
+
+
+ public static void DrawLocalCube(Transform transform, Vector3 size, Color color, Vector3 center = default(Vector3))
+ {
+ Vector3 topa = transform.TransformPoint(center + new Vector3(-size.x, size.y, -size.z) * 0.5f);
+ Vector3 topb = transform.TransformPoint(center + new Vector3(size.x, size.y, -size.z) * 0.5f);
+
+ Vector3 topc = transform.TransformPoint(center + new Vector3(size.x, size.y, size.z) * 0.5f);
+ Vector3 topd = transform.TransformPoint(center + new Vector3(-size.x, size.y, size.z) * 0.5f);
+
+ Vector3 bota = transform.TransformPoint(center + new Vector3(-size.x, -size.y, -size.z) * 0.5f);
+ Vector3 botb = transform.TransformPoint(center + new Vector3(size.x, -size.y, -size.z) * 0.5f);
+
+ Vector3 botc = transform.TransformPoint(center + new Vector3(size.x, -size.y, size.z) * 0.5f);
+ Vector3 botd = transform.TransformPoint(center + new Vector3(-size.x, -size.y, size.z) * 0.5f);
+
+ GLStart();
+ GL.Color(color);
+
+ //top
+ DrawLine(topa, topb, color);
+ DrawLine(topb, topc, color);
+ DrawLine(topc, topd, color);
+ DrawLine(topd, topa, color);
+
+ //Sides
+ DrawLine(topa, bota, color);
+ DrawLine(topb, botb, color);
+ DrawLine(topc, botc, color);
+ DrawLine(topd, botd, color);
+
+ //Bottom
+ DrawLine(bota, botb, color);
+ DrawLine(botb, botc, color);
+ DrawLine(botc, botd, color);
+ DrawLine(botd, bota, color);
+
+ GLEnd();
+ }
+
+ public static void DrawCapsule(Vector3 start, Vector3 end, Color color, float radius = 1)
+ {
+ int segments = 18;
+ float segmentsInv = 1f / segments;
+
+ Vector3 up = (end - start).normalized * radius;
+ Vector3 forward = Tangent(up).normalized * radius;
+ Vector3 right = Vector3.Cross(up, forward).normalized * radius;
+
+ float height = (start - end).magnitude;
+ float sideLength = Mathf.Max(0, height * 0.5f - radius);
+ Vector3 middle = (end + start) * 0.5f;
+
+ start = middle + (start - middle).normalized * sideLength;
+ end = middle + (end - middle).normalized * sideLength;
+
+ //Radial circles
+ DrawCircle(start, up, color, radius);
+ DrawCircle(end, -up, color, radius);
+
+ GLStart();
+ GL.Color(color);
+
+ //Side lines
+ DrawLine(start + right, end + right, color);
+ DrawLine(start - right, end - right, color);
+
+ DrawLine(start + forward, end + forward, color);
+ DrawLine(start - forward, end - forward, color);
+
+ for (int i = 1; i <= segments; i++)
+ {
+ float stepFwd = i * segmentsInv;
+ float stepBck = (i - 1) * segmentsInv;
+ //Start endcap
+ DrawLine(Vector3.Slerp(right, -up, stepFwd) + start, Vector3.Slerp(right, -up, stepBck) + start, color);
+ DrawLine(Vector3.Slerp(-right, -up, stepFwd) + start, Vector3.Slerp(-right, -up, stepBck) + start, color);
+ DrawLine(Vector3.Slerp(forward, -up, stepFwd) + start, Vector3.Slerp(forward, -up, stepBck) + start, color);
+ DrawLine(Vector3.Slerp(-forward, -up, stepFwd) + start, Vector3.Slerp(-forward, -up, stepBck) + start, color);
+
+ //End endcap
+ DrawLine(Vector3.Slerp(right, up, stepFwd) + end, Vector3.Slerp(right, up, stepBck) + end, color);
+ DrawLine(Vector3.Slerp(-right, up, stepFwd) + end, Vector3.Slerp(-right, up, stepBck) + end, color);
+ DrawLine(Vector3.Slerp(forward, up, stepFwd) + end, Vector3.Slerp(forward, up, stepBck) + end, color);
+ DrawLine(Vector3.Slerp(-forward, up, stepFwd) + end, Vector3.Slerp(-forward, up, stepBck) + end, color);
+ }
+
+ GLEnd();
+ }
+
+ public static void DrawCircle(Vector3 position, Vector3 up, Color color, float radius = 1.0f)
+ {
+ int segments = 36;
+ float step = Mathf.Deg2Rad * 360f / segments;
+
+ Vector3 upNormal = up.normalized * radius;
+ Vector3 forwardNormal = Tangent(upNormal).normalized * radius;
+ Vector3 rightNormal = Vector3.Cross(upNormal, forwardNormal).normalized * radius;
+
+ Matrix4x4 matrix = new Matrix4x4();
+
+ matrix[0] = rightNormal.x;
+ matrix[1] = rightNormal.y;
+ matrix[2] = rightNormal.z;
+
+ matrix[4] = upNormal.x;
+ matrix[5] = upNormal.y;
+ matrix[6] = upNormal.z;
+
+ matrix[8] = forwardNormal.x;
+ matrix[9] = forwardNormal.y;
+ matrix[10] = forwardNormal.z;
+
+ Vector3 lastPoint = position + matrix.MultiplyPoint3x4(Vector3.right);
+
+ GLStart();
+ GL.Color(color);
+
+ for (int i = 0; i <= segments; i++)
+ {
+ Vector3 nextPoint;
+ var angle = i * step;
+ nextPoint.x = Mathf.Cos(angle);
+ nextPoint.z = Mathf.Sin(angle);
+ nextPoint.y = 0;
+
+ nextPoint = position + matrix.MultiplyPoint3x4(nextPoint);
+
+ DrawLine(lastPoint, nextPoint, color);
+ lastPoint = nextPoint;
+ }
+ GLEnd();
+ }
+
+ public static void DrawSphere(Vector3 position, Color color, float radius = 1.0f)
+ {
+ int segments = 36;
+ float step = Mathf.Deg2Rad * 360f / segments;
+
+ Vector3 x = new Vector3(position.x, position.y, position.z + radius);
+ Vector3 y = new Vector3(position.x + radius, position.y, position.z);
+ Vector3 z = new Vector3(position.x + radius, position.y, position.z);
+
+ GLStart();
+ GL.Color(color);
+
+ for (int i = 1; i <= segments; i++)
+ {
+ float angle = step * i;
+ Vector3 nextX = new Vector3(position.x, position.y + radius * Mathf.Sin(angle), position.z + radius * Mathf.Cos(angle));
+ Vector3 nextY = new Vector3(position.x + radius * Mathf.Cos(angle), position.y, position.z + radius * Mathf.Sin(angle));
+ Vector3 nextZ = new Vector3(position.x + radius * Mathf.Cos(angle), position.y + radius * Mathf.Sin(angle), position.z);
+
+ DrawLine(x, nextX, color);
+ DrawLine(y, nextY, color);
+ DrawLine(z, nextZ, color);
+
+ x = nextX;
+ y = nextY;
+ z = nextZ;
+ }
+ GLEnd();
+ }
+
+ public static void DrawCylinder(Vector3 start, Vector3 end, Color color, float radius = 1)
+ {
+ Vector3 up = (end - start).normalized * radius;
+ Vector3 forward = Tangent(up);
+ Vector3 right = Vector3.Cross(up, forward).normalized * radius;
+
+ //Radial circles
+ DrawCircle(start, up, color, radius);
+ DrawCircle(end, -up, color, radius);
+ DrawCircle((start + end) * 0.5f, up, color, radius);
+
+ GLStart();
+ GL.Color(color);
+
+ //Sides
+ DrawLine(start + right, end + right, color);
+ DrawLine(start - right, end - right, color);
+
+ DrawLine(start + forward, end + forward, color);
+ DrawLine(start - forward, end - forward, color);
+
+ //Top
+ DrawLine(start - right, start + right, color);
+ DrawLine(start - forward, start + forward, color);
+
+ //Bottom
+ DrawLine(end - right, end + right, color);
+ DrawLine(end - forward, end + forward, color);
+ GLEnd();
+ }
+
+ private static FieldInfo jointMode;
+
+ public static void DrawJoint(PartJoint joint)
+ {
+ if (joint == null)
+ return;
+
+ if (joint.Host == null || joint.Child == null || joint.Parent == null)
+ return;
+
+ Color col = joint.Host == joint.Child ? Color.blue : Color.red;
+
+ if (jointMode == null)
+ jointMode = typeof(PartJoint).GetField("mode", BindingFlags.NonPublic | BindingFlags.Instance);
+
+ float node = ((AttachModes)jointMode.GetValue(joint)) == AttachModes.STACK ? 1 : 0.6f;
+
+ GLStart();
+ GL.Color(col * node);
+
+ DrawLine(joint.Child.transform.position, joint.Parent.transform.position, col * node);
+
+ GLEnd();
+ }
+ }
+}
diff --git a/Source/ROLib/Modules/ModuleROTank.cs b/Source/ROLib/Modules/ModuleROTank.cs
index 045bdc0..aee045e 100644
--- a/Source/ROLib/Modules/ModuleROTank.cs
+++ b/Source/ROLib/Modules/ModuleROTank.cs
@@ -11,7 +11,7 @@ namespace ROLib
/// PartModule that manages multiple models/meshes and accompanying features for model switching - resources, modules, textures, recoloring.
/// Includes 3 stack-mounted modules. All modules support model-switching, texture-switching, recoloring.
///
- public class ModuleROTank : PartModule, IPartCostModifier, IPartMassModifier, IRecolorable, IContainerVolumeContributor
+ public partial class ModuleROTank : PartModule, IPartCostModifier, IPartMassModifier, IRecolorable, IContainerVolumeContributor
{
private const string GroupDisplayName = "RO-Tanks";
private const string GroupName = "ModuleROTank";
@@ -282,6 +282,7 @@ internal void ModelChangedHandler(bool pushNodes)
UpdateMass();
if (scaleCost)
UpdateCost();
+ SetupKorolevCross();
ROLStockInterop.UpdatePartHighlighting(part);
//if (HighLogic.LoadedSceneIsEditor)
//GameEvents.onEditorShipModified.Fire(EditorLogic.fetch.ship);
@@ -318,12 +319,17 @@ public override void OnStart(StartState state)
Initialize();
ModelChangedHandler(false);
InitializeUI();
+ SetupKorolevCross();
+ GameEvents.onVesselGoOffRails.Add(OnVesselOffRails);
+ GameEvents.onVesselGoOnRails.Add(OnVesselOnRails);
}
public void OnDestroy()
{
//GameEvents.onEditorShipModified.Remove(OnEditorVesselModified);
GameEvents.onPartActionUIDismiss.Remove(OnPawClose);
+ GameEvents.onVesselGoOffRails.Remove(OnVesselOffRails);
+ GameEvents.onVesselGoOnRails.Remove(OnVesselOnRails);
}
//private void OnEditorVesselModified(ShipConstruct ship) => UpdateAvailableVariants();
@@ -576,6 +582,8 @@ public void InitializeUI()
Fields[nameof(currentCoreTexture)].uiControlEditor.onFieldChanged = coreModule.textureSetSelected;
Fields[nameof(currentMountTexture)].uiControlEditor.onFieldChanged = mountModule.textureSetSelected;
+ BindKorolevCrossUI();
+
if (HighLogic.LoadedSceneIsEditor)
{
//GameEvents.onEditorShipModified.Add(OnEditorVesselModified);
diff --git a/Source/ROLib/Modules/ModuleROTank_KorolevCross.cs b/Source/ROLib/Modules/ModuleROTank_KorolevCross.cs
new file mode 100644
index 0000000..6ea4b78
--- /dev/null
+++ b/Source/ROLib/Modules/ModuleROTank_KorolevCross.cs
@@ -0,0 +1,208 @@
+using System.Diagnostics;
+using UnityEngine;
+
+namespace ROLib
+{
+ public partial class ModuleROTank
+ {
+ ///
+ /// Whether the Korolev cross feature can be enabled on this part.
+ ///
+ [KSPField]
+ public bool supportsKorolevCross = false;
+
+ ///
+ /// Whether to try to replicate a Korolev cross on decouple.
+ ///
+ [KSPField(isPersistant = true, guiActiveEditor = true, guiName = "Korolev cross", groupName = GroupName),
+ UI_Toggle(suppressEditorShipModified = true)]
+ public bool korolevCross = false;
+
+ [KSPField(isPersistant = true)]
+ public bool isFinished = false;
+
+ ///
+ /// Determines the force of booster tank getting pushed away from core. Dry mass multiplied by this number will give kN of force.
+ ///
+ [KSPField]
+ public float force1Scale = 20;
+
+ ///
+ /// Determines the force at the tip of the booster tank that makes it spin. Dry mass multiplied by this number will give kN of force.
+ ///
+ [KSPField]
+ public float force2Scale = 8;
+
+ ///
+ /// Between -1 and 1, 0 is no offset. Positive number moves the tip of the tank closer to core.
+ ///
+ [KSPField]
+ public float topHorizOffsetFraction = 0.82f;
+
+ [KSPField]
+ public float force1Duration = 0.5f;
+ [KSPField]
+ public float force2Start = 1f;
+ [KSPField]
+ public float force2Duration = 0.5f;
+
+ private HingeJoint joint;
+ private ModuleDecouple decoupler;
+ private Vector3 tipOffsetToRoot;
+ private float fixedTimeSinceStart = 0;
+
+ private void ToggleKorolevCross()
+ {
+ CleanupJoint();
+
+ if (korolevCross)
+ {
+ CreateJoint();
+ }
+ }
+
+ private void SetupKorolevCross()
+ {
+ if (!korolevCross || isFinished) return;
+
+ if (decoupler == null)
+ {
+ ModuleDecouple[] decouplers = part.GetComponents();
+ decoupler = decouplers[0];
+ decoupler.ejectionForcePercent = 0; // Disable builtin decoupler and only add force through code
+ }
+
+ float tankTopPos = coreModule.moduleHeight - coreModule.ModuleCenter;
+ float tankRadius = coreModule.moduleDiameter / 2;
+ tipOffsetToRoot = Vector3.up * tankTopPos + Vector3.back * topHorizOffsetFraction * tankRadius * topHorizOffsetFraction;
+
+ if (HighLogic.LoadedSceneIsFlight)
+ {
+ CleanupJoint();
+ CreateJoint();
+ }
+ }
+
+ private void BindKorolevCrossUI()
+ {
+ if (!supportsKorolevCross)
+ {
+ Fields[nameof(korolevCross)].guiActiveEditor = false;
+ }
+ else
+ {
+ Fields[nameof(korolevCross)].uiControlEditor.onFieldChanged =
+ Fields[nameof(korolevCross)].uiControlEditor.onSymmetryFieldChanged = (a, b) =>
+ {
+ ToggleKorolevCross();
+ };
+ }
+ }
+
+ private void OnVesselOnRails(Vessel data)
+ {
+ if (data != vessel) return;
+
+ CleanupJoint();
+ }
+
+ private void OnVesselOffRails(Vessel data)
+ {
+ if (data != vessel) return;
+
+ SetupKorolevCross();
+ }
+
+ private void CleanupJoint()
+ {
+ if (joint != null)
+ {
+ Destroy(joint);
+ joint = null;
+ }
+ }
+
+ private void CreateJoint()
+ {
+ if (part.parent == null || isFinished) return;
+
+ joint = part.gameObject.AddComponent();
+ joint.connectedBody = part.parent.Rigidbody;
+ joint.anchor = tipOffsetToRoot;
+ joint.axis = Vector3.right;
+ joint.breakForce = float.PositiveInfinity;
+ joint.breakTorque = float.PositiveInfinity;
+ }
+
+ public override void OnFixedUpdate()
+ {
+ if (!isFinished && decoupler.isDecoupled)
+ {
+ float mass = GetPartDryMassRecursive(part);
+
+ if (fixedTimeSinceStart < force1Duration)
+ {
+ float remainingTime = force1Duration - fixedTimeSinceStart;
+ float dtScale = remainingTime < Time.fixedDeltaTime ? remainingTime / Time.fixedDeltaTime : 1;
+ float forceMagnitude = dtScale * force1Scale * mass;
+ Vector3 forceDir = part.transform.rotation * Vector3.forward;
+ part.AddForce(forceMagnitude * forceDir);
+ }
+
+ if (fixedTimeSinceStart >= force2Start)
+ {
+ if (joint != null)
+ Destroy(joint);
+
+ float remainingTime = force2Start + force2Duration - fixedTimeSinceStart;
+ float dtScale = remainingTime < Time.fixedDeltaTime ? remainingTime / Time.fixedDeltaTime : 1;
+ float forceMagnitude = dtScale * force2Scale * mass;
+ Vector3 forceDir = part.transform.rotation * Vector3.forward;
+ Vector3 pos = part.transform.position + part.transform.rotation * tipOffsetToRoot;
+ part.AddForceAtPosition(forceMagnitude * forceDir, pos);
+ }
+
+ fixedTimeSinceStart += Time.fixedDeltaTime;
+ if (fixedTimeSinceStart >= force2Start + force2Duration)
+ {
+ isFinished = true;
+ }
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public void Update()
+ {
+ if (!korolevCross || isFinished) return;
+
+ Transform partTransform = part.gameObject.transform;
+ DebugDrawer.DebugTransforms(partTransform);
+
+ var forceDir = Vector3.forward;
+ var forceVector = part.transform.rotation * forceDir;
+ DebugDrawer.DebugLine(part.transform.position, part.transform.position + forceVector * 2, Color.yellow);
+
+ forceDir = Vector3.forward;
+ forceVector = part.transform.rotation * forceDir;
+
+ var tankTipPos = part.transform.position + part.transform.rotation * tipOffsetToRoot;
+ DebugDrawer.DebugLine(tankTipPos, tankTipPos + forceVector, Color.red);
+
+ if (joint == null || part.parent == null) return;
+
+ Transform parentTransform = part.parent.transform;
+ DebugDrawer.DebugLine(joint.transform.position + joint.transform.rotation * joint.anchor, parentTransform.position, Color.magenta);
+ }
+
+ private static float GetPartDryMassRecursive(Part part)
+ {
+ float mass = part.mass;
+ foreach (Part cPart in part.children)
+ {
+ mass += GetPartDryMassRecursive(cPart);
+ }
+
+ return mass;
+ }
+ }
+}
diff --git a/Source/ROLib/ROLib.csproj b/Source/ROLib/ROLib.csproj
index 4c30bf4..6c12280 100644
--- a/Source/ROLib/ROLib.csproj
+++ b/Source/ROLib/ROLib.csproj
@@ -72,13 +72,19 @@
False
+
+ False
+
False
+
+
+