diff --git a/Parts/HeliumBalloon/heliumBalloon.cfg b/Parts/HeliumBalloon/heliumBalloon.cfg index d63fcf7..9dafc38 100644 --- a/Parts/HeliumBalloon/heliumBalloon.cfg +++ b/Parts/HeliumBalloon/heliumBalloon.cfg @@ -29,6 +29,6 @@ PART bulkheadProfiles = size1, srf MODULE { - name = ModuleAerostat + name = ModuleBalloonCase } } diff --git a/Plugin/Aerostats.csproj b/Plugin/Aerostats.csproj index deb5434..7e87f67 100644 --- a/Plugin/Aerostats.csproj +++ b/Plugin/Aerostats.csproj @@ -47,7 +47,9 @@ - + + + diff --git a/Plugin/GasUtilities.cs b/Plugin/GasUtilities.cs new file mode 100644 index 0000000..c1c0e34 --- /dev/null +++ b/Plugin/GasUtilities.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Aerostats +{ + public static class GasUtilities + { + // amount of gas Q is specified in m^3 at stp (standard pressure of 100kPa and temperature of 0°C = 273K). + // Ideal gas equation P.V = n.R.T gives us the equivalent number of moles: n = P.V/R/T = 100000.Q/8.314/273 = 44.Q mol + + public static readonly float R = 8.314f; + public static readonly float ZeroCelsius = 273.15f; + public static readonly float StandardPressure = 100000.0f; + public static readonly float StpVolumeToMoles = 100000.0f / R / 273.15f; + } +} diff --git a/Plugin/ModuleBalloon.cs b/Plugin/ModuleBalloon.cs new file mode 100644 index 0000000..3709cc2 --- /dev/null +++ b/Plugin/ModuleBalloon.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +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; } + + public float LiftingGasQuantity { get; private set; } + public float CurrentVolume { get; private set; } + public float BalloonLift { get; private set; } + public bool IsVenting { get; private set; } + + /// + /// Inflation ratio of the balloon (0=empty, 1=maximum) + /// + private float Inflation = 0; + + public float Radius { get; private set; } + + /// + /// Must be called right after creating a balloon + /// + public void Initialize(float emptyMass, Vector3 initialVelocity, float maxVolume, float gasDensity, float dragCoeff) + { + MaxBalloonVolume = maxVolume; + GasDensity = gasDensity; + BalloonDragCoeff = dragCoeff; + BalloonEmptyMass = emptyMass; + + gameObject.AddComponent(); + rigidbody.mass = BalloonEmptyMass; + rigidbody.velocity = initialVelocity; // start with the same velocity or everything explodes when deploying from a moving vessel + rigidbody.angularDrag = 10.0f; + + InjectGas(0.0001f); + } + + /// + /// Injects (positive quantity) or removes (negative quantity) gas, and returns the contained quantity after that operation + /// + public float InjectGas(float quantity) + { + LiftingGasQuantity += quantity; + + float currentMaxQuantity = GetCurrentMaxQuantity(); + + // balloon security valve + if (LiftingGasQuantity > currentMaxQuantity) + { + IsVenting = true; + Util.PostSingleScreenMessage("security valve", "Some gas has been vented by the balloon security valve"); + LiftingGasQuantity = currentMaxQuantity; + } + else + { + IsVenting = false; + } + + Vector3d worldPos = vessel.GetWorldPos3D(); + CelestialBody body = vessel.mainBody; + + float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body); + float balloonInternalTemperature = externalTemperature; + float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f); + + float currentGasMoles = GasUtilities.StpVolumeToMoles * LiftingGasQuantity; + CurrentVolume = currentGasMoles * GasUtilities.R * balloonInternalTemperature / externalPressure; + Inflation = CurrentVolume / MaxBalloonVolume; + + Util.PostSingleScreenMessage("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"); + + // V = 4/3*pi*r^3 + Radius = Mathf.Pow(CurrentVolume * 0.75f / Mathf.PI, 0.333f); + float scale = Radius * 2.0f + 0.1f; + transform.localScale = new Vector3(scale, scale, scale); + rigidbody.mass = (BalloonEmptyMass + balloonGasMass) * 0.001f; + + return LiftingGasQuantity; + } + + /// + /// Computes the maximum gas quantity that the balloon can current hold, given the external temperature and pressure conditions + /// + public float GetCurrentMaxQuantity() + { + Vector3d worldPos = vessel.GetWorldPos3D(); + CelestialBody body = vessel.mainBody; + + 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"); + + float currentMaxQuantity = MaxBalloonVolume / GasUtilities.R / balloonInternalTemperature * externalPressure / GasUtilities.StpVolumeToMoles; + + return currentMaxQuantity; + } + + private void FixedUpdate() + { + Vector3d worldPos = vessel.GetWorldPos3D(); + CelestialBody body = vessel.mainBody; + + InjectGas(0); // update security valve venting + + var gravityAccel = FlightGlobals.getGeeForceAtPosition(worldPos); + //Util.PostSingleScreenMessage("gravity", "Gravity accel = (" + gravityAccel.x + ", " + gravityAccel.y + ", " + gravityAccel.z + ")"); + + float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body); + float balloonInternalTemperature = externalTemperature; + 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); + + // 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"); + rigidbody.AddForce(-airVelocity.normalized * dragForce / 1000.0f, ForceMode.Force); + } + } +} diff --git a/Plugin/ModuleAerostat.cs b/Plugin/ModuleBalloonCase.cs similarity index 59% rename from Plugin/ModuleAerostat.cs rename to Plugin/ModuleBalloonCase.cs index b9bdfde..17ee469 100644 --- a/Plugin/ModuleAerostat.cs +++ b/Plugin/ModuleBalloonCase.cs @@ -6,14 +6,8 @@ using UnityEngine; namespace Aerostats { - public class ModuleAerostat : PartModule + public class ModuleBalloonCase : PartModule { - // In this class, amount of gas Q is specified in m^3 at stp (standard pressure of 100kPa and temperature of 0°C = 273K). Ideal gas equation P.V = n.R.T gives us the equivalent number of moles: n = P.V/R/T = 100000.Q/8.314/273 = 44.Q mol - private static readonly float R = 8.314f; - private static readonly float ZeroCelsius = 273.15f; - private static readonly float StandardPressure = 100000.0f; - private static readonly float StpVolumeToMoles = 100000.0f / R / 273.15f; - /// /// Maximum rate at which gas can be injected in the balloon to fill it, in m^3/s at stp /// @@ -95,14 +89,7 @@ namespace Aerostats [KSPField(guiName = "Volume", isPersistant = false, guiActive = true)] public string VolumeStatus; - /// - /// Inflation ratio of the balloon (0=empty, 1=maximum) - /// - private float Inflation = 0; - - private float Radius = 0.01f; - - private GameObject Balloon; + private ModuleBalloon Balloon; private LineRenderer Spring; private Vector3 EstimatedNextFramePosition; @@ -118,21 +105,19 @@ namespace Aerostats UnityEngine.Debug.Log("Aerostats: staged"); Util.PostScreenMessage("staged"); - Balloon = GameObject.CreatePrimitive(PrimitiveType.Sphere); - Balloon.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up; - Balloon.AddComponent(); - Balloon.rigidbody.mass = BalloonEmptyMass; - Balloon.rigidbody.velocity = part.rigidbody.velocity; // start with the same velocity or everything explodes when deploying from a moving vessel + var balloonObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); + 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); - Balloon.rigidbody.angularDrag = 10.0f; + LiftingGasQuantity = Balloon.InjectGas(part.RequestResource("Helium", MinimumFillQuantity)); - LiftingGasQuantity = part.RequestResource("Helium", MinimumFillQuantity); - - Spring = Balloon.AddComponent(); + Spring = part.gameObject.AddComponent(); Spring.useWorldSpace = true; Spring.material = new Material(Shader.Find("VertexLit")); Spring.SetColors(Color.black, Color.black); - Spring.SetWidth(0.1f, 0.1f); + Spring.SetWidth(0.2f, 0.2f); Spring.SetVertexCount(2); part.OnJustAboutToBeDestroyed += OnPartDestroyed; @@ -146,8 +131,8 @@ namespace Aerostats if (Destroyed || !Deployed) return; - Destroy(Balloon); // another option could be to let it float freely, but in this case the buoyancy code should be implemented in a separate MonoBehavior Balloon = null; + Destroy(Spring); Spring = null; part.OnJustAboutToBeDestroyed -= OnPartDestroyed; Destroyed = true; @@ -192,21 +177,16 @@ namespace Aerostats } EstimatedNextFramePosition = part.rigidbody.position + part.rigidbody.velocity * Time.fixedDeltaTime; - float externalTemperature = (float)FlightGlobals.getExternalTemperature(); - float balloonInternalTemperature = externalTemperature; - float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure() * 1000.0f, 0.00001f); - Util.PostSingleScreenMessage("external atmo", "Temperature = " + externalTemperature + "K, Pressure=" + externalPressure + "Pa"); - - float currentMaxQuantity = MaxBalloonVolume / R / balloonInternalTemperature * externalPressure / StpVolumeToMoles; + float currentMaxQuantity = Balloon.GetCurrentMaxQuantity(); LiftingGasTargetQuantity = Math.Max(MinimumFillQuantity, LiftingGasTargetQuantity); - if(LiftingGasTargetQuantity > LiftingGasQuantity) + if(LiftingGasTargetQuantity > Balloon.LiftingGasQuantity) { - // infalting balloon - float stepFinalQuantity = Math.Min(LiftingGasQuantity + MaxGasFillRate * Time.fixedDeltaTime, Math.Min(LiftingGasTargetQuantity, currentMaxQuantity)); - float step = Math.Max(stepFinalQuantity - LiftingGasQuantity, 0.0f); + // inflating balloon + float stepFinalQuantity = Math.Min(Balloon.LiftingGasQuantity + MaxGasFillRate * Time.fixedDeltaTime, Math.Min(LiftingGasTargetQuantity, currentMaxQuantity)); + float step = Math.Max(stepFinalQuantity - Balloon.LiftingGasQuantity, 0.0f); float stepResource = part.RequestResource("Helium", step); - LiftingGasQuantity += stepResource; + LiftingGasQuantity = Balloon.InjectGas(stepResource); if(step > 0.0f) { Status = stepResource > 0.0f ? "inflating" : "out-of-gas"; @@ -219,56 +199,24 @@ namespace Aerostats else { // deflating balloon - Status = LiftingGasTargetQuantity == LiftingGasQuantity ? "nominal" : "deflating"; - LiftingGasQuantity = Math.Max(LiftingGasQuantity - MaxGasVentRate * Time.fixedDeltaTime, LiftingGasTargetQuantity); + Status = LiftingGasTargetQuantity == Balloon.LiftingGasQuantity ? "nominal" : "deflating"; + float quantityAfterVenting = Math.Max(Balloon.LiftingGasQuantity - MaxGasVentRate * Time.fixedDeltaTime, LiftingGasTargetQuantity); + LiftingGasQuantity = Balloon.InjectGas(quantityAfterVenting - Balloon.LiftingGasQuantity); } - - // balloon security valve - if (LiftingGasQuantity > currentMaxQuantity) + if(Balloon.IsVenting) { Status = "full (venting)"; - Util.PostSingleScreenMessage("security valve", "Some gas has been vented by the balloon security valve"); - LiftingGasQuantity = currentMaxQuantity; } - - float currentGasMoles = StpVolumeToMoles * LiftingGasQuantity; - float currentGasVolume = currentGasMoles * R * balloonInternalTemperature / externalPressure; - Inflation = currentGasVolume / MaxBalloonVolume; - VolumeStatus = Mathf.Round(currentGasVolume) + " / " + Mathf.Round(MaxBalloonVolume); - - Util.PostSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%"); - - float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, vessel.mainBody); - float currentGasDensity = GasDensity * balloonInternalTemperature / ZeroCelsius / externalPressure * StandardPressure; - float balloonLift = currentGasVolume * airDensity; - float balloonGasMass = currentGasDensity * currentGasVolume; - Util.PostSingleScreenMessage("lift", "Air density = " + airDensity + "kg/m^3, Lift = " + (balloonLift - balloonGasMass) + "kg"); - var gravityAccel = FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()); - //Util.PostSingleScreenMessage("gravity", "Gravity accel = (" + gravityAccel.x + ", " + gravityAccel.y + ", " + gravityAccel.z + ")"); - - // V = 4/3*pi*r^3 - Radius = Mathf.Pow(currentGasVolume * 0.75f / Mathf.PI, 0.333f); - float scale = Radius * 2.0f + 0.1f; - Balloon.transform.localScale = new Vector3(scale,scale,scale); - Balloon.rigidbody.mass = (BalloonEmptyMass + balloonGasMass) * 0.001f; - - Balloon.rigidbody.AddForce(-gravityAccel * balloonLift / 1000.0f, ForceMode.Force); - - // balloon drag - var airVelocity = Balloon.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"); - Balloon.rigidbody.AddForce(-airVelocity.normalized * dragForce / 1000.0f, ForceMode.Force); + + VolumeStatus = Mathf.Round(Balloon.CurrentVolume) + " / " + Mathf.Round(MaxBalloonVolume); // spring between balloon and base - float restLength = SpringRestLength + Radius; + float restLength = SpringRestLength + Balloon.Radius; var springVec = Balloon.rigidbody.position - part.rigidbody.position; float springLength = springVec.magnitude; float springForceMag = 0; - var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Radius; + var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Balloon.Radius; if(springLength > restLength) { float tensingLength = springLength - restLength; @@ -293,7 +241,7 @@ namespace Aerostats if (Deployed) { - var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Radius; + var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Balloon.Radius; Spring.SetPosition(0, part.transform.position); Spring.SetPosition(1, balloonAttachPoint);