diff --git a/FerramAerospaceResearch/FARAPI.cs b/FerramAerospaceResearch/FARAPI.cs index bddcfc6d3..b6e0e101d 100644 --- a/FerramAerospaceResearch/FARAPI.cs +++ b/FerramAerospaceResearch/FARAPI.cs @@ -382,6 +382,43 @@ public static bool VesselSpoilerSetting(Vessel v) return false; } + /// + /// Returns the current aerodynamic force being experienced by the vehicle in world space + /// + /// The vessel that force is being queried + /// The force on the vessel in world space + public static Vector3 VesselAerodynamicForce(Vessel v) + { + return VesselFlightInfo(v)?.InfoParameters.aerodynamicForce ?? Vector3.zero; + } + + /// + /// Returns the current aerodynamic torque being experienced by the vehicle in world space + /// + /// The vessel that force is being queried + /// The torque on the vessel in world space + public static Vector3 VesselAerodynamicTorque(Vessel v) + { + return VesselFlightInfo(v)?.InfoParameters.aerodynamicTorque ?? Vector3.zero; + } + + /// + /// Returns the current aerodynamic force being experienced by the active vehicle in world space + /// + /// The force on the vessel in world space + public static Vector3 ActiveVesselAerodynamicForce() + { + return VesselAerodynamicForce(FlightGlobals.ActiveVessel); + } + + /// + /// Returns the current aerodynamic torque being experienced by the active vehicle in world space + /// + /// The torque on the vessel in world space + public static Vector3 ActiveVesselAerodynamicTorque() + { + return VesselAerodynamicTorque(FlightGlobals.ActiveVessel); + } #endregion #region AeroPredictions diff --git a/FerramAerospaceResearch/FARAeroComponents/FARAeroSection.cs b/FerramAerospaceResearch/FARAeroComponents/FARAeroSection.cs index e46c381fa..10df139c2 100644 --- a/FerramAerospaceResearch/FARAeroComponents/FARAeroSection.cs +++ b/FerramAerospaceResearch/FARAeroComponents/FARAeroSection.cs @@ -49,6 +49,7 @@ You should have received a copy of the GNU General Public License using UnityEngine; using FerramAerospaceResearch.FARPartGeometry; using FerramAerospaceResearch.FARUtils; +using ferram4; namespace FerramAerospaceResearch.FARAeroComponents { @@ -285,182 +286,105 @@ public void ClearAeroSection() handledAeroModulesIndexDict.Clear(); } - public void PredictionCalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag, Vector3 vel, ferram4.FARCenterQuery center) + #region Force application contexts + private interface IForceContext { - if (partData.Count == 0) - return; + /// + /// The part-relative velocity of the part whose force is being computed + /// + /// The part data for which to compute the local velocity + Vector3 LocalVelocity(PartData pd); + /// + /// Apply a calculated force to a part. + /// + /// The part data of the part that the force should be applied to + /// The local velocity of the part + /// The calculated force vector to be applied to the part + /// The calculated torque vector to be applied to the part + void ApplyForce(PartData pd, Vector3 localVel, Vector3 forceVector, Vector3 torqueVector); + } - PartData data = partData[0]; - FARAeroPartModule aeroModule = null; - for (int i = 0; i < partData.Count; i++) + private class SimulatedForceContext : IForceContext + { + /// + /// The world-space velocity of the part whose force is being simulated + /// + private Vector3 worldVel; + + /// + /// The center with which force should be accumulated + /// + private ferram4.FARCenterQuery center; + + /// + /// The atmospheric density that the force is being simulated at + /// + private float atmDensity; + + public SimulatedForceContext(Vector3 worldVel, FARCenterQuery center, float atmDensity) { - data = partData[i]; - aeroModule = data.aeroModule; - if (aeroModule.part == null || aeroModule.part.partTransform == null) - { - continue; - } - break; + this.worldVel = worldVel; + this.center = center; + this.atmDensity = atmDensity; } - if (aeroModule == null || aeroModule.part == null || aeroModule.part.transform == null) + + public void UpdateSimulationContext(Vector4 worldVel, FARCenterQuery center, float atmDensity) { - return; + this.worldVel = worldVel; + this.center = center; + this.atmDensity = atmDensity; } - double skinFrictionForce = skinFrictionDrag * xForceSkinFriction.Evaluate(machNumber); //this will be the same for each part, so why recalc it multiple times? - double xForceAoA0 = xForcePressureAoA0.Evaluate(machNumber); - double xForceAoA180 = xForcePressureAoA180.Evaluate(machNumber); - - Vector3 xRefVector = data.xRefVectorPartSpace; - Vector3 nRefVector = data.nRefVectorPartSpace; - - Vector3 velLocal = aeroModule.part.partTransform.worldToLocalMatrix.MultiplyVector(vel); - Vector3 angVelLocal = aeroModule.partLocalAngVel; - - //Vector3 angVelLocal = aeroModule.partLocalAngVel; - - //velLocal += Vector3.Cross(angVelLocal, data.centroidPartSpace); //some transform issue here, needs investigation - Vector3 velLocalNorm = velLocal.normalized; - - Vector3 localNormalForceVec = Vector3.ProjectOnPlane(-velLocalNorm, xRefVector).normalized; - double cosAoA = Vector3.Dot(xRefVector, velLocalNorm); - double cosSqrAoA = cosAoA * cosAoA; - double sinSqrAoA = Math.Max(1 - cosSqrAoA, 0); - double sinAoA = Math.Sqrt(sinSqrAoA); - double sin2AoA = 2 * sinAoA * Math.Abs(cosAoA); - double cosHalfAoA = Math.Sqrt(0.5 + 0.5 * Math.Abs(cosAoA)); - - - double nForce = 0; - nForce = potentialFlowNormalForce * Math.Sign(cosAoA) * cosHalfAoA * sin2AoA; //potential flow normal force - if (nForce < 0) //potential flow is not significant over the rear face of things - nForce = 0; - //if (machNumber > 3) - // nForce *= 2d - machNumber * 0.3333333333333333d; - - float normalForceFactor = Math.Abs(Vector3.Dot(localNormalForceVec, nRefVector)); - normalForceFactor *= normalForceFactor; - - normalForceFactor = invFlatnessRatio * (1 - normalForceFactor) + flatnessRatio * normalForceFactor; //accounts for changes in relative flatness of shape - - - float crossFlowMach, crossFlowReynolds; - crossFlowMach = machNumber * (float)sinAoA; - crossFlowReynolds = reynoldsPerUnitLength * diameter * (float)sinAoA / normalForceFactor; - - nForce += viscCrossflowDrag * sinSqrAoA * CalculateCrossFlowDrag(crossFlowMach, crossFlowReynolds); //viscous crossflow normal force - - nForce *= normalForceFactor; - - double xForce = -skinFrictionForce * Math.Sign(cosAoA) * cosSqrAoA; - double localVelForce = xForce * pseudoKnudsenNumber; - xForce -= localVelForce; - - localVelForce = Math.Abs(localVelForce); - - float moment = (float)(cosAoA * sinAoA); - float dampingMoment = 4f * moment; - - - if (cosAoA > 0) + public Vector3 LocalVelocity(PartData pd) { - xForce += cosSqrAoA * xForceAoA0; - float momentFactor; - if (machNumber > 6) - momentFactor = hypersonicMomentForward; - else if (machNumber < 0.6) - momentFactor = 0.6f * hypersonicMomentBackward; - else - { - float tmp = (-0.185185185f * machNumber + 1.11111111111f); - momentFactor = tmp * hypersonicMomentBackward * 0.6f + (1 - tmp) * hypersonicMomentForward; - } - //if (machNumber < 1.5) - // momentFactor += hypersonicMomentBackward * (0.5f - machNumber * 0.33333333333333333333333333333333f) * 0.2f; - - moment *= momentFactor; - dampingMoment *= momentFactor; + if (pd.aeroModule.part == null || pd.aeroModule.part.partTransform == null) + return Vector3.zero; + return pd.aeroModule.part.partTransform.InverseTransformVector(worldVel); } - else + + public void ApplyForce(PartData pd, Vector3 localVel, Vector3 forceVector, Vector3 torqueVector) { - xForce += cosSqrAoA * xForceAoA180; - float momentFactor; //negative to deal with the ref vector facing the opposite direction, causing the moment vector to point in the opposite direction - if (machNumber > 6) - momentFactor = hypersonicMomentBackward; - else if (machNumber < 0.6) - momentFactor = 0.6f * hypersonicMomentForward; - else + var tmp = 0.0005 * Vector3.SqrMagnitude(localVel); + var dynamicPressurekPa = tmp * atmDensity; + var dragFactor = dynamicPressurekPa * Mathf.Max(PhysicsGlobals.DragCurvePseudoReynolds.Evaluate(atmDensity * Vector3.Magnitude(localVel)), 1.0f); + var liftFactor = dynamicPressurekPa; + + var localVelNorm = Vector3.Normalize(localVel); + Vector3 localForceTemp = Vector3.Dot(localVelNorm, forceVector) * localVelNorm; + var partLocalForce = (localForceTemp * (float)dragFactor + (forceVector - localForceTemp) * (float)liftFactor); + forceVector = pd.aeroModule.part.transform.TransformDirection(partLocalForce); + torqueVector = pd.aeroModule.part.transform.TransformDirection(torqueVector * (float)dynamicPressurekPa); + if (!float.IsNaN(forceVector.x) && !float.IsNaN(torqueVector.x)) { - float tmp = (-0.185185185f * machNumber + 1.11111111111f); - momentFactor = tmp * hypersonicMomentForward * 0.6f + (1 - tmp) * hypersonicMomentBackward; + Vector3 centroid = pd.aeroModule.part.transform.TransformPoint(pd.centroidPartSpace - pd.aeroModule.part.CoMOffset); + center.AddForce(centroid, forceVector); + center.AddTorque(torqueVector); } - //if (machNumber < 1.5) - // momentFactor += hypersonicMomentForward * (0.5f - machNumber * 0.33333333333333333333333333333333f) * 0.2f; - - moment *= momentFactor; - dampingMoment *= momentFactor; } - moment /= normalForceFactor; - dampingMoment = Math.Abs(dampingMoment) * 0.1f; - //dampingMoment += (float)Math.Abs(skinFrictionForce) * 0.1f; - float rollDampingMoment = (float)(skinFrictionForce * 0.5 * diameter); //skin friction force times avg moment arm for vehicle - rollDampingMoment *= (0.75f + flatnessRatio * 0.25f); //this is just an approximation for now - - Vector3 forceVector = (float)xForce * xRefVector + (float)nForce * localNormalForceVec; - forceVector -= (float)localVelForce * velLocalNorm; - - Vector3 torqueVector = Vector3.Cross(xRefVector, localNormalForceVec) * moment; - - Vector3 axialAngLocalVel = Vector3.Dot(xRefVector, angVelLocal) * xRefVector; - Vector3 nonAxialAngLocalVel = angVelLocal - axialAngLocalVel; - - if (velLocal.sqrMagnitude > 0.001f) - torqueVector -= (dampingMoment * nonAxialAngLocalVel) + (rollDampingMoment * axialAngLocalVel * axialAngLocalVel.magnitude) / velLocal.sqrMagnitude; - else - torqueVector -= (dampingMoment * nonAxialAngLocalVel) + (rollDampingMoment * axialAngLocalVel * axialAngLocalVel.magnitude) / 0.001f; - - Matrix4x4 localToWorld = aeroModule.part.partTransform.localToWorldMatrix; - - float dynPresAndScaling = 0.0005f * atmDensity * velLocal.sqrMagnitude; //dyn pres and N -> kN conversion - - forceVector *= dynPresAndScaling; - torqueVector *= dynPresAndScaling; - - forceVector = localToWorld.MultiplyVector(forceVector); - torqueVector = localToWorld.MultiplyVector(torqueVector); - Vector3 centroid = Vector3.zero; + } - if (!float.IsNaN(forceVector.x) && !float.IsNaN(torqueVector.x)) + private class FlightForceContext : IForceContext + { + public Vector3 LocalVelocity(PartData pd) { - for (int i = 0; i < partData.Count; i++) - { - PartData data2 = partData[i]; - FARAeroPartModule module = data2.aeroModule; - if ((object)module == null) - continue; - - if (module.part == null || module.part.partTransform == null) - { - continue; - } + return pd.aeroModule.partLocalVel; + } - centroid = module.part.partTransform.localToWorldMatrix.MultiplyPoint3x4(data2.centroidPartSpace); - center.AddForce(centroid, forceVector * data2.dragFactor); - } - center.AddTorque(torqueVector); + public void ApplyForce(PartData pd, Vector3 localVel, Vector3 forceVector, Vector3 torqueVector) + { + pd.aeroModule.AddLocalForceAndTorque(forceVector, torqueVector, pd.centroidPartSpace); } - else - FARLogger.Error("NaN Prediction Section Error: Inputs: AtmDen: " + atmDensity + " Mach: " + machNumber + " Re: " + reynoldsPerUnitLength + " Kn: " + pseudoKnudsenNumber + " skin: " + skinFrictionDrag + " vel: " + vel); } + #endregion - public void FlightCalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag) + private void CalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag, + IForceContext forceContext) { - double skinFrictionForce = skinFrictionDrag * xForceSkinFriction.Evaluate(machNumber); //this will be the same for each part, so why recalc it multiple times? double xForceAoA0 = xForcePressureAoA0.Evaluate(machNumber); double xForceAoA180 = xForcePressureAoA180.Evaluate(machNumber); - for(int i = 0; i < partData.Count; i++) + for (int i = 0; i < partData.Count; i++) { PartData data = partData[i]; FARAeroPartModule aeroModule = data.aeroModule; @@ -472,7 +396,12 @@ public void FlightCalculateAeroForces(float atmDensity, float machNumber, float Vector3 xRefVector = data.xRefVectorPartSpace; Vector3 nRefVector = data.nRefVectorPartSpace; - Vector3 velLocal = aeroModule.partLocalVel; + Vector3 velLocal = forceContext.LocalVelocity(data); + // Rejects both negligable speed and invalid simulation cases + if (FARMathUtil.NearlyEqual(velLocal.sqrMagnitude, 0.0f)) + { + continue; + } Vector3 angVelLocal = aeroModule.partLocalAngVel; @@ -490,7 +419,7 @@ public void FlightCalculateAeroForces(float atmDensity, float machNumber, float double nForce = 0; - nForce = potentialFlowNormalForce * Math.Sign(cosAoA) * cosHalfAoA * sin2AoA; //potential flow normal force + nForce = potentialFlowNormalForce * Math.Sign(cosAoA) * cosHalfAoA * sin2AoA; //potential flow normal force if (nForce < 0) //potential flow is not significant over the rear face of things nForce = 0; @@ -585,9 +514,23 @@ public void FlightCalculateAeroForces(float atmDensity, float machNumber, float forceVector *= data.dragFactor; torqueVector *= data.dragFactor; - aeroModule.AddLocalForceAndTorque(forceVector, torqueVector, data.centroidPartSpace); + forceContext.ApplyForce(data, velLocal, forceVector, torqueVector); } } + + private SimulatedForceContext simContext = new SimulatedForceContext(Vector3.zero, new FARCenterQuery(), 0.0f); + public void PredictionCalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag, Vector3 vel, ferram4.FARCenterQuery center) + { + simContext.UpdateSimulationContext(vel, center, atmDensity); + CalculateAeroForces(atmDensity, machNumber, reynoldsPerUnitLength, pseudoKnudsenNumber, skinFrictionDrag, simContext); + } + + private FlightForceContext flightContext = new FlightForceContext(); + public void FlightCalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag) + { + CalculateAeroForces(atmDensity, machNumber, reynoldsPerUnitLength, pseudoKnudsenNumber, skinFrictionDrag, flightContext); + + } public static void GenerateCrossFlowDragCurve() { diff --git a/FerramAerospaceResearch/FARAeroComponents/FARVesselAero.cs b/FerramAerospaceResearch/FARAeroComponents/FARVesselAero.cs index da1204b8c..7088d6efc 100644 --- a/FerramAerospaceResearch/FARAeroComponents/FARVesselAero.cs +++ b/FerramAerospaceResearch/FARAeroComponents/FARVesselAero.cs @@ -310,6 +310,7 @@ private void CalculateAndApplyVesselAeroProperties() public void SimulateAeroProperties(out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude) { FARCenterQuery center = new FARCenterQuery(); + FARCenterQuery dummy = new FARCenterQuery(); float pressure; float density; @@ -337,7 +338,7 @@ public void SimulateAeroProperties(out Vector3 aeroForce, out Vector3 aeroTorque float skinFriction = (float)FARAeroUtil.SkinFrictionDrag(reynoldsNumber, machNumber); float pseudoKnudsenNumber = machNumber / (reynoldsNumber + machNumber); - + if (_currentAeroSections != null) { for (int i = 0; i < _currentAeroSections.Count; i++) @@ -351,7 +352,7 @@ public void SimulateAeroProperties(out Vector3 aeroForce, out Vector3 aeroTorque { FARWingAerodynamicModel curWing = _legacyWingModels[i]; if ((object)curWing != null) - curWing.PrecomputeCenterOfLift(velocityWorldVector, machNumber, density, center); + center.AddForce(curWing.transform.position, curWing.PrecomputeCenterOfLift(velocityWorldVector, machNumber, density, dummy)); } } diff --git a/FerramAerospaceResearch/FARGUI/FARFlightGUI/PhysicsCalcs.cs b/FerramAerospaceResearch/FARGUI/FARFlightGUI/PhysicsCalcs.cs index 2b55916bb..eacdf2801 100644 --- a/FerramAerospaceResearch/FARGUI/FARFlightGUI/PhysicsCalcs.cs +++ b/FerramAerospaceResearch/FARGUI/FARFlightGUI/PhysicsCalcs.cs @@ -61,7 +61,7 @@ class PhysicsCalcs List _currentAeroModules; List _LEGACY_currentWingAeroModel = new List(); - Vector3 totalAeroForceVector; + FARCenterQuery aeroForces = new FARCenterQuery(); int intakeAirId; double intakeAirDensity = 1; bool useWingArea; @@ -124,7 +124,8 @@ public VesselFlightInfo UpdatePhysicsParameters() private void CalculateTotalAeroForce() { - totalAeroForceVector = Vector3.zero; + aeroForces.ClearAll(); + if (_vessel.dynamicPressurekPa <= 0.00001) return; @@ -134,17 +135,26 @@ private void CalculateTotalAeroForce() for (int i = 0; i < _currentAeroModules.Count; i++) { FARAeroPartModule m = _currentAeroModules[i]; - if ((object)m != null) - totalAeroForceVector += m.totalWorldSpaceAeroForce; + if ((object)m != null) { + aeroForces.AddForce(m.transform.position, m.totalWorldSpaceAeroForce); + aeroForces.AddTorque(m.worldSpaceTorque); + } } } - /*for (int i = 0; i < _LEGACY_currentWingAeroModel.Count; i++) + /* + for (int i = 0; i < _LEGACY_currentWingAeroModel.Count; i++) { FARWingAerodynamicModel w = _LEGACY_currentWingAeroModel[i]; - if ((object)w != null) - totalAeroForceVector += w.worldSpaceForce; - }*/ + if ((object)w == null) + continue; + totalAeroForceVector += w.worldSpaceForce; + aeroForces.AddForce(w.AerodynamicCenter, w.worldSpaceForce); + + totalAeroForceVector += w.worldSpaceForce; + totalAeroTorqueVector += Vector3.Cross(w.AerodynamicCenter - _vessel.CoM, w.worldSpaceForce); + } + */ /*for(int i = 0; i < _vessel.parts.Count; i++) { @@ -171,10 +181,15 @@ private void CalculateForceBreakdown(Vector3d velVectorNorm, Vector3d velVector) vesselInfo.liftToDragRatio = 0; return; } + + var com_frc = aeroForces.force; + var com_trq = aeroForces.TorqueAt(_vessel.CoM); - vesselInfo.dragForce = -Vector3d.Dot(totalAeroForceVector, velVectorNorm); //reverse along vel normal will be drag + vesselInfo.aerodynamicForce = com_frc; + vesselInfo.aerodynamicTorque = com_trq; + vesselInfo.dragForce = -Vector3d.Dot(com_frc, velVectorNorm); //reverse along vel normal will be drag - Vector3d remainderVector = totalAeroForceVector + velVectorNorm * vesselInfo.dragForce; + Vector3d remainderVector = com_frc + velVectorNorm * vesselInfo.dragForce; vesselInfo.liftForce = -Vector3d.Dot(remainderVector, _vessel.ReferenceTransform.forward); //forward points down for the vessel, so reverse along that will be lift vesselInfo.sideForce = Vector3d.Dot(remainderVector, _vessel.ReferenceTransform.right); //and the side force diff --git a/FerramAerospaceResearch/FARGUI/FARFlightGUI/VesselFlightInfo.cs b/FerramAerospaceResearch/FARGUI/FARFlightGUI/VesselFlightInfo.cs index 0b8eea391..72aef8a93 100644 --- a/FerramAerospaceResearch/FARGUI/FARFlightGUI/VesselFlightInfo.cs +++ b/FerramAerospaceResearch/FARGUI/FARFlightGUI/VesselFlightInfo.cs @@ -46,12 +46,14 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Linq; using System.Text; +using UnityEngine; namespace FerramAerospaceResearch.FARGUI.FARFlightGUI { public struct VesselFlightInfo { public double liftForce, dragForce, sideForce; + public Vector3 aerodynamicForce, aerodynamicTorque; public double dynPres; public double liftCoeff, dragCoeff, sideCoeff; @@ -75,6 +77,6 @@ public struct VesselFlightInfo public double ballisticCoeff; public double termVelEst; - public double stallFraction; + public double stallFraction; } }