You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
13 KiB
250 lines
13 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text; |
|
using UnityEngine; |
|
|
|
namespace Aerostats |
|
{ |
|
public class ModuleAerostat : 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> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float MaxGasFillRate = 100; |
|
|
|
/// <summary> |
|
/// Maximum rate at which gas can be removed from the balloon to deflate it, in m^3/s at stp |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float MaxGasVentRate = 500; |
|
|
|
/// <summary> |
|
/// 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) |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float MaxBalloonVolume = 10000; |
|
|
|
/// <summary> |
|
/// Weight, in kilograms, of 1m^3 of gas at 100kPa and 0°C |
|
/// Helium is 179g/m^3 |
|
/// Hydrogen is 90g/m^3 |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float GasDensity = 0.179f; |
|
|
|
/// <summary> |
|
/// Quantity of gas that the system is trying to get inside the balloon, in m^3 at 100kPa and 0°C. If the balloon contains more gas, some will be vented, otherwise, gas will be injected. |
|
/// If the balloon can not store the target amount of gas (because the maximum volume has been reached), the system won't try to inject more gas, to avoid venting through the security valve of the balloon. |
|
/// </summary> |
|
[KSPField(isPersistant = true, guiActive = true)] |
|
[UI_FloatRange(minValue = 0, maxValue = 20000.0f, stepIncrement = 1.0f)] |
|
public float LiftingGasTargetQuantity; |
|
|
|
/// <summary> |
|
/// Mass of the empty balloon, in kg |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float BalloonEmptyMass = 50.0f; |
|
|
|
/// <summary> |
|
/// Minimum amount of gas that will be use to inflate the balloon at the beginning. |
|
/// This is needed if you want the balloon to lift itself right after staging. |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float MinimumFillQuantity = 75.0f; |
|
|
|
/// <summary> |
|
/// Drag coefficient of the balloon |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float BalloonDragCoeff = 0.3f; |
|
|
|
/// <summary> |
|
/// Length of spring that can not be extended (no force applied under this length) |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float SpringRestLength = 10.0f; |
|
|
|
/// <summary> |
|
/// Force applied by the spring between the balloon and the vessel, proportional to spring extension, in newton per meter |
|
/// </summary> |
|
[KSPField(isPersistant = false, guiActive = false)] |
|
public float SpringHardness = 5000.0f; |
|
|
|
private bool Staged; |
|
|
|
private bool Destroyed; |
|
|
|
/// <summary> |
|
/// Gas quantity currently inside the balloon |
|
/// </summary> |
|
[KSPField(isPersistant = true, guiActive = true)] |
|
public float LiftingGasQuantity; |
|
|
|
/// <summary> |
|
/// Inflation ratio of the balloon (0=empty, 1=maximum) |
|
/// </summary> |
|
private float Inflation = 0; |
|
|
|
private GameObject Balloon; |
|
private LineRenderer Spring; |
|
|
|
private Vector3 EstimatedNextFramePosition; |
|
|
|
public override void OnStart(PartModule.StartState state) |
|
{ |
|
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) { return; } |
|
|
|
ScreenMessages.PostScreenMessage("Aerostat loaded"); |
|
part.stagingIcon = "PARACHUTES"; |
|
} |
|
|
|
private void OnStaged() |
|
{ |
|
UnityEngine.Debug.Log("Aerostats: staged"); |
|
ScreenMessages.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 |
|
|
|
Balloon.rigidbody.angularDrag = 10.0f; |
|
|
|
LiftingGasQuantity = part.RequestResource("Helium", MinimumFillQuantity); |
|
|
|
Spring = Balloon.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.SetVertexCount(2); |
|
|
|
part.OnJustAboutToBeDestroyed += OnPartDestroyed; |
|
|
|
EstimatedNextFramePosition = part.Rigidbody.position; |
|
} |
|
|
|
private void OnPartDestroyed() |
|
{ |
|
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; |
|
Spring = null; |
|
part.OnJustAboutToBeDestroyed -= OnPartDestroyed; |
|
Destroyed = true; |
|
} |
|
|
|
private void FixedUpdate() |
|
{ |
|
if (Destroyed) |
|
return; |
|
|
|
if (!Staged && GameSettings.LAUNCH_STAGES.GetKeyDown() && vessel.isActiveVessel && (part.inverseStage == Staging.CurrentStage - 1 || Staging.CurrentStage == 0)) |
|
{ |
|
Staged = true; |
|
OnStaged(); |
|
} |
|
|
|
if (Staged) |
|
{ |
|
// 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) |
|
{ |
|
ScreenMessages.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; |
|
|
|
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; |
|
|
|
LiftingGasTargetQuantity = Math.Max(MinimumFillQuantity, LiftingGasTargetQuantity); |
|
if(LiftingGasTargetQuantity > LiftingGasQuantity) |
|
{ |
|
// infalting balloon |
|
float stepFinalQuantity = Math.Min(LiftingGasQuantity + MaxGasFillRate * Time.fixedDeltaTime, Math.Min(LiftingGasTargetQuantity, currentMaxQuantity)); |
|
float step = Math.Max(stepFinalQuantity - LiftingGasQuantity, 0.0f); |
|
LiftingGasQuantity += part.RequestResource("Helium", step); |
|
} |
|
else |
|
{ |
|
// deflating balloon |
|
LiftingGasQuantity = Math.Max(LiftingGasQuantity - MaxGasVentRate * Time.fixedDeltaTime, LiftingGasTargetQuantity); |
|
} |
|
|
|
|
|
// balloon security valve |
|
if (LiftingGasQuantity > currentMaxQuantity) |
|
{ |
|
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; |
|
|
|
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 |
|
float 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); |
|
|
|
// spring between balloon and base |
|
float restLength = SpringRestLength + 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; |
|
if(springLength > restLength) |
|
{ |
|
float tensingLength = springLength - restLength; |
|
springForceMag = Math.Min(tensingLength * SpringHardness, 500000.0f); |
|
var springForce = springVec * (springForceMag / springLength * 0.001f); |
|
part.rigidbody.AddForce(springForce, ForceMode.Force); |
|
Balloon.rigidbody.AddForceAtPosition(-springForce, balloonAttachPoint, ForceMode.Force); |
|
} |
|
Util.PostSingleScreenMessage("spring force", "Spring force = " + springForceMag + "N"); |
|
|
|
Spring.SetPosition(0, part.transform.position); |
|
Spring.SetPosition(1, balloonAttachPoint); |
|
} |
|
} |
|
} |
|
}
|
|
|