예제 #1
0
        public bool CanMerge(FARAeroSection otherSection)
        {
            if (mergeFactor >= 10)
            {
                return(false); //only merge up to 10 sections
            }
            float flatnessRelDiff = flatnessRatio - otherSection.flatnessRatio;

            flatnessRelDiff *= invFlatnessRatio;

            if (flatnessRelDiff < 0.05)    //allow for 5% rel difference for merging
            {
                if (flatnessRatio >= 1.05) //if it's within 5% of 1, it's good
                //allow 5 degrees error for flatnessRatio
                {
                    if (Math.Abs(Vector3.Dot(worldNormalVector, otherSection.worldNormalVector)) <= 0.999)
                    {
                        return(false); //too different in out-of-roundness, don't merge
                    }
                }
            }
            float diameterRelDiff = diameter - otherSection.diameter;
            diameterRelDiff /= diameter;

            return(diameterRelDiff < 0.05);
        }
        public void SimulateAeroProperties(out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude)
        {
            // Rodhern: It seems that this method, 'SimulateAeroProperties', is only used in FARAPI, which in turn can be used by say KSPTrajectories.
            //          The parameter 'FARCenterQuery dummy' is from a code fix by Benjamin Chung (commit 18fbb9d29431679a4de9dfc22a443f400d2d4f8b).

            FARCenterQuery center = new FARCenterQuery();
            FARCenterQuery dummy  = new FARCenterQuery();

            float pressure;
            float density;
            float temperature;
            float speedOfSound;

            CelestialBody body = vessel.mainBody;      //Calculate main gas properties

            pressure     = (float)body.GetPressure(altitude);
            temperature  = (float)body.GetTemperature(altitude);
            density      = (float)body.GetDensity(pressure, temperature);
            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     pseudoKnudsenNumber = machNumber / (reynoldsNumber + machNumber);
            float     skinFriction        = (float)FARAeroUtil.SkinFrictionDrag(reynoldsNumber, machNumber);
            FlightEnv fenv = FlightEnv.NewPredicted(vessel.mainBody, altitude, machNumber);

            if (_currentAeroSections != null)
            {
                for (int i = 0; i < _currentAeroSections.Count; i++)
                {
                    FARAeroSection curSection = _currentAeroSections[i];
                    if (curSection != null)
                    {
                        curSection.PredictionCalculateAeroForces(density, machNumber, reynoldsPerLength, pseudoKnudsenNumber, skinFriction, velocityWorldVector, center);
                    }
                }

                for (int i = 0; i < _legacyWingModels.Count; i++)
                {
                    FARWingAerodynamicModel curWing = _legacyWingModels[i];
                    if ((object)curWing != null)
                    {
                        Vector3d force = curWing.PrecomputeCenterOfLift(velocityWorldVector, fenv, dummy);
                        center.AddForce(curWing.transform.position, force);
                    }
                }
            }

            aeroForce  = center.force;
            aeroTorque = center.TorqueAt(vessel.CoM);
        }
        public void SimulateAeroProperties(out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude)
        {
            FARCenterQuery center = new FARCenterQuery();

            float pressure;
            float density;
            float temperature;
            float speedOfSound;

            CelestialBody body = vessel.mainBody;      //Calculate main gas properties

            pressure     = (float)body.GetPressure(altitude);
            temperature  = (float)body.GetTemperature(altitude);
            density      = (float)body.GetDensity(pressure, temperature);
            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)
            {
                for (int i = 0; i < _currentAeroSections.Count; i++)
                {
                    FARAeroSection curSection = _currentAeroSections[i];
                    if (curSection != null)
                    {
                        curSection.PredictionCalculateAeroForces(density, machNumber, reynoldsPerLength, pseudoKnudsenNumber, skinFriction, velocityWorldVector, center);
                    }
                }

                for (int i = 0; i < _legacyWingModels.Count; i++)
                {
                    FARWingAerodynamicModel curWing = _legacyWingModels[i];
                    if ((object)curWing != null)
                    {
                        curWing.PrecomputeCenterOfLift(velocityWorldVector, machNumber, density, center);
                    }
                }
            }

            aeroForce  = center.force;
            aeroTorque = center.TorqueAt(vessel.CoM);
        }
        public static FARAeroSection CreateNewAeroSection()
        {
            FARAeroSection section = new FARAeroSection();

            section.xForcePressureAoA0          = new FARFloatCurve(6);
            section.xForcePressureAoA180        = new FARFloatCurve(6);
            section.xForceSkinFriction          = new FARFloatCurve(3);
            section.partData                    = new List <PartData>();
            section.handledAeroModulesIndexDict = new Dictionary <FARAeroPartModule, int>(ObjectReferenceEqualityComparer <FARAeroPartModule> .Default);

            GenerateCrossFlowDragCurve();

            return(section);
        }
        public void MergeAeroSection(FARAeroSection otherSection)
        {
            //increase merge factor each time we merge to maintain relative strength of sections
            mergeFactor += 1;

            float invMergeFactorP1 = 1 / (mergeFactor + 1);

            //add simple factors
            potentialFlowNormalForce += otherSection.potentialFlowNormalForce;
            viscCrossflowDrag        += otherSection.viscCrossflowDrag;
            hypersonicMomentForward  += otherSection.hypersonicMomentForward;
            hypersonicMomentBackward += otherSection.hypersonicMomentBackward;

            flatnessRatio    = invMergeFactorP1 * (flatnessRatio * mergeFactor + otherSection.flatnessRatio);
            invFlatnessRatio = invMergeFactorP1 * (invFlatnessRatio * mergeFactor + otherSection.invFlatnessRatio);
            diameter         = invMergeFactorP1 * (diameter * mergeFactor + otherSection.diameter);

            //merge the curves; don't scale because these are actual drag values
            xForcePressureAoA0.AddCurve(otherSection.xForcePressureAoA0);
            xForcePressureAoA180.AddCurve(otherSection.xForcePressureAoA180);
            xForceSkinFriction.AddCurve(otherSection.xForceSkinFriction);

            //prep old part data for merging
            for (int i = 0; i < partData.Count; ++i)
            {
                PartData oldData = partData[i];
                oldData.dragFactor          *= mergeFactor; //scale all of these up for the incoming data
                oldData.centroidPartSpace   *= mergeFactor;
                oldData.xRefVectorPartSpace *= mergeFactor;
                oldData.nRefVectorPartSpace *= mergeFactor;
                partData[i] = oldData;
            }

            //merge PartData
            float mergeFactorP1 = mergeFactor + 1;

            for (int i = 0; i < otherSection.partData.Count; ++i)
            {
                PartData tmpOtherData = otherSection.partData[i];
                int      index        = -1;
                if (handledAeroModulesIndexDict.TryGetValue(tmpOtherData.aeroModule, out index))
                {
                    PartData tmpData = partData[index];
                    tmpData.centroidPartSpace   += tmpOtherData.centroidPartSpace;       //prep'd for averaging
                    tmpData.xRefVectorPartSpace += tmpOtherData.xRefVectorPartSpace;
                    tmpData.nRefVectorPartSpace += tmpOtherData.nRefVectorPartSpace;
                    tmpData.dragFactor          += tmpOtherData.dragFactor;

                    partData[index] = tmpData;
                }
                else
                {
                    /*tmpOtherData.centroidPartSpace = mergeFactorP1 * (tmpOtherData.centroidPartSpace);     //these must be scaled to completely counter the effect of the downscale at the end
                     * tmpOtherData.xRefVectorPartSpace = (tmpOtherData.xRefVectorPartSpace);
                     * tmpOtherData.nRefVectorPartSpace = (tmpOtherData.nRefVectorPartSpace);
                     * tmpOtherData.dragFactor = invMergeFactor * (tmpOtherData.dragFactor);       //this will already be scaled down at the end
                     */
                    tmpOtherData.centroidPartSpace *= mergeFactorP1;      //needs to be scaled this way to place the centroid in the correct location
                    partData.Add(tmpOtherData);

                    handledAeroModulesIndexDict.Add(tmpOtherData.aeroModule, partData.Count - 1);
                }
            }

            for (int i = 0; i < partData.Count; ++i)
            {
                PartData newData = partData[i];
                newData.dragFactor        *= invMergeFactorP1; //now scale everything back down to sane levels
                newData.centroidPartSpace *= invMergeFactorP1;
                newData.xRefVectorPartSpace.Normalize();
                newData.nRefVectorPartSpace.Normalize();
                partData[i] = newData;
            }
        }
        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;

            double voxelVolume = _voxel.Volume;

            double filledVolume = 0;
            for (int i = front; i <= back; i++)
                filledVolume += _vehicleCrossSection[i].area;

            filledVolume *= _sectionThickness;      //total volume taken up by the filled voxel

            double gridFillednessFactor = filledVolume / voxelVolume;     //determines how fine the grid is compared to the vehicle.  Accounts for loss in precision and added smoothing because of unused sections of voxel volume

            gridFillednessFactor *= 25;     //used to handle relatively empty, but still alright, planes
            double stdDevCutoff = 3;
            stdDevCutoff *= gridFillednessFactor;
            if (stdDevCutoff < 0.5)
                stdDevCutoff = 0.5;
            if (stdDevCutoff > 3)
                stdDevCutoff = 3;

            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) 

            int extraLowFinessRatioDerivSmoothingPasses = (int)Math.Round((5f - finenessRatio) * 0.5f) * FARSettingsScenarioModule.Settings.numDerivSmoothingPasses;
            if (extraLowFinessRatioDerivSmoothingPasses < 0)
                extraLowFinessRatioDerivSmoothingPasses = 0;

            int extraAreaSmoothingPasses = (int)Math.Round((gridFillednessFactor / 25.0 - 0.5) * 4.0);
            if (extraAreaSmoothingPasses < 0)
                extraAreaSmoothingPasses = 0;


            ThreadSafeDebugLogger.Instance.RegisterMessage("Std dev for smoothing: " + stdDevCutoff + " voxel total vol: " + voxelVolume + " filled vol: " + filledVolume);

            AdjustCrossSectionForAirDucting(_vehicleCrossSection, _currentGeoModules, front, back, ref _maxCrossSectionArea);

            GaussianSmoothCrossSections(_vehicleCrossSection, stdDevCutoff, FARSettingsScenarioModule.Settings.gaussianVehicleLengthFractionForSmoothing, _sectionThickness, _length, front, back, FARSettingsScenarioModule.Settings.numAreaSmoothingPasses + extraAreaSmoothingPasses, FARSettingsScenarioModule.Settings.numDerivSmoothingPasses + extraLowFinessRatioDerivSmoothingPasses);

            CalculateSonicPressure(_vehicleCrossSection, front, back, _sectionThickness, _maxCrossSectionArea);

            validSectionCount = numSections;
            firstSection = front;

            //recalc these with adjusted cross-sections
            invMaxRadFactor = 1f / Math.Sqrt(_maxCrossSectionArea / Math.PI);

            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 lowFinenessRatioFactor = 1f;
            lowFinenessRatioFactor += 1f/(1 + 0.5f * (float)finenessRatio);
            float lowFinenessRatioBlendFactor = lowFinenessRatioFactor--;

            _moduleAndAreasDict.Clear();
            //_newAeroSections = new List<FARAeroSection>();

            HashSet<FARAeroPartModule> tmpAeroModules = new HashSet<FARAeroPartModule>();
            _sonicDragArea = 0;

            if (_newAeroSections.Capacity < numSections + 1)
                _newAeroSections.Capacity = numSections + 1;

            int aeroSectionIndex = 0;
            FARAeroSection prevSection = null;

            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 (aeroSectionIndex < _newAeroSections.Count)
                    currentSection = _newAeroSections[aeroSectionIndex];
                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;     //^2
                hypersonicDragForwardFrac *= hypersonicDragForwardFrac;     //^4
                //hypersonicDragForwardFrac *= hypersonicDragForwardFrac;     //^8
                //hypersonicDragForwardFrac *= hypersonicDragForwardFrac;     //^16
                //hypersonicDragForwardFrac *= hypersonicDragForwardFrac;     //^32

                hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac;     //^2
                hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac;     //^4
                //hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac;     //^8
                //hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac;     //^16
                //hypersonicDragBackwardFrac *= hypersonicDragBackwardFrac;     //^32

                if (flatnessRatio >= 1)
                {
                    hypersonicDragForwardFrac /= (float)(flatnessRatio * flatnessRatio);
                    hypersonicDragBackwardFrac /= (float)(flatnessRatio * flatnessRatio);
                }
                else
                {
                    hypersonicDragForwardFrac *= (float)(flatnessRatio * flatnessRatio);
                    hypersonicDragBackwardFrac *= (float)(flatnessRatio * flatnessRatio);
                }

                /*if (hypersonicDragForwardFrac > 1)
                    hypersonicDragForwardFrac = 1;
                if (hypersonicDragBackwardFrac > 1)
                    hypersonicDragBackwardFrac = 1;*/

                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;
                }*/

                double areaForForces = ((curArea + prevArea) - (nextArea + curArea)) * 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) * lowFinenessRatioFactor, 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)) * lowFinenessRatioFactor, 0));


                    hypersonicDragBackwardFrac += 1f;       //avg fracs with 1 to get intermediate frac
                    hypersonicDragBackwardFrac *= 0.5f;

                    hypersonicDragForwardFrac += 1f;
                    hypersonicDragForwardFrac *= 0.5f;

                    sonicAoA0Drag = -(float)(cPSonicForward * (areaForForces)) + 0.3f * hypersonicDragForward * hypersonicDragForwardFrac;
                    sonicAoA0Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA0Drag += hypersonicDragForward * hypersonicDragForwardFrac * lowFinenessRatioBlendFactor * 1.4f;     //at very low finenessRatios, use a boosted version of the hypersonic drag

                    sonicAoA180Drag = (float)(cPSonicBackward * (-areaForForces)) + sonicBaseDrag - 0.3f * hypersonicDragBackward * hypersonicDragBackwardFrac;
                    sonicAoA180Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA180Drag += (-hypersonicDragBackward * hypersonicDragBackwardFrac * 1.4f + sonicBaseDrag) * lowFinenessRatioBlendFactor;     //at very low finenessRatios, use a boosted version of the hypersonic drag
                    //if(i == 0)
                    //    sonicAoA180Drag += (float)(cPSonicBackward * (curArea)) + sonicBaseDrag - hypersonicDragBackward * 0.3f * hypersonicDragBackwardFrac;
                }
                else if (sonicBaseDrag < 0)
                {
                    xForcePressureAoA0.SetPoint(0, new Vector3d(_criticalMach, (sonicBaseDrag * 0.2f + (0.325f * hypersonicDragForward * hypersonicDragForwardFrac)) * lowFinenessRatioFactor, 0));
                    xForcePressureAoA180.SetPoint(0, new Vector3d(_criticalMach, -(0.325f * hypersonicDragBackward * hypersonicDragBackwardFrac) * lowFinenessRatioFactor, 0));

                    hypersonicDragBackwardFrac += 1f;       //avg fracs with 1 to get intermediate frac
                    hypersonicDragBackwardFrac *= 0.5f;

                    hypersonicDragForwardFrac += 1f;
                    hypersonicDragForwardFrac *= 0.5f;

                    sonicAoA0Drag = -(float)(cPSonicForward * (areaForForces)) + sonicBaseDrag + 0.3f * hypersonicDragForward * hypersonicDragForwardFrac;
                    sonicAoA0Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA0Drag += (hypersonicDragForward * hypersonicDragForwardFrac * 1.4f + sonicBaseDrag) * lowFinenessRatioBlendFactor;     //at very low finenessRatios, use a boosted version of the hypersonic drag

                    sonicAoA180Drag = (float)(cPSonicBackward * (-areaForForces)) - 0.3f * hypersonicDragBackward * hypersonicDragBackwardFrac;
                    sonicAoA180Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA180Drag += (-hypersonicDragBackward * hypersonicDragBackwardFrac * 1.4f) * lowFinenessRatioBlendFactor;     //at very low finenessRatios, use a boosted version of the hypersonic drag

                    //if (i == numSections)
                    //    sonicAoA0Drag += -(float)(cPSonicForward * (-curArea)) + sonicBaseDrag + hypersonicDragForward * 0.3f * hypersonicDragForwardFrac;
                }
                else
                {
                    xForcePressureAoA0.SetPoint(0, new Vector3d(_criticalMach, (0.325f * hypersonicDragForward * hypersonicDragForwardFrac) * lowFinenessRatioFactor, 0));
                    xForcePressureAoA180.SetPoint(0, new Vector3d(_criticalMach, -(0.325f * hypersonicDragBackward * hypersonicDragBackwardFrac) * lowFinenessRatioFactor, 0));

                    hypersonicDragBackwardFrac += 1f;       //avg fracs with 1 to get intermediate frac
                    hypersonicDragBackwardFrac *= 0.5f;

                    hypersonicDragForwardFrac += 1f;
                    hypersonicDragForwardFrac *= 0.5f;

                    sonicAoA0Drag = -(float)(cPSonicForward * (areaForForces)) + 0.3f * hypersonicDragForward * hypersonicDragForwardFrac;
                    sonicAoA0Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA0Drag += hypersonicDragForward * hypersonicDragForwardFrac * lowFinenessRatioBlendFactor * 1.4f;     //at very low finenessRatios, use a boosted version of the hypersonic drag

                    sonicAoA180Drag = (float)(cPSonicBackward * (-areaForForces)) - 0.3f * hypersonicDragBackward * hypersonicDragBackwardFrac;
                    sonicAoA180Drag *= (1 - lowFinenessRatioBlendFactor);      //at high finenessRatios, use the entire above section for sonic drag
                    sonicAoA180Drag += (-hypersonicDragBackward * hypersonicDragBackwardFrac * 1.4f) * lowFinenessRatioBlendFactor;     //at very low finenessRatios, use a boosted version of the hypersonic drag
                }
                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 (_moduleAndAreasDict.ContainsKey(m))
                        _moduleAndAreasDict[m] += areas;
                    else
                        _moduleAndAreasDict[m] = areas;

                    weightingFactor += (float)pair.Value.exposedAreaCount;
                    weighting.Add((float)pair.Value.exposedAreaCount);
                }

                weightingFactor = 1 / weightingFactor;
                for (int j = 0; j < weighting.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, _partWorldToLocalMatrixDict);


                if (prevSection != null && prevSection.CanMerge(currentSection))
                {
                    prevSection.MergeAeroSection(currentSection);
                    currentSection.ClearAeroSection();
                }
                else
                {
                    if (aeroSectionIndex < _newAeroSections.Count)
                        _newAeroSections[aeroSectionIndex] = currentSection;
                    else
                        _newAeroSections.Add(currentSection);

                    prevSection = currentSection;
                    ++aeroSectionIndex;
                }

                for (int j = 0; j < includedModules.Count; j++)
                {
                    FARAeroPartModule a = includedModules[j];
                    tmpAeroModules.Add(a);
                }

                includedModules.Clear();
                weighting.Clear();

            }
            if (_newAeroSections.Count > aeroSectionIndex + 1)        //deal with sections that are unneeded now
            {
                lock (_commonLocker)
                    for (int i = _newAeroSections.Count - 1; i > aeroSectionIndex; --i)
                    {
                        FARAeroSection unusedSection = _newAeroSections[i];
                        _newAeroSections.RemoveAt(i);

                        unusedSection.ClearAeroSection();
                        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 = null;
                        }
                    }
            }
            foreach (KeyValuePair<FARAeroPartModule, FARAeroPartModule.ProjectedArea> pair in _moduleAndAreasDict)
            {
                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);
            }
            //UpdateSonicDragArea();
        }
        public void MergeAeroSection(FARAeroSection otherSection)
        {
            //increase merge factor each time we merge to maintain relative strength of sections
            mergeFactor += 1;

            float invMergeFactor = 1 / (mergeFactor + 1);

            //merge simple factors
            potentialFlowNormalForce += otherSection.potentialFlowNormalForce;
            viscCrossflowDrag += otherSection.viscCrossflowDrag;
            hypersonicMomentForward += otherSection.hypersonicMomentForward;
            hypersonicMomentBackward += otherSection.hypersonicMomentBackward;

            flatnessRatio = invMergeFactor * (flatnessRatio * mergeFactor + otherSection.flatnessRatio);
            invFlatnessRatio = invMergeFactor * (invFlatnessRatio * mergeFactor + otherSection.invFlatnessRatio);
            diameter = invMergeFactor * (diameter * mergeFactor + otherSection.diameter);

            //merge the curves; don't scale because these are actual drag values
            xForcePressureAoA0.AddCurve(otherSection.xForcePressureAoA0);
            xForcePressureAoA180.AddCurve(otherSection.xForcePressureAoA180);
            xForceSkinFriction.AddCurve(otherSection.xForceSkinFriction);

            //prep old data for merging
            for (int i = 0; i < partData.Count; ++i)
            {
                PartData oldData = partData[i];
                oldData.dragFactor *= mergeFactor;      //scale all of these up for the incoming data
                oldData.centroidPartSpace *= mergeFactor;
                oldData.xRefVectorPartSpace *= mergeFactor;
                oldData.nRefVectorPartSpace *= mergeFactor;
                partData[i] = oldData;
            }

            //merge PartData
            float mergeFactorP1 = mergeFactor + 1;
            for (int i = 0; i < otherSection.partData.Count; ++i)
            {
                PartData tmpOtherData = otherSection.partData[i];
                int index = -1;
                if (handledAeroModulesIndexDict.TryGetValue(tmpOtherData.aeroModule, out index))
                {
                    PartData tmpData = partData[index];
                    tmpData.centroidPartSpace += tmpOtherData.centroidPartSpace;         //prep'd for averaging
                    tmpData.xRefVectorPartSpace += tmpOtherData.xRefVectorPartSpace;
                    tmpData.nRefVectorPartSpace += tmpOtherData.nRefVectorPartSpace;
                    tmpData.dragFactor += tmpOtherData.dragFactor;

                    partData[index] = tmpData;
                }
                else
                {
                    /*tmpOtherData.centroidPartSpace = mergeFactorP1 * (tmpOtherData.centroidPartSpace);     //these must be scaled to completely counter the effect of the downscale at the end
                    tmpOtherData.xRefVectorPartSpace = (tmpOtherData.xRefVectorPartSpace);
                    tmpOtherData.nRefVectorPartSpace = (tmpOtherData.nRefVectorPartSpace);
                    tmpOtherData.dragFactor = invMergeFactor * (tmpOtherData.dragFactor);       //this will already be scaled down at the end
                    */
                    partData.Add(tmpOtherData);

                    handledAeroModulesIndexDict.Add(tmpOtherData.aeroModule, partData.Count - 1);
                }
            }

            for (int i = 0; i < partData.Count; ++i)
            {
                PartData newData = partData[i];
                newData.dragFactor *= invMergeFactor;      //now scale everything back down to sane levels
                newData.centroidPartSpace *= invMergeFactor;
                newData.xRefVectorPartSpace.Normalize();
                newData.nRefVectorPartSpace.Normalize();
                partData[i] = newData;
            }
        }
        public bool CanMerge(FARAeroSection otherSection)
        {
            if (mergeFactor >= 4)
                return false;       //only merge up to 5 sections

            bool merge = true;

            float flatnessRelDiff = flatnessRatio - otherSection.flatnessRatio;
            flatnessRelDiff *= invFlatnessRatio;

            if (flatnessRelDiff < 0.05)  //allow for 5% rel difference for merging
                if ((flatnessRatio - 1) < 0.05)  //if it's within 5% of 1, it's good
                    merge &= true;
                else if (Math.Abs(Vector3.Dot(worldNormalVector, otherSection.worldNormalVector)) > 0.999)    //allow 5 degrees error for flatnessRatio
                    merge &= true;
                else
                    merge &= false;         //too different in out-of-roundness, don't merge

            float diameterRelDiff = diameter - otherSection.diameter;
            diameterRelDiff /= diameter;

            if (diameterRelDiff < 0.05)
                merge &= true;
            else
                merge &= false;

            return merge;
        }