diff --git a/Plugin/ModuleAerostat.cs b/Plugin/ModuleAerostat.cs index 737fb2c..49337a0 100644 --- a/Plugin/ModuleAerostat.cs +++ b/Plugin/ModuleAerostat.cs @@ -16,6 +16,8 @@ namespace Aerostats // 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; /// @@ -60,9 +62,34 @@ namespace Aerostats /// 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. /// [KSPField(isPersistant = true, guiActive = true)] - [UI_FloatRange(minValue = 0.0f, maxValue = 20000.0f, stepIncrement = 1.0f)] + [UI_FloatRange(minValue = 0, maxValue = 20000.0f, stepIncrement = 1.0f)] public float LiftingGasTargetQuantity; + /// + /// Mass of the empty balloon, in kg + /// + [KSPField(isPersistant = false, guiActive = false)] + public float BalloonEmptyMass = 50.0f; + + /// + /// 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. + /// + [KSPField(isPersistant = false, guiActive = false)] + public float MinimumFillQuantity = 75.0f; + + /// + /// Drag coefficient of the balloon + /// + [KSPField(isPersistant = false, guiActive = false)] + public float BalloonDragCoeff = 0.3f; + + /// + /// Force applied by the spring between the balloon and the vessel, proportional to spring extension, in newton per meter + /// + [KSPField(isPersistant = false, guiActive = false)] + public float SpringHardness = 20000.0f; + private bool Staged; /// @@ -76,6 +103,8 @@ namespace Aerostats /// private float Inflation = 0; + private GameObject Balloon; + private void PlayAnimation(string animationName, float animationSpeed) { foreach (Animation animation in part.FindModelAnimators(animationName)) @@ -117,12 +146,22 @@ namespace Aerostats { UnityEngine.Debug.Log("Aerostats: staged"); ScreenMessages.PostScreenMessage("staged"); - PlayAnimation(DeployAnimation, 1.0f); + //PlayAnimation(DeployAnimation, 1.0f); + + Balloon = GameObject.CreatePrimitive(PrimitiveType.Sphere); + Balloon.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up; + Balloon.AddComponent(); + Balloon.rigidbody.mass = BalloonEmptyMass; + + Balloon.rigidbody.angularDrag = 10.0f; + + LiftingGasQuantity = Math.Min(MinimumFillQuantity, RemainingCompressedGas); + RemainingCompressedGas -= LiftingGasQuantity; } private void FixedUpdate() { - if (!Staged && part.inverseStage >= Staging.CurrentStage) + if (!Staged && GameSettings.LAUNCH_STAGES.GetKeyDown() && vessel.isActiveVessel && (part.inverseStage == Staging.CurrentStage - 1 || Staging.CurrentStage == 0)) { Staged = true; OnStaged(); @@ -130,21 +169,25 @@ namespace Aerostats if (Staged) { - if (!IsAnimationPlaying(DeployAnimation)) + //if (!IsAnimationPlaying(DeployAnimation)) { float externalTemperature = (float)FlightGlobals.getExternalTemperature(); float balloonInternalTemperature = externalTemperature; - float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure() * 1000, 0.00001f); + 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 += step; RemainingCompressedGas -= step; + + // gas exhausted if(RemainingCompressedGas < 0) { Util.PostSingleScreenMessage("out of gas", "Lifting gas reserve exhausted"); @@ -154,6 +197,7 @@ namespace Aerostats } else { + // deflating balloon LiftingGasQuantity = Math.Max(LiftingGasQuantity - MaxGasVentRate * Time.fixedDeltaTime, LiftingGasTargetQuantity); } @@ -170,16 +214,56 @@ namespace Aerostats Inflation = currentGasVolume / MaxBalloonVolume; Util.PostSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%"); - SetInflation(Inflation); + //SetInflation(Inflation); float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, vessel.mainBody); - float balloonLift = currentGasVolume * (airDensity - GasDensity); - Util.PostSingleScreenMessage("lift", "Air density = " + airDensity + "kg/m^3, Lift = " + balloonLift + "kg"); + 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 + ")"); - part.Rigidbody.AddForce(-gravityAccel * balloonLift / 1000.0f, ForceMode.Force); + + // V = 4/3*pi*r^3 + float radius = Mathf.Pow(currentGasVolume * 0.75f / Mathf.PI, 0.333f); + float scale = radius + 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 = 2.0f + radius; + var springVec = Balloon.rigidbody.position - part.rigidbody.position; + float springLength = springVec.magnitude; + float springForceMag = 0; + if(springLength > restLength) + { + float tensingLength = springLength - restLength; + springForceMag = tensingLength * SpringHardness; + var springForce = springVec * (springForceMag / springLength * 0.001f); + part.rigidbody.AddForce(springForce, ForceMode.Force); + Balloon.rigidbody.AddForceAtPosition(-springForce, Balloon.rigidbody.position - Balloon.transform.up * radius, ForceMode.Force); + } + Util.PostSingleScreenMessage("spring force", "Spring force = " + springForceMag + "N"); } } } + + public void Update() + { + if (Balloon != null) + { + Gizmos.DrawLine(part.transform.position, Balloon.transform.position - Balloon.transform.up * Balloon.transform.localScale.x); + } + } } } diff --git a/doc/references/balloon_almost_empty.jpg b/doc/references/balloon_almost_empty.jpg new file mode 100644 index 0000000..6cb9e72 Binary files /dev/null and b/doc/references/balloon_almost_empty.jpg differ diff --git a/doc/references/balloon_full.jpg b/doc/references/balloon_full.jpg new file mode 100644 index 0000000..80286f9 Binary files /dev/null and b/doc/references/balloon_full.jpg differ