A Kerbal Space Program mod that adds balloon parts to create aerostats
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.

203 lines
9.0 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Aerostats
{
public class ModuleBalloon : PartModule
{
/// <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 MaxVolume = 15000;
/// <summary>
/// Drag coefficient of the balloon
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float DragCoeff = 0.3f;
public float GasMolarMass { get; private set; }
/// <summary>
/// Mass of the empty balloon, in kg
/// </summary>
public float EmptyMass { get; private set; }
public float LiftingGasQuantity { get; private set; }
public float CurrentVolume { get; private set; }
public float Lift { 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; }
public ModuleBalloonCase AttachedCase { get; private set; }
/// <summary>
/// Must be called right after creating a balloon
/// </summary>
public void Initialize(ModuleBalloonCase attachedCase, Vector3 initialVelocity, float gasDensity, float initialGasQuantity)
{
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);
var meshFilter = gameObject.GetComponentInChildren<MeshFilter>();
meshFilter.mesh = sphere.GetComponent<MeshFilter>().mesh;
var t = meshFilter.transform;
while (t != transform)
{
t.localPosition = new Vector3(0, 0, 0);
t.localRotation = Quaternion.identity;
t.localScale = new Vector3(1, 1, 1);
t = t.parent;
}
var colliderObject = part.collider.gameObject;
Destroy(colliderObject.collider);
var collider = colliderObject.AddComponent<SphereCollider>();
collider.radius = 0.5f;
Destroy(sphere);
gameObject.AddComponent<Rigidbody>();
rigidbody.mass = EmptyMass * 0.001f;
part.mass = rigidbody.mass;
rigidbody.velocity = initialVelocity;
rigidbody.position = transform.position;
part.rb = rigidbody;
InjectGas(initialGasQuantity);
part.OnJustAboutToBeDestroyed += OnPartDestroyed;
}
public void OnDetached()
{
AttachedCase = null;
}
private void OnPartDestroyed()
{
if(AttachedCase != null)
AttachedCase.Cut();
part.OnJustAboutToBeDestroyed -= OnPartDestroyed;
}
/// <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.PostDebugSingleScreenMessage("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 = Mathf.Max(externalTemperature, 20.0f);
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 / MaxVolume;
Util.PostDebugSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%");
float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body);
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 = Mathf.Max(Radius * 2.0f, 0.1f);
if(AttachedCase != null)
{
float dist = Vector3.Distance(AttachedCase.transform.position, transform.position);
scale = Mathf.Min(Mathf.Max(dist, transform.localScale.x * 0.5f) - 0.005f, scale * 0.5f) * 2.0f; // to prevent physics explosion due to interpenetration of objects, don't scale the balloon more than the space available between the case and the balloon (this happens when expanding fast after deploy in vaccum for example)
}
transform.localScale = new Vector3(scale, scale, scale);
rigidbody.mass = (EmptyMass + balloonGasMass) * 0.001f;
part.mass = rigidbody.mass;
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 = Mathf.Max(externalTemperature, 20.0f);
float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f);
Util.PostDebugSingleScreenMessage("external atmo", "Temperature = " + externalTemperature + "K, Pressure=" + externalPressure + "Pa");
float currentMaxQuantity = MaxVolume / GasUtilities.R / balloonInternalTemperature * externalPressure / GasUtilities.StpVolumeToMoles;
return currentMaxQuantity;
}
private void FixedUpdate()
{
Vector3d worldPos = vessel.GetWorldPos3D();
CelestialBody body = vessel.mainBody;
rigidbody.WakeUp();
InjectGas(0); // update security valve venting
var gravityAccel = FlightGlobals.getGeeForceAtPosition(rigidbody.position);
//Util.PostSingleScreenMessage("gravity", "Gravity accel = (" + gravityAccel.x + ", " + gravityAccel.y + ", " + gravityAccel.z + ")");
float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body);
float externalPressure = (float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f;
float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body);
rigidbody.angularDrag = Mathf.Max(0.5f, 10.0f * externalPressure / GasUtilities.StandardPressure);
if (externalPressure > 0.00001f)
{
// lift
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 * DragCoeff * (Mathf.PI * Radius * Radius);
Util.PostDebugSingleScreenMessage("balloon drag", "Drag = " + dragForce + "N");
rigidbody.AddForce(-airVelocity.normalized * dragForce / 1000.0f, ForceMode.Force);
}
}
}
}