public void SimulateAeroProperties( out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude ) { var center = new FARCenterQuery(); var dummy = new FARCenterQuery(); //Calculate main gas properties GasProperties properties = FARAtmosphere.GetGasProperties(vessel, altitude, Planetarium.GetUniversalTime()); if (properties.Pressure <= 0 || properties.Temperature <= 0) { aeroForce = Vector3.zero; aeroTorque = Vector3.zero; return; } float velocityMag = velocityWorldVector.magnitude; float machNumber = (float)(velocityMag / properties.SpeedOfSound); float reynoldsNumber = (float)FARAeroUtil.CalculateReynoldsNumber(properties.Density, Length, velocityMag, machNumber, properties.Temperature, properties.AdiabaticIndex); float reynoldsPerLength = reynoldsNumber / (float)Length; float skinFriction = (float)FARAeroUtil.SkinFrictionDrag(reynoldsNumber, machNumber); float pseudoKnudsenNumber = machNumber / (reynoldsNumber + machNumber); if (_currentAeroSections != null) { foreach (FARAeroSection curSection in _currentAeroSections) { curSection?.PredictionCalculateAeroForces((float)properties.Density, machNumber, reynoldsPerLength, pseudoKnudsenNumber, skinFriction, velocityWorldVector, center); } foreach (FARWingAerodynamicModel curWing in _legacyWingModels) { if (curWing != null) { center.AddForce(curWing.transform.position, curWing.PrecomputeCenterOfLift(velocityWorldVector, machNumber, properties.Density, dummy)); } } } aeroForce = center.force; aeroTorque = center.TorqueAt(vessel.CoM); }
public void GetClCdCmSteady( InstantConditionSimInput input, out InstantConditionSimOutput output, bool clear, bool reset_stall = false ) { output = new InstantConditionSimOutput(); double area = 0; double MAC = 0; double b_2 = 0; Vector3d forward = Vector3.forward; Vector3d up = Vector3.up; Vector3d right = Vector3.right; Vector3d CoM = Vector3d.zero; if (EditorDriver.editorFacility == EditorFacility.VAB) { forward = Vector3.up; up = -Vector3.forward; } double mass = 0; List <Part> partsList = EditorLogic.SortedShipList; foreach (Part p in partsList) { if (FARAeroUtil.IsNonphysical(p)) { continue; } double partMass = p.mass; if (p.Resources.Count > 0) { partMass += p.GetResourceMass(); } // If you want to use GetModuleMass, you need to start from p.partInfo.mass, not p.mass CoM += partMass * (Vector3d)p.transform.TransformPoint(p.CoMOffset); mass += partMass; } CoM /= mass; // Rodhern: The original reference directions (velocity, liftVector, sideways) did not form an orthonormal // basis. That in turn produced some counterintuitive calculation results, such as coupled yaw and pitch // derivatives. A more thorough discussion of the topic can be found on the KSP forums: // https://forum.kerbalspaceprogram.com/index.php?/topic/19321-131-ferram-aerospace-research-v01591-liepmann-4218/&do=findComment&comment=2781270 // The reference directions have been replaced by new ones that are orthonormal by construction. // In dkavolis branch Vector3.Cross() and Vector3d.Normalize() are used explicitly. There is no apparent // benefit to this other than possibly improved readability. double sinAlpha = Math.Sin(input.alpha * Math.PI / 180); double cosAlpha = Math.Sqrt(Math.Max(1 - sinAlpha * sinAlpha, 0)); double sinBeta = Math.Sin(input.beta * Math.PI / 180); double cosBeta = Math.Sqrt(Math.Max(1 - sinBeta * sinBeta, 0)); double sinPhi = Math.Sin(input.phi * Math.PI / 180); double cosPhi = Math.Sqrt(Math.Max(1 - sinPhi * sinPhi, 0)); double alphaDot = input.alphaDot * Math.PI / 180; double betaDot = input.betaDot * Math.PI / 180; double phiDot = input.phiDot * Math.PI / 180; Vector3d velocity = forward * cosAlpha * cosBeta; velocity += right * (sinPhi * sinAlpha * cosBeta + cosPhi * sinBeta); velocity += -up * (cosPhi * sinAlpha * cosBeta - sinPhi * sinBeta); velocity.Normalize(); Vector3d liftDown = -forward * sinAlpha; liftDown += right * sinPhi * cosAlpha; liftDown += -up * cosPhi * cosAlpha; liftDown.Normalize(); Vector3d sideways = Vector3.Cross(velocity, liftDown); sideways.Normalize(); Vector3d angVel = forward * (phiDot - sinAlpha * betaDot); angVel += right * (cosPhi * alphaDot + cosAlpha * sinPhi * betaDot); angVel += up * (sinPhi * alphaDot - cosAlpha * cosPhi * betaDot); foreach (FARWingAerodynamicModel w in _wingAerodynamicModel) { if (!(w && w.part)) { continue; } w.ComputeForceEditor(velocity, input.machNumber, 2); if (clear) { w.EditorClClear(reset_stall); } Vector3d relPos = w.GetAerodynamicCenter() - CoM; Vector3d vel = velocity + Vector3d.Cross(angVel, relPos); if (w is FARControllableSurface controllableSurface) { controllableSurface.SetControlStateEditor(CoM, vel, (float)input.pitchValue, 0, 0, input.flaps, input.spoilers); } else if (w.isShielded) { continue; } Vector3d force = w.ComputeForceEditor(vel.normalized, input.machNumber, 2) * 1000; output.Cl += -Vector3d.Dot(force, liftDown); output.Cy += Vector3d.Dot(force, sideways); output.Cd += -Vector3d.Dot(force, velocity); Vector3d moment = -Vector3d.Cross(relPos, force); output.Cm += Vector3d.Dot(moment, sideways); output.Cn += Vector3d.Dot(moment, liftDown); output.C_roll += Vector3d.Dot(moment, velocity); area += w.S; MAC += w.GetMAC() * w.S; b_2 += w.Getb_2() * w.S; } var center = new FARCenterQuery(); foreach (FARAeroSection aeroSection in _currentAeroSections) { aeroSection.PredictionCalculateAeroForces(2, (float)input.machNumber, 10000, 0, 0.005f, velocity.normalized, center); } Vector3d centerForce = center.force * 1000; output.Cl += -Vector3d.Dot(centerForce, liftDown); output.Cy += Vector3d.Dot(centerForce, sideways); output.Cd += -Vector3d.Dot(centerForce, velocity); Vector3d centerMoment = -center.TorqueAt(CoM) * 1000; output.Cm += Vector3d.Dot(centerMoment, sideways); output.Cn += Vector3d.Dot(centerMoment, liftDown); output.C_roll += Vector3d.Dot(centerMoment, velocity); if (area.NearlyEqual(0)) { area = _maxCrossSectionFromBody; b_2 = 1; MAC = _bodyLength; } double recipArea = 1 / area; MAC *= recipArea; b_2 *= recipArea; output.Cl *= recipArea; output.Cd *= recipArea; output.Cm *= recipArea / MAC; output.Cy *= recipArea; output.Cn *= recipArea / b_2; output.C_roll *= recipArea / b_2; }
void UpdateAerodynamicCenter() { FARCenterQuery aeroSection, dummy; aeroSection = new FARCenterQuery(); dummy = new FARCenterQuery(); if ((object)EditorLogic.RootPart == null) { return; } Vector3 vel_base, vel_fuzz; Transform rootPartTrans = EditorLogic.RootPart.partTransform; if (EditorDriver.editorFacility == EditorFacility.SPH) { vel_base = Vector3.forward; vel_fuzz = 0.02f * Vector3.up; } else { vel_base = Vector3.up; vel_fuzz = -0.02f * Vector3.forward; } Vector3 vel = (vel_base - vel_fuzz).normalized; for (int i = 0; i < _currentAeroSections.Count; i++) { FARAeroSection section = _currentAeroSections[i]; section.PredictionCalculateAeroForces(1, 0.5f, 100000, 0, 0.005f, vel, aeroSection); } FARBaseAerodynamics.PrecomputeGlobalCenterOfLift(aeroSection, dummy, vel); Vector3 pos = Vector3.zero;//rootPartTrans.position; float mass = 0; for (int i = 0; i < EditorLogic.SortedShipList.Count; i++) { Part p = EditorLogic.SortedShipList[i]; float tmpMass = p.mass + p.GetResourceMass(); mass += tmpMass; pos += p.partTransform.position * tmpMass; } pos /= mass; Vector3 avgForcePos = Vector3.zero; Vector3 force0, moment0; force0 = aeroSection.force; moment0 = aeroSection.TorqueAt(pos); avgForcePos += aeroSection.GetPos(); //aeroSection.force = -aeroSection.force; //aeroSection.torque = -aeroSection.torque; aeroSection.ClearAll(); vel = (vel_base + vel_fuzz).normalized; for (int i = 0; i < _currentAeroSections.Count; i++) { FARAeroSection section = _currentAeroSections[i]; section.PredictionCalculateAeroForces(1, 0.5f, 100000, 0, 0.005f, vel, aeroSection); } FARBaseAerodynamics.PrecomputeGlobalCenterOfLift(aeroSection, dummy, vel); Vector3 force1, moment1; force1 = aeroSection.force; moment1 = aeroSection.TorqueAt(pos); avgForcePos += aeroSection.GetPos(); aeroSection.ClearAll(); avgForcePos *= 0.5f; Vector3 deltaForce = force1 - force0; Vector3 deltaMoment = moment1 - moment0; Vector3 deltaForcePerp = Vector3.ProjectOnPlane(deltaForce, vel_base); float deltaForcePerpMag = deltaForcePerp.magnitude; Vector3 deltaForcePerpNorm = deltaForcePerp / deltaForcePerpMag; Vector3 deltaMomentPerp = deltaMoment - Vector3.Dot(deltaMoment, deltaForcePerpNorm) * deltaForcePerpNorm - Vector3.Project(deltaMoment, vel_base); //float dist = deltaMomentPerp.magnitude / deltaForcePerpMag; //vesselRootLocalAeroCenter = vel_base * dist; vesselRootLocalAeroCenter = deltaMomentPerp.magnitude / deltaForcePerpMag * vel_base * Math.Sign(Vector3.Dot(Vector3.Cross(deltaForce, deltaMoment), vel_base)); //FARLogger.Info("" + dist + " " + deltaMomentPerp.magnitude + " " + deltaForcePerpMag); //vesselRootLocalAeroCenter += avgForcePos; //avgForcePos = rootPartTrans.worldToLocalMatrix.MultiplyPoint3x4(avgForcePos); //vesselRootLocalAeroCenter += Vector3.ProjectOnPlane(avgForcePos, Vector3.up); //vesselRootLocalAeroCenter = aeroSection.GetPos(); vesselRootLocalAeroCenter += pos;//Vector3.ProjectOnPlane(avgForcePos - pos, vesselRootLocalAeroCenter) + pos; vesselRootLocalAeroCenter = rootPartTrans.worldToLocalMatrix.MultiplyPoint3x4(vesselRootLocalAeroCenter); }
public void GetClCdCmSteady(InstantConditionSimInput input, out InstantConditionSimOutput output, bool clear, bool reset_stall = false) { output = new InstantConditionSimOutput(); double area = 0; double MAC = 0; double b_2 = 0; Vector3d forward = Vector3.forward; Vector3d up = Vector3.up; Vector3d right = Vector3.right; Vector3d CoM = Vector3d.zero; double mass = 0; List <Part> partsList = EditorLogic.SortedShipList; for (int i = 0; i < partsList.Count; i++) { Part p = partsList[i]; if (FARAeroUtil.IsNonphysical(p)) { continue; } double partMass = p.mass; if (p.Resources.Count > 0) { partMass += p.GetResourceMass(); } //partMass += p.GetModuleMass(p.mass); // If you want to use GetModuleMass, you need to start from p.partInfo.mass, not p.mass CoM += partMass * (Vector3d)p.transform.TransformPoint(p.CoMOffset); mass += partMass; } CoM /= mass; if (EditorDriver.editorFacility == EditorFacility.VAB) { forward = Vector3.up; up = -Vector3.forward; } double sinAlpha = Math.Sin(input.alpha * Math.PI / 180); double cosAlpha = Math.Sqrt(Math.Max(1 - sinAlpha * sinAlpha, 0)); double sinBeta = Math.Sin(input.beta * Math.PI / 180); double cosBeta = Math.Sqrt(Math.Max(1 - sinBeta * sinBeta, 0)); double sinPhi = Math.Sin(input.phi * Math.PI / 180); double cosPhi = Math.Sqrt(Math.Max(1 - sinPhi * sinPhi, 0)); double alphaDot = input.alphaDot * Math.PI / 180; double betaDot = input.betaDot * Math.PI / 180; double phiDot = input.phiDot * Math.PI / 180; Vector3d AngVel = (phiDot - sinAlpha * betaDot) * forward; AngVel += (cosPhi * alphaDot + cosAlpha * sinPhi * betaDot) * right; AngVel += (sinPhi * alphaDot - cosAlpha * cosPhi * betaDot) * up; Vector3d velocity = forward * cosAlpha * cosBeta; velocity += right * (sinPhi * cosAlpha * cosBeta + cosPhi * sinBeta); velocity += -up * cosPhi * (sinAlpha * cosBeta + sinBeta); velocity.Normalize(); //this is negative wrt the ground Vector3d liftVector = -forward * sinAlpha + right * sinPhi * cosAlpha - up * cosPhi * cosAlpha; Vector3d sideways = Vector3.Cross(velocity, liftVector).normalized; for (int i = 0; i < _wingAerodynamicModel.Count; i++) { FARWingAerodynamicModel w = _wingAerodynamicModel[i]; if (!(w && w.part)) { continue; } w.ComputeForceEditor(velocity.normalized, input.machNumber, 2); if (clear) { w.EditorClClear(reset_stall); } Vector3d relPos = w.GetAerodynamicCenter() - CoM; Vector3d vel = velocity + Vector3d.Cross(AngVel, relPos); if (w is FARControllableSurface) { (w as FARControllableSurface).SetControlStateEditor(CoM, vel, (float)input.pitchValue, 0, 0, input.flaps, input.spoilers); } else if (w.isShielded) { continue; } //w.ComputeForceEditor(velocity, input.machNumber); //do this just to get the AC right Vector3d force = w.ComputeForceEditor(vel.normalized, input.machNumber, 2) * 1000; output.Cl += -Vector3d.Dot(force, liftVector); output.Cy += Vector3d.Dot(force, sideways); output.Cd += -Vector3d.Dot(force, velocity); Vector3d moment = -Vector3d.Cross(relPos, force); output.Cm += Vector3d.Dot(moment, sideways); output.Cn += Vector3d.Dot(moment, liftVector); output.C_roll += Vector3d.Dot(moment, velocity); //w.ComputeClCdEditor(vel.normalized, input.machNumber); /*double tmpCl = w.GetCl() * w.S; * output.Cl += tmpCl * -Vector3d.Dot(w.GetLiftDirection(), liftVector); * output.Cy += tmpCl * -Vector3d.Dot(w.GetLiftDirection(), sideways); * double tmpCd = w.GetCd() * w.S; * output.Cd += tmpCd; * output.Cm += tmpCl * Vector3d.Dot((relPos), velocity) * -Vector3d.Dot(w.GetLiftDirection(), liftVector) + tmpCd * -Vector3d.Dot((relPos), liftVector); * output.Cn += tmpCd * Vector3d.Dot((relPos), sideways) + tmpCl * Vector3d.Dot((relPos), velocity) * -Vector3d.Dot(w.GetLiftDirection(), sideways); * output.C_roll += tmpCl * Vector3d.Dot((relPos), sideways) * -Vector3d.Dot(w.GetLiftDirection(), liftVector);*/ area += w.S; MAC += w.GetMAC() * w.S; b_2 += w.Getb_2() * w.S; } FARCenterQuery center = new FARCenterQuery(); for (int i = 0; i < _currentAeroSections.Count; i++) { _currentAeroSections[i].PredictionCalculateAeroForces(2, (float)input.machNumber, 10000, 0.005f, velocity.normalized, center); } Vector3d centerForce = center.force * 1000; output.Cl += -Vector3d.Dot(centerForce, liftVector); output.Cy += Vector3d.Dot(centerForce, sideways); output.Cd += -Vector3d.Dot(centerForce, velocity); Vector3d centerMoment = -center.TorqueAt(CoM) * 1000; output.Cm += Vector3d.Dot(centerMoment, sideways); output.Cn += Vector3d.Dot(centerMoment, liftVector); output.C_roll += Vector3d.Dot(centerMoment, velocity); /*for (int i = 0; i < FARAeroUtil.CurEditorParts.Count; i++) * { * Part p = FARAeroUtil.CurEditorParts[i]; * if (FARAeroUtil.IsNonphysical(p)) * continue; * * Vector3 part_pos = p.transform.TransformPoint(p.CoMOffset) - CoM; * double partMass = p.mass; * if (p.Resources.Count > 0) * partMass += p.GetResourceMass(); * * double stock_drag = partMass * p.maximum_drag * FlightGlobals.DragMultiplier * 1000; * output.Cd += stock_drag; * output.Cm += stock_drag * -Vector3d.Dot(part_pos, liftVector); * output.Cn += stock_drag * Vector3d.Dot(part_pos, sideways); * }*/ if (area == 0) { area = _maxCrossSectionFromBody; b_2 = 1; MAC = _bodyLength; } double recipArea = 1 / area; MAC *= recipArea; b_2 *= recipArea; output.Cl *= recipArea; output.Cd *= recipArea; output.Cm *= recipArea / MAC; output.Cy *= recipArea; output.Cn *= recipArea / b_2; output.C_roll *= recipArea / b_2; }
private void UpdateAerodynamicCenter() { var aeroSection = new FARCenterQuery(); var dummy = new FARCenterQuery(); if (EditorLogic.RootPart is null) { return; } Vector3 vel_base, vel_fuzz; Transform rootPartTrans = EditorLogic.RootPart.partTransform; if (EditorDriver.editorFacility == EditorFacility.SPH) { vel_base = Vector3.forward; vel_fuzz = 0.02f * Vector3.up; } else { vel_base = Vector3.up; vel_fuzz = -0.02f * Vector3.forward; } Vector3 vel = (vel_base - vel_fuzz).normalized; foreach (FARAeroSection section in _currentAeroSections) { section.PredictionCalculateAeroForces(1, 0.5f, 100000, 0, 0.005f, vel, aeroSection); } FARBaseAerodynamics.PrecomputeGlobalCenterOfLift(aeroSection, dummy, vel, 1); Vector3 pos = Vector3.zero; float mass = 0; foreach (Part p in EditorLogic.SortedShipList) { float tmpMass = p.mass + p.GetResourceMass(); mass += tmpMass; pos += p.partTransform.position * tmpMass; } pos /= mass; Vector3 force0 = aeroSection.force; Vector3 moment0 = aeroSection.TorqueAt(pos); aeroSection.ClearAll(); vel = (vel_base + vel_fuzz).normalized; foreach (FARAeroSection section in _currentAeroSections) { section.PredictionCalculateAeroForces(1, 0.5f, 100000, 0, 0.005f, vel, aeroSection); } FARBaseAerodynamics.PrecomputeGlobalCenterOfLift(aeroSection, dummy, vel, 1); Vector3 force1 = aeroSection.force; Vector3 moment1 = aeroSection.TorqueAt(pos); aeroSection.ClearAll(); Vector3 deltaForce = force1 - force0; Vector3 deltaMoment = moment1 - moment0; Vector3 deltaForcePerp = Vector3.ProjectOnPlane(deltaForce, vel_base); float deltaForcePerpMag = deltaForcePerp.magnitude; Vector3 deltaForcePerpNorm = deltaForcePerp / deltaForcePerpMag; Vector3 deltaMomentPerp = deltaMoment - Vector3.Dot(deltaMoment, deltaForcePerpNorm) * deltaForcePerpNorm - Vector3.Project(deltaMoment, vel_base); vesselRootLocalAeroCenter = deltaMomentPerp.magnitude / deltaForcePerpMag * Math.Sign(Vector3.Dot(Vector3.Cross(deltaForce, deltaMoment), vel_base)) * vel_base; vesselRootLocalAeroCenter += pos; vesselRootLocalAeroCenter = rootPartTrans.worldToLocalMatrix.MultiplyPoint3x4(vesselRootLocalAeroCenter); }
public void SimulateAeroProperties( out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude ) { var center = new FARCenterQuery(); var dummy = new FARCenterQuery(); CelestialBody body = vessel.mainBody; //Calculate main gas properties float pressure = (float)body.GetPressure(altitude); float temperature = (float)body.GetTemperature(altitude); float density = (float)body.GetDensity(pressure, temperature); float speedOfSound = (float)body.GetSpeedOfSound(pressure, density); if (pressure <= 0 || temperature <= 0 || density <= 0 || speedOfSound <= 0) { aeroForce = Vector3.zero; aeroTorque = Vector3.zero; return; } float velocityMag = velocityWorldVector.magnitude; float machNumber = velocityMag / speedOfSound; float reynoldsNumber = (float)FARAeroUtil.CalculateReynoldsNumber(density, Length, velocityMag, machNumber, temperature, body.atmosphereAdiabaticIndex); float reynoldsPerLength = reynoldsNumber / (float)Length; float skinFriction = (float)FARAeroUtil.SkinFrictionDrag(reynoldsNumber, machNumber); float pseudoKnudsenNumber = machNumber / (reynoldsNumber + machNumber); if (_currentAeroSections != null) { foreach (FARAeroSection curSection in _currentAeroSections) { curSection?.PredictionCalculateAeroForces(density, machNumber, reynoldsPerLength, pseudoKnudsenNumber, skinFriction, velocityWorldVector, center); } foreach (FARWingAerodynamicModel curWing in _legacyWingModels) { if (!(curWing is null)) { center.AddForce(curWing.transform.position, curWing.PrecomputeCenterOfLift(velocityWorldVector, machNumber, density, dummy)); } } } aeroForce = center.force; aeroTorque = center.TorqueAt(vessel.CoM); }