diff --git a/Parts/HeliumBalloon/heliumBalloon.cfg b/Parts/HeliumBalloon/heliumBalloon.cfg index 9dafc38..dcc1939 100644 --- a/Parts/HeliumBalloon/heliumBalloon.cfg +++ b/Parts/HeliumBalloon/heliumBalloon.cfg @@ -4,24 +4,17 @@ PART module = Part author = Youen mesh = model.mu - scale = 0.1 - rescaleFactor = 1 - node_stack_bottom = 0.0, -0.020649, 0.0, 0.0, -1.0, 0.0, 1 - node_attach = 0.0, -0.020649, 0.0, 0.0, -1.0, 0.0 - sound_parachute_open = activate - TechRequired = advLanding - entryCost = 3500 - cost = 5000 - category = Utility + TechRequired = Unresearcheable + entryCost = 0 + cost = 0 + category = none subcategory = 0 title = Balloon - description = A big balloon that can be inflated with helium (sold separately). - attachRules = 1,0,0,1,0 - mass = 0.2 + mass = 0.050 dragModelType = default - angularDrag = 3 + angularDrag = 10 crashTolerance = 12 - maxTemp = 2000 // = 3100 + maxTemp = 1000 breakingForce = 100 breakingTorque = 50 bodyLiftMultiplier = 0 @@ -29,6 +22,6 @@ PART bulkheadProfiles = size1, srf MODULE { - name = ModuleBalloonCase + name = ModuleBalloon } } diff --git a/Parts/HeliumBalloon/model.mu b/Parts/HeliumBalloon/model.mu index f3db2ba..2dcffb4 100644 Binary files a/Parts/HeliumBalloon/model.mu and b/Parts/HeliumBalloon/model.mu differ diff --git a/Parts/HeliumBalloon/model000.dds b/Parts/HeliumBalloon/model000.dds index de7aecb..4c98d86 100644 Binary files a/Parts/HeliumBalloon/model000.dds and b/Parts/HeliumBalloon/model000.dds differ diff --git a/Parts/HeliumBalloon/model001.dds b/Parts/HeliumBalloon/model001.dds index 98a1b3f..4c98d86 100644 Binary files a/Parts/HeliumBalloon/model001.dds and b/Parts/HeliumBalloon/model001.dds differ diff --git a/Parts/HeliumBalloonCase/heliumBalloonCase.cfg b/Parts/HeliumBalloonCase/heliumBalloonCase.cfg new file mode 100644 index 0000000..104e878 --- /dev/null +++ b/Parts/HeliumBalloonCase/heliumBalloonCase.cfg @@ -0,0 +1,34 @@ +PART +{ + name = heliumBalloonCase + module = Part + author = Youen + mesh = model.mu + scale = 0.1 + rescaleFactor = 1 + node_stack_bottom = 0.0, -0.020649, 0.0, 0.0, -1.0, 0.0, 1 + node_attach = 0.0, -0.020649, 0.0, 0.0, -1.0, 0.0 + sound_parachute_open = activate + TechRequired = advLanding + entryCost = 3500 + cost = 5000 + category = Utility + subcategory = 0 + title = Balloon + description = A big balloon that can be inflated with helium (sold separately). + attachRules = 1,0,0,1,0 + mass = 0.2 + dragModelType = default + angularDrag = 3 + crashTolerance = 12 + maxTemp = 2000 // = 3100 + breakingForce = 100 + breakingTorque = 50 + bodyLiftMultiplier = 0 + stageOffset = -1 + bulkheadProfiles = size1, srf + MODULE + { + name = ModuleBalloonCase + } +} diff --git a/Parts/HeliumBalloonCase/model.mu b/Parts/HeliumBalloonCase/model.mu new file mode 100644 index 0000000..f3db2ba Binary files /dev/null and b/Parts/HeliumBalloonCase/model.mu differ diff --git a/Parts/HeliumBalloonCase/model000.dds b/Parts/HeliumBalloonCase/model000.dds new file mode 100644 index 0000000..de7aecb Binary files /dev/null and b/Parts/HeliumBalloonCase/model000.dds differ diff --git a/Parts/HeliumBalloonCase/model001.dds b/Parts/HeliumBalloonCase/model001.dds new file mode 100644 index 0000000..98a1b3f Binary files /dev/null and b/Parts/HeliumBalloonCase/model001.dds differ diff --git a/Plugin/ModuleBalloon.cs b/Plugin/ModuleBalloon.cs index 3709cc2..21ebe5e 100644 --- a/Plugin/ModuleBalloon.cs +++ b/Plugin/ModuleBalloon.cs @@ -8,14 +8,29 @@ namespace Aerostats { public class ModuleBalloon : PartModule { - public float MaxBalloonVolume { get; private set; } - public float GasDensity { get; private set; } - public float BalloonDragCoeff { get; private set; } - public float BalloonEmptyMass { get; private set; } + /// + /// Volume at which the balloon is full. Trying to add more gas, or simply having already stored gas expand due to lower exterior pressure or increased temperature, will cause gas to be vented through the security valve. + /// Air density at sea level is about 1.2kg/m^3, which means a 1000m^3 balloon will be able to lift about 1 ton at sea level (after substracting the balloon gas weight) + /// + [KSPField(isPersistant = false, guiActive = false)] + public float MaxVolume = 15000; + + /// + /// Drag coefficient of the balloon + /// + [KSPField(isPersistant = false, guiActive = false)] + public float DragCoeff = 0.3f; + + public float GasMolarMass { get; private set; } + + /// + /// Mass of the empty balloon, in kg + /// + public float EmptyMass { get; private set; } public float LiftingGasQuantity { get; private set; } public float CurrentVolume { get; private set; } - public float BalloonLift { get; private set; } + public float Lift { get; private set; } public bool IsVenting { get; private set; } /// @@ -25,22 +40,47 @@ namespace Aerostats public float Radius { get; private set; } + public ModuleBalloonCase AttachedCase { get; private set; } + /// /// Must be called right after creating a balloon /// - public void Initialize(float emptyMass, Vector3 initialVelocity, float maxVolume, float gasDensity, float dragCoeff) + public void Initialize(ModuleBalloonCase attachedCase, Vector3 initialVelocity, float gasDensity, float initialGasQuantity) { - MaxBalloonVolume = maxVolume; - GasDensity = gasDensity; - BalloonDragCoeff = dragCoeff; - BalloonEmptyMass = emptyMass; + AttachedCase = attachedCase; + GasMolarMass = gasDensity / GasUtilities.StpVolumeToMoles; + EmptyMass = part.mass * 1000.0f; + + // replace mesh and collider by a sphere (hack until proper assets are created) + var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); + gameObject.GetComponentInChildren().mesh = sphere.GetComponent().mesh; + Destroy(gameObject.collider); + var collider = gameObject.AddComponent(); + collider.radius = 0.67f; // don't ask me, I don't know why + Destroy(sphere); gameObject.AddComponent(); - rigidbody.mass = BalloonEmptyMass; + rigidbody.mass = EmptyMass * 0.001f; rigidbody.velocity = initialVelocity; // start with the same velocity or everything explodes when deploying from a moving vessel rigidbody.angularDrag = 10.0f; + rigidbody.position = transform.position; - InjectGas(0.0001f); + InjectGas(initialGasQuantity); + + part.OnJustAboutToBeDestroyed += OnPartDestroyed; + } + + public void OnDetached() + { + AttachedCase = null; + } + + private void OnPartDestroyed() + { + if(AttachedCase != null) + AttachedCase.Cut(); + + part.OnJustAboutToBeDestroyed -= OnPartDestroyed; } /// @@ -56,7 +96,7 @@ namespace Aerostats if (LiftingGasQuantity > currentMaxQuantity) { IsVenting = true; - Util.PostSingleScreenMessage("security valve", "Some gas has been vented by the balloon security valve"); + Util.PostDebugSingleScreenMessage("security valve", "Some gas has been vented by the balloon security valve"); LiftingGasQuantity = currentMaxQuantity; } else @@ -73,21 +113,20 @@ namespace Aerostats float currentGasMoles = GasUtilities.StpVolumeToMoles * LiftingGasQuantity; CurrentVolume = currentGasMoles * GasUtilities.R * balloonInternalTemperature / externalPressure; - Inflation = CurrentVolume / MaxBalloonVolume; + Inflation = CurrentVolume / MaxVolume; - Util.PostSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%"); + Util.PostDebugSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%"); float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body); - float currentGasDensity = GasDensity * balloonInternalTemperature / GasUtilities.ZeroCelsius / externalPressure * GasUtilities.StandardPressure; - BalloonLift = CurrentVolume * airDensity; - float balloonGasMass = currentGasDensity * CurrentVolume; - Util.PostSingleScreenMessage("lift", "Air density = " + airDensity + "kg/m^3, Lift = " + (BalloonLift - balloonGasMass) + "kg"); + Lift = CurrentVolume * airDensity; + float balloonGasMass = GasMolarMass * currentGasMoles; + Util.PostDebugSingleScreenMessage("lift", "Air density = " + airDensity + "kg/m^3, Lift = " + (Lift - balloonGasMass) + "kg"); // V = 4/3*pi*r^3 Radius = Mathf.Pow(CurrentVolume * 0.75f / Mathf.PI, 0.333f); - float scale = Radius * 2.0f + 0.1f; + float scale = Radius + 0.1f; transform.localScale = new Vector3(scale, scale, scale); - rigidbody.mass = (BalloonEmptyMass + balloonGasMass) * 0.001f; + rigidbody.mass = (EmptyMass + balloonGasMass) * 0.001f; return LiftingGasQuantity; } @@ -103,9 +142,9 @@ namespace Aerostats float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body); float balloonInternalTemperature = externalTemperature; float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f); - Util.PostSingleScreenMessage("external atmo", "Temperature = " + externalTemperature + "K, Pressure=" + externalPressure + "Pa"); + Util.PostDebugSingleScreenMessage("external atmo", "Temperature = " + externalTemperature + "K, Pressure=" + externalPressure + "Pa"); - float currentMaxQuantity = MaxBalloonVolume / GasUtilities.R / balloonInternalTemperature * externalPressure / GasUtilities.StpVolumeToMoles; + float currentMaxQuantity = MaxVolume / GasUtilities.R / balloonInternalTemperature * externalPressure / GasUtilities.StpVolumeToMoles; return currentMaxQuantity; } @@ -115,6 +154,8 @@ namespace Aerostats Vector3d worldPos = vessel.GetWorldPos3D(); CelestialBody body = vessel.mainBody; + rigidbody.WakeUp(); + InjectGas(0); // update security valve venting var gravityAccel = FlightGlobals.getGeeForceAtPosition(worldPos); @@ -125,14 +166,14 @@ namespace Aerostats float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f); float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body); - rigidbody.AddForce(-gravityAccel * BalloonLift / 1000.0f, ForceMode.Force); + rigidbody.AddForce(-gravityAccel * Lift / 1000.0f, ForceMode.Force); // balloon drag var airVelocity = rigidbody.velocity + Krakensbane.GetFrameVelocity() /*- vessel.mainBody.getRFrmVel(vessel.GetWorldPos3D())*/; float sqVel = (float)airVelocity.magnitude; sqVel *= sqVel; - float dragForce = 0.5f * airDensity * sqVel * BalloonDragCoeff * (Mathf.PI * Radius * Radius); - Util.PostSingleScreenMessage("balloon drag", "Drag = " + dragForce + "N"); + float dragForce = 0.5f * airDensity * sqVel * DragCoeff * (Mathf.PI * Radius * Radius); + Util.PostDebugSingleScreenMessage("balloon drag", "Drag = " + dragForce + "N"); rigidbody.AddForce(-airVelocity.normalized * dragForce / 1000.0f, ForceMode.Force); } } diff --git a/Plugin/ModuleBalloonCase.cs b/Plugin/ModuleBalloonCase.cs index 17ee469..0b4e916 100644 --- a/Plugin/ModuleBalloonCase.cs +++ b/Plugin/ModuleBalloonCase.cs @@ -20,13 +20,6 @@ namespace Aerostats [KSPField(isPersistant = false, guiActive = false)] public float MaxGasVentRate = 500; - /// - /// Volume at which the balloon is full. Trying to add more gas, or simply having already stored gas expand due to lower exterior pressure or increased temperature, will cause gas to be vented through the security valve. - /// Air density at sea level is about 1.2kg/m^3, which means a 1000m^3 balloon will be able to lift about 1 ton at sea level (after substracting the balloon gas weight) - /// - [KSPField(isPersistant = false, guiActive = false)] - public float MaxBalloonVolume = 15000; - /// /// Weight, in kilograms, of 1m^3 of gas at 100kPa and 0°C /// Helium is 179g/m^3 @@ -43,24 +36,12 @@ namespace Aerostats [UI_FloatRange(minValue = 0, maxValue = 20000.0f, stepIncrement = 100.0f)] public float LiftingGasTargetQuantity = 100.0f; - /// - /// Mass of the empty balloon, in kg - /// - [KSPField(isPersistant = false, guiActive = false)] - public float BalloonEmptyMass = 50.0f; - /// /// Minimum amount of gas that will be used to inflate the balloon at the beginning. The balloon can not be deflated below this amount. /// [KSPField(isPersistant = false, guiActive = false)] public float MinimumFillQuantity = 10.0f; - /// - /// Drag coefficient of the balloon - /// - [KSPField(isPersistant = false, guiActive = false)] - public float BalloonDragCoeff = 0.3f; - /// /// Length of spring that can not be extended (no force applied under this length) /// @@ -80,6 +61,8 @@ namespace Aerostats private bool Destroyed; + private Vector3 EstimatedNextFramePosition; + /// /// Gas quantity currently inside the balloon /// @@ -92,26 +75,60 @@ namespace Aerostats private ModuleBalloon Balloon; private LineRenderer Spring; - private Vector3 EstimatedNextFramePosition; + public static void RemoveAttachJointBetween(Part part1, Part part2) + { + if (part1.attachJoint && ((part1.attachJoint.Host == part1 && part1.attachJoint.Target == part2) || (part1.attachJoint.Host == part2 && part1.attachJoint.Target == part1))) + { + part1.attachJoint.DestroyJoint(); + } + if (part2.attachJoint && ((part2.attachJoint.Host == part2 && part2.attachJoint.Target == part1) || (part2.attachJoint.Host == part1 && part2.attachJoint.Target == part2))) + { + part2.attachJoint.DestroyJoint(); + } + } - [KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Deploy balloon", unfocusedRange = 5)] - public void Deploy() - { + [KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Deploy balloon", unfocusedRange = 5)] + public void Deploy() + { if (Destroyed || Deployed) return; Deployed = true; UnityEngine.Debug.Log("Aerostats: staged"); - Util.PostScreenMessage("staged"); + Util.PostDebugScreenMessage("staged"); - var balloonObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); + /*var balloonObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); + balloonObject.SetActive(false); balloonObject.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up; - Balloon = balloonObject.AddComponent(); - Balloon.part = part; // TODO: remove this hack and properly instantiate a new vessel? - Balloon.Initialize(BalloonEmptyMass, part.rigidbody.velocity, MaxBalloonVolume, GasDensity, BalloonDragCoeff); - - LiftingGasQuantity = Balloon.InjectGas(part.RequestResource("Helium", MinimumFillQuantity)); + var balloonPart = balloonObject.AddComponent(); + balloonPart.children = new List(); + balloonPart.partTransform = balloonObject.transform; + balloonPart.name = "heliumBalloon"; + new GameObject("model").transform.parent = balloonPart.partTransform; + balloonPart.SetHierarchyRoot(balloonPart); + balloonPart.vessel = vessel; + balloonPart.setParent(part);*/ + + AvailablePart avPart = PartLoader.Instance.parts.Single(p => p.name == "heliumBalloon"); + var balloonPart = (Part)UnityEngine.Object.Instantiate(avPart.partPrefab); + var balloonObject = balloonPart.gameObject; + balloonObject.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up * 3.0f; + //balloonObject.transform.rotation = part.Rigidbody.rotation; + balloonPart.SetHierarchyRoot(balloonPart); + balloonPart.vessel = vessel; + balloonPart.setParent(part); + + Balloon = balloonObject.GetComponent(); + Balloon.part = balloonPart; + float initialGasQuantity = Mathf.Min(0.1f, part.RequestResource("Helium", MinimumFillQuantity)); + + balloonObject.SetActive(true); + GameEvents.onVesselWasModified.Fire(vessel); + + Balloon.Initialize(this, part.rigidbody.velocity, GasDensity, initialGasQuantity); + + LiftingGasQuantity = Balloon.LiftingGasQuantity; Spring = part.gameObject.AddComponent(); Spring.useWorldSpace = true; @@ -122,27 +139,33 @@ namespace Aerostats part.OnJustAboutToBeDestroyed += OnPartDestroyed; - EstimatedNextFramePosition = part.Rigidbody.position; + EstimatedNextFramePosition = part.Rigidbody.position; } - [KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Cut rope", unfocusedRange = 5)] - public void Cut() - { - if (Destroyed || !Deployed) + [KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Cut rope", unfocusedRange = 5)] + public void Cut() + { + if (Destroyed || !Deployed) return; - Balloon = null; + if (Balloon != null) + { + Balloon.OnDetached(); + Balloon.part.decouple(); // after that, the balloon becomes an independent vessel + Balloon.part.vessel.vesselName = vessel.vesselName + " balloon"; + Balloon = null; + } Destroy(Spring); Spring = null; part.OnJustAboutToBeDestroyed -= OnPartDestroyed; - Destroyed = true; + Destroyed = true; } public override void OnStart(PartModule.StartState state) { if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) { return; } - Util.PostScreenMessage("Aerostat loaded"); + Util.PostDebugScreenMessage("Aerostat loaded"); part.stagingIcon = "PARACHUTES"; } @@ -151,6 +174,19 @@ namespace Aerostats Cut(); } + private void CheckKrakensbane() + { + // detect Krakensbane teleportation, and fix up the balloon position (otherwise it results in instant ship disintegration due to extreme forces on the spring) + if ((part.Rigidbody.position - EstimatedNextFramePosition).magnitude > 1000.0f) + { + Util.PostDebugScreenMessage("Krakensbane teleportation detected! (dist=" + (part.Rigidbody.position - EstimatedNextFramePosition).magnitude + ")"); + var offset = part.rigidbody.position - EstimatedNextFramePosition; + Balloon.rigidbody.position += offset; + Balloon.transform.position = Balloon.rigidbody.position; + } + EstimatedNextFramePosition = part.rigidbody.position + part.rigidbody.velocity * Time.fixedDeltaTime; + } + private void FixedUpdate() { if (Destroyed) @@ -167,15 +203,10 @@ namespace Aerostats if (Deployed) { - // detect Krakensbane teleportation, and fix up the balloon position (otherwise it results in instant ship disintegration due to extreme forces on the spring) - if ((part.Rigidbody.position - EstimatedNextFramePosition).magnitude > 1000.0f) - { - Util.PostScreenMessage("Krakensbane teleportation detected! (dist=" + (part.Rigidbody.position - EstimatedNextFramePosition).magnitude+")"); - var offset = part.rigidbody.position - EstimatedNextFramePosition; - Balloon.rigidbody.position += offset; - Balloon.transform.position = Balloon.rigidbody.position; - } - EstimatedNextFramePosition = part.rigidbody.position + part.rigidbody.velocity * Time.fixedDeltaTime; + // makes sure the balloon can move freely relatively to the vessel (apparently, KSP creates a joint when attaching a part) + RemoveAttachJointBetween(Balloon.part, part); + + CheckKrakensbane(); float currentMaxQuantity = Balloon.GetCurrentMaxQuantity(); @@ -209,7 +240,7 @@ namespace Aerostats Status = "full (venting)"; } - VolumeStatus = Mathf.Round(Balloon.CurrentVolume) + " / " + Mathf.Round(MaxBalloonVolume); + VolumeStatus = Mathf.Round(Balloon.CurrentVolume) + " / " + Mathf.Round(Balloon.MaxVolume); // spring between balloon and base float restLength = SpringRestLength + Balloon.Radius; @@ -225,7 +256,7 @@ namespace Aerostats part.rigidbody.AddForce(springForce, ForceMode.Force); Balloon.rigidbody.AddForceAtPosition(-springForce, balloonAttachPoint, ForceMode.Force); } - Util.PostSingleScreenMessage("spring force", "Spring force = " + springForceMag + "N"); + Util.PostDebugSingleScreenMessage("spring force", "Spring force = " + springForceMag + "N"); } else { @@ -241,11 +272,23 @@ namespace Aerostats if (Deployed) { + CheckKrakensbane(); var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Balloon.Radius; Spring.SetPosition(0, part.transform.position); Spring.SetPosition(1, balloonAttachPoint); } } + + private void Update() + { + if (Destroyed) + return; + + if (Deployed) + { + CheckKrakensbane(); + } + } } } diff --git a/Plugin/Util.cs b/Plugin/Util.cs index 8518997..8e77b35 100644 --- a/Plugin/Util.cs +++ b/Plugin/Util.cs @@ -13,7 +13,7 @@ namespace Aerostats { private static Dictionary messages = new Dictionary(); - public static void PostSingleScreenMessage(string id, string message) + public static void PostDebugSingleScreenMessage(string id, string message) { #if ENABLE_ONSCREEN_DEBUG if (messages.ContainsKey(id)) @@ -22,7 +22,7 @@ namespace Aerostats #endif } - public static void PostScreenMessage(string message) + public static void PostDebugScreenMessage(string message) { #if ENABLE_ONSCREEN_DEBUG ScreenMessages.PostScreenMessage(message); diff --git a/todo.txt b/todo.txt index db949ee..dee4aa9 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,4 @@ save/load balloon state test in orbit -button to deploy -button to cut the rope +test reentry (before and after deploying the balloon) +test with RSS / RO \ No newline at end of file