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);