public void UpdateSonicDragArea() { ferram4.FARCenterQuery center = new ferram4.FARCenterQuery(); Vector3 worldMainAxis = _localToWorldMatrix.MultiplyVector(_vehicleMainAxis); worldMainAxis.Normalize(); for (int i = 0; i < _newAeroSections.Count; i++) { FARAeroSection a = _newAeroSections[i]; a.PredictionCalculateAeroForces(2f, 1f, 50000f, 0.005f, worldMainAxis, center); } _sonicDragArea = Vector3.Dot(center.force, worldMainAxis) * -1000; }
public void PredictionCalculateAeroForces(float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag, Vector3 vel, ferram4.FARCenterQuery center) { if (partData.Count == 0) { return; } PartData data = partData[0]; FARAeroPartModule aeroModule = null; for (int i = 0; i < partData.Count; i++) { data = partData[i]; aeroModule = data.aeroModule; if (aeroModule.part == null || aeroModule.part.partTransform == null) { continue; } break; } if (aeroModule.part == null || aeroModule.part.transform == null) { return; } 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; //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 = cosHalfAoA * sin2AoA * potentialFlowNormalForce * Math.Sign(cosAoA); //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 * normalForceFactor * (float)sinAoA; 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); if (cosAoA > 0) { 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; } else { 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 { float tmp = (-0.185185185f * machNumber + 1.11111111111f); momentFactor = tmp * hypersonicMomentForward * 0.6f + (1 - tmp) * hypersonicMomentBackward; } //if (machNumber < 1.5) // momentFactor += hypersonicMomentForward * (0.5f - machNumber * 0.33333333333333333333333333333333f) * 0.2f; moment *= momentFactor; } moment /= normalForceFactor; Vector3 forceVector = (float)xForce * xRefVector + (float)nForce * localNormalForceVec; forceVector -= (float)localVelForce * velLocalNorm; Vector3 torqueVector = Vector3.Cross(xRefVector, localNormalForceVec) * moment; 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; 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; } centroid = module.part.partTransform.localToWorldMatrix.MultiplyPoint3x4(data2.centroidPartSpace); center.AddForce(centroid, forceVector * data2.dragFactor); } center.AddTorque(torqueVector); }
private void CalculateVesselAeroProperties() { int front, back, numSections; _voxel.CrossSectionData(_vehicleCrossSection, _vehicleMainAxis, out front, out back, out _sectionThickness, out _maxCrossSectionArea); numSections = back - front; _length = _sectionThickness * numSections; AdjustCrossSectionForAirDucting(_vehicleCrossSection, _currentGeoModules, front, back, ref _maxCrossSectionArea); GaussianSmoothCrossSections(_vehicleCrossSection, 3, FARSettingsScenarioModule.Settings.gaussianVehicleLengthFractionForSmoothing, _sectionThickness, _length, front, back, FARSettingsScenarioModule.Settings.numAreaSmoothingPasses, FARSettingsScenarioModule.Settings.numDerivSmoothingPasses); CalculateSonicPressure(_vehicleCrossSection, front, back, _sectionThickness, _maxCrossSectionArea); validSectionCount = numSections; firstSection = front; double invMaxRadFactor = 1f / Math.Sqrt(_maxCrossSectionArea / Math.PI); double finenessRatio = _sectionThickness * numSections * 0.5 * invMaxRadFactor; //vehicle length / max diameter, as calculated from sect thickness * num sections / (2 * max radius) //skin friction and pressure drag for a body, taken from 1978 USAF Stability And Control DATCOM, Section 4.2.3.1, Paragraph A double viscousDragFactor = 0; viscousDragFactor = 60 / (finenessRatio * finenessRatio * finenessRatio) + 0.0025 * finenessRatio; //pressure drag for a subsonic / transonic body due to skin friction viscousDragFactor++; viscousDragFactor /= (double)numSections; //fraction of viscous drag applied to each section double criticalMachNumber = CalculateCriticalMachNumber(finenessRatio); _criticalMach = criticalMachNumber * CriticalMachFactorForUnsmoothCrossSection(_vehicleCrossSection, finenessRatio, _sectionThickness); float lowFinenessRatioSubsonicFactor = 1f; lowFinenessRatioSubsonicFactor += 1f/(2f * (float)finenessRatio); _moduleAndAreas.Clear(); //_newAeroSections = new List<FARAeroSection>(); HashSet<FARAeroPartModule> tmpAeroModules = new HashSet<FARAeroPartModule>(); _sonicDragArea = 0; if (_newAeroSections.Capacity < numSections) _newAeroSections.Capacity = numSections; for (int i = 0; i <= numSections; i++) //index in the cross sections { int index = i + front; //index along the actual body double prevArea, curArea, nextArea; curArea = _vehicleCrossSection[index].area; if (i == 0) prevArea = 0; else prevArea = _vehicleCrossSection[index - 1].area; if (i == numSections) nextArea = 0; else nextArea = _vehicleCrossSection[index + 1].area; FARAeroSection currentSection = null; if (i < _newAeroSections.Count) currentSection = _newAeroSections[i]; else { lock (_commonLocker) if (currentlyUnusedSections.Count > 0) currentSection = currentlyUnusedSections.Pop(); } if (currentSection == null) currentSection = new FARAeroSection(); FARFloatCurve xForcePressureAoA0 = currentSection.xForcePressureAoA0; FARFloatCurve xForcePressureAoA180 = currentSection.xForcePressureAoA180; FARFloatCurve xForceSkinFriction = currentSection.xForceSkinFriction; //Potential and Viscous lift calcs float potentialFlowNormalForce; if(i == 0) potentialFlowNormalForce = (float)(nextArea - curArea); else if(i == numSections) potentialFlowNormalForce = (float)(curArea - prevArea); else potentialFlowNormalForce = (float)(nextArea - prevArea) * 0.5f; //calcualted from area change float areaChangeMax = (float)Math.Min(Math.Min(nextArea, prevArea) * 0.1, _length * 0.01); float sonicBaseDrag = 0.21f; sonicBaseDrag *= potentialFlowNormalForce; //area base drag acts over if (potentialFlowNormalForce > areaChangeMax) potentialFlowNormalForce = areaChangeMax; else if (potentialFlowNormalForce < -areaChangeMax) potentialFlowNormalForce = -areaChangeMax; else if(areaChangeMax != 0) sonicBaseDrag *= Math.Abs(potentialFlowNormalForce / areaChangeMax); //some scaling for small changes in cross-section double flatnessRatio = _vehicleCrossSection[index].flatnessRatio; if (flatnessRatio >= 1) sonicBaseDrag /= (float)(flatnessRatio * flatnessRatio); else sonicBaseDrag *= (float)(flatnessRatio * flatnessRatio); //float sonicWaveDrag = (float)CalculateTransonicWaveDrag(i, index, numSections, front, _sectionThickness, Math.Min(_maxCrossSectionArea * 2, curArea * 16));//Math.Min(maxCrossSectionArea * 0.1, curArea * 0.25)); //sonicWaveDrag *= (float)FARSettingsScenarioModule.Settings.fractionTransonicDrag; //this is just to account for the higher drag being felt due to the inherent blockiness of the model being used and noise introduced by the limited control over shape and through voxelization float hypersonicDragForward = (float)CalculateHypersonicDrag(prevArea, curArea, _sectionThickness); //negative forces float hypersonicDragBackward = (float)CalculateHypersonicDrag(nextArea, curArea, _sectionThickness); float hypersonicDragForwardFrac = 0, hypersonicDragBackwardFrac = 0; if(curArea - prevArea != 0) hypersonicDragForwardFrac = Math.Abs(hypersonicDragForward * 0.5f / (float)(curArea - prevArea)); if(curArea - nextArea != 0) hypersonicDragBackwardFrac = Math.Abs(hypersonicDragBackward * 0.5f / (float)(curArea - nextArea)); hypersonicDragForwardFrac *= hypersonicDragForwardFrac; hypersonicDragForwardFrac *= hypersonicDragForwardFrac; hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac; hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac; if (flatnessRatio >= 1) { hypersonicDragForwardFrac /= (float)(flatnessRatio * flatnessRatio); hypersonicDragBackwardFrac /= (float)(flatnessRatio * flatnessRatio); } else { hypersonicDragForwardFrac *= (float)(flatnessRatio * flatnessRatio); hypersonicDragBackwardFrac *= (float)(flatnessRatio * flatnessRatio); } float hypersonicMomentForward = (float)CalculateHypersonicMoment(prevArea, curArea, _sectionThickness); float hypersonicMomentBackward = (float)CalculateHypersonicMoment(nextArea, curArea, _sectionThickness); xForcePressureAoA0.SetPoint(5, new Vector3d(35, hypersonicDragForward, 0)); xForcePressureAoA180.SetPoint(5, new Vector3d(35, -hypersonicDragBackward, 0)); float sonicAoA0Drag, sonicAoA180Drag; double cPSonicForward, cPSonicBackward; cPSonicForward = _vehicleCrossSection[index].cpSonicForward; if (index > front) { cPSonicForward += _vehicleCrossSection[index - 1].cpSonicForward; cPSonicForward *= 0.5; } cPSonicBackward = _vehicleCrossSection[index].cpSonicBackward; if (index < back) { cPSonicBackward += _vehicleCrossSection[index + 1].cpSonicBackward; cPSonicBackward *= 0.5; } if (sonicBaseDrag > 0) //occurs with increase in area; force applied at 180 AoA { xForcePressureAoA0.SetPoint(0, new Vector3d(_criticalMach, (0.325f * hypersonicDragForward * hypersonicDragForwardFrac) * lowFinenessRatioSubsonicFactor, 0)); //hypersonic drag used as a proxy for effects due to flow separation xForcePressureAoA180.SetPoint(0, new Vector3d(_criticalMach, (sonicBaseDrag * 0.2f - (0.325f * hypersonicDragBackward * hypersonicDragBackwardFrac)) * lowFinenessRatioSubsonicFactor, 0)); hypersonicDragBackwardFrac += 1f; //avg fracs with 1 to get intermediate frac hypersonicDragBackwardFrac *= 0.5f; hypersonicDragForwardFrac += 1f; hypersonicDragForwardFrac *= 0.5f; sonicAoA0Drag = -(float)(cPSonicForward * (curArea - prevArea)) + hypersonicDragForward * 0.3f * hypersonicDragForwardFrac; sonicAoA180Drag = (float)(cPSonicBackward * (curArea - nextArea)) + sonicBaseDrag - hypersonicDragBackward * 0.3f * hypersonicDragBackwardFrac; } else if (sonicBaseDrag < 0) { xForcePressureAoA0.SetPoint(0, new Vector3d(_criticalMach, (sonicBaseDrag * 0.2f + (0.3f * hypersonicDragForward * hypersonicDragForwardFrac)) * lowFinenessRatioSubsonicFactor, 0)); xForcePressureAoA180.SetPoint(0, new Vector3d(_criticalMach, -(0.3f * hypersonicDragBackward * hypersonicDragBackwardFrac) * lowFinenessRatioSubsonicFactor, 0)); hypersonicDragBackwardFrac += 1f; //avg fracs with 1 to get intermediate frac hypersonicDragBackwardFrac *= 0.5f; hypersonicDragForwardFrac += 1f; hypersonicDragForwardFrac *= 0.5f; sonicAoA0Drag = -(float)(cPSonicForward * (curArea - prevArea)) + sonicBaseDrag + hypersonicDragForward * 0.3f * hypersonicDragForwardFrac; sonicAoA180Drag = (float)(cPSonicBackward * (curArea - nextArea)) - hypersonicDragBackward * 0.3f * hypersonicDragBackwardFrac; } else { xForcePressureAoA0.SetPoint(0, new Vector3d(_criticalMach, (0.3f * hypersonicDragForward * hypersonicDragForwardFrac) * lowFinenessRatioSubsonicFactor, 0)); xForcePressureAoA180.SetPoint(0, new Vector3d(_criticalMach, -(0.3f * hypersonicDragBackward * hypersonicDragBackwardFrac) * lowFinenessRatioSubsonicFactor, 0)); hypersonicDragBackwardFrac += 1f; //avg fracs with 1 to get intermediate frac hypersonicDragBackwardFrac *= 0.5f; hypersonicDragForwardFrac += 1f; hypersonicDragForwardFrac *= 0.5f; sonicAoA0Drag = -(float)(cPSonicForward * (curArea - prevArea)) + hypersonicDragForward * 0.3f * hypersonicDragForwardFrac; sonicAoA180Drag = (float)(cPSonicBackward * (curArea - nextArea)) - hypersonicDragBackward * 0.3f * hypersonicDragBackwardFrac; } float diffSonicHyperAoA0 = Math.Abs(sonicAoA0Drag) - Math.Abs(hypersonicDragForward); float diffSonicHyperAoA180 = Math.Abs(sonicAoA180Drag) - Math.Abs(hypersonicDragBackward); xForcePressureAoA0.SetPoint(1, new Vector3d(1f, sonicAoA0Drag, 0)); xForcePressureAoA180.SetPoint(1, new Vector3d(1f, sonicAoA180Drag, 0)); xForcePressureAoA0.SetPoint(2, new Vector3d(2f, sonicAoA0Drag * 0.5773503f + (1 - 0.5773503f) * hypersonicDragForward, -0.2735292 * diffSonicHyperAoA0)); //need to recalc slope here xForcePressureAoA180.SetPoint(2, new Vector3d(2f, sonicAoA180Drag * 0.5773503f - (1 - 0.5773503f) * hypersonicDragBackward, -0.2735292 * diffSonicHyperAoA180)); xForcePressureAoA0.SetPoint(3, new Vector3d(5f, sonicAoA0Drag * 0.2041242f + (1 - 0.2041242f) * hypersonicDragForward, -0.04252587f * diffSonicHyperAoA0)); xForcePressureAoA180.SetPoint(3, new Vector3d(5f, sonicAoA180Drag * 0.2041242f - (1 - 0.2041242f) * hypersonicDragBackward, -0.04252587f * diffSonicHyperAoA180)); xForcePressureAoA0.SetPoint(4, new Vector3d(10f, sonicAoA0Drag * 0.1005038f + (1 - 0.1005038f) * hypersonicDragForward, -0.0101519f * diffSonicHyperAoA0)); xForcePressureAoA180.SetPoint(4, new Vector3d(10f, sonicAoA180Drag * 0.1005038f - (1 - 0.1005038f) * hypersonicDragBackward, -0.0101519f * diffSonicHyperAoA180)); Vector3 xRefVector; if (index == front || index == back) xRefVector = _vehicleMainAxis; else { xRefVector = (Vector3)(_vehicleCrossSection[index - 1].centroid - _vehicleCrossSection[index + 1].centroid); Vector3 offMainAxisVec = Vector3.ProjectOnPlane(xRefVector, _vehicleMainAxis); float tanAoA = offMainAxisVec.magnitude / (2f * (float)_sectionThickness); if (tanAoA > 0.17632698070846497347109038686862f) { offMainAxisVec.Normalize(); offMainAxisVec *= 0.17632698070846497347109038686862f; //max acceptable is 10 degrees xRefVector = _vehicleMainAxis + offMainAxisVec; } xRefVector.Normalize(); } Vector3 nRefVector = Matrix4x4.TRS(Vector3.zero, Quaternion.FromToRotation(_vehicleMainAxis, xRefVector), Vector3.one).MultiplyVector(_vehicleCrossSection[index].flatNormalVector); Vector3 centroid = _localToWorldMatrix.MultiplyPoint3x4(_vehicleCrossSection[index].centroid); xRefVector = _localToWorldMatrix.MultiplyVector(xRefVector); nRefVector = _localToWorldMatrix.MultiplyVector(nRefVector); Dictionary<Part, VoxelCrossSection.SideAreaValues> includedPartsAndAreas = _vehicleCrossSection[index].partSideAreaValues; float weightingFactor = 0; double surfaceArea = 0; foreach (KeyValuePair<Part, VoxelCrossSection.SideAreaValues> pair in includedPartsAndAreas) { VoxelCrossSection.SideAreaValues areas = pair.Value; surfaceArea += areas.iN + areas.iP + areas.jN + areas.jP + areas.kN + areas.kP; Part key = pair.Key; if (key == null) continue; if (!key.Modules.Contains("FARAeroPartModule")) continue; FARAeroPartModule m = (FARAeroPartModule)key.Modules["FARAeroPartModule"]; if ((object)m != null) includedModules.Add(m); if (_moduleAndAreas.ContainsKey(m)) _moduleAndAreas[m] += areas; else _moduleAndAreas[m] = areas; weightingFactor += (float)pair.Value.exposedAreaCount; weighting.Add((float)pair.Value.exposedAreaCount); } weightingFactor = 1 / weightingFactor; for (int j = 0; j < includedPartsAndAreas.Count; j++) { weighting[j] *= weightingFactor; } float viscCrossflowDrag = (float)(Math.Sqrt(curArea / Math.PI) * _sectionThickness * 2d); xForceSkinFriction.SetPoint(0, new Vector3d(0, (surfaceArea * viscousDragFactor), 0)); //subsonic incomp visc drag xForceSkinFriction.SetPoint(1, new Vector3d(1, (surfaceArea * viscousDragFactor), 0)); //transonic visc drag xForceSkinFriction.SetPoint(2, new Vector3d(2, (float)surfaceArea, 0)); //above Mach 1.4, visc is purely surface drag, no pressure-related components simulated currentSection.UpdateAeroSection(potentialFlowNormalForce, viscCrossflowDrag ,viscCrossflowDrag / (float)(_sectionThickness), (float)flatnessRatio, hypersonicMomentForward, hypersonicMomentBackward, centroid, xRefVector, nRefVector, _localToWorldMatrix, _vehicleMainAxis, includedModules, weighting, _partWorldToLocalMatrix); if (i < _newAeroSections.Count) _newAeroSections[i] = currentSection; else _newAeroSections.Add(currentSection); for (int j = 0; j < includedModules.Count; j++) { FARAeroPartModule a = includedModules[j]; tmpAeroModules.Add(a); } includedModules.Clear(); weighting.Clear(); } if(_newAeroSections.Count > numSections) //deal with sections that are unneeded now { lock (_commonLocker) for (int i = _newAeroSections.Count - 1; i > numSections; --i) { FARAeroSection unusedSection = _newAeroSections[i]; _newAeroSections.RemoveAt(i); if (currentlyUnusedSections.Count < 64) currentlyUnusedSections.Push(unusedSection); //if there aren't that many extra ones stored, add them to the stack to be reused else { unusedSection.ClearAeroSection(); unusedSection = null; } } } foreach (KeyValuePair<FARAeroPartModule, FARAeroPartModule.ProjectedArea> pair in _moduleAndAreas) { pair.Key.SetProjectedArea(pair.Value, _localToWorldMatrix); } //_newAeroModules = tmpAeroModules.ToList(); //this method creates lots of garbage int aeroIndex = 0; if (_newAeroModules.Capacity < tmpAeroModules.Count) _newAeroModules.Capacity = tmpAeroModules.Count; foreach(FARAeroPartModule module in tmpAeroModules) { if (aeroIndex < _newAeroModules.Count) _newAeroModules[aeroIndex] = module; else _newAeroModules.Add(module); ++aeroIndex; } //at this point, aeroIndex is what the count of _newAeroModules _should_ be, but due to the possibility of the previous state having more modules, this is not guaranteed for (int i = _newAeroModules.Count - 1; i >= aeroIndex; --i) { _newAeroModules.RemoveAt(i); //steadily remove the modules from the end that shouldn't be there } _newUnusedAeroModules.Clear(); for (int i = 0; i < _currentGeoModules.Count; i++) { if (!_currentGeoModules[i]) continue; FARAeroPartModule aeroModule = _currentGeoModules[i].GetComponent<FARAeroPartModule>(); if (aeroModule != null && !tmpAeroModules.Contains(aeroModule)) _newUnusedAeroModules.Add(aeroModule); } if (HighLogic.LoadedSceneIsEditor) { ferram4.FARCenterQuery center = new ferram4.FARCenterQuery(); Vector3 worldMainAxis = _localToWorldMatrix.MultiplyVector(_vehicleMainAxis); worldMainAxis.Normalize(); for (int i = 0; i < _newAeroSections.Count; i++) { FARAeroSection a = _newAeroSections[i]; a.PredictionCalculateAeroForces(2f, 1f, 50000f, 0.005f, worldMainAxis, center); } _sonicDragArea = Vector3.Dot(center.force, worldMainAxis) * -1000; } }