├── Wind Tunnel ├── Wind Tunnel │ ├── VesselCache │ │ ├── IReleasable.cs │ │ ├── LICENSE_VesselCache.md │ │ ├── SimCurves.cs │ │ ├── SimulatedLiftingSurface.cs │ │ └── SimulatedEngine.cs │ ├── Extensions │ │ ├── IListClone.cs │ │ ├── GUILayoutHelper.cs │ │ ├── KSPClassExtensions.cs │ │ ├── NetObjectDeepCopy.cs │ │ ├── FloatCurve2.cs │ │ └── Linq2.cs │ ├── Framework │ │ ├── LICENSE_Framework.md │ │ ├── ExtensionsUnity.cs │ │ ├── FrameworkExt │ │ │ ├── Extensions.cs │ │ │ └── KSPDateStructure.cs │ │ └── ConfigNodeStorage.cs │ ├── LICENSE.md │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Accord.Net Framework │ │ ├── ConvergenceException.cs │ │ ├── IOptimizationMethod.cs │ │ └── Constants.cs │ ├── DataGenerators │ │ ├── GraphGenerator.cs │ │ └── EnvelopePoint.cs │ ├── FARVesselCache │ │ ├── FARHook.cs │ │ ├── Borrowed Code │ │ │ ├── InstantConditionSim.cs │ │ │ ├── FARAeroUtil.cs │ │ │ └── FARWingInteraction.cs │ │ ├── FARAeroUtil.cs │ │ ├── FARMethodAssist.cs │ │ ├── FARCloneAssist.cs │ │ └── FARVesselCache.cs │ ├── StockAero.cs │ ├── WindTunnelSettingsDialog.cs │ ├── Wind Tunnel.csproj │ └── AeroPredictor.cs └── Wind Tunnel.sln ├── .gitignore ├── README.md ├── LICENSE └── LICENSE.md /Wind Tunnel/Wind Tunnel/VesselCache/IReleasable.cs: -------------------------------------------------------------------------------- 1 | namespace KerbalWindTunnel.VesselCache 2 | { 3 | public interface IReleasable 4 | { 5 | void Release(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Wind Tunnel/.vs/ 2 | Wind Tunnel/Wind Tunnel/bin/ 3 | Wind Tunnel/Wind Tunnel/obj/ 4 | Wind Tunnel/packages/ 5 | Wind Tunnel/Wind Tunnel/packages.config 6 | Wind Tunnel/DeadCode.ruleset 7 | Wind Tunnel/Wind Tunnel/GlobalSuppressions.cs 8 | WindTunnel.*.zip 9 | Wind Tunnel/Wind Tunnel/FARVesselCache/Deprecated/ 10 | Builds/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KerbalWindTunnel 2 | 3 | This project is archived - please see Kerbal Wind Tunnel 2 at https://github.com/DBooots/KerbalWindTunnel-2 for active development. 4 | 5 | A mod for Kerbal Space Program to provide atmospheric performance data for spaceplanes and aircraft. 6 | 7 | Depends on Unity-Graphing, my graphing library aimed at KSP modders. 8 | https://github.com/DBooots/Unity-Graphing 9 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/IListClone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | 5 | namespace KerbalWindTunnel.Extensions 6 | { 7 | public static class IListClone 8 | { 9 | public static IList ToIList(this IList original) 10 | { 11 | IList result = (IList)Activator.CreateInstance(original.GetType()); 12 | int length = original.Count; 13 | for (int i = 0; i < length; i++) 14 | result.Add(original[i]); 15 | return result; 16 | } 17 | public static IList ToIList(this IList original, Func constructor) 18 | { 19 | IList result = constructor(); 20 | int length = original.Count; 21 | for (int i = 0; i < length; i++) 22 | result.Add(original[i]); 23 | return result; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Framework/LICENSE_Framework.md: -------------------------------------------------------------------------------- 1 | The following license applies to the KSP Plugin Framework and the FrameworkExtension 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2014, David Tregoning 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/LICENSE.md: -------------------------------------------------------------------------------- 1 | The majority of Kerbal Wind Tunnel is licensed under the MIT License found below. 2 | However, like most coding projects, it has benefitted from the works of many 3 | others. Derived and copied code within this project will have the license from 4 | its progenitor included either in the root of its folder or as a header for the 5 | class or method to which it applies. 6 | 7 | MIT License 8 | 9 | Copyright (c) 2018 David Boots 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Wind Tunnel")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Wind Tunnel")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("13572b17-7b9f-4252-b723-28dac8796a91")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.3.1.1")] 36 | [assembly: AssemblyFileVersion("1.3.1.1")] 37 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/GUILayoutHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace KerbalWindTunnel.Extensions 8 | { 9 | public static class GUILayoutHelper 10 | { 11 | public static bool[] ToggleGrid(string[] strings, bool[] values, int xCount) 12 | { 13 | bool[] valuesOut = new bool[values.Length]; 14 | GUILayout.BeginVertical(); 15 | int marginWidthL = HighLogic.Skin.toggle.margin.left; 16 | int marginWidthR = HighLogic.Skin.label.margin.right; 17 | 18 | int elementWidth = 0; 19 | Rect position = new Rect(0,0,0,0); 20 | for (int i = 0; i < values.Length; i++) 21 | { 22 | if (i % xCount == 0) 23 | { 24 | position = GUILayoutUtility.GetRect(new GUIContent(""), GUIStyle.none, GUILayout.ExpandWidth(true)); 25 | GUILayout.Space(2); 26 | float width = position.width; 27 | position.x += marginWidthL; 28 | int availableWidth = (int)width;// - marginWidthL - marginWidthR; 29 | elementWidth = availableWidth / xCount; 30 | } 31 | Rect toggle = position; 32 | toggle.x += elementWidth * (i % xCount);//marginWidthL + 33 | toggle.width = elementWidth - marginWidthR; 34 | valuesOut[i] = GUI.Toggle(toggle, values[i], strings[i]); 35 | } 36 | GUILayout.EndVertical(); 37 | return valuesOut; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/VesselCache/LICENSE_VesselCache.md: -------------------------------------------------------------------------------- 1 | The following license applies to the files within the VesselCache folder. 2 | SimulatedLiftingSurface and SimulatedEngine are derived from SimulatedPart 3 | and thus could be considered to be a modified form of it and so also fall 4 | under this license. 5 | 6 | Used with permission under the following license. 7 | 8 | Copyright (c) 2018, Sébastien GAGGINI AKA Sarbian, France 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 17 | 2. Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The majority of Kerbal Wind Tunnel is licensed under the MIT License found below. 2 | However, like most coding projects, it has benefitted from the works of many 3 | others. Derived and copied code within this project will have the license from 4 | its progenitor included either in the root of its folder or as a header for the 5 | class or method to which it applies. 6 | 7 | In particular, this mod includes the Accord.Net library for advanced math, which 8 | is released under the GNU Lesser Public License v.2.1. Of note, their website 9 | highlights that distributing it under its own licenses while referencing it from 10 | a project under another license is permissible. 11 | 12 | MIT License 13 | 14 | Copyright (c) 2018 David Boots 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The majority of Kerbal Wind Tunnel is licensed under the MIT License found below. 2 | However, like most coding projects, it has benefitted from the works of many 3 | others. Derived and copied code within this project will have the license from 4 | its progenitor included either in the root of its folder or as a header for the 5 | class or method to which it applies. 6 | 7 | In particular, this mod includes the Accord.Net library for advanced math, which 8 | is released under the GNU Lesser Public License v.2.1. Of note, their website 9 | highlights that distributing it under its own licenses while referencing it from 10 | a project under another license is permissible. 11 | 12 | MIT License 13 | 14 | Copyright (c) 2018 David Boots 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Framework/ExtensionsUnity.cs: -------------------------------------------------------------------------------- 1 | /* Part of KSPPluginFramework 2 | Version 1.2 3 | 4 | Forum Thread:http://forum.kerbalspaceprogram.com/threads/66503-KSP-Plugin-Framework 5 | Author: TriggerAu, 2014 6 | License: The MIT License (MIT) 7 | */ 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | 13 | using KSP; 14 | using UnityEngine; 15 | 16 | namespace KSPPluginFramework 17 | { 18 | /// 19 | /// CLass containing some extension methods for Unity Objects 20 | /// 21 | public static class UnityExtensions 22 | { 23 | /// 24 | /// Ensure that the Rect remains within the screen bounds 25 | /// 26 | public static Rect ClampToScreen(this Rect r) 27 | { 28 | return r.ClampToScreen(new RectOffset(0, 0, 0, 0)); 29 | } 30 | 31 | /// 32 | /// Ensure that the Rect remains within the screen bounds 33 | /// 34 | /// A Border to the screen bounds that the Rect will be clamped inside (can be negative) 35 | public static Rect ClampToScreen(this Rect r, RectOffset ScreenBorder) 36 | { 37 | r.x = Mathf.Clamp(r.x, ScreenBorder.left, Screen.width - r.width - ScreenBorder.right); 38 | r.y = Mathf.Clamp(r.y, ScreenBorder.top, Screen.height - r.height - ScreenBorder.bottom); 39 | return r; 40 | } 41 | 42 | public static GUIStyle PaddingChange(this GUIStyle g, Int32 PaddingValue) 43 | { 44 | GUIStyle gReturn = new GUIStyle(g); 45 | gReturn.padding = new RectOffset(PaddingValue, PaddingValue, PaddingValue, PaddingValue); 46 | return gReturn; 47 | } 48 | public static GUIStyle PaddingChangeBottom(this GUIStyle g, Int32 PaddingValue) 49 | { 50 | GUIStyle gReturn = new GUIStyle(g); 51 | gReturn.padding.bottom = PaddingValue; 52 | return gReturn; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2002 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wind Tunnel", "Wind Tunnel\Wind Tunnel.csproj", "{13572B17-7B9F-4252-B723-28DAC8796A91}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Graphing", "..\..\Unity Graphing\Graphing\Graphing.csproj", "{3C216395-20C9-4F59-813E-9326882D7092}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | UnityEditor|Any CPU = UnityEditor|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {13572B17-7B9F-4252-B723-28DAC8796A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {13572B17-7B9F-4252-B723-28DAC8796A91}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {13572B17-7B9F-4252-B723-28DAC8796A91}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {13572B17-7B9F-4252-B723-28DAC8796A91}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {13572B17-7B9F-4252-B723-28DAC8796A91}.UnityEditor|Any CPU.ActiveCfg = UnityEditor|Any CPU 22 | {13572B17-7B9F-4252-B723-28DAC8796A91}.UnityEditor|Any CPU.Build.0 = UnityEditor|Any CPU 23 | {3C216395-20C9-4F59-813E-9326882D7092}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {3C216395-20C9-4F59-813E-9326882D7092}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {3C216395-20C9-4F59-813E-9326882D7092}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {3C216395-20C9-4F59-813E-9326882D7092}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {3C216395-20C9-4F59-813E-9326882D7092}.UnityEditor|Any CPU.ActiveCfg = Debug|Any CPU 28 | {3C216395-20C9-4F59-813E-9326882D7092}.UnityEditor|Any CPU.Build.0 = Debug|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {0822F145-F46F-4315-8F09-E041A94D097D} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/KSPClassExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace KerbalWindTunnel.Extensions 7 | { 8 | public static class KSPClassExtensions 9 | { 10 | /* 11 | From Trajectories 12 | Copyright 2014, Youen Toupin 13 | This method is part of Trajectories, under MIT license. 14 | StockAeroUtil by atomicfury 15 | */ 16 | /// 17 | /// Gets the air density (rho) for the specified altitude on the specified body. 18 | /// This is an approximation, because actual calculations, taking sun exposure into account to compute air temperature, require to know the actual point on the body where the density is to be computed (knowing the altitude is not enough). 19 | /// However, the difference is small for high altitudes, so it makes very little difference for trajectory prediction. 20 | /// From StockAeroUtil.cs from Trajectories 21 | /// 22 | /// 23 | /// Altitude above sea level (in meters) 24 | /// 25 | public static double GetDensity(this CelestialBody body, double altitude) 26 | { 27 | if (!body.atmosphere) 28 | return 0; 29 | 30 | if (altitude > body.atmosphereDepth) 31 | return 0; 32 | 33 | double pressure = body.GetPressure(altitude); 34 | 35 | // get an average day/night temperature at the equator 36 | double sunDot = 0.5; 37 | float sunAxialDot = 0; 38 | double atmosphereTemperatureOffset = (double)body.latitudeTemperatureBiasCurve.Evaluate(0) 39 | + (double)body.latitudeTemperatureSunMultCurve.Evaluate(0) * sunDot 40 | + (double)body.axialTemperatureSunMultCurve.Evaluate(sunAxialDot); 41 | double temperature = // body.GetFullTemperature(altitude, atmosphereTemperatureOffset); 42 | body.GetTemperature(altitude) 43 | + (double)body.atmosphereTemperatureSunMultCurve.Evaluate((float)altitude) * atmosphereTemperatureOffset; 44 | 45 | 46 | return body.GetDensity(pressure, temperature); 47 | } 48 | 49 | public static FloatCurve Clone(this FloatCurve inCurve) 50 | { 51 | return new FloatCurve(inCurve.Curve.keys); 52 | } 53 | 54 | public static UnityEngine.Vector3 ProjectOnPlaneSafe(UnityEngine.Vector3 vector, UnityEngine.Vector3 planeNormal) 55 | { 56 | return vector - UnityEngine.Vector3.Dot(vector, planeNormal) / planeNormal.sqrMagnitude * planeNormal; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Framework/FrameworkExt/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace KSPPluginFramework 8 | { 9 | public static class EnumExtensions 10 | { 11 | public static String Description(this Enum e) 12 | { 13 | DescriptionAttribute[] desc = (DescriptionAttribute[])e.GetType().GetMember(e.ToString())[0].GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false); 14 | if (desc.Length > 0) 15 | return desc[0].Description; 16 | else 17 | return e.ToString(); 18 | } 19 | 20 | //public static List> ToEnumDescriptionsList(TEnum value) 21 | //{ 22 | // return Enum 23 | // .GetValues(typeof(TEnum)) 24 | // .Cast() 25 | // .Select(x => new KeyValuePair(x, ((Enum)((object)x)).Description())) 26 | // .ToList(); 27 | //} 28 | //public static List> ToEnumDescriptionsList() 29 | //{ 30 | // return ToEnumDescriptionsList(default(TEnum)); 31 | //} 32 | 33 | //limit it to accept enums only 34 | public static List ToEnumDescriptions(TEnum value) where TEnum : struct,IConvertible 35 | { 36 | List> temp = Enum 37 | .GetValues(typeof(TEnum)) 38 | .Cast() 39 | .Select(x => new KeyValuePair(x, ((Enum)((System.Object)x)).Description())) 40 | .ToList(); 41 | return temp.Select(x => x.Value).ToList(); 42 | } 43 | public static List ToEnumDescriptions() where TEnum : struct,IConvertible 44 | { 45 | return ToEnumDescriptions(default(TEnum)).ToList(); 46 | } 47 | 48 | 49 | 50 | public static T Clamp(this T val, T min, T max) where T : IComparable 51 | { 52 | if (val.CompareTo(min) < 0) return min; 53 | else if (val.CompareTo(max) > 0) return max; 54 | else return val; 55 | } 56 | 57 | public static Int32 ToInt32(this String s) 58 | { 59 | return Convert.ToInt32(s); 60 | } 61 | 62 | public static Int32 NormalizeAngle360(this Int32 val) { 63 | return (Int32)Convert.ToDouble(val).NormalizeAngle360(); 64 | } 65 | 66 | public static Double NormalizeAngle360(this Double val) 67 | { 68 | val %= 360; 69 | if (val < 0) 70 | val += 360; 71 | return val; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Accord.Net Framework/ConvergenceException.cs: -------------------------------------------------------------------------------- 1 | // Accord Core Library 2 | // The Accord.NET Framework 3 | // http://accord-framework.net 4 | // 5 | // Copyright © César Souza, 2009-2017 6 | // cesarsouza at gmail.com 7 | // 8 | // This library is free software; you can redistribute it and/or 9 | // modify it under the terms of the GNU Lesser General Public 10 | // License as published by the Free Software Foundation; either 11 | // version 2.1 of the License, or (at your option) any later version. 12 | // 13 | // This library is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | // Lesser General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU Lesser General Public 19 | // License along with this library; if not, write to the Free Software 20 | // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | // 22 | 23 | namespace Accord 24 | { 25 | using System; 26 | using System.Runtime.Serialization; 27 | //using Accord.Compat; 28 | 29 | /// 30 | /// Algorithm Convergence Exception. 31 | /// 32 | /// 33 | /// The algorithm convergence exception is thrown in cases where a iterative 34 | /// algorithm could not converge to a finite solution. 35 | /// 36 | /// 37 | [Serializable] 38 | public class ConvergenceException : Exception 39 | { 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// 44 | public ConvergenceException() { } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// 50 | /// Message providing some additional information. 51 | /// 52 | public ConvergenceException(string message) : 53 | base(message) 54 | { } 55 | 56 | /// 57 | /// Initializes a new instance of the class. 58 | /// 59 | /// 60 | /// Message providing some additional information. 61 | /// The exception that is the cause of the current exception. 62 | /// 63 | public ConvergenceException(string message, Exception innerException) : 64 | base(message, innerException) 65 | { } 66 | 67 | 68 | /// 69 | /// Initializes a new instance of the class. 70 | /// 71 | /// 72 | /// The that holds the serialized object data about the exception being thrown. 73 | /// The that contains contextual information about the source or destination. 74 | /// 75 | /// The parameter is null. 76 | /// 77 | /// 78 | /// The class name is null or is zero (0). 79 | /// 80 | /// 81 | protected ConvergenceException(SerializationInfo info, StreamingContext context) : 82 | base(info, context) 83 | { } 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/DataGenerators/GraphGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Graphing; 7 | //using KerbalWindTunnel.Threading; 8 | 9 | namespace KerbalWindTunnel.DataGenerators 10 | { 11 | public abstract class DataSetGenerator : IDisposable 12 | { 13 | public GraphableCollection Graphables { get => graphables; } 14 | protected GraphableCollection graphables = new GraphableCollection(); 15 | protected Task task; 16 | protected CancellationTokenSource cancellationTokenSource; 17 | protected System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); 18 | protected bool valuesSet = false; 19 | 20 | public IGraphable this[string name] { get => graphables[name]; set => graphables[name] = value; } 21 | public static explicit operator GraphableCollection (DataSetGenerator me) => me.Graphables; 22 | 23 | public virtual TaskStatus Status 24 | { 25 | get 26 | { 27 | if (valuesSet) 28 | return TaskStatus.RanToCompletion; 29 | if (task == null) 30 | return TaskStatus.WaitingToRun; 31 | TaskStatus status = task.Status; 32 | if (status == TaskStatus.RanToCompletion) 33 | return TaskStatus.Running; 34 | return status; 35 | } 36 | } 37 | 38 | public virtual TaskStatus InternalStatus 39 | { 40 | get 41 | { 42 | if (task == null) 43 | return TaskStatus.WaitingForActivation; 44 | return task.Status; 45 | } 46 | } 47 | 48 | public abstract float PercentComplete { get; } 49 | 50 | public virtual float InternalPercentComplete => PercentComplete; 51 | 52 | /// 53 | /// Returns the current progress rate in amount per second. 54 | /// 55 | public virtual float ProgressRate { get => float.NaN; } 56 | 57 | public virtual TimeSpan ElapsedTime 58 | { 59 | get 60 | { 61 | if (!stopwatch.IsRunning) 62 | return new TimeSpan(0); 63 | return stopwatch.Elapsed; 64 | } 65 | } 66 | 67 | public virtual TimeSpan TimeRemaining 68 | { 69 | get 70 | { 71 | if (Status == TaskStatus.RanToCompletion) 72 | return new TimeSpan(0); 73 | double secondsAtAverageRate = ElapsedTime.TotalSeconds * (1 - 1 / PercentComplete); 74 | if (!float.IsNaN(ProgressRate)) 75 | return TimeSpan.FromSeconds((secondsAtAverageRate + (1 - PercentComplete) / ProgressRate) / 2); 76 | else 77 | return TimeSpan.FromSeconds(secondsAtAverageRate); 78 | } 79 | } 80 | 81 | public virtual void Cancel() 82 | { 83 | valuesSet = false; 84 | cancellationTokenSource?.Cancel(); 85 | DisposeOfCancellationToken(); 86 | } 87 | 88 | public virtual void Clear() 89 | { 90 | Cancel(); 91 | } 92 | 93 | public void Dispose() 94 | { 95 | Dispose(true); 96 | } 97 | protected virtual void Dispose(bool disposing) 98 | { 99 | if (disposing) 100 | DisposeOfCancellationToken(); 101 | } 102 | 103 | protected void DisposeOfCancellationToken() 104 | { 105 | if (cancellationTokenSource == null) 106 | return; 107 | 108 | if (!cancellationTokenSource.IsCancellationRequested) 109 | cancellationTokenSource.Cancel(); 110 | 111 | CancellationTokenSource closureSource = cancellationTokenSource; 112 | 113 | if (task != null && task.Status < TaskStatus.RanToCompletion) 114 | task.ContinueWith((t) => closureSource.Dispose()); 115 | else 116 | cancellationTokenSource.Dispose(); 117 | 118 | cancellationTokenSource = null; 119 | } 120 | 121 | public virtual void UpdateGraphs() { } 122 | 123 | public abstract void OnAxesChanged(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/FARHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace KerbalWindTunnel.FARVesselCache 6 | { 7 | public static class FARHook 8 | { 9 | private static bool initiated = false; 10 | private static bool installed = false; 11 | private static Assembly FARassembly; 12 | public static Type simulationType; 13 | public static Type FARWingInteractionType; 14 | public static Type FARWingAerodynamicModelType; 15 | public static Type FARControllableSurfaceType; 16 | public static Type FARAeroSectionType; 17 | 18 | public static bool FARInstalled 19 | { 20 | get 21 | { 22 | if (!initiated) 23 | Initiate(); 24 | return installed; 25 | } 26 | } 27 | 28 | public static void Initiate() 29 | { 30 | if (initiated) return; 31 | try 32 | { 33 | const string assemblyName = "FerramAerospaceResearch"; 34 | FARassembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.Contains(assemblyName) && !a.FullName.Contains(assemblyName + ".Base")); 35 | if (FARassembly == null) 36 | { 37 | UnityEngine.Debug.Log("KerbalWindTunnel: Using Stock Aero."); 38 | installed = false; 39 | initiated = true; 40 | return; 41 | } 42 | UnityEngine.Debug.Log("KerbalWindTunnel: Using FAR Aero."); 43 | installed = true; 44 | 45 | simulationType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("InstantConditionSim")); 46 | Type editorGUIType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("EditorGUI")); 47 | Type FARAeroUtilType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARAeroUtil")); 48 | Type FARAtmosphereType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARAtmosphere")); 49 | FARWingInteractionType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARWingInteraction")); 50 | FARWingAerodynamicModelType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARWingAerodynamicModel")); 51 | FARAeroSectionType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARAeroSection")); 52 | FARControllableSurfaceType = FARassembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARControllableSurface")); 53 | 54 | if (simulationType == null || FARAeroUtilType == null || FARAtmosphereType == null || editorGUIType == null || FARWingInteractionType == null || FARWingAerodynamicModelType == null || FARAeroSectionType == null) 55 | { 56 | UnityEngine.Debug.LogError("KerbalWindTunnel: FAR API type not found!"); 57 | if (simulationType == null) UnityEngine.Debug.LogError("InstantConditionSim not found."); 58 | if (editorGUIType == null) UnityEngine.Debug.LogError("EditorGUI not found."); 59 | if (FARAeroUtilType == null) UnityEngine.Debug.LogError("FARAeroUtil not found."); 60 | if (FARAtmosphereType == null) UnityEngine.Debug.LogError("FARAtmosphere not found."); 61 | if (FARWingAerodynamicModelType == null) UnityEngine.Debug.LogError("FARWingAerodynamicModel not found."); 62 | if (FARWingInteractionType == null) UnityEngine.Debug.LogError("FARWingInteraction not found."); 63 | if (FARAeroSectionType == null) UnityEngine.Debug.LogError("FARAeroSection not found."); 64 | installed = false; 65 | initiated = true; 66 | return; 67 | } 68 | 69 | if (!FARVesselCache.SetMethods(simulationType, editorGUIType)) 70 | installed = false; 71 | if (!FARMethodAssist.Initialize(FARassembly)) 72 | installed = false; 73 | if (!FARAeroUtil.InitializeMethods(FARAeroUtilType, FARAtmosphereType)) 74 | installed = false; 75 | if (!FARWingAerodynamicModelWrapper.InitializeMethods(FARWingAerodynamicModelType)) 76 | installed = false; 77 | if (!FARWingInteractionWrapper.InitializeMethods(FARWingInteractionType)) 78 | installed = false; 79 | if (!FARCloneAssist.InitializeMethods(FARAeroSectionType)) 80 | installed = false; 81 | if (installed == false) 82 | UnityEngine.Debug.LogError("KerbalWindTunnel: Some FAR initialization failed."); 83 | 84 | initiated = true; 85 | } 86 | catch (Exception ex) 87 | { 88 | UnityEngine.Debug.LogError("KerbalWindTunnel: Exception when initiating FAR Aero. Using Stock Aero."); 89 | installed = false; 90 | initiated = true; 91 | UnityEngine.Debug.LogException(ex); 92 | return; 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/StockAero.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace KerbalWindTunnel 8 | { 9 | public class StockAero : AeroPredictor 10 | { 11 | public override float Mass 12 | { 13 | get 14 | { 15 | return EditorLogic.fetch.ship.GetTotalMass(); 16 | } 17 | } 18 | 19 | public override Vector3 GetAeroForce(Conditions conditions, float AoA, float pitchInput = 0) 20 | { 21 | return StockAeroUtil.SimAeroForce(conditions.body, EditorLogic.fetch.ship, InflowVect(AoA) * conditions.speed, conditions.altitude); 22 | } 23 | public override Vector3 GetLiftForce(Conditions conditions, float AoA, float pitchInput = 0) 24 | { 25 | return StockAeroUtil.SimLiftForce(conditions.body, EditorLogic.fetch.ship, InflowVect(AoA) * conditions.speed, conditions.altitude); 26 | } 27 | 28 | public override Vector3 GetThrustForce(float mach, float atmDensity, float atmPressure, bool oxygenPresent) 29 | { 30 | Vector3 thrust = Vector3.zero; 31 | int stage = 0; 32 | for (int i = EditorLogic.fetch.ship.Parts.Count - 1; i >= 0; i--) 33 | { 34 | Part part = EditorLogic.fetch.ship.Parts[i]; 35 | //if (part.inStageIndex != 0) 36 | // continue; 37 | 38 | List engines = part.FindModulesImplementing(); 39 | if (engines.Count == 0) 40 | continue; 41 | 42 | //Debug.Log("Engine #" + i + " is in stage " + part.inverseStage); 43 | if (part.inverseStage < stage) 44 | continue; 45 | if (part.inverseStage > stage) 46 | { 47 | thrust = Vector3.zero; 48 | stage = part.inverseStage; 49 | //Debug.Log("Found a new, higher stage."); 50 | } 51 | 52 | ModuleEngines primaryEngine; 53 | if (part.FindModulesImplementing().FirstOrDefault() != null) 54 | primaryEngine = engines.Find(x => x.engineID == part.FindModulesImplementing().FirstOrDefault().primaryEngineID); 55 | else 56 | primaryEngine = engines.FirstOrDefault(); 57 | 58 | if (primaryEngine == null) 59 | { 60 | Debug.Log("No primary engine??"); 61 | continue; 62 | } 63 | 64 | if (primaryEngine.propellants.Any(p => p.name == "IntakeAir") && !oxygenPresent) 65 | { 66 | //Debug.Log("Skipping as needs oxygen"); 67 | continue; 68 | } 69 | 70 | float thrustLevel = 1; 71 | if (primaryEngine.atmChangeFlow) 72 | { 73 | if (primaryEngine.useAtmCurve) 74 | thrustLevel = primaryEngine.atmCurve.Evaluate(atmDensity * (40f / 49f)); 75 | else 76 | thrustLevel = atmDensity * (40f / 49f); 77 | } 78 | thrustLevel *= primaryEngine.useVelCurve ? primaryEngine.velCurve.Evaluate(mach) : 1; 79 | 80 | if (thrustLevel > primaryEngine.flowMultCap) 81 | thrustLevel = primaryEngine.flowMultCap + (thrustLevel - primaryEngine.flowMultCap) / (primaryEngine.flowMultCapSharpness + thrustLevel / primaryEngine.flowMultCap - 1); 82 | thrustLevel = Mathf.Max(thrustLevel, primaryEngine.CLAMP); 83 | 84 | float isp = primaryEngine.atmosphereCurve.Evaluate(atmPressure); 85 | if (primaryEngine.useThrottleIspCurve) 86 | isp *= Mathf.Lerp(1f, primaryEngine.throttleIspCurve.Evaluate(1), primaryEngine.throttleIspCurveAtmStrength.Evaluate(atmPressure)); 87 | 88 | Vector3d engineThrust = Vector3d.zero; 89 | for (int j = primaryEngine.thrustTransforms.Count - 1; j >= 0; j--) 90 | { 91 | engineThrust -= primaryEngine.thrustTransforms[j].forward * primaryEngine.thrustTransformMultipliers[j]; 92 | } 93 | 94 | engineThrust *= thrustLevel * isp * primaryEngine.g * primaryEngine.multIsp * primaryEngine.maxFuelFlow * primaryEngine.multFlow * (primaryEngine.thrustPercentage / 100f); 95 | //Debug.Log("Thrust: " + engineThrust); 96 | //Debug.Log("Adding engine " + primaryEngine.part.name); 97 | thrust += engineThrust; 98 | } 99 | 100 | return thrust; 101 | } 102 | 103 | public override float GetFuelBurnRate(float mach, float atmDensity, float atmPressure, bool oxygenPresent) 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public override Vector3 GetAeroTorque(Conditions conditions, float AoA, float pitchInput = 0, bool dryTorque = false) 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | 113 | public override float GetPitchInput(Conditions conditions, float AoA, bool dryTorque = false, float guess = float.NaN) 114 | { 115 | return 0; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/Borrowed Code/InstantConditionSim.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Code copied and/or derived from Ferram Aerospace Research https://github.com/dkavolis/Ferram-Aerospace-Research/ 3 | ========================= 4 | Aerodynamics model for Kerbal Space Program 5 | 6 | Copyright 2020, Michael Ferrara, aka Ferram4 7 | 8 | This file is derived from part of Ferram Aerospace Research. 9 | 10 | Ferram Aerospace Research is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Ferram Aerospace Research is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Ferram Aerospace Research. If not, see . 22 | 23 | Serious thanks: a.g., for tons of bugfixes and code-refactorings 24 | stupid_chris, for the RealChuteLite implementation 25 | Taverius, for correcting a ton of incorrect values 26 | Tetryds, for finding lots of bugs and issues and not letting me get away with them, and work on example crafts 27 | sarbian, for refactoring code for working with MechJeb, and the Module Manager updates 28 | ialdabaoth (who is awesome), who originally created Module Manager 29 | Regex, for adding RPM support 30 | DaMichel, for some ferramGraph updates and some control surface-related features 31 | Duxwing, for copy editing the readme 32 | */ 33 | 34 | using UnityEngine; 35 | 36 | namespace KerbalWindTunnel.FARVesselCache 37 | { 38 | public partial class FARVesselCache 39 | { 40 | public void GetClCdCmSteady( 41 | Conditions conditions, 42 | Vector3 inflow, 43 | float pitchInput, 44 | Vector3 torquePoint, 45 | out Vector3 aeroforce, 46 | out Vector3 torque 47 | ) 48 | { 49 | SetAerodynamicModelVels(inflow); 50 | 51 | aeroforce = torque = Vector3.zero; 52 | double ReynoldsNumber = FARAeroUtil.CalculateReynoldsNumber(conditions.atmDensity, 53 | bodyLength, 54 | conditions.speed, 55 | conditions.mach, 56 | FARAeroUtil.GetTemperature(conditions), 57 | FARAeroUtil.GetAdiabaticIndex(conditions)); 58 | float skinFrictionDragCoefficient = (float)FARAeroUtil.SkinFrictionDrag(ReynoldsNumber, conditions.mach); 59 | 60 | float pseudoKnudsenNumber = (float)(conditions.mach / (ReynoldsNumber + conditions.mach)); 61 | 62 | Vector3d velocity = inflow.normalized; 63 | 64 | foreach (FARWingAerodynamicModelWrapper wingAerodynamicModel in _wingAerodynamicModel) 65 | { 66 | if (wingAerodynamicModel == null) 67 | continue; 68 | 69 | //w.ComputeForceEditor(velocity, input.MachNumber, 2); 70 | 71 | wingAerodynamicModel.EditorClClear(true); 72 | 73 | /*if (FARHook.FARControllableSurfaceType.IsAssignableFrom(wingAerodynamicModel.WrappedObject.GetType())) 74 | FARMethodAssist.FARControllableSurface_SetControlStateEditor(wingAerodynamicModel.WrappedObject, 75 | torquePoint, 76 | velocity, 77 | pitchInput, // TODO: Confirm that I am using the same pitch input convention. 78 | 0, 79 | 0, 80 | 0, //Flaps 81 | false); 82 | 83 | else*/ 84 | if (wingAerodynamicModel.isShielded) 85 | continue; 86 | 87 | Vector3 force = wingAerodynamicModel.ComputeForceEditor(velocity, conditions.mach, conditions.atmDensity, conditions) * 1000; 88 | aeroforce += force; 89 | 90 | Vector3 relPos = wingAerodynamicModel.AerodynamicCenter - torquePoint; 91 | 92 | torque += -Vector3d.Cross(relPos, force); 93 | } 94 | 95 | var center = FARMethodAssist.NewFARCenterQuery(); 96 | // aeroSection.simContext.center.ClearAll() would allow for perpetual reuse of an object. 97 | foreach (object aeroSection in _currentAeroSections) 98 | FARMethodAssist.FARAeroSection_PredictionCalculateAeroForces(aeroSection, 99 | conditions.atmDensity, 100 | conditions.mach, 101 | (float)(ReynoldsNumber / bodyLength), 102 | pseudoKnudsenNumber, 103 | skinFrictionDragCoefficient, 104 | velocity, 105 | center); 106 | 107 | aeroforce += FARMethodAssist.FARCenterQuery_force(center) * 1000; 108 | torque += -FARMethodAssist.FARCenterQuery_TorqueAt(center, torquePoint) * 1000; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Accord.Net Framework/IOptimizationMethod.cs: -------------------------------------------------------------------------------- 1 | // Accord Math Library 2 | // The Accord.NET Framework 3 | // http://accord-framework.net 4 | // 5 | // Copyright © César Souza, 2009-2017 6 | // cesarsouza at gmail.com 7 | // 8 | // This library is free software; you can redistribute it and/or 9 | // modify it under the terms of the GNU Lesser General Public 10 | // License as published by the Free Software Foundation; either 11 | // version 2.1 of the License, or (at your option) any later version. 12 | // 13 | // This library is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | // Lesser General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU Lesser General Public 19 | // License along with this library; if not, write to the Free Software 20 | // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | // 22 | 23 | namespace Accord.Math.Optimization 24 | { 25 | using System; 26 | 27 | /// 28 | /// Common interface for function optimization methods. 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | public interface IOptimizationMethod : IOptimizationMethod 38 | { 39 | // For backward compatibility 40 | } 41 | 42 | /// 43 | /// Common interface for function optimization methods. 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | /// 52 | public interface IOptimizationMethod : IOptimizationMethod, IOptimizationMethod 53 | where TCode : struct 54 | { 55 | // For backward compatibility 56 | } 57 | 58 | /// 59 | /// Common interface for function optimization methods. 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | public interface IOptimizationMethod 69 | { 70 | 71 | /// 72 | /// Gets the number of variables (free parameters) 73 | /// in the optimization problem. 74 | /// 75 | /// 76 | /// The number of parameters. 77 | /// 78 | int NumberOfVariables { get; set; } 79 | 80 | /// 81 | /// Gets the current solution found, the values of 82 | /// the parameters which optimizes the function. 83 | /// 84 | /// 85 | TInput Solution { get; set; } 86 | 87 | /// 88 | /// Gets the output of the function at the current . 89 | /// 90 | /// 91 | TOutput Value { get; } 92 | 93 | /// 94 | /// Finds the minimum value of a function. The solution vector 95 | /// will be made available at the property. 96 | /// 97 | /// 98 | /// Returns true if the method converged to a . 99 | /// In this case, the found value will also be available at the 100 | /// property. 101 | /// 102 | bool Minimize(); 103 | 104 | /// 105 | /// Finds the maximum value of a function. The solution vector 106 | /// will be made available at the property. 107 | /// 108 | /// 109 | /// Returns true if the method converged to a . 110 | /// In this case, the found value will also be available at the 111 | /// property. 112 | /// 113 | bool Maximize(); 114 | 115 | } 116 | 117 | 118 | 119 | /// 120 | /// Common interface for function optimization methods. 121 | /// 122 | /// 123 | /// 124 | /// 125 | /// 126 | /// 127 | /// 128 | /// 129 | public interface IOptimizationMethod : IOptimizationMethod 130 | where TCode : struct 131 | { 132 | /// 133 | /// Get the exit code returned in the last call to the 134 | /// or 135 | /// methods. 136 | /// 137 | /// 138 | TCode Status { get; } 139 | } 140 | } -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/FARAeroUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using KerbalWindTunnel.Extensions.Reflection; 3 | 4 | namespace KerbalWindTunnel.FARVesselCache 5 | { 6 | public static partial class FARAeroUtil 7 | { 8 | private static Func skinFrictionDrag_method1; 9 | private static Func skinFrictionDrag_method2; 10 | private static Func getTemperature_method; 11 | private static Func getAdiabaticIndex_method; 12 | private static Func calculateOswaldsEfficiencyNitaScholz_method; 13 | private static Func calculateSinMaxShockAngle_method; 14 | private static Func calculateSinWeakObliqueShockAngle_method; 15 | private static Action updateCurrentActiveBody_method; 16 | private static Func calculateReynoldsNumber_method; 17 | 18 | //public static FloatCurve PrandtlMeyerMach; 19 | //public static FloatCurve PrandtlMeyerAngle; 20 | //public static double maxPrandtlMeyerTurnAngle; 21 | 22 | public const double lat = 0; 23 | public const double lon = 0; 24 | public const double ut = 0; 25 | 26 | public static double SkinFrictionDrag(double density, double lengthScale, double vel, double machNumber, double externalTemp, double gamma) 27 | => skinFrictionDrag_method1(density, lengthScale, vel, machNumber, externalTemp, gamma); 28 | public static double SkinFrictionDrag(double reynoldsNumber, double machNumber) 29 | => skinFrictionDrag_method2(reynoldsNumber, machNumber); 30 | public static double GetTemperature(AeroPredictor.Conditions conditions) 31 | => getTemperature_method(conditions.body, new Vector3d(lat, lon, conditions.altitude), ut); 32 | public static double GetAdiabaticIndex(AeroPredictor.Conditions conditions) 33 | => getAdiabaticIndex_method(conditions.body, new Vector3d(lat, lon, conditions.altitude), ut); 34 | public static double CalculateOswaldsEfficiencyNitaScholz(double AR, double CosSweepAngle, double Cd0, double taperRatio) 35 | => calculateOswaldsEfficiencyNitaScholz_method(AR, CosSweepAngle, Cd0, taperRatio); 36 | public static double CalculateSinMaxShockAngle(double MachNumber, double gamma) 37 | => calculateSinMaxShockAngle_method(MachNumber, gamma); 38 | public static double CalculateSinWeakObliqueShockAngle(double MachNumber, double gamma, double deflectionAngle) 39 | => calculateSinWeakObliqueShockAngle_method(MachNumber, gamma, deflectionAngle); 40 | public static double CalculateReynoldsNumber(double density, double lengthScale, double vel, double machNumber, double externalTemp, double gamma) 41 | => calculateReynoldsNumber_method(density, lengthScale, vel, machNumber, externalTemp, gamma); 42 | 43 | public static void UpdateCurrentActiveBody(CelestialBody body) 44 | { 45 | updateCurrentActiveBody_method(body); 46 | PrandtlMeyerAngle.ClearCache(body); 47 | //PrandtlMeyerMach = prandtlMeyerMach_get().Clone(); 48 | //PrandtlMeyerAngle = prandtlMeyerAngle_get().Clone(); 49 | } 50 | 51 | public static bool InitializeMethods(Type FARAeroUtilType, Type FARAtmosphereType) 52 | { 53 | skinFrictionDrag_method1 = FARAeroUtilType.StaticMethod>("SkinFrictionDrag"); 54 | skinFrictionDrag_method2 = FARAeroUtilType.StaticMethod>("SkinFrictionDrag"); 55 | getTemperature_method = FARAtmosphereType.StaticMethod>("GetTemperature"); 56 | getAdiabaticIndex_method = FARAtmosphereType.StaticMethod>("GetAdiabaticIndex"); 57 | calculateOswaldsEfficiencyNitaScholz_method = FARAeroUtilType.StaticMethod>("CalculateOswaldsEfficiencyNitaScholz"); 58 | calculateSinMaxShockAngle_method = FARAeroUtilType.StaticMethod>("CalculateSinMaxShockAngle"); 59 | calculateSinWeakObliqueShockAngle_method = FARAeroUtilType.StaticMethod>("CalculateSinWeakObliqueShockAngle"); 60 | //prandtlMeyerMach_get = FARAeroUtilType.StaticMethod>(FARAeroUtilType.GetProperty("PrandtlMeyerMach", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).GetGetMethod()); 61 | //prandtlMeyerAngle_get = FARAeroUtilType.StaticMethod>(FARAeroUtilType.GetProperty("PrandtlMeyerAngle", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).GetGetMethod()); 62 | //prandtlMeyerMach_set = FARAeroUtilType.StaticFieldSet(FARAeroUtilType.GetField("prandtlMeyerMach", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)); 63 | //prandtlMeyerAngle_set = FARAeroUtilType.StaticFieldSet(FARAeroUtilType.GetField("prandtlMeyerAngle", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)); 64 | updateCurrentActiveBody_method = FARAeroUtilType.StaticMethod>("UpdateCurrentActiveBody"); 65 | calculateReynoldsNumber_method = FARAeroUtilType.StaticMethod>("CalculateReynoldsNumber"); 66 | 67 | return true; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/FARMethodAssist.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using System.Reflection; 5 | using KerbalWindTunnel.Extensions.Reflection; 6 | using UnityEngine; 7 | 8 | namespace KerbalWindTunnel.FARVesselCache 9 | { 10 | public static class FARMethodAssist 11 | { 12 | private static bool initialized = false; 13 | 14 | private static Func InstantConditionSim__maxCrossSectionFromBody_get; 15 | private static Func InstantConditionSim__bodyLength_get; 16 | private static Func InstantConditionSim__currentAeroSections_get; 17 | private static Func InstantConditionSim__wingAerodynamicModel_get; 18 | 19 | private static Action FARControllableSurface_SetControlStateEditor_method; 20 | 21 | private static Func FARCenterQuery_ctor; 22 | private static Func FARCenterQuery_force_get; 23 | private static Func FARCenterQuery_TorqueAt_method; 24 | 25 | private static Action FARAeroSection_PredictionCalculateAeroForces_method; 26 | 27 | 28 | public static bool Initialize(Assembly assembly) 29 | { 30 | //if (initialized) 31 | //return true; 32 | 33 | Type InstantConditionSim = assembly.GetTypes().FirstOrDefault(t => t.Name.Contains("InstantConditionSim")); 34 | InstantConditionSim__maxCrossSectionFromBody_get = InstantConditionSim.FieldGet("_maxCrossSectionFromBody"); 35 | InstantConditionSim__bodyLength_get = InstantConditionSim.FieldGet("_bodyLength"); 36 | //InstantConditionSim__currentAeroSections_get = InstantConditionSim.FieldGet(InstantConditionSim.GetField("_currentAeroSections", BindingFlags.Instance | BindingFlags.NonPublic)); 37 | InstantConditionSim__currentAeroSections_get = InstantConditionSim.FieldGet(InstantConditionSim.GetField("_currentAeroSections", BindingFlags.Instance | BindingFlags.NonPublic)); 38 | InstantConditionSim__wingAerodynamicModel_get = InstantConditionSim.FieldGet(InstantConditionSim.GetField("_wingAerodynamicModel", BindingFlags.Instance | BindingFlags.NonPublic)); 39 | 40 | Type FARControllableSurface = assembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARControllableSurface")); 41 | FARControllableSurface_SetControlStateEditor_method = FARControllableSurface.InstanceMethod>("SetControlStateEditor", new Type[] { typeof(object), typeof(Vector3), typeof(Vector3), typeof(float), typeof(float), typeof(float), typeof(int), typeof(bool) }); 42 | //FARControllableSurface_SetControlStateEditor_method = (o, v1, v2, f1, f12, f3, i, b) => { }; 43 | 44 | Type FARCenterQuery = assembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARCenterQuery")); 45 | FARCenterQuery_ctor = FARCenterQuery.Constructor(); 46 | FARCenterQuery_force_get = FARCenterQuery.FieldGet("force"); 47 | FARCenterQuery_TorqueAt_method = FARCenterQuery.InstanceMethod>("TorqueAt"); 48 | 49 | Type FARAeroSection = assembly.GetTypes().FirstOrDefault(t => t.Name.Contains("FARAeroSection")); 50 | FARAeroSection_PredictionCalculateAeroForces_method = FARAeroSection.InstanceMethod>(FARAeroSection.GetMethod("PredictionCalculateAeroForces", BindingFlags.Instance | BindingFlags.Public));//, new Type[] { typeof(object), typeof(float), typeof(float), typeof(float), typeof(float), typeof(float), typeof(Vector3), FARCenterQuery }); 51 | 52 | initialized = true; 53 | return true; 54 | } 55 | 56 | public static float InstantConditionSim__maxCrossSectionFromBody(object InstantConditionSimObj) 57 | => (float)InstantConditionSim__maxCrossSectionFromBody_get(InstantConditionSimObj); 58 | public static float InstantConditionSim__bodyLength(object InstantConditionSimObj) 59 | => (float)InstantConditionSim__bodyLength_get(InstantConditionSimObj); 60 | public static IList InstantConditionSim__currentAeroSections(object InstantConditionSimObj) 61 | //=> InstantConditionSim__currentAeroSections_get(InstantConditionSimObj); 62 | { 63 | object value = InstantConditionSim__currentAeroSections_get(InstantConditionSimObj); 64 | return (IList)value; 65 | } 66 | public static IList InstantConditionSim__wingAerodynamicModel(object InstantConditionSimObj) 67 | => InstantConditionSim__wingAerodynamicModel_get(InstantConditionSimObj); 68 | 69 | public static void FARControllableSurface_SetControlStateEditor(object FARControllableSurfaceObj, 70 | Vector3 CoM, Vector3 velocityVec, float pitch, float yaw, float roll, int flap, bool braking) 71 | => FARControllableSurface_SetControlStateEditor_method(FARControllableSurfaceObj, CoM, velocityVec, pitch, yaw, roll, flap, braking); 72 | 73 | public static object NewFARCenterQuery() => FARCenterQuery_ctor(); 74 | public static Vector3 FARCenterQuery_force(object FARCenterQueryObj) => FARCenterQuery_force_get(FARCenterQueryObj); 75 | public static Vector3 FARCenterQuery_TorqueAt(object FARCenterQueryObj, Vector3 point) 76 | => FARCenterQuery_TorqueAt_method(FARCenterQueryObj, point); 77 | 78 | public static void FARAeroSection_PredictionCalculateAeroForces(object FARAeroSectionObj, float atmDensity, float machNumber, float reynoldsPerUnitLength, float pseudoKnudsenNumber, float skinFrictionDrag, Vector3 vel, object center) 79 | => FARAeroSection_PredictionCalculateAeroForces_method(FARAeroSectionObj, atmDensity, machNumber, reynoldsPerUnitLength, pseudoKnudsenNumber, skinFrictionDrag, vel, center); 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/FARCloneAssist.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using KerbalWindTunnel.Extensions.Reflection; 6 | 7 | namespace KerbalWindTunnel.FARVesselCache 8 | { 9 | public static class FARCloneAssist 10 | { 11 | public static readonly System.Reflection.MethodInfo MemberwiseClone_method = typeof(System.Object).GetMethod("MemberwiseClone", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 12 | 13 | private static Action xForceSkinFriction_set; 14 | private static Func xForceSkinFriction_get; 15 | private static Action xForcePressureAoA0_set; 16 | private static Func xForcePressureAoA0_get; 17 | private static Action xForcePressureAoA180_set; 18 | private static Func xForcePressureAoA180_get; 19 | private static Func partData_get; 20 | 21 | private static Func aeroModule_get; 22 | private static Action aeroModule_set; 23 | 24 | private static Type PartDataType; 25 | 26 | public static List CloneListFARAeroSections(IList ListFARAeroSections) 27 | { 28 | if (ListFARAeroSections.Count == 0) 29 | return new List(); 30 | 31 | if (!FARHook.FARAeroSectionType.IsAssignableFrom(ListFARAeroSections[0].GetType())) 32 | throw new ArgumentException(); 33 | 34 | List result = new List(); 35 | 36 | foreach (object section in ListFARAeroSections) 37 | { 38 | object clonedSection = MemberwiseClone_method.Invoke(section, null); 39 | 40 | IList partData = partData_get(clonedSection); 41 | for (int i = partData.Count - 1; i >= 0; i--) 42 | { 43 | object newPartData = MemberwiseClone_method.Invoke(partData[i], null); 44 | object oldAeroModule = aeroModule_get(partData[i]); 45 | object newAeroModule = MemberwiseClone_method.Invoke(aeroModule_get(partData[i]), null); 46 | aeroModule_set(newPartData, newAeroModule); 47 | partData[i] = newPartData; 48 | } 49 | 50 | xForceSkinFriction_set(clonedSection, System.ObjectExtensions.ObjectExtensions.Copy(xForceSkinFriction_get(section))); 51 | xForcePressureAoA0_set(clonedSection, System.ObjectExtensions.ObjectExtensions.Copy(xForcePressureAoA0_get(section))); 52 | xForcePressureAoA180_set(clonedSection, System.ObjectExtensions.ObjectExtensions.Copy(xForcePressureAoA180_get(section))); 53 | } 54 | 55 | return result; 56 | } 57 | 58 | public static List CloneListFARAeroSectionsSafe(List ListFARAeroSections) 59 | => ListFARAeroSections.Select(section => MemberwiseClone_method.Invoke(section, null)).ToList(); // might be able to just return ListFARAeroSections directly... 60 | 61 | public static List CloneListFARWingAerodynamicModels(IList ListFARWingAerodynamicModels) 62 | { 63 | if (ListFARWingAerodynamicModels.Count == 0) 64 | return new List(); 65 | 66 | if (ListFARWingAerodynamicModels[0].GetType() == typeof(FARWingAerodynamicModelWrapper)) // Mixed lists should be impossible 67 | return CloneListFARWingAerodynamicModelsSafe(ListFARWingAerodynamicModels.Cast()); 68 | 69 | if (!FARHook.FARWingAerodynamicModelType.IsAssignableFrom(ListFARWingAerodynamicModels[0].GetType())) 70 | throw new ArgumentException(); 71 | 72 | List result = new List(); 73 | // Key is pre-clone, value is post-clone 74 | Dictionary correlationDict = new Dictionary(); 75 | 76 | foreach (object wingModel in ListFARWingAerodynamicModels) 77 | { 78 | FARWingAerodynamicModelWrapper wrappedClone = FARWingAerodynamicModelWrapper.WrapAndCloneObject(wingModel); 79 | result.Add(wrappedClone); 80 | correlationDict.Add(wingModel, wrappedClone); 81 | } 82 | foreach (FARWingAerodynamicModelWrapper aeroModelWrapper in result) 83 | aeroModelWrapper.wingInteraction.SetNearbyWingModulesLists(correlationDict); 84 | 85 | return result; 86 | } 87 | public static List CloneListFARWingAerodynamicModelsSafe(IEnumerable wrappedWings) 88 | { 89 | List result = new List(); 90 | // Key is pre-clone, value is post-clone 91 | Dictionary correlationDict = new Dictionary(); 92 | foreach (FARWingAerodynamicModelWrapper wrapper in wrappedWings) 93 | { 94 | FARWingAerodynamicModelWrapper wrappedClone = wrapper.Clone(); 95 | result.Add(wrappedClone); 96 | correlationDict.Add(wrapper.WrappedObject, wrappedClone); 97 | } 98 | 99 | foreach (FARWingAerodynamicModelWrapper aeroModelWrapper in result) 100 | aeroModelWrapper.wingInteraction.SetNearbyWingModulesLists(correlationDict); 101 | 102 | return result; 103 | } 104 | 105 | public static bool InitializeMethods(Type FARAeroSectionType) 106 | { 107 | PartDataType = FARAeroSectionType.GetNestedTypes().FirstOrDefault(t => t.Name.Contains("PartData")); 108 | aeroModule_get = PartDataType.FieldGet("aeroModule"); 109 | aeroModule_set = PartDataType.FieldSet("aeroModule"); 110 | 111 | xForceSkinFriction_set = FARAeroSectionType.FieldSet("xForceSkinFriction"); 112 | xForceSkinFriction_get = FARAeroSectionType.FieldGet("xForceSkinFriction"); 113 | xForcePressureAoA0_set = FARAeroSectionType.FieldSet("xForcePressureAoA0"); 114 | xForcePressureAoA0_get = FARAeroSectionType.FieldGet("xForcePressureAoA0"); 115 | xForcePressureAoA180_set = FARAeroSectionType.FieldSet("xForcePressureAoA180"); 116 | xForcePressureAoA180_get = FARAeroSectionType.FieldGet("xForcePressureAoA180"); 117 | partData_get = FARAeroSectionType.FieldGet(FARAeroSectionType.GetField("partData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)); 118 | 119 | return true; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/NetObjectDeepCopy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using System.ObjectExtensions.ArrayExtensions; 4 | 5 | /* 6 | Taken and adapted from https://github.com/Burtsev-Alexey/net-object-deep-copy 7 | Source code is released under the MIT license. 8 | 9 | The MIT License (MIT) 10 | Copyright (c) 2014 Burtsev Alexey 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 13 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | namespace System.ObjectExtensions 25 | { 26 | public static class ObjectExtensions 27 | { 28 | private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); 29 | 30 | public static bool IsPrimitive(this Type type) 31 | { 32 | if (type == typeof(String)) return true; 33 | return (type.IsValueType & type.IsPrimitive); 34 | } 35 | 36 | public static Object Copy(this Object originalObject) 37 | { 38 | return InternalCopy(originalObject, new Dictionary(new ReferenceEqualityComparer())); 39 | } 40 | private static Object InternalCopy(Object originalObject, IDictionary visited) 41 | { 42 | if (originalObject == null) return null; 43 | var typeToReflect = originalObject.GetType(); 44 | if (IsPrimitive(typeToReflect)) return originalObject; 45 | if (visited.ContainsKey(originalObject)) return visited[originalObject]; 46 | if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null; 47 | var cloneObject = CloneMethod.Invoke(originalObject, null); 48 | if (typeToReflect.IsArray) 49 | { 50 | var arrayType = typeToReflect.GetElementType(); 51 | if (IsPrimitive(arrayType) == false) 52 | { 53 | Array clonedArray = (Array)cloneObject; 54 | clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices)); 55 | } 56 | 57 | } 58 | visited.Add(originalObject, cloneObject); 59 | CopyFields(originalObject, visited, cloneObject, typeToReflect); 60 | RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect); 61 | return cloneObject; 62 | } 63 | 64 | private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect) 65 | { 66 | if (typeToReflect.BaseType != null) 67 | { 68 | RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType); 69 | CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate); 70 | } 71 | } 72 | 73 | private static void CopyFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func filter = null) 74 | { 75 | foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) 76 | { 77 | if (filter != null && filter(fieldInfo) == false) continue; 78 | if (IsPrimitive(fieldInfo.FieldType)) continue; 79 | var originalFieldValue = fieldInfo.GetValue(originalObject); 80 | var clonedFieldValue = InternalCopy(originalFieldValue, visited); 81 | fieldInfo.SetValue(cloneObject, clonedFieldValue); 82 | } 83 | } 84 | public static T Copy(this T original) 85 | { 86 | return (T)Copy((Object)original); 87 | } 88 | } 89 | 90 | public class ReferenceEqualityComparer : EqualityComparer 91 | { 92 | public override bool Equals(object x, object y) 93 | { 94 | return ReferenceEquals(x, y); 95 | } 96 | public override int GetHashCode(object obj) 97 | { 98 | if (obj == null) return 0; 99 | return obj.GetHashCode(); 100 | } 101 | } 102 | 103 | namespace ArrayExtensions 104 | { 105 | public static class ArrayExtensions 106 | { 107 | public static void ForEach(this Array array, Action action) 108 | { 109 | if (array.LongLength == 0) return; 110 | ArrayTraverse walker = new ArrayTraverse(array); 111 | do action(array, walker.Position); 112 | while (walker.Step()); 113 | } 114 | } 115 | 116 | internal class ArrayTraverse 117 | { 118 | public int[] Position; 119 | private int[] maxLengths; 120 | 121 | public ArrayTraverse(Array array) 122 | { 123 | maxLengths = new int[array.Rank]; 124 | for (int i = 0; i < array.Rank; ++i) 125 | { 126 | maxLengths[i] = array.GetLength(i) - 1; 127 | } 128 | Position = new int[array.Rank]; 129 | } 130 | 131 | public bool Step() 132 | { 133 | for (int i = 0; i < Position.Length; ++i) 134 | { 135 | if (Position[i] < maxLengths[i]) 136 | { 137 | Position[i]++; 138 | for (int j = 0; j < i; j++) 139 | { 140 | Position[j] = 0; 141 | } 142 | return true; 143 | } 144 | } 145 | return false; 146 | } 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/Borrowed Code/FARAeroUtil.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Code copied and/or derived from Ferram Aerospace Research https://github.com/dkavolis/Ferram-Aerospace-Research/ 3 | ========================= 4 | Aerodynamics model for Kerbal Space Program 5 | 6 | Copyright 2020, Michael Ferrara, aka Ferram4 7 | 8 | This file is derived from part of Ferram Aerospace Research. 9 | 10 | Ferram Aerospace Research is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Ferram Aerospace Research is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Ferram Aerospace Research. If not, see . 22 | 23 | Serious thanks: a.g., for tons of bugfixes and code-refactorings 24 | stupid_chris, for the RealChuteLite implementation 25 | Taverius, for correcting a ton of incorrect values 26 | Tetryds, for finding lots of bugs and issues and not letting me get away with them, and work on example crafts 27 | sarbian, for refactoring code for working with MechJeb, and the Module Manager updates 28 | ialdabaoth (who is awesome), who originally created Module Manager 29 | Regex, for adding RPM support 30 | DaMichel, for some ferramGraph updates and some control surface-related features 31 | Duxwing, for copy editing the readme 32 | */ 33 | 34 | using System; 35 | 36 | namespace KerbalWindTunnel.FARVesselCache 37 | { 38 | public static partial class FARAeroUtil 39 | { 40 | public static double MachBehindShockCalc(double M, AeroPredictor.Conditions conditions) 41 | { 42 | double gamma = GetAdiabaticIndex(conditions); 43 | 44 | double ratio = gamma - 1; 45 | ratio *= M * M; 46 | ratio += 2; 47 | ratio /= 2 * gamma * M * M - (gamma - 1); 48 | ratio = Math.Sqrt(ratio); 49 | 50 | return ratio; 51 | } 52 | 53 | public static double PressureBehindShockCalc(double M, AeroPredictor.Conditions conditions) 54 | { 55 | double gamma = GetAdiabaticIndex(conditions); 56 | 57 | double ratio = M * M; 58 | ratio *= 2 * gamma; 59 | ratio -= gamma - 1; 60 | ratio /= gamma + 1; 61 | 62 | return ratio; 63 | } 64 | 65 | public static double StagnationPressureCalc(double M, AeroPredictor.Conditions conditions) 66 | { 67 | double gamma = GetAdiabaticIndex(conditions); 68 | 69 | double ratio = M * M; 70 | ratio *= gamma - 1; 71 | ratio *= 0.5; 72 | ratio++; 73 | 74 | ratio = Math.Pow(ratio, gamma / (gamma - 1)); 75 | return ratio; 76 | } 77 | 78 | public static double maxPrandtlMeyerTurnAngle(AeroPredictor.Conditions conditions) 79 | { 80 | double gamma = GetAdiabaticIndex(conditions); 81 | double gamma_ = Math.Sqrt((gamma + 1) / (gamma - 1)); 82 | double maxPrandtlMeyerTurnAngle = gamma_ - 1; 83 | maxPrandtlMeyerTurnAngle *= 90; 84 | return maxPrandtlMeyerTurnAngle; 85 | } 86 | 87 | public static class PrandtlMeyerMach 88 | { 89 | public static double Evaluate(float inM, AeroPredictor.Conditions conditions) 90 | { 91 | double gamma = GetAdiabaticIndex(conditions); 92 | double gamma_ = Math.Sqrt((gamma + 1) / (gamma - 1)); 93 | double mach = Math.Sqrt(inM * inM - 1); 94 | double nu = Math.Atan(mach / gamma_); 95 | nu *= gamma_; 96 | nu -= Math.Atan(mach); 97 | nu *= UnityEngine.Mathf.Rad2Deg; 98 | return nu; 99 | } 100 | } 101 | 102 | public static class PrandtlMeyerAngle 103 | { 104 | private static CelestialBody body = null; 105 | private static System.Collections.Generic.Dictionary cache = new System.Collections.Generic.Dictionary(); 106 | public static double Evaluate(double nu, AeroPredictor.Conditions conditions) 107 | { 108 | double gamma = GetAdiabaticIndex(conditions); 109 | FloatCurve curve; 110 | bool foundInCache; 111 | lock (cache) 112 | foundInCache = cache.TryGetValue(gamma, out curve); 113 | if (!foundInCache) 114 | { 115 | curve = new FloatCurve(); 116 | double M = 1; 117 | double gamma_ = Math.Sqrt((gamma + 1) / (gamma - 1)); 118 | while (M < 250) 119 | { 120 | double mach = Math.Sqrt(M * M - 1); 121 | 122 | double nu_key = Math.Atan(mach / gamma_); 123 | nu_key *= gamma_; 124 | nu_key -= Math.Atan(mach); 125 | nu_key *= UnityEngine.Mathf.Rad2Deg; 126 | 127 | double nu_mach = (gamma - 1) / 2; 128 | nu_mach *= M * M; 129 | nu_mach++; 130 | nu_mach *= M; 131 | nu_mach = mach / nu_mach; 132 | nu_mach *= UnityEngine.Mathf.Rad2Deg; 133 | 134 | nu_mach = 1 / nu_mach; 135 | 136 | curve.Add((float)nu_key, (float)M, (float)nu_mach, (float)nu_mach); 137 | 138 | if (M < 3) 139 | M += 0.1; 140 | else if (M < 10) 141 | M += 0.5; 142 | else if (M < 25) 143 | M += 2; 144 | else 145 | M += 25; 146 | } 147 | lock (cache) 148 | { 149 | // In case another thread got through that faster. 150 | if (!cache.ContainsKey(gamma)) 151 | cache.Add(gamma, curve); 152 | } 153 | } 154 | lock (curve) 155 | return curve.Evaluate((float)nu); 156 | } 157 | internal static void ClearCache(CelestialBody newBody) 158 | { 159 | if (newBody == body) 160 | return; 161 | body = newBody; 162 | lock (cache) 163 | cache.Clear(); 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/VesselCache/SimCurves.cs: -------------------------------------------------------------------------------- 1 | using Smooth.Pools; 2 | using KerbalWindTunnel.Extensions; 3 | 4 | namespace KerbalWindTunnel.VesselCache 5 | { 6 | // FloatCurve (Unity Animation curve) are not thread safe so we need a local copy of the curves for the thread 7 | public class SimCurves 8 | { 9 | private static readonly Pool pool = new Pool(Create, Reset); 10 | 11 | private SimCurves() 12 | { 13 | } 14 | 15 | public static int PoolSize 16 | { 17 | get { return pool.Size; } 18 | } 19 | 20 | private static SimCurves Create() 21 | { 22 | return new SimCurves(); 23 | } 24 | 25 | public virtual void Release() 26 | { 27 | pool.Release(this); 28 | } 29 | 30 | private static void Reset(SimCurves obj) 31 | { 32 | } 33 | 34 | public static SimCurves Borrow(CelestialBody newBody) 35 | { 36 | SimCurves curve; 37 | lock (pool) 38 | curve = pool.Borrow(); 39 | curve.Setup(newBody); 40 | return curve; 41 | } 42 | 43 | private void Setup(CelestialBody newBody) 44 | { 45 | // No point in copying those again if we already have them loaded 46 | if (!loaded) 47 | { 48 | DragCurvePseudoReynolds = PhysicsGlobals.DragCurvePseudoReynolds.Clone(); 49 | 50 | DragCurveCd = PhysicsGlobals.DragCurveCd.Clone(); 51 | DragCurveCdPower = PhysicsGlobals.DragCurveCdPower.Clone(); 52 | DragCurveMultiplier = PhysicsGlobals.DragCurveMultiplier.Clone(); 53 | 54 | DragCurveSurface = PhysicsGlobals.SurfaceCurves.dragCurveSurface.Clone(); 55 | DragCurveTail = PhysicsGlobals.SurfaceCurves.dragCurveTail.Clone(); 56 | DragCurveTip = PhysicsGlobals.SurfaceCurves.dragCurveTip.Clone(); 57 | 58 | LiftCurve = PhysicsGlobals.BodyLiftCurve.liftCurve.Clone(); 59 | LiftMachCurve = PhysicsGlobals.BodyLiftCurve.liftMachCurve.Clone(); 60 | DragCurve = PhysicsGlobals.BodyLiftCurve.dragCurve.Clone(); 61 | DragMachCurve = PhysicsGlobals.BodyLiftCurve.dragMachCurve.Clone(); 62 | 63 | SpaceTemperature = PhysicsGlobals.SpaceTemperature; 64 | loaded = true; 65 | } 66 | 67 | if (newBody != body) 68 | { 69 | body = newBody; 70 | if (body != null) 71 | { 72 | AtmospherePressureCurve = newBody.atmospherePressureCurve.Clone(); 73 | AtmosphereTemperatureSunMultCurve = newBody.atmosphereTemperatureSunMultCurve.Clone(); 74 | LatitudeTemperatureBiasCurve = newBody.latitudeTemperatureBiasCurve.Clone(); 75 | LatitudeTemperatureSunMultCurve = newBody.latitudeTemperatureSunMultCurve.Clone(); 76 | AtmosphereTemperatureCurve = newBody.atmosphereTemperatureCurve.Clone(); 77 | AxialTemperatureSunMultCurve = newBody.axialTemperatureSunMultCurve.Clone(); 78 | } 79 | } 80 | } 81 | 82 | public float GetPressure(float altitude) 83 | { 84 | if (!body.atmosphere) 85 | { 86 | return 0; 87 | } 88 | 89 | if (altitude >= body.atmosphereDepth) 90 | { 91 | return 0; 92 | } 93 | if (!body.atmosphereUsePressureCurve) 94 | { 95 | return (float)(body.atmospherePressureSeaLevel * System.Math.Pow(1 - body.atmosphereTemperatureLapseRate * altitude / body.atmosphereTemperatureSeaLevel, body.atmosphereGasMassLapseRate)); 96 | } 97 | if (!body.atmospherePressureCurveIsNormalized) 98 | { 99 | float res = 0; 100 | lock(this.AtmospherePressureCurve) 101 | res = this.AtmospherePressureCurve.Evaluate(altitude); 102 | return res; 103 | } 104 | float result = 0; 105 | lock (this.AtmospherePressureCurve) 106 | result = this.AtmospherePressureCurve.Evaluate((float)(altitude / body.atmosphereDepth)); 107 | return UnityEngine.Mathf.Lerp(0f, (float)body.atmospherePressureSeaLevel, result); 108 | } 109 | 110 | public float GetDensity(float altitude) 111 | { 112 | if (!body.atmosphere) 113 | return 0; 114 | 115 | double pressure = GetPressure(altitude); 116 | double temp = GetTemperature(altitude); 117 | 118 | return (float)body.GetDensity(pressure, temp); //(float)FlightGlobals.getAtmDensity(pressure, temp, body); 119 | } 120 | 121 | public float GetTemperature(float altitude) 122 | { 123 | double temperature = 0; 124 | if (altitude >= body.atmosphereDepth) 125 | { 126 | return (float)this.SpaceTemperature; 127 | } 128 | if (!body.atmosphereUseTemperatureCurve) 129 | { 130 | temperature = body.atmosphereTemperatureSeaLevel - body.atmosphereTemperatureLapseRate * altitude; 131 | } 132 | else 133 | { 134 | lock (this.AtmosphereTemperatureCurve) 135 | temperature = !body.atmosphereTemperatureCurveIsNormalized ? 136 | this.AtmosphereTemperatureCurve.Evaluate((float)altitude) : 137 | UtilMath.Lerp(this.SpaceTemperature, body.atmosphereTemperatureSeaLevel, this.AtmosphereTemperatureCurve.Evaluate((float)(altitude / body.atmosphereDepth))); 138 | } 139 | lock (this.AtmosphereTemperatureSunMultCurve) 140 | temperature += this.AtmosphereTemperatureSunMultCurve.Evaluate(altitude) 141 | * (this.LatitudeTemperatureBiasCurve.Evaluate(0) 142 | + this.LatitudeTemperatureSunMultCurve.Evaluate(0) 143 | + this.AxialTemperatureSunMultCurve.Evaluate(0)); 144 | 145 | return (float)temperature; 146 | } 147 | 148 | private bool loaded = false; 149 | 150 | private CelestialBody body; 151 | 152 | public FloatCurve DragCurvePseudoReynolds { get; private set; } 153 | 154 | public FloatCurve LiftCurve { get; private set; } 155 | 156 | public FloatCurve LiftMachCurve { get; private set; } 157 | 158 | public FloatCurve DragCurve { get; private set; } 159 | 160 | public FloatCurve DragMachCurve { get; private set; } 161 | 162 | public FloatCurve DragCurveTail { get; private set; } 163 | 164 | public FloatCurve DragCurveSurface { get; private set; } 165 | 166 | public FloatCurve DragCurveTip { get; private set; } 167 | 168 | public FloatCurve DragCurveCd { get; private set; } 169 | 170 | public FloatCurve DragCurveCdPower { get; private set; } 171 | 172 | public FloatCurve DragCurveMultiplier { get; private set; } 173 | 174 | public FloatCurve AtmospherePressureCurve { get; private set; } 175 | 176 | public FloatCurve AtmosphereTemperatureSunMultCurve { get; private set; } 177 | 178 | public FloatCurve LatitudeTemperatureBiasCurve { get; private set; } 179 | 180 | public FloatCurve LatitudeTemperatureSunMultCurve { get; private set; } 181 | 182 | public FloatCurve AxialTemperatureSunMultCurve { get; private set; } 183 | 184 | public FloatCurve AtmosphereTemperatureCurve { get; private set; } 185 | 186 | public double SpaceTemperature { get; private set; } 187 | 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/DataGenerators/EnvelopePoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace KerbalWindTunnel.DataGenerators 5 | { 6 | public struct EnvelopePoint 7 | { 8 | public readonly float AoA_level; 9 | public readonly float Thrust_excess; 10 | public readonly float Accel_excess; 11 | public readonly float Lift_max; 12 | public readonly float AoA_max; 13 | public readonly float Thrust_available; 14 | public readonly float altitude; 15 | public readonly float speed; 16 | public readonly float LDRatio; 17 | public readonly Vector3 force; 18 | public readonly Vector3 aeroforce; 19 | public readonly float mach; 20 | public readonly float dynamicPressure; 21 | public readonly float dLift; 22 | public readonly float drag; 23 | public readonly float pitchInput; 24 | public readonly float fuelBurnRate; 25 | //public readonly float stabilityRange; 26 | //public readonly float stabilityScore; 27 | //public readonly float stabilityDerivative; 28 | public readonly bool completed; 29 | 30 | public EnvelopePoint(AeroPredictor vessel, CelestialBody body, float altitude, float speed, float AoA_guess = float.NaN, float maxA_guess = float.NaN, float pitchI_guess = float.NaN) 31 | { 32 | #if ENABLE_PROFILER 33 | UnityEngine.Profiling.Profiler.BeginSample("EnvelopePoint..ctor"); 34 | #endif 35 | this.altitude = altitude; 36 | this.speed = speed; 37 | AeroPredictor.Conditions conditions = new AeroPredictor.Conditions(body, speed, altitude); 38 | float gravParameter, radius; 39 | gravParameter = (float)body.gravParameter; 40 | radius = (float)body.Radius; 41 | this.mach = conditions.mach; 42 | this.dynamicPressure = 0.0005f * conditions.atmDensity * speed * speed; 43 | float weight = (vessel.Mass * gravParameter / ((radius + altitude) * (radius + altitude))) - (vessel.Mass * speed * speed / (radius + altitude)); 44 | //AoA_max = vessel.GetMaxAoA(conditions, out Lift_max, maxA_guess); 45 | AoA_level = vessel.GetAoA(conditions, weight, guess: AoA_guess, pitchInputGuess: 0, lockPitchInput: true); 46 | Vector3 thrustForce = vessel.GetThrustForce(conditions, AoA_level); 47 | fuelBurnRate = vessel.GetFuelBurnRate(conditions, AoA_level); 48 | if (float.IsNaN(maxA_guess)) 49 | { 50 | AoA_max = vessel.GetMaxAoA(conditions, out Lift_max, maxA_guess); 51 | //Lift_max = AeroPredictor.GetLiftForceMagnitude(vessel.GetAeroForce(conditions, AoA_max, 1) + thrustForce, AoA_max); 52 | } 53 | else 54 | { 55 | AoA_max = maxA_guess; 56 | Lift_max = AeroPredictor.GetLiftForceMagnitude(vessel.GetLiftForce(conditions, AoA_max, 1) + (vessel.ThrustIsConstantWithAoA ? AeroPredictor.ToVesselFrame(thrustForce, AoA_max) : vessel.GetThrustForce(conditions, AoA_max)), AoA_max); 57 | } 58 | 59 | if (AoA_level < AoA_max) 60 | pitchInput = vessel.GetPitchInput(conditions, AoA_level, guess: pitchI_guess); 61 | else 62 | pitchInput = 1; 63 | 64 | if (speed < 5 && Math.Abs(altitude) < 10) 65 | AoA_level = 0; 66 | 67 | Thrust_available = AeroPredictor.GetUsefulThrustMagnitude(thrustForce); 68 | 69 | //vessel.GetAeroCombined(conditions, AoA_level, pitchInput, out force, out Vector3 torque); 70 | force = vessel.GetAeroForce(conditions, AoA_level, pitchInput); 71 | aeroforce = AeroPredictor.ToFlightFrame(force, AoA_level); //vessel.GetLiftForce(body, speed, altitude, AoA_level, mach, atmDensity); 72 | drag = -aeroforce.z; 73 | float lift = aeroforce.y; 74 | Thrust_excess = -drag - AeroPredictor.GetDragForceMagnitude(thrustForce, AoA_level); 75 | if (weight > Lift_max)// AoA_level >= AoA_max) 76 | { 77 | Thrust_excess = Lift_max - weight; 78 | AoA_level = AoA_max; 79 | } 80 | Accel_excess = (Thrust_excess / vessel.Mass / WindTunnelWindow.gAccel); 81 | LDRatio = Math.Abs(lift / drag); 82 | dLift = (vessel.GetLiftForceMagnitude(conditions, AoA_level + WindTunnelWindow.AoAdelta, pitchInput) - lift) 83 | / (WindTunnelWindow.AoAdelta * Mathf.Rad2Deg); 84 | //stabilityDerivative = (vessel.GetAeroTorque(conditions, AoA_level + WindTunnelWindow.AoAdelta, pitchInput).x - torque.x) 85 | // / (WindTunnelWindow.AoAdelta * Mathf.Rad2Deg); 86 | //GetStabilityValues(vessel, conditions, AoA_level, out stabilityRange, out stabilityScore); 87 | 88 | completed = true; 89 | 90 | #if ENABLE_PROFILER 91 | UnityEngine.Profiling.Profiler.EndSample(); 92 | #endif 93 | } 94 | 95 | private static void GetStabilityValues(AeroPredictor vessel, AeroPredictor.Conditions conditions, float AoA_centre, out float stabilityRange, out float stabilityScore) 96 | { 97 | const int step = 5; 98 | const int range = 90; 99 | const int alphaSteps = range / step; 100 | float[] torques = new float[2 * alphaSteps + 1]; 101 | float[] aoas = new float[2 * alphaSteps + 1]; 102 | int start, end; 103 | for (int i = 0; i <= 2 * alphaSteps; i++) 104 | { 105 | aoas[i] = (i - alphaSteps) * step * Mathf.Deg2Rad; 106 | torques[i] = vessel.GetAeroTorque(conditions, aoas[i], 0).x; 107 | } 108 | int eq = 0 + alphaSteps; 109 | int dir = (int)Math.Sign(torques[eq]); 110 | if (dir == 0) 111 | { 112 | start = eq - 1; 113 | end = eq + 1; 114 | } 115 | else 116 | { 117 | while (eq > 0 && eq < 2 * alphaSteps) 118 | { 119 | eq += dir; 120 | if (Math.Sign(torques[eq]) != dir) 121 | break; 122 | } 123 | if (eq == 0 || eq == 2 * alphaSteps) 124 | { 125 | stabilityRange = 0; 126 | stabilityScore = 0; 127 | return; 128 | } 129 | if (dir < 0) 130 | { 131 | start = eq; 132 | end = eq + 1; 133 | } 134 | else 135 | { 136 | start = eq - 1; 137 | end = eq; 138 | } 139 | } 140 | while (torques[start] > 0 && start > 0) 141 | start -= 1; 142 | while (torques[end] < 0 && end < 2 * alphaSteps - 1) 143 | end += 1; 144 | float min = (Mathf.InverseLerp(torques[start], torques[start + 1], 0) + start) * step; 145 | float max = (-Mathf.InverseLerp(torques[end], torques[end - 1], 0) + end) * step; 146 | stabilityRange = max - min; 147 | stabilityScore = 0; 148 | for (int i = start; i < end; i++) 149 | { 150 | stabilityScore += (torques[i] + torques[i + 1]) / 2 * step; 151 | } 152 | } 153 | 154 | public override string ToString() 155 | { 156 | return String.Format("Altitude:\t{0:N0}m\n" + "Speed:\t{1:N0}m/s\n" + "Mach:\t{9:N2}\n" + "Level Flight AoA:\t{2:N2}°\n" + 157 | "Excess Thrust:\t{3:N0}kN\n" + "Excess Acceleration:\t{4:N2}g\n" + "Max Lift Force:\t{5:N0}kN\n" + 158 | "Max Lift AoA:\t{6:N2}°\n" + "Lift/Drag Ratio:\t{8:N2}\n" + "Available Thrust:\t{7:N0}kN", 159 | altitude, speed, AoA_level * Mathf.Rad2Deg, 160 | Thrust_excess, Accel_excess, Lift_max, 161 | AoA_max * Mathf.Rad2Deg, Thrust_available, LDRatio, 162 | mach); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Framework/FrameworkExt/KSPDateStructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace KSPPluginFramework 8 | { 9 | 10 | /// 11 | /// Static class to control the Calendar used by KSPDateTime and KSPTimeSpan 12 | /// 13 | public static class KSPDateStructure 14 | { 15 | //Define the Epoch 16 | /// What Day does UT 0 represent 17 | static public Int32 EpochDayOfYear { get; private set; } 18 | /// What Year does UT 0 represent 19 | static public Int32 EpochYear { get; private set; } 20 | 21 | 22 | static public KSPDateTime EpochAsKSPDateTime { 23 | get { 24 | return new KSPDateTime(EpochYear, EpochDayOfYear); 25 | } 26 | } 27 | 28 | 29 | //Define the Calendar 30 | /// How many seconds (game UT) make up a minute 31 | static public Int32 SecondsPerMinute { get; private set; } 32 | /// How many minutes make up an hour 33 | static public Int32 MinutesPerHour { get; private set; } 34 | /// How many hours make up a day 35 | static public Int32 HoursPerDay { get; private set; } 36 | /// How many days make up a year 37 | static public Int32 DaysPerYear { get; private set; } 38 | 39 | /// How many seconds (game UT) make up an hour 40 | static public Int32 SecondsPerHour { get { return SecondsPerMinute * MinutesPerHour; } } 41 | /// How many seconds (game UT) make up a day 42 | static public Int32 SecondsPerDay { get { return SecondsPerHour * HoursPerDay; } } 43 | /// How many seconds (game UT) make up a year - not relevant for Earth time 44 | static public Int32 SecondsPerYear { get { return SecondsPerDay * DaysPerYear; } } 45 | 46 | /// How many seconds (game UT) make up a year - not relevant for Earth time 47 | static public Int32 HoursPerYear { get { return HoursPerDay * DaysPerYear; } } 48 | 49 | 50 | /// What Earth date does UT 0 represent 51 | static public DateTime CustomEpochEarth { get; private set; } 52 | 53 | /// What type of Calendar is being used - KSPStock, Earth, or custom 54 | static public CalendarTypeEnum CalendarType {get; private set;} 55 | 56 | /// Sets the Date Structure to be stock KSP 57 | static public void SetKSPStockCalendar() 58 | { 59 | CalendarType = CalendarTypeEnum.KSPStock; 60 | 61 | EpochYear = 1; 62 | EpochDayOfYear = 1; 63 | SecondsPerMinute = 60; 64 | MinutesPerHour = 60; 65 | 66 | HoursPerDay = GameSettings.KERBIN_TIME ? 6 : 24; 67 | DaysPerYear = GameSettings.KERBIN_TIME ? 426 : 365; 68 | } 69 | 70 | /// Sets the Date Structure to be Earth based - Accepts Epoch date as string 71 | /// Date in form of yyyy-MM-dd - eg 1951-02-20 72 | static public void SetEarthCalendar(String EpochString) 73 | { 74 | KSPDateStructure.SetEarthCalendar(EpochString.Split('-')[0].ToInt32(), 75 | EpochString.Split('-')[1].ToInt32(), 76 | EpochString.Split('-')[2].ToInt32()); 77 | } 78 | /// Sets the Date Structure to be Earth based - Epoch of 1/1/1951 (RSS default) 79 | static public void SetEarthCalendar() 80 | { 81 | SetEarthCalendar(1951, 1, 1); 82 | } 83 | /// Sets the Date Structure to be Earth based - With an epoch date supplied 84 | /// year represented by UT0 85 | /// month represented by UT0 86 | /// day represented by UT0 87 | static public void SetEarthCalendar(Int32 epochyear, Int32 epochmonth, Int32 epochday) 88 | { 89 | CalendarType = CalendarTypeEnum.Earth; 90 | 91 | CustomEpochEarth = new DateTime(epochyear, epochmonth, epochday); 92 | 93 | EpochYear = epochyear; 94 | EpochDayOfYear = CustomEpochEarth.DayOfYear; 95 | SecondsPerMinute = 60; 96 | MinutesPerHour = 60; 97 | 98 | HoursPerDay = 24; 99 | DaysPerYear = 365; 100 | 101 | } 102 | 103 | /// Set Calendar type to be a custom type 104 | static public void SetCustomCalendar() 105 | { 106 | SetKSPStockCalendar(); 107 | CalendarType = CalendarTypeEnum.Custom; 108 | } 109 | 110 | /// Set Calendar type be a custom type with the supplied values 111 | /// Year represented by UT 0 112 | /// DayOfYear represented by UT 0 113 | /// How many days per year in this calendar 114 | /// How many hours per day in this calendar 115 | /// How many minutes per hour in this calendar 116 | /// How many seconds per minute in this calendar 117 | static public void SetCustomCalendar(Int32 CustomEpochYear, Int32 CustomEpochDayOfYear, Int32 CustomDaysPerYear, Int32 CustomHoursPerDay, Int32 CustomMinutesPerHour, Int32 CustomSecondsPerMinute) 118 | { 119 | CalendarType = CalendarTypeEnum.Custom; 120 | 121 | EpochYear = CustomEpochYear; 122 | EpochDayOfYear = CustomEpochDayOfYear; 123 | SecondsPerMinute = CustomSecondsPerMinute; 124 | MinutesPerHour = CustomMinutesPerHour; 125 | HoursPerDay = CustomHoursPerDay; 126 | DaysPerYear = CustomDaysPerYear; 127 | 128 | } 129 | 130 | /// Default Constructor 131 | static KSPDateStructure() 132 | { 133 | SetKSPStockCalendar(); 134 | 135 | Months = new List(); 136 | //LeapDays = new List(); 137 | } 138 | 139 | /// List of KSPMonth objects representing the months in the year 140 | static public List Months { get; set; } 141 | /// How many months have been defined 142 | static public Int32 MonthCount { get { return Months.Count; } } 143 | 144 | //static public List LeapDays { get; set; } 145 | //static public Int32 LeapDaysCount { get { return LeapDays.Count; } } 146 | } 147 | 148 | /// 149 | /// options for KSPDateStructure Calendar Type 150 | /// 151 | public enum CalendarTypeEnum 152 | { 153 | [Description("KSP Stock Calendar")] KSPStock, 154 | [Description("Earth Calendar")] Earth, 155 | [Description("Custom Calendar")] Custom 156 | } 157 | 158 | /// 159 | /// Definition of a calendar month 160 | /// 161 | public class KSPMonth 162 | { 163 | public KSPMonth(String name, Int32 days) { Name = name; Days = days; } 164 | 165 | /// 166 | /// Name of the month 167 | /// 168 | public String Name { get; set; } 169 | /// 170 | /// How many days in this month 171 | /// 172 | public Int32 Days { get; set; } 173 | } 174 | 175 | //public class KSPLeapDay 176 | //{ 177 | // public Int32 Frequency { get; set; } 178 | // public String MonthApplied { get; set; } 179 | // public Int32 DaysToAdd { get; set; } 180 | //} 181 | 182 | 183 | } 184 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/VesselCache/SimulatedLiftingSurface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using KerbalWindTunnel.Extensions; 4 | using Smooth.Pools; 5 | using UnityEngine; 6 | 7 | namespace KerbalWindTunnel.VesselCache 8 | { 9 | public class SimulatedLiftingSurface 10 | { 11 | private static readonly Pool pool = new Pool(Create, Reset); 12 | 13 | public AeroPredictor vessel; 14 | 15 | public Vector3 liftVector; 16 | public bool omnidirectional; 17 | public bool perpendicularOnly; 18 | public FloatCurve liftCurve; 19 | public FloatCurve liftMachCurve; 20 | public FloatCurve dragCurve; 21 | public FloatCurve dragMachCurve; 22 | public float deflectionLiftCoeff; 23 | public bool useInternalDragModel; 24 | public SimulatedPart part; 25 | public Vector3 velocityOffset; 26 | 27 | private static SimulatedLiftingSurface Create() 28 | { 29 | SimulatedLiftingSurface surface = new SimulatedLiftingSurface(); 30 | return surface; 31 | } 32 | 33 | private static void Reset(SimulatedLiftingSurface surface) { } 34 | 35 | virtual public void Release() 36 | { 37 | pool.Release(this); 38 | } 39 | 40 | public static void Release(List objList) 41 | { 42 | for (int i = 0; i < objList.Count; ++i) 43 | { 44 | objList[i].Release(); 45 | } 46 | } 47 | 48 | public static SimulatedLiftingSurface Borrow(ModuleLiftingSurface module, SimulatedPart part) 49 | { 50 | SimulatedLiftingSurface surface = pool.Borrow(); 51 | surface.vessel = part.vessel; 52 | surface.Init(module, part); 53 | return surface; 54 | } 55 | public static SimulatedLiftingSurface BorrowClone(SimulatedLiftingSurface surface, SimulatedPart part) 56 | { 57 | SimulatedLiftingSurface clone = pool.Borrow(); 58 | clone.vessel = part.vessel; 59 | clone.InitClone(surface, part); 60 | return clone; 61 | } 62 | 63 | protected void Init(ModuleLiftingSurface surface, SimulatedPart part) 64 | { 65 | surface.SetupCoefficients(Vector3.forward, out _, out this.liftVector, out _, out _); 66 | this.omnidirectional = surface.omnidirectional; 67 | this.perpendicularOnly = surface.perpendicularOnly; 68 | this.liftCurve = surface.liftCurve.Clone(); 69 | this.liftMachCurve = surface.liftMachCurve.Clone(); 70 | this.dragCurve = surface.dragCurve.Clone(); 71 | this.dragMachCurve = surface.dragMachCurve.Clone(); 72 | this.deflectionLiftCoeff = surface.deflectionLiftCoeff; 73 | this.useInternalDragModel = surface.useInternalDragModel; 74 | this.part = part; 75 | if (surface.displaceVelocity) 76 | this.velocityOffset = surface.part.transform.TransformVector(surface.velocityOffset); 77 | else 78 | this.velocityOffset = Vector3.zero; 79 | 80 | if (surface is ModuleControlSurface ctrl) 81 | this.deflectionLiftCoeff *= (1 - ctrl.ctrlSurfaceArea); 82 | } 83 | 84 | protected void InitClone(SimulatedLiftingSurface surface, SimulatedPart part) 85 | { 86 | this.liftVector = surface.liftVector; 87 | this.omnidirectional = surface.omnidirectional; 88 | this.perpendicularOnly = surface.perpendicularOnly; 89 | this.liftCurve = surface.liftCurve.Clone(); 90 | this.liftMachCurve = surface.liftMachCurve.Clone(); 91 | this.dragCurve = surface.dragCurve.Clone(); 92 | this.dragMachCurve = surface.dragMachCurve.Clone(); 93 | this.deflectionLiftCoeff = surface.deflectionLiftCoeff; 94 | this.useInternalDragModel = surface.useInternalDragModel; 95 | this.velocityOffset = surface.velocityOffset; 96 | this.part = part; 97 | } 98 | 99 | virtual public Vector3 GetLift(Vector3 velocityVect, float mach) 100 | { 101 | float dot = Vector3.Dot(velocityVect, liftVector); 102 | float absdot = omnidirectional ? Math.Abs(dot) : Mathf.Clamp01(dot); 103 | Vector3 lift; 104 | lock (this.liftCurve) 105 | lift = -liftVector * Math.Sign(dot) * liftCurve.Evaluate(absdot) * liftMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftMultiplier; 106 | if (perpendicularOnly) 107 | lift = Vector3.ProjectOnPlane(lift, -velocityVect); 108 | return lift * 1000; 109 | } 110 | virtual public Vector3 GetLift(Vector3 velocityVect, float mach, out Vector3 torque, Vector3 torquePoint) 111 | { 112 | Vector3 liftForce = GetLift(velocityVect, mach); 113 | torque = Vector3.Cross(liftForce, part.CoL - torquePoint); 114 | return liftForce; 115 | } 116 | 117 | virtual public Vector3 GetDrag(Vector3 velocityVect, float mach) 118 | { 119 | if (!useInternalDragModel) 120 | return Vector3.zero; 121 | float dot = Vector3.Dot(velocityVect, liftVector); 122 | float absdot = omnidirectional ? Math.Abs(dot) : Mathf.Clamp01(dot); 123 | Vector3 drag; 124 | lock (this.dragCurve) 125 | drag = -velocityVect * dragCurve.Evaluate(absdot) * dragMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftDragMultiplier; 126 | return drag * 1000; 127 | } 128 | virtual public Vector3 GetDrag(Vector3 velocityVect, float mach, out Vector3 torque, Vector3 torquePoint) 129 | { 130 | if (!useInternalDragModel) 131 | return torque = Vector3.zero; 132 | 133 | Vector3 dragForce = GetDrag(velocityVect, mach); 134 | torque = Vector3.Cross(dragForce, part.CoP - torquePoint); 135 | return dragForce; 136 | } 137 | 138 | virtual public Vector3 GetForce(Vector3 velocityVect, float mach) 139 | { 140 | float dot = Vector3.Dot(velocityVect, liftVector); 141 | float absdot = omnidirectional ? Math.Abs(dot) : Mathf.Clamp01(dot); 142 | Vector3 lift = Vector3.zero; 143 | lock (this.liftCurve) 144 | lift = -liftVector * Math.Sign(dot) * liftCurve.Evaluate(absdot) * liftMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftMultiplier; 145 | if (perpendicularOnly) 146 | lift = Vector3.ProjectOnPlane(lift, -velocityVect); 147 | if (!useInternalDragModel) 148 | return lift * 1000; 149 | Vector3 drag; 150 | lock (this.dragCurve) 151 | drag = -velocityVect * dragCurve.Evaluate(absdot) * dragMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftDragMultiplier; 152 | 153 | return (lift + drag) * 1000; 154 | } 155 | virtual public Vector3 GetForce(Vector3 velocityVect, float mach, out Vector3 torque, Vector3 torquePoint) 156 | { 157 | float dot = Vector3.Dot(velocityVect, liftVector); 158 | float absdot = omnidirectional ? Math.Abs(dot) : Mathf.Clamp01(dot); 159 | Vector3 lift = Vector3.zero; 160 | lock (this.liftCurve) 161 | lift = -liftVector * Math.Sign(dot) * liftCurve.Evaluate(absdot) * liftMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftMultiplier; 162 | if (perpendicularOnly) 163 | lift = Vector3.ProjectOnPlane(lift, -velocityVect); 164 | torque = Vector3.Cross(lift * 1000, part.CoL - torquePoint); 165 | if (!useInternalDragModel) 166 | return lift * 1000; 167 | 168 | Vector3 drag; 169 | lock (this.dragCurve) 170 | drag = -velocityVect * dragCurve.Evaluate(absdot) * dragMachCurve.Evaluate(mach) * deflectionLiftCoeff * PhysicsGlobals.LiftDragMultiplier; 171 | 172 | torque += Vector3.Cross(drag * 1000, part.CoP - torquePoint); 173 | return (lift + drag) * 1000; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Accord.Net Framework/Constants.cs: -------------------------------------------------------------------------------- 1 | // Accord Math Library 2 | // The Accord.NET Framework 3 | // http://accord-framework.net 4 | // 5 | // Copyright © César Souza, 2009-2017 6 | // cesarsouza at gmail.com 7 | // 8 | // This library is free software; you can redistribute it and/or 9 | // modify it under the terms of the GNU Lesser General Public 10 | // License as published by the Free Software Foundation; either 11 | // version 2.1 of the License, or (at your option) any later version. 12 | // 13 | // This library is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | // Lesser General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU Lesser General Public 19 | // License along with this library; if not, write to the Free Software 20 | // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | // 22 | 23 | // Contains functions from the Cephes Math Library Release 2.8: 24 | // June, 2000 Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier 25 | // 26 | // Original license is listed below: 27 | // 28 | // Some software in this archive may be from the book _Methods and 29 | // Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster 30 | // International, 1989) or from the Cephes Mathematical Library, a 31 | // commercial product. In either event, it is copyrighted by the author. 32 | // What you see here may be used freely but it comes with no support or 33 | // guarantee. 34 | // 35 | // The two known misprints in the book are repaired here in the 36 | // source listings for the gamma function and the incomplete beta 37 | // integral. 38 | // 39 | // 40 | // Stephen L. Moshier 41 | // moshier@na-net.ornl.gov 42 | // 43 | 44 | namespace Accord.Math 45 | { 46 | using System; 47 | 48 | /// 49 | /// Common mathematical constants. 50 | /// 51 | /// 52 | /// 53 | /// References: 54 | /// 55 | /// 56 | /// Cephes Math Library, http://www.netlib.org/cephes/ 57 | /// 58 | /// http://www.johndcook.com/cpp_expm1.html 59 | /// 60 | /// 61 | /// 62 | public static class Constants 63 | { 64 | 65 | /// 66 | /// Euler-Mascheroni constant. 67 | /// 68 | /// 69 | /// 70 | /// This constant is defined as 0.5772156649015328606065120. 71 | /// 72 | /// 73 | public const double EulerGamma = 0.5772156649015328606065120; 74 | 75 | /// 76 | /// Double-precision machine round-off error. 77 | /// 78 | /// 79 | /// 80 | /// This value is actually different from Double.Epsilon. It 81 | /// is defined as 1.11022302462515654042E-16. 82 | /// 83 | /// 84 | public const double DoubleEpsilon = 1.11022302462515654042e-16; 85 | 86 | /// 87 | /// Double-precision machine round-off error. 88 | /// 89 | /// 90 | /// 91 | /// This value is actually different from Double.Epsilon. It 92 | /// is defined as 1.11022302462515654042E-16. 93 | /// 94 | /// 95 | public const decimal DecimalEpsilon = 0.0000000000000000000000000001M; 96 | 97 | /// 98 | /// Single-precision machine round-off error. 99 | /// 100 | /// 101 | /// 102 | /// This value is actually different from Single.Epsilon. It 103 | /// is defined as 1.1920929E-07f. 104 | /// 105 | /// 106 | public const float SingleEpsilon = 1.1920929E-07f; 107 | 108 | /// 109 | /// Double-precision small value. 110 | /// 111 | /// 112 | /// 113 | /// This constant is defined as 1.493221789605150e-300. 114 | /// 115 | /// 116 | public const double DoubleSmall = 1.493221789605150e-300; 117 | 118 | /// 119 | /// Single-precision small value. 120 | /// 121 | /// 122 | /// 123 | /// This constant is defined as 1.493221789605150e-40f. 124 | /// 125 | /// 126 | public const float SingleSmall = 1.493221789605150e-40f; 127 | 128 | /// 129 | /// Fixed-precision small value. 130 | /// 131 | /// 132 | public const decimal DecimalSmall = Decimal.MinValue; 133 | 134 | /// 135 | /// Maximum log on the machine. 136 | /// 137 | /// 138 | /// 139 | /// This constant is defined as 7.09782712893383996732E2. 140 | /// 141 | /// 142 | public const double LogMax = 7.09782712893383996732E2; 143 | 144 | /// 145 | /// Minimum log on the machine. 146 | /// 147 | /// 148 | /// 149 | /// This constant is defined as -7.451332191019412076235E2. 150 | /// 151 | /// 152 | public const double LogMin = -7.451332191019412076235E2; 153 | 154 | /// 155 | /// Catalan's constant. 156 | /// 157 | /// 158 | public const double Catalan = 0.915965594177219015054603514; 159 | 160 | /// 161 | /// Log of number pi: log(pi). 162 | /// 163 | /// 164 | /// 165 | /// This constant has the value 1.14472988584940017414. 166 | /// 167 | /// 168 | public const double LogPI = 1.14472988584940017414; 169 | 170 | /// 171 | /// Log of two: log(2). 172 | /// 173 | /// 174 | /// 175 | /// This constant has the value 0.69314718055994530941. 176 | /// 177 | /// 178 | public const double Log2 = 0.69314718055994530941; 179 | 180 | /// 181 | /// Log of three: log(3). 182 | /// 183 | /// 184 | /// 185 | /// This constant has the value 1.098612288668109691395. 186 | /// 187 | /// 188 | public const double Log3 = 1.098612288668109691395; 189 | 190 | /// 191 | /// Log of square root of twice number pi: sqrt(log(2*π). 192 | /// 193 | /// 194 | /// 195 | /// This constant has the value 0.91893853320467274178032973640562. 196 | /// 197 | /// 198 | public const double LogSqrt2PI = 0.91893853320467274178032973640562; 199 | 200 | /// 201 | /// Log of twice number pi: log(2*pi). 202 | /// 203 | /// 204 | /// 205 | /// 206 | /// This constant has the value 1.837877066409345483556. 207 | /// 208 | /// 209 | public const double Log2PI = 1.837877066409345483556; 210 | 211 | /// 212 | /// Square root of twice number pi: sqrt(2*π). 213 | /// 214 | /// 215 | /// 216 | /// This constant has the value 2.50662827463100050242E0. 217 | /// 218 | /// 219 | public const double Sqrt2PI = 2.50662827463100050242E0; 220 | 221 | /// 222 | /// Square root of half number π: sqrt(π/2). 223 | /// 224 | /// 225 | /// 226 | /// This constant has the value 1.25331413731550025121E0. 227 | /// 228 | /// 229 | public const double SqrtHalfPI = 1.25331413731550025121E0; 230 | 231 | /// 232 | /// Square root of 2: sqrt(2). 233 | /// 234 | /// 235 | /// 236 | /// This constant has the value 1.4142135623730950488016887. 237 | /// 238 | /// 239 | public const double Sqrt2 = 1.4142135623730950488016887; 240 | 241 | /// 242 | /// Half square root of 2: sqrt(2)/2. 243 | /// 244 | /// 245 | /// 246 | /// This constant has the value 7.07106781186547524401E-1. 247 | /// 248 | /// 249 | public const double Sqrt2H = 7.07106781186547524401E-1; 250 | 251 | } 252 | } -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/WindTunnelSettingsDialog.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace KerbalWindTunnel 5 | { 6 | public class WindTunnelSettings 7 | { 8 | public static bool UseCoefficients 9 | { 10 | get { return Instance.useCoefficients; } 11 | set 12 | { 13 | Instance.useCoefficients = value; 14 | settingsChanged = true; 15 | } 16 | } 17 | [Persistent] 18 | public bool useCoefficients = true; 19 | 20 | public static bool DefaultToMach 21 | { 22 | get { return Instance.defaultToMach; } 23 | set 24 | { 25 | Instance.defaultToMach = value; 26 | settingsChanged = true; 27 | } 28 | } 29 | [Persistent] 30 | public bool defaultToMach; 31 | 32 | public static bool StartMinimized 33 | { 34 | get { return Instance.startMinimized; } 35 | set 36 | { 37 | Instance.startMinimized = value; 38 | settingsChanged = true; 39 | } 40 | } 41 | [Persistent] 42 | public bool startMinimized; 43 | 44 | public static bool UseSingleColorHighlighting 45 | { 46 | get { return Instance.useSingleColorHighlighting; } 47 | set 48 | { 49 | Instance.useSingleColorHighlighting = value; 50 | settingsChanged = true; 51 | } 52 | } 53 | 54 | [Persistent] 55 | public bool useSingleColorHighlighting = true; 56 | 57 | public static bool HighlightIgnoresLiftingSurfaces 58 | { 59 | get { return Instance.highlightIgnoresLiftingSurfaces; } 60 | set 61 | { 62 | Instance.highlightIgnoresLiftingSurfaces = value; 63 | settingsChanged = true; 64 | } 65 | } 66 | 67 | [Persistent] 68 | public bool highlightIgnoresLiftingSurfaces = false; 69 | 70 | public static bool ShowEnvelopeMask 71 | { 72 | get { return Instance.showEnvelopeMask; } 73 | set 74 | { 75 | Instance.showEnvelopeMask = value; 76 | settingsChanged = true; 77 | } 78 | } 79 | [Persistent] 80 | public bool showEnvelopeMask = true; 81 | 82 | public static bool ShowEnvelopeMaskAlways 83 | { 84 | get { return Instance.showEnvelopeMaskAlways; } 85 | set 86 | { 87 | Instance.showEnvelopeMaskAlways = value; 88 | settingsChanged = true; 89 | } 90 | } 91 | [Persistent] 92 | public bool showEnvelopeMaskAlways = false; 93 | 94 | public static bool UseBlizzy 95 | { 96 | get { return Instance.useBlizzy; } 97 | set 98 | { 99 | Instance.useBlizzy = value; 100 | settingsChanged = true; 101 | } 102 | } 103 | [Persistent] 104 | public bool useBlizzy = false; 105 | 106 | public static bool AutoFitAxes 107 | { 108 | get { return Instance.autoFitAxes; } 109 | set 110 | { 111 | Instance.autoFitAxes = value; 112 | settingsChanged = true; 113 | } 114 | } 115 | [Persistent] 116 | public bool autoFitAxes = true; 117 | 118 | public static int RotationCount 119 | { 120 | get => Instance.rotationCount; 121 | set 122 | { 123 | Instance.rotationCount = value; 124 | settingsChanged = true; 125 | } 126 | } 127 | [Persistent] 128 | public int rotationCount = 1; 129 | 130 | private static bool settingsChanged = false; 131 | private static bool settingsLoaded = false; 132 | 133 | internal static WindTunnelSettings Instance = new WindTunnelSettings(); 134 | 135 | public static void InitializeSettings() 136 | { 137 | if (settingsLoaded) 138 | return; 139 | 140 | Instance.LoadSettingsFromFile(); 141 | 142 | settingsLoaded = true; 143 | } 144 | private void LoadSettingsFromFile() 145 | { 146 | ConfigNode[] settingsNode = GameDatabase.Instance.GetConfigNodes("KerbalWindTunnelSettings"); 147 | if (settingsNode.Length < 1) 148 | { 149 | Debug.Log("Kerbal Wind Tunnel Settings file note found."); 150 | // To trigger creating a settings file. 151 | settingsChanged = true; 152 | return; 153 | } 154 | ConfigNode.LoadObjectFromConfig(this, settingsNode[0]); 155 | } 156 | 157 | public static void SaveSettings() 158 | { 159 | Instance.SaveSettingsToFile(); 160 | } 161 | private void SaveSettingsToFile() 162 | { 163 | if (!settingsChanged) 164 | return; 165 | 166 | ConfigNode data = ConfigNode.CreateConfigFromObject(this, 0, new ConfigNode("KerbalWindTunnelSettings")); 167 | 168 | ConfigNode save = new ConfigNode(); 169 | save.AddNode(data); 170 | save.Save("GameData/WindTunnel/KerbalWindTunnelSettings.cfg"); 171 | } 172 | } 173 | 174 | public partial class WindTunnelWindow 175 | { 176 | private PopupDialog settingsDialog; 177 | private PopupDialog SpawnDialog() 178 | { 179 | List dialog = new List 180 | { 181 | new DialogGUIToggle(WindTunnelSettings.UseCoefficients, "Lift, Drag as coefficients", 182 | delegate (bool b) { 183 | //Instance.graphDirty = true; 184 | //Instance.graphRequested = false; 185 | WindTunnelSettings.UseCoefficients = !WindTunnelSettings.UseCoefficients; 186 | GraphGenerator.UpdateGraphs(); 187 | }), 188 | new DialogGUIToggle(WindTunnelSettings.DefaultToMach, "Default to speed as Mach", delegate (bool b) { WindTunnelSettings.DefaultToMach = !WindTunnelSettings.DefaultToMach; }), 189 | new DialogGUIToggle(WindTunnelSettings.StartMinimized, "Start minimized", delegate (bool b) { WindTunnelSettings.StartMinimized = !WindTunnelSettings.StartMinimized; }), 190 | new DialogGUIToggle(WindTunnelSettings.UseSingleColorHighlighting, "Use simple part highlighting", delegate (bool b) {WindTunnelSettings.UseSingleColorHighlighting = !WindTunnelSettings.UseSingleColorHighlighting; }), 191 | new DialogGUIToggle(WindTunnelSettings.ShowEnvelopeMask, "Show flight envelope outline on graphs", delegate (bool b) {WindTunnelSettings.ShowEnvelopeMask = !WindTunnelSettings.ShowEnvelopeMask; }), 192 | new DialogGUIToggle(WindTunnelSettings.ShowEnvelopeMaskAlways && WindTunnelSettings.ShowEnvelopeMask, "Show flight envelope outline even on flight envelope", delegate (bool b) {if(WindTunnelSettings.ShowEnvelopeMask) WindTunnelSettings.ShowEnvelopeMaskAlways = !WindTunnelSettings.ShowEnvelopeMaskAlways; }), 193 | new DialogGUIToggle(WindTunnelSettings.AutoFitAxes, string.Format("Auto-fit axes to graph data\n(Uncheck to plot full range)"), delegate (bool b) {WindTunnelSettings.AutoFitAxes = !WindTunnelSettings.AutoFitAxes; grapher.AutoFitAxes = WindTunnelSettings.AutoFitAxes; }) 194 | }; 195 | 196 | if (!FARVesselCache.FARHook.FARInstalled) 197 | dialog.Add(new DialogGUIHorizontalLayout(TextAnchor.MiddleLeft, 198 | new DialogGUILabel(() => string.Format("Propeller rotation evaluations: {0}", WindTunnelSettings.RotationCount), UISkinManager.defaultSkin.toggle, true), 199 | new DialogGUISlider(() => Mathf.Log(WindTunnelSettings.RotationCount, 2), 0, 4, true, 100, 20, value => WindTunnelSettings.RotationCount = (int)Mathf.Pow(2, value)))); 200 | 201 | if (ToolbarManager.ToolbarAvailable) 202 | dialog.Add(new DialogGUIToggle(WindTunnelSettings.UseBlizzy, "Use Blizzy's Toolbar", delegate (bool b) { WindTunnelSettings.UseBlizzy = !WindTunnelSettings.UseBlizzy; })); 203 | 204 | dialog.Add(new DialogGUIButton("Accept", delegate { 205 | WindTunnelWindow.Instance.Visible = true; 206 | settingsDialog.Dismiss(); 207 | })); 208 | 209 | return PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), 210 | new MultiOptionDialog("KWTSettings", "", "Kerbal Wind Tunnel Settings", UISkinManager.defaultSkin, dialog.ToArray()), 211 | false, UISkinManager.defaultSkin); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/Borrowed Code/FARWingInteraction.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Code copied and/or derived from Ferram Aerospace Research https://github.com/dkavolis/Ferram-Aerospace-Research/ 3 | ========================= 4 | Aerodynamics model for Kerbal Space Program 5 | 6 | Copyright 2020, Michael Ferrara, aka Ferram4 7 | 8 | This file is derived from part of Ferram Aerospace Research. 9 | 10 | Ferram Aerospace Research is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Ferram Aerospace Research is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Ferram Aerospace Research. If not, see . 22 | 23 | Serious thanks: a.g., for tons of bugfixes and code-refactorings 24 | stupid_chris, for the RealChuteLite implementation 25 | Taverius, for correcting a ton of incorrect values 26 | Tetryds, for finding lots of bugs and issues and not letting me get away with them, and work on example crafts 27 | sarbian, for refactoring code for working with MechJeb, and the Module Manager updates 28 | ialdabaoth (who is awesome), who originally created Module Manager 29 | Regex, for adding RPM support 30 | DaMichel, for some ferramGraph updates and some control surface-related features 31 | Duxwing, for copy editing the readme 32 | */ 33 | 34 | using System; 35 | using System.Collections.Generic; 36 | using KSPPluginFramework; 37 | using UnityEngine; 38 | 39 | namespace KerbalWindTunnel.FARVesselCache 40 | { 41 | public partial class FARWingInteractionWrapper 42 | { 43 | public void CalculateEffectsOfUpstreamWing( 44 | double thisWingAoA, 45 | double thisWingMachNumber, 46 | Vector3d parallelInPlaneLocal, 47 | ref double ACweight, 48 | ref double ACshift, 49 | ref double ClIncrementFromRear 50 | ) 51 | { 52 | double thisWingMAC = parentWingModule.Effective_MAC; 53 | double thisWingb_2 = parentWingModule.Effective_b_2; 54 | 55 | EffectiveUpstreamMAC = 0; 56 | EffectiveUpstreamb_2 = 0; 57 | EffectiveUpstreamArea = 0; 58 | 59 | EffectiveUpstreamLiftSlope = 0; 60 | EffectiveUpstreamStall = 0; 61 | EffectiveUpstreamCosSweepAngle = 0; 62 | EffectiveUpstreamAoAMax = 0; 63 | EffectiveUpstreamAoA = 0; 64 | EffectiveUpstreamCd0 = 0; 65 | EffectiveUpstreamInfluence = 0; 66 | 67 | double wingForwardDir = parallelInPlaneLocal.y; 68 | double wingRightwardDir = parallelInPlaneLocal.x * srfAttachFlipped; 69 | 70 | if (wingForwardDir > 0) 71 | { 72 | wingForwardDir *= wingForwardDir; 73 | UpdateUpstreamValuesFromWingModules(nearbyWingModulesForwardList, 74 | nearbyWingModulesForwardInfluence, 75 | wingForwardDir, 76 | thisWingAoA); 77 | } 78 | else 79 | { 80 | wingForwardDir *= wingForwardDir; 81 | UpdateUpstreamValuesFromWingModules(nearbyWingModulesBackwardList, 82 | nearbyWingModulesBackwardInfluence, 83 | wingForwardDir, 84 | thisWingAoA); 85 | } 86 | 87 | if (wingRightwardDir > 0) 88 | { 89 | wingRightwardDir *= wingRightwardDir; 90 | UpdateUpstreamValuesFromWingModules(nearbyWingModulesRightwardList, 91 | nearbyWingModulesRightwardInfluence, 92 | wingRightwardDir, 93 | thisWingAoA); 94 | } 95 | else 96 | { 97 | wingRightwardDir *= wingRightwardDir; 98 | UpdateUpstreamValuesFromWingModules(nearbyWingModulesLeftwardList, 99 | nearbyWingModulesLeftwardInfluence, 100 | wingRightwardDir, 101 | thisWingAoA); 102 | } 103 | 104 | double MachCoeff = (1 - thisWingMachNumber * thisWingMachNumber).Clamp(0, 1); 105 | 106 | if (MachCoeff == 0 || Math.Abs(MachCoeff) < 1e-14 * double.Epsilon) 107 | return; 108 | double flapRatio = (thisWingMAC / (thisWingMAC + EffectiveUpstreamMAC)).Clamp(0, 1); 109 | float flt_flapRatio = (float)flapRatio; 110 | //Flap Effectiveness Factor 111 | double flapFactor = wingCamberFactor.Evaluate(flt_flapRatio); 112 | //Change in moment due to change in lift from flap 113 | double dCm_dCl = wingCamberMoment.Evaluate(flt_flapRatio); 114 | 115 | //This accounts for the wing possibly having a longer span than the flap 116 | double WingFraction = (thisWingb_2 / EffectiveUpstreamb_2).Clamp(0, 1); 117 | //This accounts for the flap possibly having a longer span than the wing it's attached to 118 | double FlapFraction = (EffectiveUpstreamb_2 / thisWingb_2).Clamp(0, 1); 119 | 120 | //Lift created by the flap interaction 121 | double ClIncrement = flapFactor * EffectiveUpstreamLiftSlope * EffectiveUpstreamAoA; 122 | //Increase the Cl so that even though we're working with the flap's area, it accounts for the added lift across the entire object 123 | ClIncrement *= (parentWingModule.S * FlapFraction + EffectiveUpstreamArea * WingFraction) / 124 | parentWingModule.S; 125 | 126 | // Total flap Cl for the purpose of applying ACshift, including the bit subtracted below 127 | ACweight = ClIncrement * MachCoeff; 128 | 129 | //Removing additional angle so that lift of the flap is calculated as lift at wing angle + lift due to flap interaction rather than being greater 130 | ClIncrement -= FlapFraction * EffectiveUpstreamLiftSlope * EffectiveUpstreamAoA; 131 | 132 | //Change in Cm with change in Cl 133 | ACshift = (dCm_dCl + 0.75 * (1 - flapRatio)) * (thisWingMAC + EffectiveUpstreamMAC); 134 | 135 | ClIncrementFromRear = ClIncrement * MachCoeff; 136 | 137 | effectiveUpstreamCd0_set(WrappedObject, EffectiveUpstreamCd0); 138 | effectiveUpstreamInfluence_set(WrappedObject, EffectiveUpstreamInfluence); 139 | effectiveUpstreamStall_set(WrappedObject, EffectiveUpstreamStall); 140 | } 141 | 142 | private void UpdateUpstreamValuesFromWingModules( 143 | List wingModules, 144 | List associatedInfluences, 145 | double directionalInfluence, 146 | double thisWingAoA 147 | ) 148 | { 149 | directionalInfluence = Math.Abs(directionalInfluence); 150 | for (int i = 0; i < wingModules.Count; i++) 151 | { 152 | FARWingAerodynamicModelWrapper wingModule = wingModules[i]; 153 | double wingInfluenceFactor = associatedInfluences[i] * directionalInfluence; 154 | 155 | if (wingModule == null) 156 | { 157 | //HandleNullPart(wingModules, associatedInfluences, i); 158 | //i--; 159 | continue; 160 | } 161 | 162 | if (wingModule.isShielded) 163 | continue; 164 | 165 | double tmp = Vector3.Dot(wingModule.part_transform.forward, parentWingModule.part_transform.forward); 166 | 167 | EffectiveUpstreamMAC += wingModule.Effective_MAC * wingInfluenceFactor; 168 | EffectiveUpstreamb_2 += wingModule.Effective_b_2 * wingInfluenceFactor; 169 | EffectiveUpstreamArea += wingModule.S * wingInfluenceFactor; 170 | 171 | EffectiveUpstreamLiftSlope += wingModule.RawLiftSlope * wingInfluenceFactor; 172 | EffectiveUpstreamStall += wingModule.Stall * wingInfluenceFactor; 173 | EffectiveUpstreamCosSweepAngle += wingModule.CosSweepAngle * wingInfluenceFactor; 174 | EffectiveUpstreamAoAMax += wingModule.rawAoAmax * wingInfluenceFactor; 175 | EffectiveUpstreamCd0 += wingModule.ZeroLiftCdIncrement * wingInfluenceFactor; 176 | EffectiveUpstreamInfluence += wingInfluenceFactor; 177 | 178 | double wAoA = wingModule.CalculateAoA(wingModule.Vel) * Math.Sign(tmp); 179 | //First, make sure that the AoA are wrt the same direction; then account for any strange angling of the part that shouldn't be there 180 | tmp = (thisWingAoA - wAoA) * wingInfluenceFactor; 181 | 182 | EffectiveUpstreamAoA += tmp; 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Wind Tunnel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {13572B17-7B9F-4252-B723-28DAC8796A91} 8 | Library 9 | Properties 10 | KerbalWindTunnel 11 | Wind Tunnel 12 | v4.6.1 13 | 512 14 | 15 | 16 | 17 | 18 | 19 | true 20 | portable 21 | false 22 | bin\Debug\ 23 | TRACE;DEBUG;ENABLE_PROFILER 24 | prompt 25 | 4 26 | false 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | OnBuildSuccess 39 | 40 | 41 | true 42 | bin\UnityEditor\ 43 | TRACE;DEBUG;UnityEditor 44 | portable 45 | AnyCPU 46 | prompt 47 | MinimumRecommendedRules.ruleset 48 | 49 | 50 | 51 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp.dll 52 | 53 | 54 | ..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll 55 | 56 | 57 | 58 | 59 | ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.dll 67 | 68 | 69 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll 70 | 71 | 72 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll 73 | 74 | 75 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.ImageConversionModule.dll 76 | 77 | 78 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.IMGUIModule.dll 79 | 80 | 81 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.PhysicsModule.dll 82 | 83 | 84 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.TextRenderingModule.dll 85 | 86 | 87 | ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UI.dll 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | {3c216395-20c9-4f59-813e-9326882d7092} 159 | Graphing 160 | 161 | 162 | 163 | 164 | 165 | IF "$(ConfigurationName)"=="Release" ( 166 | copy "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\WindTunnel\Plugins" 167 | ) ELSE IF "$(ConfigurationName)"=="Debug" ( 168 | copy "$(TargetDir)$(TargetName).*" "C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program Debugging\GameData\WindTunnel\Plugins" 169 | ) 170 | 171 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/VesselCache/SimulatedEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Smooth.Pools; 5 | using UnityEngine; 6 | using KerbalWindTunnel.Extensions; 7 | 8 | namespace KerbalWindTunnel.VesselCache 9 | { 10 | public class SimulatedEngine 11 | { 12 | private static readonly Pool pool = new Pool(Create, Reset); 13 | 14 | public AeroPredictor vessel; 15 | 16 | public float flameoutBar; 17 | public bool atmChangeFlow; 18 | public bool useAtmCurve; 19 | public bool useAtmCurveIsp; 20 | public FloatCurve atmCurve; 21 | public FloatCurve atmCurveIsp; 22 | public bool useVelCurve; 23 | public bool useVelCurveIsp; 24 | public FloatCurve velCurve; 25 | public FloatCurve velCurveIsp; 26 | public bool useThrustCurve; 27 | public FloatCurve thrustCurve; 28 | public float flowMultCap; 29 | public float flowMultCapSharpness; 30 | public FloatCurve atmosphereCurve; 31 | public bool useThrottleIspCurve; 32 | public FloatCurve throttleIspCurve; 33 | public FloatCurve throttleIspCurveAtmStrength; 34 | public bool requiresOxygen; 35 | public float g; 36 | public float multIsp; 37 | public float maxFuelFlow; 38 | public float multFlow; 39 | public float thrustPercentage; 40 | public Vector3 thrustVector; 41 | public Vector3 thrustPoint; 42 | public float CLAMP; 43 | public int stage; 44 | public SimulatedPart part; 45 | 46 | private static SimulatedEngine Create() 47 | { 48 | SimulatedEngine engine = new SimulatedEngine(); 49 | return engine; 50 | } 51 | 52 | private static void Reset(SimulatedEngine simulatedEngine) { } 53 | 54 | public void Release() 55 | { 56 | pool.Release(this); 57 | } 58 | 59 | public static void Release(List objList) 60 | { 61 | for (int i = 0; i < objList.Count; ++i) 62 | { 63 | objList[i].Release(); 64 | } 65 | } 66 | 67 | public static SimulatedEngine Borrow(ModuleEngines module, SimulatedPart part) 68 | { 69 | SimulatedEngine engine = pool.Borrow(); 70 | engine.vessel = part.vessel; 71 | engine.Init(module, part); 72 | return engine; 73 | } 74 | public static SimulatedEngine Borrow(ModuleEngines module, AeroPredictor vessel) 75 | { 76 | SimulatedEngine engine = pool.Borrow(); 77 | engine.vessel = vessel; 78 | engine.Init(module, null); 79 | return engine; 80 | } 81 | public static SimulatedEngine BorrowClone(SimulatedEngine engine, SimulatedPart part) 82 | { 83 | SimulatedEngine clone = pool.Borrow(); 84 | clone.vessel = part?.vessel; 85 | clone.InitClone(engine, part); 86 | return clone; 87 | } 88 | 89 | protected void Init(ModuleEngines engine, SimulatedPart part) 90 | { 91 | this.flameoutBar = engine.flameoutBar; 92 | this.atmChangeFlow = engine.atmChangeFlow; 93 | this.useAtmCurve = engine.useAtmCurve; 94 | this.useAtmCurveIsp = engine.useAtmCurveIsp; 95 | this.atmCurve = engine.atmCurve.Clone(); 96 | this.atmCurveIsp = engine.atmCurveIsp.Clone(); 97 | this.useVelCurve = engine.useVelCurve; 98 | this.useVelCurveIsp = engine.useVelCurveIsp; 99 | this.velCurve = engine.velCurve.Clone(); 100 | this.velCurveIsp = engine.velCurveIsp.Clone(); 101 | this.flowMultCap = engine.flowMultCap; 102 | this.flowMultCapSharpness = engine.flowMultCapSharpness; 103 | this.atmosphereCurve = engine.atmosphereCurve.Clone(); 104 | this.useThrustCurve = engine.useThrustCurve; 105 | this.thrustCurve = engine.thrustCurve.Clone(); 106 | this.useThrottleIspCurve = engine.useThrottleIspCurve; 107 | this.throttleIspCurve = engine.throttleIspCurve.Clone(); 108 | this.throttleIspCurveAtmStrength = engine.throttleIspCurveAtmStrength.Clone(); 109 | this.requiresOxygen = engine.propellants.Any(propellant => propellant.name == "IntakeAir"); 110 | this.g = engine.g; 111 | this.multIsp = engine.multIsp; 112 | this.maxFuelFlow = engine.maxFuelFlow; 113 | this.multFlow = engine.multFlow; 114 | this.thrustPercentage = engine.thrustPercentage; 115 | this.thrustVector = Vector3.zero; 116 | float thrustTransformMultiplierSum = 0; 117 | this.thrustPoint = Vector3.zero; 118 | for (int i = engine.thrustTransforms.Count - 1; i >= 0; i--) 119 | { 120 | this.thrustVector -= engine.thrustTransforms[i].forward * engine.thrustTransformMultipliers[i]; 121 | this.thrustPoint += engine.thrustTransforms[i].position * engine.thrustTransformMultipliers[i]; 122 | thrustTransformMultiplierSum += engine.thrustTransformMultipliers[i]; 123 | } 124 | this.thrustPoint /= thrustTransformMultiplierSum; 125 | this.CLAMP = engine.CLAMP; 126 | this.stage = engine.part.inverseStage; 127 | this.part = part; 128 | } 129 | protected void InitClone(SimulatedEngine engine, SimulatedPart part) 130 | { 131 | this.flameoutBar = engine.flameoutBar; 132 | this.atmChangeFlow = engine.atmChangeFlow; 133 | this.useAtmCurve = engine.useAtmCurve; 134 | this.useAtmCurveIsp = engine.useAtmCurveIsp; 135 | this.atmCurve = engine.atmCurve.Clone(); 136 | this.atmCurveIsp = engine.atmCurveIsp.Clone(); 137 | this.useVelCurve = engine.useVelCurve; 138 | this.useVelCurveIsp = engine.useVelCurveIsp; 139 | this.velCurve = engine.velCurve.Clone(); 140 | this.velCurveIsp = engine.velCurveIsp.Clone(); 141 | this.flowMultCap = engine.flowMultCap; 142 | this.flowMultCapSharpness = engine.flowMultCapSharpness; 143 | this.atmosphereCurve = engine.atmosphereCurve.Clone(); 144 | this.useThrustCurve = engine.useThrustCurve; 145 | this.thrustCurve = engine.thrustCurve.Clone(); 146 | this.useThrottleIspCurve = engine.useThrottleIspCurve; 147 | this.throttleIspCurve = engine.throttleIspCurve.Clone(); 148 | this.throttleIspCurveAtmStrength = engine.throttleIspCurveAtmStrength.Clone(); 149 | this.requiresOxygen = engine.requiresOxygen; 150 | this.g = engine.g; 151 | this.multIsp = engine.multIsp; 152 | this.maxFuelFlow = engine.maxFuelFlow; 153 | this.multFlow = engine.multFlow; 154 | this.thrustPercentage = engine.thrustPercentage; 155 | this.thrustVector = engine.thrustVector; 156 | this.thrustPoint = engine.thrustPoint; 157 | this.CLAMP = engine.CLAMP; 158 | this.stage = engine.stage; 159 | this.part = part; 160 | } 161 | 162 | public Vector3 GetThrust(float mach, float atmDensity, float atmPressure, bool oxygenPresent) 163 | { 164 | return GetThrust(mach, atmDensity, atmPressure, oxygenPresent, out _); 165 | } 166 | public Vector3 GetThrust(float mach, float atmDensity, float atmPressure, bool oxygenPresent, out float fuelBurnRate) 167 | { 168 | atmPressure *= 0.00986923267f; 169 | fuelBurnRate = 0; 170 | if (requiresOxygen && !oxygenPresent) 171 | return Vector3.zero; 172 | 173 | fuelBurnRate = GetFuelBurnRate(mach, atmDensity); 174 | if (fuelBurnRate <= 0) 175 | return Vector3.zero; 176 | 177 | float isp = 0; 178 | lock (atmosphereCurve) 179 | isp = atmosphereCurve.Evaluate(atmPressure); 180 | if (useThrottleIspCurve) 181 | lock (throttleIspCurve) 182 | isp *= Mathf.Lerp(1f, throttleIspCurve.Evaluate(1), throttleIspCurveAtmStrength.Evaluate(atmPressure)); 183 | if (useAtmCurveIsp) 184 | lock (atmCurveIsp) 185 | isp *= atmCurveIsp.Evaluate(atmDensity * 40 / 49); 186 | if (useVelCurveIsp) 187 | lock (velCurveIsp) 188 | isp *= velCurveIsp.Evaluate(mach); 189 | 190 | #if DEBUG 191 | if (!requiresOxygen) 192 | Debug.LogFormat("Fuel: {0:F3}, ISP: {1:F1}, Thrust: {2:F2}", fuelBurnRate, isp, fuelBurnRate * g * multIsp * thrustPercentage / 100f); 193 | #endif 194 | return thrustVector * fuelBurnRate * g * multIsp * isp * (thrustPercentage / 100f); 195 | } 196 | 197 | public float GetFuelBurnRate(float mach, float atmDensity) 198 | { 199 | float flowMultiplier = 1; 200 | if (atmChangeFlow) 201 | { 202 | if (useAtmCurve) 203 | lock (atmCurve) 204 | flowMultiplier = atmCurve.Evaluate(atmDensity * 40 / 49); 205 | else 206 | flowMultiplier = atmDensity * 40 / 49; 207 | } 208 | if (useThrustCurve) 209 | lock (thrustCurve) 210 | flowMultiplier *= thrustCurve.Evaluate(1f); 211 | if (useVelCurve) 212 | lock (velCurve) 213 | flowMultiplier *= velCurve.Evaluate(mach); 214 | if (flowMultiplier > flowMultCap) 215 | { 216 | float excess = flowMultiplier - flowMultCap; 217 | flowMultiplier = flowMultCap + excess / (flowMultCapSharpness + excess / flowMultCap); 218 | } 219 | if (flowMultiplier < CLAMP) 220 | flowMultiplier = CLAMP; 221 | 222 | if (flowMultiplier < flameoutBar) 223 | return 0; 224 | return flowMultiplier * maxFuelFlow * multFlow; 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Framework/ConfigNodeStorage.cs: -------------------------------------------------------------------------------- 1 | /* Part of KSPPluginFramework 2 | Version 1.2 3 | 4 | Forum Thread:http://forum.kerbalspaceprogram.com/threads/66503-KSP-Plugin-Framework 5 | Author: TriggerAu, 2014 6 | License: The MIT License (MIT) 7 | */ 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | 13 | using KSP; 14 | using UnityEngine; 15 | 16 | namespace KSPPluginFramework 17 | { 18 | public abstract class ConfigNodeStorage : IPersistenceLoad, IPersistenceSave 19 | { 20 | #region Constructors 21 | /// 22 | /// Class Constructor 23 | /// 24 | public ConfigNodeStorage() { } 25 | /// 26 | /// Class Constructor 27 | /// 28 | /// Set the path for saving and loading. This can be an absolute path (eg c:\test.cfg) or a relative path from the location of the assembly dll (eg. ../config/test) 29 | public ConfigNodeStorage(String FilePath) { this.FilePath = FilePath; } 30 | #endregion 31 | 32 | #region Properties 33 | private String _FilePath; 34 | /// 35 | /// Location of file for saving and loading methods 36 | /// 37 | /// This can be an absolute path (eg c:\test.cfg) or a relative path from the location of the assembly dll (eg. ../config/test) 38 | /// 39 | public String FilePath 40 | { 41 | get { return _FilePath; } 42 | set 43 | { 44 | //Combine the Location of the assembly and the provided string. This means we can use relative or absolute paths 45 | _FilePath = System.IO.Path.Combine(_AssemblyFolder, value).Replace("\\","/"); 46 | } 47 | } 48 | 49 | /// 50 | /// Gets the filename portion of the FullPath 51 | /// 52 | public String FileName 53 | { 54 | get { return System.IO.Path.GetFileName(FilePath); } 55 | } 56 | #endregion 57 | 58 | #region Interface Methods 59 | /// 60 | /// Wrapper for our overridable functions 61 | /// 62 | void IPersistenceLoad.PersistenceLoad() 63 | { 64 | OnDecodeFromConfigNode(); 65 | } 66 | /// 67 | /// Wrapper for our overridable functions 68 | /// 69 | void IPersistenceSave.PersistenceSave() 70 | { 71 | OnEncodeToConfigNode(); 72 | } 73 | 74 | /// 75 | /// This overridable function executes whenever the object is loaded from a config node structure. Use this for complex classes that need decoding from simple confignode values 76 | /// 77 | public virtual void OnDecodeFromConfigNode() { } 78 | /// 79 | /// This overridable function executes whenever the object is encoded to a config node structure. Use this for complex classes that need encoding into simple confignode values 80 | /// 81 | public virtual void OnEncodeToConfigNode() { } 82 | #endregion 83 | 84 | /// 85 | /// Test whether the configured FilePath exists 86 | /// 87 | /// True if its there 88 | public Boolean FileExists 89 | { 90 | get 91 | { 92 | return System.IO.File.Exists(FilePath); 93 | } 94 | } 95 | 96 | /// 97 | /// Loads the object from the ConfigNode structure in the previously supplied file 98 | /// 99 | /// Succes of Load 100 | public Boolean Load() 101 | { 102 | return this.Load(FilePath); 103 | } 104 | /// 105 | /// Loads the object from the ConfigNode structure in a file 106 | /// 107 | /// Absolute Path to the file to load the ConfigNode structure from 108 | /// Success of Load 109 | public Boolean Load(String fileFullName) 110 | { 111 | Boolean blnReturn = false; 112 | try 113 | { 114 | LogFormatted_DebugOnly("Loading ConfigNode"); 115 | if (FileExists) 116 | { 117 | //Load the file into a config node 118 | ConfigNode cnToLoad = ConfigNode.Load(fileFullName); 119 | //remove the wrapper node that names the class stored 120 | ConfigNode cnUnwrapped = cnToLoad.GetNode(this.GetType().Name); 121 | //plug it in to the object 122 | ConfigNode.LoadObjectFromConfig(this, cnUnwrapped); 123 | blnReturn = true; 124 | } 125 | else 126 | { 127 | LogFormatted("File could not be found to load({0})", fileFullName); 128 | blnReturn = false; 129 | } 130 | } 131 | catch (Exception ex) 132 | { 133 | LogFormatted("Failed to Load ConfigNode from file({0})-Error:{1}", fileFullName, ex.Message); 134 | LogFormatted("Storing old config - {0}", fileFullName + ".err-" + string.Format("ddMMyyyy-HHmmss", DateTime.Now)); 135 | System.IO.File.Copy(fileFullName, fileFullName + ".err-" + string.Format("ddMMyyyy-HHmmss", DateTime.Now),true); 136 | blnReturn = false; 137 | } 138 | return blnReturn; 139 | } 140 | 141 | /// 142 | /// Saves the object to a ConfigNode structure in the previously supplied file 143 | /// 144 | /// Succes of Save 145 | public Boolean Save() 146 | { 147 | LogFormatted_DebugOnly("Saving ConfigNode"); 148 | return this.Save(FilePath); 149 | } 150 | 151 | /// 152 | /// Saves the object to a ConfigNode structure in a file 153 | /// 154 | /// Absolute Path to the file to load the ConfigNode structure from 155 | /// Success of Save 156 | public Boolean Save(String fileFullName) 157 | { 158 | Boolean blnReturn = false; 159 | try 160 | { 161 | if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(fileFullName))) 162 | { 163 | System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fileFullName)); 164 | } 165 | } 166 | catch (Exception ex) 167 | { 168 | LogFormatted("Unable to create directory for ConfigNode file({0})-Error:{1}", fileFullName, ex.Message); 169 | blnReturn = false; 170 | } 171 | 172 | try 173 | { 174 | //Encode the current object 175 | ConfigNode cnToSave = this.AsConfigNode; 176 | //Wrap it in a node with a name of the class 177 | ConfigNode cnSaveWrapper = new ConfigNode(this.GetType().Name); 178 | cnSaveWrapper.AddNode(cnToSave); 179 | //Save it to the file 180 | cnSaveWrapper.Save(fileFullName); 181 | blnReturn = true; 182 | } 183 | catch (Exception ex) 184 | { 185 | LogFormatted("Failed to Save ConfigNode to file({0})-Error:{1}", fileFullName, ex.Message); 186 | blnReturn = false; 187 | } 188 | return blnReturn; 189 | } 190 | 191 | /// 192 | /// Returns the current object as a ConfigNode 193 | /// 194 | public ConfigNode AsConfigNode 195 | { 196 | get 197 | { 198 | try 199 | { 200 | //Create a new Empty Node with the class name 201 | ConfigNode cnTemp = new ConfigNode(this.GetType().Name); 202 | //Load the current object in there 203 | cnTemp = ConfigNode.CreateConfigFromObject(this, cnTemp); 204 | return cnTemp; 205 | } 206 | catch (Exception ex) 207 | { 208 | LogFormatted("Failed to generate ConfigNode-Error;{0}", ex.Message); 209 | //Logging and return value? 210 | return new ConfigNode(this.GetType().Name); 211 | } 212 | } 213 | } 214 | 215 | 216 | #region Assembly/Class Information 217 | /// 218 | /// Name of the Assembly that is running this MonoBehaviour 219 | /// 220 | internal static String _AssemblyName 221 | { get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; } } 222 | 223 | /// 224 | /// Full Path of the executing Assembly 225 | /// 226 | internal static String _AssemblyLocation 227 | { get { return System.Reflection.Assembly.GetExecutingAssembly().Location; } } 228 | 229 | /// 230 | /// Folder containing the executing Assembly 231 | /// 232 | internal static String _AssemblyFolder 233 | { get { return System.IO.Path.GetDirectoryName(_AssemblyLocation); } } 234 | 235 | #endregion 236 | 237 | #region Logging 238 | /// 239 | /// Some Structured logging to the debug file - ONLY RUNS WHEN DLL COMPILED IN DEBUG MODE 240 | /// 241 | /// Text to be printed - can be formatted as per String.format 242 | /// Objects to feed into a String.format 243 | [System.Diagnostics.Conditional("DEBUG")] 244 | internal static void LogFormatted_DebugOnly(String Message, params object[] strParams) 245 | { 246 | LogFormatted("DEBUG: " + Message, strParams); 247 | } 248 | 249 | /// 250 | /// Some Structured logging to the debug file 251 | /// 252 | /// Text to be printed - can be formatted as per String.format 253 | /// Objects to feed into a String.format 254 | internal static void LogFormatted(String Message, params object[] strParams) 255 | { 256 | Message = String.Format(Message, strParams); // This fills the params into the message 257 | String strMessageLine = String.Format("{0},{2},{1}", 258 | DateTime.Now, Message, 259 | _AssemblyName); // This adds our standardised wrapper to each line 260 | UnityEngine.Debug.Log(strMessageLine); // And this puts it in the log 261 | } 262 | 263 | #endregion 264 | 265 | } 266 | } -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/FloatCurve2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace AscentProfilePlanner 8 | { 9 | class FloatCurve2 10 | { 11 | public Keyframe2[,] keys { get { return (Keyframe2[,])_keys.Clone(); } } 12 | private Keyframe2[,] _keys; 13 | public float[] xTimes { get; private set; } 14 | public float[] yTimes { get; private set; } 15 | 16 | private double[,][] coeffCache; 17 | 18 | public Keyframe2 this[int indexX, int indexY] 19 | { 20 | get 21 | { 22 | return _keys[indexX, indexY]; 23 | } 24 | set 25 | { 26 | _keys[indexX, indexY] = value; 27 | } 28 | } 29 | public Keyframe2 this[float timeX, float timeY] 30 | { 31 | get 32 | { 33 | int indexX = xTimes.IndexOf(timeX); 34 | int indexY = yTimes.IndexOf(timeY); 35 | 36 | if (indexX == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeX"); 37 | if (indexY == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeY"); 38 | 39 | return this[indexX, indexY]; 40 | } 41 | set 42 | { 43 | int indexX = xTimes.IndexOf(timeX); 44 | int indexY = yTimes.IndexOf(timeY); 45 | 46 | if (indexX == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeX"); 47 | if (indexY == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeY"); 48 | 49 | this[indexX, indexY] = value; 50 | } 51 | } 52 | 53 | public int[] size { get { return new int[] { _keys.GetUpperBound(0), _keys.GetUpperBound(1) }; } } 54 | public int length { get { return _keys.Length; } } 55 | 56 | public int GetUpperBound(int dimension) { return _keys.GetUpperBound(dimension); } 57 | 58 | /*public FloatCurve2(int xNum, int yNum) 59 | { 60 | _keys = new Keyframe[xNum, yNum]; 61 | }*/ 62 | public FloatCurve2(float[] xTimes, float[] yTimes) 63 | { 64 | _keys = new Keyframe2[xTimes.Length, yTimes.Length]; 65 | this.xTimes = xTimes; 66 | this.yTimes = yTimes; 67 | Array.Sort(this.xTimes); 68 | Array.Sort(this.yTimes); 69 | this.coeffCache = new double[xTimes.Length - 1, yTimes.Length - 1][]; 70 | } 71 | /*public FloatCurve2(float[] xTimes, float[] yTimes, Keyframe[,] values) : this(xTimes, yTimes) 72 | { 73 | _keys = (Keyframe[,])values.Clone(); 74 | }*/ 75 | 76 | public int[] AddKey(float timeX, float timeY, float value) 77 | { 78 | return AddKey(timeX, timeY, value, 0, 0, 0); 79 | } 80 | public int[] AddKey(float timeX, float timeY, float value, float ddx, float ddy) 81 | { 82 | return AddKey(timeX, timeY, value, ddx, ddy, 0); 83 | } 84 | public int[] AddKey(float timeX, float timeY, float value, float ddx, float ddy, float dddxdy) 85 | { 86 | int indexX = xTimes.IndexOf(timeX); 87 | int indexY = yTimes.IndexOf(timeY); 88 | if (indexX >= 0 && indexY >= 0) 89 | _keys[indexX, indexY] = new Keyframe2(timeX, timeY, value, ddx, ddy, dddxdy); 90 | else 91 | { 92 | if (indexX == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeX"); 93 | if (indexY == -1) throw new ArgumentException("Given value did not match the allowable entries.", "timeY"); 94 | } 95 | return new int[2] { indexX, indexY }; 96 | } 97 | 98 | public float Evaluate(float timeX, float timeY) 99 | { 100 | int xSquare;// = Array.FindIndex(xTimes, x => timeX < x) - 1; 101 | int ySquare;// = Array.FindIndex(yTimes, y => timeY < y) - 1; 102 | if (timeX < xTimes[0]) 103 | xSquare = 0; 104 | else if (timeX > xTimes[xTimes.Length - 1]) 105 | xSquare = xTimes.Length - 2; 106 | else 107 | xSquare = Array.FindIndex(xTimes, x => timeX < x) - 1; 108 | 109 | if (timeY < yTimes[0]) 110 | ySquare = 0; 111 | else if (timeY > yTimes[yTimes.Length - 1]) 112 | ySquare = yTimes.Length - 2; 113 | else 114 | ySquare = Array.FindIndex(yTimes, y => timeY < y) - 1; 115 | 116 | float dx = (xTimes[xSquare + 1] - xTimes[xSquare]); 117 | float dy = (yTimes[ySquare + 1] - yTimes[ySquare]); 118 | float xN = Mathf.Clamp01((timeX - xTimes[xSquare]) / dx); 119 | float yN = Mathf.Clamp01((timeY - yTimes[ySquare]) / dy); 120 | 121 | if (coeffCache[xSquare, ySquare].Length <= 0) 122 | { 123 | 124 | float[] knowns = new float[16] { 125 | _keys[xSquare,ySquare].value, 126 | _keys[xSquare + 1,ySquare].value, 127 | _keys[xSquare,ySquare + 1].value, 128 | _keys[xSquare + 1,ySquare + 1].value, 129 | _keys[xSquare,ySquare].dDx * dx, 130 | _keys[xSquare + 1,ySquare].dDx * dx, 131 | _keys[xSquare,ySquare + 1].dDx * dx, 132 | _keys[xSquare + 1,ySquare + 1].dDx * dx, 133 | _keys[xSquare,ySquare].dDy * dy, 134 | _keys[xSquare + 1,ySquare].dDy * dy, 135 | _keys[xSquare,ySquare + 1].dDy * dy, 136 | _keys[xSquare + 1,ySquare + 1].dDy * dy, 137 | _keys[xSquare,ySquare].ddDxDy * dx * dy, 138 | _keys[xSquare + 1,ySquare].ddDxDy * dx * dy, 139 | _keys[xSquare,ySquare + 1].ddDxDy * dx * dy, 140 | _keys[xSquare + 1,ySquare + 1].ddDxDy * dx * dy 141 | }; 142 | 143 | coeffCache[xSquare, ySquare] = new double[16] { 144 | 1 * knowns[0], 145 | 1 * knowns[4], 146 | -3 * knowns[0] + 3 * knowns[1] - 2 * knowns[4] - 1 * knowns[5], 147 | 2 * knowns[0] - 2 * knowns[1] + 1 * knowns[4] + 1 * knowns[5], 148 | 1 * knowns[8], 149 | 1 * knowns[12], 150 | -3 * knowns[8] + 3 * knowns[9] - 2 * knowns[12] - 1 * knowns[13], 151 | 2 * knowns[8] - 2 * knowns[9] + 1 * knowns[12] + 1 * knowns[13], 152 | -3 * knowns[0] + 3 * knowns[2] - 2 * knowns[8] - 1 * knowns[10], 153 | -3 * knowns[4] + 3 * knowns[6] - 2 * knowns[12] - 1 * knowns[14], 154 | 9 * knowns[0] - 9 * knowns[1] - 9 * knowns[2] + 9 * knowns[3] + 6 * knowns[4] + 3 * knowns[5] - 6 * knowns[6] - 3 * knowns[7] + 6 * knowns[8] - 6 * knowns[9] + 3 * knowns[10] - 3 * knowns[11] + 4 * knowns[12] + 2 * knowns[13] + 2 * knowns[14] + 1 * knowns[15], 155 | -6 * knowns[0] + 6 * knowns[1] + 6 * knowns[2] - 6 * knowns[3] - 3 * knowns[4] - 3 * knowns[5] + 3 * knowns[6] + 3 * knowns[7] - 4 * knowns[8] + 4 * knowns[9] - 2 * knowns[10] + 2 * knowns[11] - 2 * knowns[12] - 2 * knowns[13] - 1 * knowns[14] - 1 * knowns[15], 156 | 2 * knowns[0] - 2 * knowns[2] + 1 * knowns[8] + 1 * knowns[10], 157 | 2 * knowns[4] - 2 * knowns[6] + 1 * knowns[12] + 1 * knowns[14], 158 | -6 * knowns[0] + 6 * knowns[1] + 6 * knowns[2] - 6 * knowns[3] - 4 * knowns[4] - 2 * knowns[5] + 4 * knowns[6] + 2 * knowns[7] - 3 * knowns[8] + 3 * knowns[9] - 3 * knowns[10] + 3 * knowns[11] - 2 * knowns[12] - 1 * knowns[13] - 2 * knowns[14] - 1 * knowns[15], 159 | 4 * knowns[0] - 4 * knowns[1] - 4 * knowns[2] + 4 * knowns[3] + 2 * knowns[4] + 2 * knowns[5] - 2 * knowns[6] - 2 * knowns[7] + 2 * knowns[8] - 2 * knowns[9] + 2 * knowns[10] - 2 * knowns[11] + 1 * knowns[12] + 1 * knowns[13] + 1 * knowns[14] + 1 * knowns[15] 160 | }; 161 | } 162 | 163 | return (float)Solve(coeffCache[xSquare, ySquare], xN, yN); 164 | } 165 | 166 | private double Solve(double[] coeffs, float x, float y) 167 | { 168 | float x2 = x * x; 169 | float x3 = x2 * x; 170 | float y2 = y * y; 171 | float y3 = y2 * y; 172 | 173 | return (coeffs[0] + coeffs[1] * x + coeffs[2] * x2 + coeffs[3] * x3) + 174 | (coeffs[4] + coeffs[5] * x + coeffs[6] * x2 + coeffs[7] * x3) * y + 175 | (coeffs[8] + coeffs[9] * x + coeffs[10] * x2 + coeffs[11] * x3) * y2 + 176 | (coeffs[12] + coeffs[13] * x + coeffs[14] * x2 + coeffs[15] * x3) * y3; 177 | } 178 | 179 | public struct Keyframe2 180 | { 181 | public float timeX { get; set; } 182 | public float timeY { get; set; } 183 | public float value { get; set; } 184 | public float dDx { get; set; } 185 | public float dDy { get; set; } 186 | public float ddDxDy { get; set; } 187 | 188 | public int tangentMode { get { return 0; } set { } } 189 | 190 | public Keyframe2(float timex, float timey, float value) 191 | { 192 | this.timeX = timex; 193 | this.timeY = timey; 194 | this.value = value; 195 | this.dDx = 0.0f; 196 | this.dDy = 0.0f; 197 | this.ddDxDy = 0.0f; 198 | } 199 | 200 | public Keyframe2(float timex, float timey, float value, float ddx, float ddy) 201 | { 202 | this.timeX = timex; 203 | this.timeY = timey; 204 | this.value = value; 205 | this.dDx = ddx; 206 | this.dDy = ddy; 207 | this.ddDxDy = 0.0f; 208 | } 209 | 210 | public Keyframe2(float timex, float timey, float value, float ddx, float ddy, float dddxdy) 211 | { 212 | this.timeX = timex; 213 | this.timeY = timey; 214 | this.value = value; 215 | this.dDx = ddx; 216 | this.dDy = ddy; 217 | this.ddDxDy = dddxdy; 218 | } 219 | 220 | public static Keyframe2 operator + (Keyframe2 key, float value) 221 | { 222 | key.value += value; 223 | return key; 224 | } 225 | public static Keyframe2 operator + (Keyframe2 key1, Keyframe2 key2) 226 | { 227 | if (key1.timeX != key2.timeX || key1.timeY != key2.timeY) 228 | throw new ArgumentException("The given keys did not match coordinates."); 229 | return new Keyframe2(key1.timeX, key2.timeX, key1.value + key2.value, key1.dDx + key2.dDx, key1.dDy + key2.dDy, key1.ddDxDy + key2.ddDxDy); 230 | } 231 | public static implicit operator float(Keyframe2 key) 232 | { 233 | return key.value; 234 | } 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/Extensions/Linq2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace KerbalWindTunnel.Extensions 6 | { 7 | public static class Linq2 8 | { 9 | public static T[,] Subset(this T[,] vals, int lowerBound0, int upperBound0, int lowerBound1, int upperBound1) 10 | { 11 | if (vals == null) 12 | throw new ArgumentNullException(); 13 | T[,] result = new T[upperBound0 - lowerBound0 + 1, upperBound1 - lowerBound1 + 1]; 14 | for (int i = lowerBound0; i <= upperBound0; i++) 15 | { 16 | for (int j = lowerBound1; j <= upperBound1; j++) 17 | { 18 | result[i - lowerBound0, j - lowerBound1] = vals[i, j]; 19 | } 20 | } 21 | return result; 22 | } 23 | 24 | public static T[] Subset(this T[] vals, int lowerBound, int upperBound) 25 | { 26 | if (vals == null) 27 | throw new ArgumentNullException(); 28 | T[] result = new T[upperBound - lowerBound + 1]; 29 | for (int i = lowerBound; i <= upperBound; i++) 30 | { 31 | result[i - lowerBound] = vals[i]; 32 | } 33 | return result; 34 | } 35 | 36 | public static TResult[,] SelectToArray(this TInput[,] vals, Func selector) 37 | { 38 | if (vals == null) 39 | throw new ArgumentNullException(); 40 | int bound0 = vals.GetUpperBound(0); 41 | int bound1 = vals.GetUpperBound(1); 42 | TResult[,] results = new TResult[bound0 + 1, bound1 + 1]; 43 | 44 | for (int i = 0; i <= bound0; i++) 45 | { 46 | for(int j = 0; j<=bound1; j++) 47 | { 48 | results[i, j] = selector(vals[i, j]); 49 | } 50 | } 51 | 52 | return results; 53 | } 54 | 55 | public static float Max(this float[,] vals, bool excludeInfinity = false) 56 | { 57 | if (vals == null) 58 | throw new ArgumentNullException(); 59 | int bound0 = vals.GetUpperBound(0); 60 | int bound1 = vals.GetUpperBound(1); 61 | if (bound0 < 0 || bound1 < 0) 62 | throw new InvalidOperationException("The source sequence is empty."); 63 | float result = float.MinValue; 64 | for(int i = 0; i < bound0; i++) 65 | { 66 | for (int j = 0; j < bound1; j++) 67 | { 68 | if (excludeInfinity && float.IsPositiveInfinity(vals[i, j])) 69 | continue; 70 | if (vals[i, j] > result && !float.IsNaN(vals[i, j])) 71 | result = vals[i, j]; 72 | } 73 | } 74 | return result; 75 | } 76 | 77 | public static float Min(this float[,] vals, bool excludeInfinity = false) 78 | { 79 | if (vals == null) 80 | throw new ArgumentNullException(); 81 | int bound0 = vals.GetUpperBound(0); 82 | int bound1 = vals.GetUpperBound(1); 83 | if (bound0 < 0 || bound1 < 0) 84 | throw new InvalidOperationException("The source sequence is empty."); 85 | float result = float.MaxValue; 86 | for (int i = 0; i < bound0; i++) 87 | { 88 | for (int j = 0; j < bound1; j++) 89 | { 90 | if (excludeInfinity && float.IsNegativeInfinity(vals[i, j])) 91 | continue; 92 | if (vals[i, j] < result && !float.IsNaN(vals[i, j])) 93 | result = vals[i, j]; 94 | } 95 | } 96 | return result; 97 | } 98 | 99 | public static int First(this T[,] vals, int dimension, int index, Predicate predicate) 100 | { 101 | if (vals == null) 102 | throw new ArgumentNullException(); 103 | if (dimension > 1) 104 | throw new ArgumentOutOfRangeException("dimension", dimension, "The provided dimension was out of range."); 105 | int limit = vals.GetUpperBound(dimension); 106 | if (limit < 0) 107 | throw new InvalidOperationException("The source sequence is empty."); 108 | if (dimension == 0) 109 | { 110 | for (int i = 0; i <= limit; i++) 111 | if (predicate(vals[i, index])) 112 | return i; 113 | } 114 | else if (dimension == 1) 115 | { 116 | for (int i = 0; i <= limit; i++) 117 | if (predicate(vals[index, i])) 118 | return i; 119 | } 120 | else 121 | throw new ArgumentOutOfRangeException("dimension"); 122 | throw new InvalidOperationException("No element satisfies the condition in Predicate."); 123 | } 124 | 125 | public static T[] To1Dmension(this T[,] vals) 126 | { 127 | if (vals == null) 128 | throw new ArgumentNullException(); 129 | int length = vals.Length; 130 | int columns = vals.GetUpperBound(1) + 1; 131 | T[] result = new T[length]; 132 | 133 | for (int i = length - 1; i >= 0; i--) 134 | { 135 | result[i] = vals[i % columns, i / columns]; 136 | } 137 | 138 | return result; 139 | } 140 | 141 | public static T[,] To2Dimension(this T[] vals, int columns) 142 | { 143 | if (vals == null) 144 | throw new ArgumentNullException(); 145 | int length = vals.Length; 146 | if (length % columns != 0) 147 | throw new ArgumentException(String.Format("The input data {0} cannot be fit to the supplied number of columns {1}", length, columns)); 148 | T[,] result = new T[columns, length / columns]; 149 | 150 | for (int i = length - 1; i >= 0; i--) 151 | { 152 | result[i % columns, i / columns] = vals[i]; 153 | } 154 | 155 | return result; 156 | } 157 | 158 | public static float Lerp2(this float[,] vals, float x, float y) 159 | { 160 | if (vals == null) 161 | throw new ArgumentNullException(); 162 | int xI1, xI2; 163 | float fX; 164 | if (x <= 0) 165 | { 166 | xI1 = xI2 = 0; 167 | fX = 0; 168 | } 169 | else 170 | { 171 | int lengthX = vals.GetUpperBound(0); 172 | if (lengthX < 0) 173 | return 0; 174 | if (x >= 1) 175 | { 176 | xI1 = xI2 = lengthX; 177 | fX = 1; 178 | } 179 | else 180 | { 181 | float stepX = 1f / lengthX; 182 | xI1 = (int)Math.Floor(x / stepX); 183 | fX = x / stepX % 1; 184 | xI2 = xI1 + 1; 185 | if (fX == 0) 186 | xI2 = xI1; 187 | else 188 | xI2 = xI1 + 1; 189 | } 190 | } 191 | 192 | if (y <= 0) 193 | { 194 | if (xI1 == xI2) return vals[xI1, 0]; 195 | return vals[xI1, 0] * (1 - fX) + vals[xI2, 0] * fX; 196 | } 197 | else 198 | { 199 | int lengthY = vals.GetUpperBound(1); 200 | if (lengthY < 0) 201 | return 0; 202 | if (y >= 1) 203 | { 204 | if (xI1 == xI2) return vals[xI1, 0]; 205 | return vals[xI1, lengthY] * (1 - fX) + vals[xI2, lengthY] * fX; 206 | } 207 | else 208 | { 209 | float stepY = 1f / lengthY; 210 | int yI1 = (int)Math.Floor(y / stepY); 211 | float fY = y / stepY % 1; 212 | int yI2; 213 | if (fY == 0) 214 | yI2 = yI1; 215 | else 216 | yI2 = yI1 + 1; 217 | 218 | if (xI1 == xI2 && yI1 == yI2) 219 | return vals[xI1, yI1]; 220 | else if (xI1 == xI2) 221 | return vals[xI1, yI1] * (1 - fY) + vals[xI1, yI2] * fY; 222 | else if (yI1 == yI2) 223 | return vals[xI1, yI1] * (1 - fX) + vals[xI2, yI1] * fX; 224 | 225 | return vals[xI1, yI1] * (1 - fX) * (1 - fY) + 226 | vals[xI2, yI1] * fX * (1 - fY) + 227 | vals[xI1, yI2] * (1 - fX) * fY + 228 | vals[xI2, yI2] * fX * fY; 229 | } 230 | } 231 | } 232 | 233 | public static IEnumerator GetTaxicabNeighbors(this T[,] vals, int startX, int startY, int maxRange = -1) 234 | { 235 | if (vals == null) 236 | throw new ArgumentNullException(); 237 | int width = vals.GetUpperBound(0); 238 | int height = vals.GetUpperBound(1); 239 | if (startX < 0 || startX > width || startY < 0 || startY > height) 240 | yield break; 241 | 242 | yield return vals[startX, startY]; 243 | if (maxRange < 0) maxRange = width + height; 244 | for (int r = 1; r <= maxRange; r++) 245 | { 246 | for (int r2 = 0; r2 < r; r2++) 247 | { 248 | if (startY + (r - r2) <= height && startX + r2 <= width) yield return vals[startX + r2, startY + (r - r2)]; 249 | if (startX + (r - r2) <= width && startY - r2 >= 0) yield return vals[startX + (r - r2), startY - r2]; 250 | if (startY - (r - r2) >= 0 && startX - r2 >= 0) yield return vals[startX - r2, startY - (r - r2)]; 251 | if (startX - (r - r2) >= 0 && startY + r2 <= height) yield return vals[startX - (r - r2), startY + r2]; 252 | } 253 | } 254 | } 255 | 256 | public static IEnumerator GetTaxicabNeighbors(this T[,] vals, int startX, int startY, int maxRange = -1, 257 | params Quadrant[] quadrants) 258 | { 259 | if (vals == null) 260 | throw new ArgumentNullException(); 261 | bool[] quads = new bool[4]; 262 | for (int i = 0; i < quadrants.Length; i++) 263 | if ((int)quadrants[i] - 1 >= 0) 264 | quads[(int)quadrants[i] - 1] = true; 265 | 266 | return GetTaxicabNeighbors(vals, startX, startY, maxRange, quads); 267 | } 268 | 269 | public static IEnumerator GetTaxicabNeighbors(this T[,] vals, int startX, int startY, int maxRange, 270 | bool[] quads) 271 | { 272 | if (vals == null) 273 | throw new ArgumentNullException(); 274 | int width = vals.GetUpperBound(0); 275 | int height = vals.GetUpperBound(1); 276 | if (startX < 0 || startX > width || startY < 0 || startY > height) 277 | yield break; 278 | 279 | yield return vals[startX, startY]; 280 | if (maxRange < 0) maxRange = width + height; 281 | for (int r = 1; r <= maxRange; r++) 282 | { 283 | for (int r2 = 0; r2 < r; r2++) 284 | { 285 | if (quads[0] && startY + (r - r2) <= height && startX + r2 <= width) yield return vals[startX + r2, startY + (r - r2)]; 286 | if (quads[3] && startX + (r - r2) <= width && startY - r2 >= 0) yield return vals[startX + (r - r2), startY - r2]; 287 | if (quads[2] && startY - (r - r2) >= 0 && startX - r2 >= 0) yield return vals[startX - r2, startY - (r - r2)]; 288 | if (quads[1] && startX - (r - r2) >= 0 && startY + r2 <= height) yield return vals[startX - (r - r2), startY + r2]; 289 | } 290 | } 291 | } 292 | 293 | public enum Quadrant : int 294 | { 295 | I = 1, 296 | II = 2, 297 | III = 3, 298 | IV = 4, 299 | Default = 0 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/AeroPredictor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace KerbalWindTunnel 5 | { 6 | public abstract class AeroPredictor 7 | { 8 | public virtual bool ThreadSafe { get { return false; } } 9 | 10 | public abstract float Mass { get; } 11 | public abstract bool ThrustIsConstantWithAoA { get; } 12 | public Vector3 CoM; 13 | public Vector3 CoM_dry; 14 | 15 | public abstract float Area { get; } 16 | 17 | public virtual float GetMaxAoA(Conditions conditions, float tolerance = 0.0003f) 18 | { 19 | return (float)Accord.Math.Optimization.BrentSearch.Maximize((aoa) => GetLiftForceMagnitude(conditions, (float)aoa, 1), 10 * Mathf.Deg2Rad, 60 * Mathf.Deg2Rad, tolerance); 20 | } 21 | public virtual float GetMaxAoA(Conditions conditions, out float lift, float guess = float.NaN, float tolerance = 0.0003f) 22 | { 23 | #if ENABLE_PROFILER 24 | UnityEngine.Profiling.Profiler.BeginSample("AeroPredictor.GetMaxAoA()"); 25 | #endif 26 | Accord.Math.Optimization.BrentSearch maximizer = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(conditions, (float)aoa, 1), 10 * Mathf.Deg2Rad, 60 * Mathf.Deg2Rad, tolerance); 27 | if (float.IsNaN(guess) || float.IsInfinity(guess)) 28 | maximizer.Maximize(); 29 | else 30 | { 31 | maximizer.LowerBound = guess - 5 * Mathf.Deg2Rad; 32 | maximizer.UpperBound = guess + 5 * Mathf.Deg2Rad; 33 | if (!maximizer.Maximize()) 34 | { 35 | maximizer.LowerBound = guess - 10 * Mathf.Deg2Rad; 36 | maximizer.UpperBound = guess + 10 * Mathf.Deg2Rad; 37 | if (!maximizer.Maximize()) 38 | { 39 | maximizer.LowerBound = Math.Min(10 * Mathf.Deg2Rad, guess - 15 * Mathf.Deg2Rad); 40 | maximizer.UpperBound = Mathf.Clamp(guess + 15 * Mathf.Deg2Rad, 60 * Mathf.Deg2Rad, 90 * Mathf.Deg2Rad); 41 | maximizer.Maximize(); 42 | } 43 | } 44 | } 45 | lift = (float)maximizer.Value; 46 | #if ENABLE_PROFILER 47 | UnityEngine.Profiling.Profiler.EndSample(); 48 | #endif 49 | return (float)maximizer.Solution; 50 | } 51 | public virtual float GetMinAoA(Conditions conditions, float guess = float.NaN, float tolerance = 0.0003f) 52 | { 53 | Accord.Math.Optimization.BrentSearch minimizer = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(conditions, (float)aoa, 1), -60 * Mathf.Deg2Rad, -10 * Mathf.Deg2Rad, tolerance); 54 | if (float.IsNaN(guess) || float.IsInfinity(guess)) 55 | minimizer.Maximize(); 56 | else 57 | { 58 | minimizer.LowerBound = guess - 2 * Mathf.Deg2Rad; 59 | minimizer.UpperBound = guess + 2 * Mathf.Deg2Rad; 60 | if (!minimizer.Maximize()) 61 | { 62 | minimizer.LowerBound = guess - 5 * Mathf.Deg2Rad; 63 | minimizer.UpperBound = guess + 5 * Mathf.Deg2Rad; 64 | if (!minimizer.Maximize()) 65 | { 66 | minimizer.LowerBound = Mathf.Clamp(guess - 10 * Mathf.Deg2Rad, -90 * Mathf.Deg2Rad, -60 * Mathf.Deg2Rad); 67 | minimizer.UpperBound = Math.Max(-10 * Mathf.Deg2Rad, guess + 10 * Mathf.Deg2Rad); 68 | minimizer.Maximize(); 69 | } 70 | } 71 | } 72 | return (float)minimizer.Solution; 73 | } 74 | 75 | public virtual float GetAoA(Conditions conditions, float offsettingForce, bool useThrust = true, bool dryTorque = false, float guess = float.NaN, float pitchInputGuess = float.NaN, bool lockPitchInput = false, float tolerance = 0.0003f) 76 | { 77 | #if ENABLE_PROFILER 78 | UnityEngine.Profiling.Profiler.BeginSample("AeroPredictor.GetAoA(Conditions, float, bool, bool, float, float, bool, float"); 79 | #endif 80 | if (lockPitchInput && (float.IsNaN(pitchInputGuess) || float.IsInfinity(pitchInputGuess))) 81 | pitchInputGuess = 0; 82 | Accord.Math.Optimization.BrentSearch solver; 83 | switch (ThrustIsConstantWithAoA) 84 | { 85 | case true when lockPitchInput: 86 | Vector3 thrustForce = useThrust ? this.GetThrustForce(conditions) : Vector3.zero; 87 | solver = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(this.GetLiftForce(conditions, (float)aoa, pitchInputGuess) + thrustForce, (float)aoa) - offsettingForce, 88 | -10 * Mathf.Deg2Rad, 35 * Mathf.Deg2Rad, tolerance); 89 | break; 90 | case true when !lockPitchInput: 91 | thrustForce = useThrust ? this.GetThrustForce(conditions) : Vector3.zero; 92 | solver = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(this.GetLiftForce(conditions, (float)aoa, GetPitchInput(conditions, (float)aoa, dryTorque, pitchInputGuess)) + thrustForce, (float)aoa) 93 | - offsettingForce, -10 * Mathf.Deg2Rad, 35 * Mathf.Deg2Rad, tolerance); 94 | break; 95 | case false when lockPitchInput: 96 | solver = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(this.GetLiftForce(conditions, (float)aoa, pitchInputGuess) + this.GetThrustForce(conditions, (float)aoa), (float)aoa) - offsettingForce, 97 | -10 * Mathf.Deg2Rad, 35 * Mathf.Deg2Rad, tolerance); 98 | break; 99 | case false when !lockPitchInput: 100 | default: 101 | solver = new Accord.Math.Optimization.BrentSearch((aoa) => GetLiftForceMagnitude(this.GetLiftForce(conditions, (float)aoa, GetPitchInput(conditions, (float)aoa, dryTorque, pitchInputGuess)) + this.GetThrustForce(conditions, (float)aoa), (float)aoa) 102 | - offsettingForce, -10 * Mathf.Deg2Rad, 35 * Mathf.Deg2Rad, tolerance); 103 | break; 104 | 105 | } 106 | 107 | if (float.IsNaN(guess) || float.IsInfinity(guess)) 108 | solver.FindRoot(); 109 | else 110 | { 111 | solver.LowerBound = guess - 2 * Mathf.Deg2Rad; 112 | solver.UpperBound = guess + 2 * Mathf.Deg2Rad; 113 | if (!solver.FindRoot()) 114 | { 115 | solver.LowerBound = guess - 5 * Mathf.Deg2Rad; 116 | solver.UpperBound = guess + 5 * Mathf.Deg2Rad; 117 | if (!solver.FindRoot()) 118 | { 119 | solver.LowerBound = Math.Min(-10 * Mathf.Deg2Rad, guess - 10 * Mathf.Deg2Rad); 120 | solver.UpperBound = Math.Max(35 * Mathf.Deg2Rad, guess + 10 * Mathf.Deg2Rad); 121 | solver.FindRoot(); 122 | } 123 | } 124 | } 125 | 126 | #if ENABLE_PROFILER 127 | UnityEngine.Profiling.Profiler.EndSample(); 128 | #endif 129 | return (float)solver.Solution; 130 | } 131 | 132 | public abstract float GetPitchInput(Conditions conditions, float AoA, bool dryTorque = false, float guess = float.NaN, float tolerance = 0.0003f); 133 | 134 | public abstract Vector3 GetAeroForce(Conditions conditions, float AoA, float pitchInput = 0); 135 | 136 | public virtual Vector3 GetLiftForce(Conditions conditions, float AoA, float pitchInput = 0) 137 | { 138 | return GetAeroForce(conditions, AoA, pitchInput); 139 | } 140 | 141 | public abstract Vector3 GetAeroTorque(Conditions conditions, float AoA, float pitchInput = 0, bool dryTorque = false); 142 | 143 | public virtual void GetAeroCombined(Conditions conditions, float AoA, float pitchInput, out Vector3 forces, out Vector3 torques, bool dryTorque = false) 144 | { 145 | forces = GetAeroForce(conditions, AoA, pitchInput); 146 | torques = GetAeroTorque(conditions, AoA, pitchInput); 147 | } 148 | 149 | public virtual float GetLiftForceMagnitude(Conditions conditions, float AoA, float pitchInput = 0) 150 | { 151 | return GetLiftForceMagnitude(GetLiftForce(conditions, AoA, pitchInput), AoA); 152 | } 153 | public static float GetLiftForceMagnitude(Vector3 force, float AoA) 154 | { 155 | return ToFlightFrame(force, AoA).y; 156 | } 157 | 158 | public virtual float GetDragForceMagnitude(Conditions conditions, float AoA, float pitchInput = 0) 159 | { 160 | return GetDragForceMagnitude(GetAeroForce(conditions, AoA, pitchInput), AoA); 161 | } 162 | public static float GetDragForceMagnitude(Vector3 force, float AoA) 163 | { 164 | return -ToFlightFrame(force, AoA).z; 165 | } 166 | 167 | public abstract Vector3 GetThrustForce(Conditions conditions, float AoA); 168 | public virtual Vector3 GetThrustForce(Conditions conditions) => GetThrustForce(conditions, 0); 169 | public virtual Vector3 GetthrustForceFlightFrame(Conditions conditions, float AoA) 170 | { 171 | return ToFlightFrame(GetThrustForce(conditions, AoA), AoA); 172 | } 173 | 174 | public virtual Vector2 GetThrustForce2D(Conditions conditions) => GetThrustForce2D(conditions, 0); 175 | public virtual Vector2 GetThrustForce2D(Conditions conditions, float AoA) 176 | { 177 | Vector3 thrustForce = GetThrustForce(conditions, AoA); 178 | return new Vector2(thrustForce.z, thrustForce.y); 179 | } 180 | public virtual Vector2 GetThrustForce2DFlightFrame(Conditions conditions, float AoA) 181 | { 182 | Vector3 thrustForce = ToFlightFrame(GetThrustForce(conditions, AoA), AoA); 183 | return new Vector2(thrustForce.z, thrustForce.y); 184 | } 185 | 186 | public virtual float GetFuelBurnRate(Conditions conditions) => GetFuelBurnRate(conditions, 0); 187 | public abstract float GetFuelBurnRate(Conditions conditions, float AoA); 188 | 189 | public static Vector3 ToFlightFrame(Vector3 force, float AoA) 190 | { 191 | return Quaternion.AngleAxis((AoA * Mathf.Rad2Deg), Vector3.left) * force; 192 | } 193 | public static Vector3 ToVesselFrame(Vector3 force, float AoA) 194 | { 195 | return Quaternion.AngleAxis((-AoA * Mathf.Rad2Deg), Vector3.left) * force; 196 | } 197 | 198 | public static float GetUsefulThrustMagnitude(Vector3 thrustVector) 199 | { 200 | Vector2 usefulThrust = new Vector2(Math.Max(thrustVector.z, 0), Math.Max(thrustVector.y, 0)); 201 | if (usefulThrust.x == thrustVector.z && usefulThrust.y == thrustVector.y) 202 | return usefulThrust.magnitude; 203 | Vector2 antiThrust = new Vector2(Math.Min(thrustVector.z, 0), Math.Min(thrustVector.y, 0)); 204 | return usefulThrust.magnitude - antiThrust.magnitude; 205 | } 206 | 207 | public static Vector3 InflowVect(float AoA) 208 | { 209 | Vector3 vesselForward = Vector3d.forward; 210 | Vector3 vesselUp = Vector3d.up; 211 | return vesselForward * Mathf.Cos(-AoA) + vesselUp * Mathf.Sin(-AoA); 212 | } 213 | 214 | //public abstract AeroPredictor Clone(); 215 | 216 | public struct Conditions 217 | { 218 | public readonly CelestialBody body; 219 | public readonly float speed; 220 | public readonly float altitude; 221 | public readonly float mach; 222 | public readonly float atmDensity; 223 | public readonly float atmPressure; 224 | public readonly float pseudoReDragMult; 225 | public readonly bool oxygenAvailable; 226 | public readonly float speedOfSound; 227 | 228 | public Conditions(CelestialBody body, float speed, float altitude) 229 | { 230 | this.body = body; 231 | this.speed = speed; 232 | this.altitude = altitude; 233 | 234 | lock (body) 235 | { 236 | this.atmPressure = (float)body.GetPressure(altitude); 237 | this.atmDensity = (float)Extensions.KSPClassExtensions.GetDensity(body, altitude); 238 | this.speedOfSound = (float) body.GetSpeedOfSound(atmPressure, atmDensity); 239 | this.oxygenAvailable = body.atmosphereContainsOxygen; 240 | } 241 | this.mach = speed / speedOfSound; 242 | 243 | lock (PhysicsGlobals.DragCurvePseudoReynolds) 244 | this.pseudoReDragMult = PhysicsGlobals.DragCurvePseudoReynolds.Evaluate(atmDensity * speed); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Wind Tunnel/Wind Tunnel/FARVesselCache/FARVesselCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEngine; 6 | using Smooth.Pools; 7 | using KerbalWindTunnel.Extensions.Reflection; 8 | 9 | namespace KerbalWindTunnel.FARVesselCache 10 | { 11 | public partial class FARVesselCache : AeroPredictor, VesselCache.IReleasable 12 | { 13 | public List engines = new List(); 14 | 15 | public override float Mass => totalMass; 16 | public override float Area => area; 17 | public override bool ThreadSafe => true; 18 | private static readonly Pool pool = new Pool(Create, Reset); 19 | 20 | public static bool accountForControls = false; 21 | 22 | public float totalMass = 0; 23 | public float dryMass = 0; 24 | public float area = 0; 25 | public float MAC = 0; 26 | public float b_2 = 0; 27 | public float maxCrossSectionFromBody = 0; 28 | public float bodyLength = 0; 29 | 30 | private FARVesselCache parent = null; 31 | 32 | public static int PoolSize 33 | { 34 | get { return pool.Size; } 35 | } 36 | 37 | public override bool ThrustIsConstantWithAoA => true; 38 | 39 | private static Func getFAREditorGUIInstance; 40 | private static Func getFARSimInstance; 41 | 42 | private List _wingAerodynamicModel; 43 | private List _currentAeroSections; 44 | 45 | internal static bool SetMethods(Type FARType, Type editorGUIType) 46 | { 47 | getFAREditorGUIInstance = editorGUIType.StaticMethod>(editorGUIType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public).GetGetMethod()); 48 | getFARSimInstance = editorGUIType.FieldGet(editorGUIType.GetField("_instantSim", BindingFlags.Instance | BindingFlags.NonPublic)); 49 | 50 | return true; 51 | } 52 | 53 | private static FARVesselCache Create() 54 | { 55 | return new FARVesselCache(); 56 | } 57 | 58 | public void Release() 59 | { 60 | pool.Release(this); 61 | } 62 | 63 | private static void Reset(FARVesselCache obj) 64 | { 65 | VesselCache.SimulatedEngine.Release(obj.engines); 66 | obj.engines.Clear(); 67 | } 68 | 69 | public static FARVesselCache Borrow(IShipconstruct v, CelestialBody body) 70 | { 71 | FARVesselCache vessel; 72 | lock (pool) 73 | vessel = pool.Borrow(); 74 | vessel.Init(v, body); 75 | return vessel; 76 | } 77 | public static FARVesselCache BorrowClone(FARVesselCache predictor) 78 | { 79 | FARVesselCache clone; 80 | lock (pool) 81 | clone = pool.Borrow(); 82 | clone.InitClone(predictor); 83 | return clone; 84 | } 85 | 86 | private static object GetFARSimulationInstance() 87 | { 88 | return getFARSimInstance(getFAREditorGUIInstance()); 89 | } 90 | 91 | public void Init(IShipconstruct v, CelestialBody body) 92 | { 93 | FARAeroUtil.UpdateCurrentActiveBody(body); 94 | object simInstance = GetFARSimulationInstance(); 95 | 96 | maxCrossSectionFromBody = FARMethodAssist.InstantConditionSim__maxCrossSectionFromBody(simInstance); 97 | bodyLength = FARMethodAssist.InstantConditionSim__bodyLength(simInstance); 98 | 99 | _wingAerodynamicModel = FARCloneAssist.CloneListFARWingAerodynamicModels(FARMethodAssist.InstantConditionSim__wingAerodynamicModel(simInstance)); 100 | _currentAeroSections = FARCloneAssist.CloneListFARAeroSections(FARMethodAssist.InstantConditionSim__currentAeroSections(simInstance)); 101 | 102 | parent = null; 103 | 104 | List oParts = v.Parts; 105 | int count = oParts.Count; 106 | 107 | bool lgWarning = false; 108 | int stage = 0; 109 | for (int i = 0; i < count; i++) 110 | { 111 | if (!lgWarning) 112 | { 113 | ModuleWheels.ModuleWheelDeployment gear = oParts[i].FindModuleImplementing(); 114 | bool forcedRetract = !oParts[i].ShieldedFromAirstream && gear != null && gear.Position > 0; 115 | 116 | if (forcedRetract) 117 | lgWarning = true; 118 | } 119 | 120 | totalMass += oParts[i].mass + oParts[i].GetResourceMass(); 121 | dryMass += oParts[i].mass; 122 | CoM += (oParts[i].mass + oParts[i].GetResourceMass()) * oParts[i].transform.TransformPoint(oParts[i].CoMOffset); 123 | CoM_dry += (oParts[i].mass) * oParts[i].transform.TransformPoint(oParts[i].CoMOffset); 124 | 125 | if (oParts[i].inverseStage > stage) 126 | { 127 | VesselCache.SimulatedEngine.Release(engines); 128 | engines.Clear(); 129 | stage = oParts[i].inverseStage; 130 | } 131 | if (oParts[i].inverseStage >= stage) 132 | { 133 | MultiModeEngine multiMode = oParts[i].FindModuleImplementing(); 134 | if (multiMode != null) 135 | { 136 | engines.Add(VesselCache.SimulatedEngine.Borrow(oParts[i].FindModulesImplementing().Find(engine => engine.engineID == multiMode.mode), this)); 137 | } 138 | else 139 | { 140 | ModuleEngines engine = oParts[i].FindModulesImplementing().FirstOrDefault(); 141 | if (engine != null) 142 | engines.Add(VesselCache.SimulatedEngine.Borrow(engine, this)); 143 | } 144 | } 145 | } 146 | 147 | double area = 0; 148 | double MAC = 0; 149 | double b_2 = 0; 150 | foreach (FARWingAerodynamicModelWrapper wingAerodynamicModel in _wingAerodynamicModel) 151 | { 152 | if (!(wingAerodynamicModel != null && ((PartModule)wingAerodynamicModel.WrappedObject).part != null)) 153 | continue; 154 | if (!(FARHook.FARControllableSurfaceType.IsAssignableFrom(wingAerodynamicModel.WrappedObject.GetType()) && !wingAerodynamicModel.isShielded)) 155 | continue; 156 | 157 | float S = (float)wingAerodynamicModel.S; 158 | area += S; 159 | MAC += wingAerodynamicModel.Effective_MAC * S; 160 | b_2 += wingAerodynamicModel.Effective_b_2 * S; 161 | 162 | if (area == 0 || Math.Abs(area) < 1e-14 * double.Epsilon) 163 | { 164 | area = maxCrossSectionFromBody; 165 | b_2 = 1; 166 | MAC = bodyLength; 167 | } 168 | } 169 | double recipArea = 1 / area; 170 | MAC *= recipArea; 171 | b_2 *= recipArea; 172 | this.area = (float)area; 173 | this.MAC = (float)MAC; 174 | this.b_2 = (float)b_2; 175 | } 176 | 177 | public void InitClone(FARVesselCache vessel) 178 | { 179 | totalMass = vessel.totalMass; 180 | dryMass = vessel.dryMass; 181 | CoM = vessel.CoM; 182 | CoM_dry = vessel.CoM_dry; 183 | maxCrossSectionFromBody = vessel.maxCrossSectionFromBody; 184 | bodyLength = vessel.bodyLength; 185 | area = vessel.area; 186 | MAC = vessel.MAC; 187 | b_2 = vessel.b_2; 188 | 189 | engines.Clear(); 190 | foreach (VesselCache.SimulatedEngine engine in vessel.engines) 191 | engines.Add(VesselCache.SimulatedEngine.BorrowClone(engine, null)); 192 | 193 | if (parent == null || !ReferenceEquals(GetRootParent(), vessel.GetRootParent())) 194 | { 195 | _wingAerodynamicModel = FARCloneAssist.CloneListFARWingAerodynamicModelsSafe(vessel._wingAerodynamicModel); 196 | _currentAeroSections = FARCloneAssist.CloneListFARAeroSectionsSafe(vessel._currentAeroSections); 197 | } 198 | parent = vessel; 199 | } 200 | 201 | private FARVesselCache GetRootParent() 202 | { 203 | FARVesselCache rootParent = this; 204 | while (rootParent.parent != null) 205 | rootParent = rootParent.parent; 206 | return rootParent; 207 | } 208 | 209 | private void SetAerodynamicModelVels(Vector3d inflow) 210 | { 211 | foreach (FARWingAerodynamicModelWrapper aeroModel in _wingAerodynamicModel) 212 | aeroModel.Vel = inflow; 213 | } 214 | 215 | public override void GetAeroCombined(Conditions conditions, float AoA, float pitchInput, out Vector3 forces, out Vector3 torques, bool dryTorque = false) 216 | { 217 | GetClCdCmSteady(conditions, InflowVect(AoA), pitchInput, dryTorque ? CoM_dry : CoM, out forces, out torques); 218 | 219 | //float Q = 0.0005f * conditions.atmDensity * conditions.speed * conditions.speed; 220 | //torques *= Q; 221 | //forces *= Q; 222 | } 223 | 224 | public override Vector3 GetAeroForce(Conditions conditions, float AoA, float pitchInput = 0) 225 | { 226 | GetClCdCmSteady(conditions, InflowVect(AoA), pitchInput, Vector3.zero, out Vector3 forces, out _); 227 | 228 | //float Q = 0.0005f * conditions.atmDensity * conditions.speed * conditions.speed; 229 | return forces;// * Q; 230 | } 231 | 232 | public override Vector3 GetAeroTorque(Conditions conditions, float AoA, float pitchInput = 0, bool dryTorque = false) 233 | { 234 | GetClCdCmSteady(conditions, InflowVect(AoA), pitchInput, dryTorque ? CoM_dry : CoM, out _, out Vector3 torques); 235 | 236 | //float Q = 0.0005f * conditions.atmDensity * conditions.speed * conditions.speed; 237 | return torques;// * Q; 238 | } 239 | 240 | // TODO: Add ITorqueProvider and thrust effect on torque 241 | public override float GetAoA(Conditions conditions, float offsettingForce, bool useThrust = true, bool dryTorque = false, float guess = float.NaN, float pitchInputGuess = float.NaN, bool lockPitchInput = false, float tolerance = 0.0003F) 242 | { 243 | Vector3 thrustForce = useThrust ? this.GetThrustForce(conditions) : Vector3.zero; 244 | 245 | if (!accountForControls) 246 | return base.GetAoA(conditions, offsettingForce, useThrust, dryTorque, guess, 0, true); 247 | if (lockPitchInput) 248 | return base.GetAoA(conditions, offsettingForce, useThrust, dryTorque, guess, pitchInputGuess, lockPitchInput); 249 | 250 | float approxAoA = GetAoA(conditions, offsettingForce, useThrust, dryTorque, guess, pitchInputGuess, true, 1 * Mathf.Deg2Rad); 251 | return base.GetAoA(conditions, offsettingForce, useThrust, dryTorque, approxAoA, pitchInputGuess, lockPitchInput); 252 | } 253 | 254 | public override float GetFuelBurnRate(Conditions conditions, float AoA) 255 | { 256 | float burnRate = 0; 257 | for (int i = engines.Count - 1; i >= 0; i--) 258 | { 259 | burnRate += engines[i].GetFuelBurnRate(conditions.mach, conditions.atmDensity); 260 | } 261 | return burnRate; 262 | } 263 | 264 | public override float GetPitchInput(Conditions conditions, float AoA, bool dryTorque = false, float guess = float.NaN, float tolerance = 0.0003F) 265 | { 266 | Accord.Math.Optimization.BrentSearch solver = new Accord.Math.Optimization.BrentSearch((input) => this.GetAeroTorque(conditions, AoA, (float)input, dryTorque).x, -0.3, 0.3, 0.0001); 267 | if (solver.FindRoot()) 268 | return (float)solver.Solution; 269 | solver.LowerBound = -1; 270 | solver.UpperBound = 1; 271 | if (solver.FindRoot()) 272 | return (float)solver.Solution; 273 | if (this.GetAeroTorque(conditions, AoA, 0, dryTorque).x > 0) 274 | return -1; 275 | else 276 | return 1; 277 | } 278 | 279 | public override Vector3 GetThrustForce(Conditions conditions, float AoA) 280 | { 281 | Vector3 thrust = Vector3.zero; 282 | for (int i = engines.Count - 1; i >= 0; i--) 283 | { 284 | thrust += engines[i].GetThrust(conditions.mach, conditions.atmDensity, conditions.atmPressure, conditions.oxygenAvailable); 285 | } 286 | return thrust; 287 | } 288 | } 289 | } --------------------------------------------------------------------------------