Browse Source

refactored the balloon module so that there is now a balloon case that can deploy an independent balloon (which starts attached by a spring, but can be detached later)

master
Youen Toupin 9 years ago
parent
commit
057d997626
  1. 2
      Parts/HeliumBalloon/heliumBalloon.cfg
  2. 4
      Plugin/Aerostats.csproj
  3. 18
      Plugin/GasUtilities.cs
  4. 139
      Plugin/ModuleBalloon.cs
  5. 104
      Plugin/ModuleBalloonCase.cs

2
Parts/HeliumBalloon/heliumBalloon.cfg

@ -29,6 +29,6 @@ PART
bulkheadProfiles = size1, srf
MODULE
{
name = ModuleAerostat
name = ModuleBalloonCase
}
}

4
Plugin/Aerostats.csproj

@ -47,7 +47,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ModuleAerostat.cs" />
<Compile Include="GasUtilities.cs" />
<Compile Include="ModuleBalloonCase.cs" />
<Compile Include="ModuleBalloon.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Util.cs" />
</ItemGroup>

18
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;
}
}

139
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; }
/// <summary>
/// Inflation ratio of the balloon (0=empty, 1=maximum)
/// </summary>
private float Inflation = 0;
public float Radius { get; private set; }
/// <summary>
/// Must be called right after creating a balloon
/// </summary>
public void Initialize(float emptyMass, Vector3 initialVelocity, float maxVolume, float gasDensity, float dragCoeff)
{
MaxBalloonVolume = maxVolume;
GasDensity = gasDensity;
BalloonDragCoeff = dragCoeff;
BalloonEmptyMass = emptyMass;
gameObject.AddComponent<Rigidbody>();
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);
}
/// <summary>
/// Injects (positive quantity) or removes (negative quantity) gas, and returns the contained quantity after that operation
/// </summary>
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;
}
/// <summary>
/// Computes the maximum gas quantity that the balloon can current hold, given the external temperature and pressure conditions
/// </summary>
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);
}
}
}

104
Plugin/ModuleAerostat.cs → 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;
/// <summary>
/// Maximum rate at which gas can be injected in the balloon to fill it, in m^3/s at stp
/// </summary>
@ -95,14 +89,7 @@ namespace Aerostats
[KSPField(guiName = "Volume", isPersistant = false, guiActive = true)]
public string VolumeStatus;
/// <summary>
/// Inflation ratio of the balloon (0=empty, 1=maximum)
/// </summary>
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<Rigidbody>();
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<ModuleBalloon>();
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<LineRenderer>();
Spring = part.gameObject.AddComponent<LineRenderer>();
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);
Loading…
Cancel
Save