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 + + +