From 0bdc942855e064f526e39db3260474da7dbe2fdb Mon Sep 17 00:00:00 2001 From: asarium Date: Thu, 25 Jul 2013 12:38:09 +0200 Subject: [PATCH 1/2] Quick fix for 0.21 compatibility. It looks like CelestialBody.GetRelativeSurfacePosition is broken and returns wrong values. --- PAPIPlugin/PAPIAddon.cs | 4 ++-- PAPIPlugin/PAPIArray.cs | 15 +++++++++------ PAPIPlugin/PAPIPlugin.csproj | 10 ++++++---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/PAPIPlugin/PAPIAddon.cs b/PAPIPlugin/PAPIAddon.cs index 2cc30e0..29bfe94 100644 --- a/PAPIPlugin/PAPIAddon.cs +++ b/PAPIPlugin/PAPIAddon.cs @@ -15,8 +15,8 @@ public void Awake() { _arrayManager = new LightArrayManager(); - _arrayManager.AddArray(new PAPIArray(-0.0468, -74.688889, 78, 270)); - _arrayManager.AddArray(new PAPIArray(-0.036, -74.52763, 78, 90)); + _arrayManager.AddArray(new PAPIArray(-0.0468, -74.708889, 73, 270)); + _arrayManager.AddArray(new PAPIArray(-0.036, -74.50963, 73, 90)); } public void Update() diff --git a/PAPIPlugin/PAPIArray.cs b/PAPIPlugin/PAPIArray.cs index 30b6861..cb985be 100644 --- a/PAPIPlugin/PAPIArray.cs +++ b/PAPIPlugin/PAPIArray.cs @@ -26,6 +26,7 @@ public class PAPIArray : ILightArray private readonly CelestialBody _kerbinBody; private GameObject _papiGameObject; + private GameObject[] _partObjects; private Vector3d _relativeSurfacePosition; @@ -44,15 +45,15 @@ public PAPIArray(double lat, double lon, double alt, double heading) /// The latitude /// The longitude /// The altitude - /// The heading. + /// The heading in radians. private void InitializePAPIParts(double lat, double lon, double alt, double heading) { - _relativeSurfacePosition = _kerbinBody.GetRelSurfacePosition(lat, lon, alt); + _relativeSurfacePosition = _kerbinBody.transform.InverseTransformPoint(_kerbinBody.GetWorldSurfacePosition(lat, lon, alt)); - var surfaceNormal = _kerbinBody.GetRelSurfaceNVector(lat, lon); + var surfaceNormal = _kerbinBody.transform.InverseTransformDirection(_kerbinBody.GetSurfaceNVector(lat, lon)); var pqs = _kerbinBody.pqsController; - var north = Vector3.up * (float) pqs.radius; // We are in local space to up * radius is the north pole + var north = Vector3.up * (float) pqs.radius; // We are in local space so up * radius is the north pole var directionToNorth = (north - surfaceNormal).normalized; @@ -62,8 +63,10 @@ private void InitializePAPIParts(double lat, double lon, double alt, double head var headingVector = orthogonalNorthDir * Math.Cos(heading) + anotherVector * Math.Sin(heading); + Util.LogWarning(_relativeSurfacePosition.ToString()); + _papiGameObject = new GameObject(); - _papiGameObject.transform.parent = pqs.transform; + _papiGameObject.transform.parent = _kerbinBody.transform; _papiGameObject.transform.localPosition = _relativeSurfacePosition; _papiGameObject.transform.localRotation = Quaternion.LookRotation(headingVector, surfaceNormal); @@ -99,7 +102,7 @@ private static LineRenderer AddPAPIPart(GameObject obj) lineRenderer.SetWidth(PAPILightRadius, PAPILightRadius); lineRenderer.SetVertexCount(2); lineRenderer.SetPosition(0, Vector3.zero); - lineRenderer.SetPosition(1, Vector3.forward * PAPILightRadius); + lineRenderer.SetPosition(1, Vector3.up * PAPILightRadius); return lineRenderer; } diff --git a/PAPIPlugin/PAPIPlugin.csproj b/PAPIPlugin/PAPIPlugin.csproj index cdf4603..81c2fb7 100644 --- a/PAPIPlugin/PAPIPlugin.csproj +++ b/PAPIPlugin/PAPIPlugin.csproj @@ -31,13 +31,15 @@ 4 - - G:\Programme\KSP\0.20\KSP_Data\Managed\Assembly-CSharp.dll + + False + G:\Programme\KSP\0.21\KSP_Data\Managed\Assembly-CSharp.dll - - G:\Programme\KSP\0.20\KSP_Data\Managed\UnityEngine.dll + + False + G:\Programme\KSP\0.21\KSP_Data\Managed\UnityEngine.dll From c0ca5c40fb44a9f1e46f554afc83a5c665f6d022 Mon Sep 17 00:00:00 2001 From: asarium Date: Mon, 29 Jul 2013 11:35:12 +0200 Subject: [PATCH 2/2] Added modular light arrays. Now it is possible to configre light arrays in a config file and by creating an implementation of ILightArray new lights can be easily added. --- .gitignore | 3 + PAPIPlugin/Arrays/AbstractLightArray.cs | 71 +++++ PAPIPlugin/Arrays/PAPIArray.cs | 262 ++++++++++++++++++ PAPIPlugin/ILightArray.cs | 7 - PAPIPlugin/Impl/DefaultLightArrayConfig.cs | 72 +++++ PAPIPlugin/Impl/DefaultLightArrayManager.cs | 95 +++++++ PAPIPlugin/Impl/DefaultLightGroup.cs | 226 +++++++++++++++ PAPIPlugin/Impl/PAPITypeManager.cs | 34 +++ PAPIPlugin/Interfaces/ILightArray.cs | 54 ++++ PAPIPlugin/Interfaces/ILightArrayConfig.cs | 37 +++ PAPIPlugin/Interfaces/ILightArrayManager.cs | 19 ++ PAPIPlugin/Interfaces/ILightGroup.cs | 22 ++ PAPIPlugin/Interfaces/ILightTypeManager.cs | 7 + .../Internal/CelestialBodyExtensions.cs | 20 ++ PAPIPlugin/Internal/ConfigNodeExtensions.cs | 59 ++++ PAPIPlugin/Internal/ExtensionMethods.cs | 38 +++ PAPIPlugin/{ => Internal}/Util.cs | 5 +- PAPIPlugin/LightArrayEventArguments.cs | 15 + PAPIPlugin/LightArrayManager.cs | 30 -- PAPIPlugin/PAPIAddon.cs | 48 +++- PAPIPlugin/PAPIArray.cs | 165 ----------- PAPIPlugin/PAPIPlugin.csproj | 43 ++- assets/PluginData/PAPIPlugin/resize.png | Bin 0 -> 2909 bytes assets/lights.cfg | 27 ++ 24 files changed, 1143 insertions(+), 216 deletions(-) create mode 100644 PAPIPlugin/Arrays/AbstractLightArray.cs create mode 100644 PAPIPlugin/Arrays/PAPIArray.cs delete mode 100644 PAPIPlugin/ILightArray.cs create mode 100644 PAPIPlugin/Impl/DefaultLightArrayConfig.cs create mode 100644 PAPIPlugin/Impl/DefaultLightArrayManager.cs create mode 100644 PAPIPlugin/Impl/DefaultLightGroup.cs create mode 100644 PAPIPlugin/Impl/PAPITypeManager.cs create mode 100644 PAPIPlugin/Interfaces/ILightArray.cs create mode 100644 PAPIPlugin/Interfaces/ILightArrayConfig.cs create mode 100644 PAPIPlugin/Interfaces/ILightArrayManager.cs create mode 100644 PAPIPlugin/Interfaces/ILightGroup.cs create mode 100644 PAPIPlugin/Interfaces/ILightTypeManager.cs create mode 100644 PAPIPlugin/Internal/CelestialBodyExtensions.cs create mode 100644 PAPIPlugin/Internal/ConfigNodeExtensions.cs create mode 100644 PAPIPlugin/Internal/ExtensionMethods.cs rename PAPIPlugin/{ => Internal}/Util.cs (82%) create mode 100644 PAPIPlugin/LightArrayEventArguments.cs delete mode 100644 PAPIPlugin/LightArrayManager.cs delete mode 100644 PAPIPlugin/PAPIArray.cs create mode 100644 assets/PluginData/PAPIPlugin/resize.png create mode 100644 assets/lights.cfg diff --git a/.gitignore b/.gitignore index bdc3535..e7194cf 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ Generated_Code #added for RIA/Silverlight projects _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML + +### Plugin specific ignores ### +assets/Plugins/ \ No newline at end of file diff --git a/PAPIPlugin/Arrays/AbstractLightArray.cs b/PAPIPlugin/Arrays/AbstractLightArray.cs new file mode 100644 index 0000000..9fd5fec --- /dev/null +++ b/PAPIPlugin/Arrays/AbstractLightArray.cs @@ -0,0 +1,71 @@ +#region Usings + +using System; +using PAPIPlugin.Interfaces; + +#endregion + +namespace PAPIPlugin.Arrays +{ + public abstract class AbstractLightArray : ILightArray + { + private bool _enabled; + + protected ILightGroup ParentGroup { get; private set; } + + protected ILightArrayManager ParentManager { get; private set; } + + protected AbstractLightArray() + { + Enabled = true; + } + + #region ILightArray Members + + public bool Enabled + { + get { return _enabled; } + set + { + if (Equals(_enabled, value)) + { + return; + } + + _enabled = value; + + OnEnabledChanged(); + } + } + + public virtual void Initialize(ILightGroup group) + { + ParentGroup = group; + } + + public abstract void Update(); + + public virtual void Destroy() + { + ParentManager = null; + } + + public virtual void InitializeDisplay(ILightArrayManager arrayManager) + { + ParentManager = arrayManager; + } + + #endregion + + protected event EventHandler EnabledChanged; + + protected virtual void OnEnabledChanged() + { + var handler = EnabledChanged; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + } +} diff --git a/PAPIPlugin/Arrays/PAPIArray.cs b/PAPIPlugin/Arrays/PAPIArray.cs new file mode 100644 index 0000000..6cab74c --- /dev/null +++ b/PAPIPlugin/Arrays/PAPIArray.cs @@ -0,0 +1,262 @@ +#region Usings + +using System; +using PAPIPlugin.Impl; +using PAPIPlugin.Interfaces; +using PAPIPlugin.Internal; +using UnityEngine; +using Object = UnityEngine.Object; + +#endregion + +namespace PAPIPlugin.Arrays +{ + public class PAPIArray : AbstractLightArray, IConfigNode + { + private const int PAPIPartCount = 4; + + private const float PAPILightRadius = 10.0f; + + private const double DefaultTargetGlidePath = 6; + + /// + /// If the difference of the gliepath from the target is more than this the whole array will show either red or white. + /// + private const double DefaultBadGlidepathVariance = 1.5; + + private static readonly Vector3 PAPILightDifference = Vector3.right * PAPILightRadius * 1.5f; + + private GameObject _papiGameObject; + + private GameObject[] _partObjects; + + private Vector3d _relativeSurfacePosition; + + public PAPIArray() + { + TargetGlidePath = DefaultTargetGlidePath; + BadGlidepathVariance = DefaultBadGlidepathVariance; + + EnabledChanged += (sender, args) => + { + if (Enabled) + { + return; + } + + foreach (var partObject in _partObjects) + { + partObject.SetActive(false); + } + }; + } + + public double BadGlidepathVariance { get; set; } + + public double TargetGlidePath { get; set; } + + public double Longitude { get; set; } + + public double Latitude { get; set; } + + public double Heading { get; set; } + + #region IConfigNode Members + + public void Load(ConfigNode node) + { + BadGlidepathVariance = node.ConvertValue("BadGlidepath", DefaultBadGlidepathVariance); + TargetGlidePath = node.ConvertValue("TargetGlidepath", DefaultTargetGlidePath); + + try + { + Longitude = node.ConvertValueWithException("Longitude").ClampAndLog(-180, 180); + Latitude = node.ConvertValueWithException("Latitude").ClampAndLog(-90, 90); + + var headingDeg = node.ConvertValueWithException("Heading").ClampAndLog(0, 360); + Heading = (headingDeg / 180) * Math.PI; + } + catch (FormatException e) + { + Util.LogWarning(e.Message); + } + } + + public void Save(ConfigNode node) + { + throw new NotImplementedException(); + } + + #endregion + + public override void Destroy() + { + foreach (var partObject in _partObjects) + { + Object.Destroy(partObject); + } + + Object.Destroy(_papiGameObject); + + base.Destroy(); + } + + public override void Update() + { + if (!Enabled) + { + return; + } + + var currentCamera = Camera.main; + + var relativePosition = _papiGameObject.transform.InverseTransformPoint(currentCamera.transform.position); + + var normalizedPosition = relativePosition.normalized; + // As the local normal is (0, 1, 0), y is the result of normal * normalizedPosition. + var normalDot = normalizedPosition.y; + + var directionDot = normalizedPosition.z; + + var angle = 90 - Math.Acos(normalDot) * (180 / Math.PI); + + var difference = angle - TargetGlidePath; + + for (var i = 0; i < PAPIPartCount; i++) + { + if (directionDot <= 0) + { + _partObjects[i].SetActive(false); + } + else + { + _partObjects[i].SetActive(true); + + // Use the direction dot for alpha to fade the lights out + UpdatePAPIPart(i, difference, directionDot); + } + } + } + + public override void InitializeDisplay(ILightArrayManager arrayManager) + { + base.InitializeDisplay(arrayManager); + + InitializePAPIParts(Latitude, Longitude, Heading); + } + + public override void Initialize(ILightGroup @group) + { + base.Initialize(@group); + + @group.GetOrAddTypeManager(); + } + + /// + /// Initializes the whole array at the given latitude and longitude with the given altitude. The heading is the + /// direction to array looks to and should be in the range [0, 2 * PI). + /// + /// The latitude + /// The longitude + /// The heading in radians. + private void InitializePAPIParts(double lat, double lon, double heading) + { + var parentBody = ParentGroup.ParentBody; + + var pqs = parentBody.pqsController; + + var surfaceNormal = parentBody.transform.InverseTransformDirection(parentBody.GetSurfaceNVector(lat, lon)); + var zeroAltSurface = pqs.transform.InverseTransformPoint(parentBody.GetWorldSurfacePosition(lat, lon, 0)); + + var north = Vector3.up * (float) pqs.radius; // We are in local space so up * radius is the north pole + + var directionToNorth = (north - zeroAltSurface).normalized; + + var orthogonalNorthDir = Orthonormalise(directionToNorth, surfaceNormal); + + var anotherVector = Vector3d.Cross(surfaceNormal, orthogonalNorthDir); + + var headingVector = orthogonalNorthDir * Math.Cos(heading) + anotherVector * Math.Sin(heading); + + _papiGameObject = new GameObject(); + _papiGameObject.transform.parent = parentBody.transform; + _papiGameObject.transform.localPosition = zeroAltSurface; + _papiGameObject.transform.localRotation = Quaternion.LookRotation(headingVector, surfaceNormal); + + var maxHeight = double.MinValue; + _partObjects = new GameObject[PAPIPartCount]; + for (var i = 0; i < PAPIPartCount; i++) + { + var obj = new GameObject(); + + AddPAPIPart(obj); + + obj.transform.parent = _papiGameObject.transform; + obj.transform.localPosition = (i - (PAPIPartCount / 2)) * PAPILightDifference; + + maxHeight = Math.Max(maxHeight, parentBody.GetSurfaceHeight(Latitude, Longitude)); + + _partObjects[i] = obj; + } + + maxHeight = Math.Max(0, maxHeight); + _relativeSurfacePosition = + parentBody.transform.InverseTransformPoint(parentBody.GetWorldSurfacePosition(lat, lon, maxHeight + PAPILightRadius * 0.5)); + _papiGameObject.transform.localPosition = _relativeSurfacePosition; + } + + private static Vector3d Orthonormalise(Vector3d direction, Vector3d firstVector) + { + // This is basically the first step of a Gram–Schmidt process + // See http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process + + return direction - Vector3d.Dot(firstVector, direction) * firstVector; + } + + private static void AddPAPIPart(GameObject obj) + { + var lineRenderer = obj.AddComponent(); + + lineRenderer.useWorldSpace = false; + lineRenderer.transform.parent = obj.transform; + lineRenderer.transform.localPosition = Vector3.zero; + lineRenderer.transform.eulerAngles = Vector3.zero; + + lineRenderer.material = new Material(Shader.Find("Particles/Additive")); + lineRenderer.SetColors(Color.red, Color.red); + lineRenderer.SetWidth(PAPILightRadius, PAPILightRadius); + lineRenderer.SetVertexCount(2); + lineRenderer.SetPosition(0, Vector3.zero); + lineRenderer.SetPosition(1, Vector3.up * PAPILightRadius); + } + + private void UpdatePAPIPart(int index, double difference, float alpha) + { + var gameObj = _partObjects[index]; + + var lineRenderer = gameObj.GetComponent(); + + var color = GetArrayPartColor(index, difference); + color.a = alpha; + lineRenderer.SetColors(color, color); + } + + private Color GetArrayPartColor(int index, double difference) + { + if (difference < -BadGlidepathVariance) + { + return Color.red; + } + if (difference > BadGlidepathVariance) + { + return Color.white; + } + + // This should map temp into [-1, 1] + double temp = index - (PAPIPartCount / 2); + temp = temp / (PAPIPartCount / 2); + + return temp > difference ? Color.red : Color.white; + } + } +} diff --git a/PAPIPlugin/ILightArray.cs b/PAPIPlugin/ILightArray.cs deleted file mode 100644 index ba5e689..0000000 --- a/PAPIPlugin/ILightArray.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PAPIPlugin -{ - public interface ILightArray - { - void Update(); - } -} \ No newline at end of file diff --git a/PAPIPlugin/Impl/DefaultLightArrayConfig.cs b/PAPIPlugin/Impl/DefaultLightArrayConfig.cs new file mode 100644 index 0000000..cc43a3d --- /dev/null +++ b/PAPIPlugin/Impl/DefaultLightArrayConfig.cs @@ -0,0 +1,72 @@ +#region Usings + +using System.Collections.Generic; +using System.Linq; +using PAPIPlugin.Interfaces; +using PAPIPlugin.Internal; + +#endregion + +namespace PAPIPlugin.Impl +{ + public class DefaultLightArrayConfig : ILightArrayConfig + { + private const string LightGroupNodeName = "LightGroup"; + + private const string LightConfigNodeName = "LightConfig"; + + private readonly ICollection _lightGroups = new List(); + + #region ILightArrayConfig Members + + public IEnumerable LightArrayGroups + { + get { return _lightGroups; } + } + + public bool DebugMode { get; set; } + + public void Destroy() + { + foreach (var lightArray in _lightGroups.SelectMany(group => group.LightArrays)) + { + lightArray.Destroy(); + } + } + + #endregion + + public void LoadConfig() + { + foreach (var configNode in GameDatabase.Instance.GetConfigNodes(LightGroupNodeName)) + { + var lightGroup = CreateLightGroup(); + + var success = ConfigNode.LoadObjectFromConfig(lightGroup, configNode); + + if (!success) + { + continue; + } + + var node = lightGroup as IConfigNode; + if (node != null) + { + node.Load(configNode); + } + + _lightGroups.Add(lightGroup); + } + + foreach (var configNode in GameDatabase.Instance.GetConfigNodes(LightConfigNodeName)) + { + DebugMode = configNode.ConvertValue("Debug", false); + } + } + + protected virtual ILightGroup CreateLightGroup() + { + return new DefaultLightGroup(); + } + } +} diff --git a/PAPIPlugin/Impl/DefaultLightArrayManager.cs b/PAPIPlugin/Impl/DefaultLightArrayManager.cs new file mode 100644 index 0000000..76c3498 --- /dev/null +++ b/PAPIPlugin/Impl/DefaultLightArrayManager.cs @@ -0,0 +1,95 @@ +#region Usings + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using PAPIPlugin.Interfaces; +using PAPIPlugin.Internal; + +#endregion + +namespace PAPIPlugin.Impl +{ + public class DefaultLightArrayManager : ILightArrayManager + { + private ILightArrayConfig _lightConfig; + + #region ILightArrayManager Members + + public event EventHandler ParsingFinished; + + public ILightArrayConfig LightConfig + { + get { return _lightConfig; } + set + { + if (Equals(_lightConfig, value)) + return; + + _lightConfig = value; + + InitializeConfig(_lightConfig); + } + } + + private void InitializeConfig(ILightArrayConfig lightConfig) + { + foreach (var lightArray in lightConfig.LightArrayGroups.SelectMany(group => group.LightArrays)) + { + lightArray.InitializeDisplay(this); + } + } + + public void LoadConfig() + { + Util.LogInfo("Starting to parse light definitions..."); + + var stopwatch = Stopwatch.StartNew(); + + var defaultConfig = new DefaultLightArrayConfig(); + defaultConfig.LoadConfig(); + + LightConfig = defaultConfig; + + Util.LogInfo(string.Format("Finished parsing definitions. Found {0} light groups with a total of {1} light arrays in a time of {2}.", + LightConfig.LightArrayGroups.Count(), LightConfig.LightArrayGroups.Sum(group => group.LightArrays.Count()), stopwatch.Elapsed)); + + OnParsingFinished(); + } + + public void Update() + { + foreach (var lightGroup in LightConfig.LightArrayGroups) + { + lightGroup.Update(); + } + } + + public void Dispose() + { + Dispose(true); + } + + #endregion + + protected virtual void OnParsingFinished() + { + var handler = ParsingFinished; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + ~DefaultLightArrayManager() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + LightConfig.Destroy(); + } + } +} diff --git a/PAPIPlugin/Impl/DefaultLightGroup.cs b/PAPIPlugin/Impl/DefaultLightGroup.cs new file mode 100644 index 0000000..9177568 --- /dev/null +++ b/PAPIPlugin/Impl/DefaultLightGroup.cs @@ -0,0 +1,226 @@ +#region Usings + +using System; +using System.Collections.Generic; +using System.Linq; +using PAPIPlugin.Interfaces; +using PAPIPlugin.Internal; + +#endregion + +namespace PAPIPlugin.Impl +{ + public class DefaultLightGroup : ILightGroup, IConfigNode + { + private const string LightNodeName = "LightArray"; + + private readonly ICollection _lightArrays = new List(); + + private readonly IDictionary _managers = new Dictionary(); + + #region IConfigNode Members + + public void Load(ConfigNode node) + { + var name = node.GetValue("Name"); + + if (string.IsNullOrEmpty(name)) + { + Util.LogWarning("Name value not found in config!"); + return; + } + + Name = name; + + var bodyName = node.GetValue("Body"); + + if (string.IsNullOrEmpty(bodyName)) + { + Util.LogWarning(string.Format("The parent body value of light group {0} is missing!", Name)); + return; + } + + ParentBody = FlightGlobals.Bodies.FirstOrDefault(body => body.name == bodyName); + + if (ParentBody == null) + { + Util.LogWarning(string.Format("The parent body {0} of light group {1} could not be found.", bodyName, Name)); + return; + } + + Util.LogInfo(string.Format("Found light group {0} on body {1}.", Name, ParentBody.name)); + + var configNodes = node.GetNodes(LightNodeName); + + foreach (var configNode in configNodes) + { + var arrayType = GetArrayType(configNode); + + if (arrayType == null) + { + continue; + } + + Util.LogInfo(string.Format("Found array of type {0}.", arrayType.FullName)); + + var arrayObject = Activator.CreateInstance(arrayType); + + if (!(arrayObject is ILightArray)) + { + Util.LogWarning(string.Format("The type {0} is no light array!", arrayType)); + continue; + } + + var success = ConfigNode.LoadObjectFromConfig(arrayObject, configNode); + + if (success) + { + var lightArray = arrayObject as ILightArray; + + var asConfigNode = lightArray as IConfigNode; + if (asConfigNode != null) + { + asConfigNode.Load(configNode); + } + + AddArray(lightArray); + + lightArray.Initialize(this); + } + } + } + + public void Save(ConfigNode node) + { + throw new NotSupportedException(); + } + + #endregion + + #region ILightGroup Members + + public event EventHandler LightArrayAdded; + + public string Name { get; private set; } + + public CelestialBody ParentBody { get; private set; } + + public IEnumerable LightArrays + { + get { return _lightArrays; } + } + + public void Update() + { + foreach (var lightArray in _lightArrays) + { + lightArray.Update(); + } + } + + public T GetOrAddTypeManager() where T : ILightTypeManager, new() + { + if (_managers.ContainsKey(typeof(T))) + { + return (T) _managers[typeof(T)]; + } + + var newManager = new T(); + + _managers.Add(typeof(T), newManager); + + newManager.Initialize(this); + + return newManager; + } + + public T GetOrAddTypeManager(Func creatorFunc) where T : ILightTypeManager + { + if (_managers.ContainsKey(typeof(T))) + { + return (T) _managers[typeof(T)]; + } + + var newManager = creatorFunc(); + + _managers.Add(typeof(T), newManager); + + newManager.Initialize(this); + + return newManager; + } + + public void Destroy() + { + foreach (var lightArray in _lightArrays) + { + lightArray.Destroy(); + } + } + + #endregion + + protected virtual void OnLightArrayAdded(LightArrayEventArguments e) + { + var handler = LightArrayAdded; + if (handler != null) + { + handler(this, e); + } + } + + public void AddArray(ILightArray array) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + _lightArrays.Add(array); + + OnLightArrayAdded(new LightArrayEventArguments(array)); + } + + private static Type GetArrayType(ConfigNode configNode) + { + var typeName = configNode.GetValue("Type"); + var namespaceName = configNode.GetValue("Namespace"); + + if (string.IsNullOrEmpty(typeName)) + { + Util.LogWarning("Type name is required for light array definition!"); + return null; + } + + Type type; + + if (string.IsNullOrEmpty(namespaceName)) + { + type = AssemblyLoader.loadedAssemblies.SelectMany(asm => asm.assembly.GetTypes()).FirstOrDefault(t => t.Name == typeName); + + if (type == null) + { + Util.LogWarning(string.Format("Type name \"{0}\" is not a known type.", typeName)); + } + } + else + { + type = + AssemblyLoader.loadedAssemblies.SelectMany(asm => asm.assembly.GetTypes()) + .FirstOrDefault(t => t.Namespace == namespaceName && t.Name == typeName); + + if (type == null) + { + Util.LogWarning(string.Format("Type name \"{0}.{1}\" is not a known type.", namespaceName, typeName)); + } + } + + return type; + } + + ~DefaultLightGroup() + { + Destroy(); + } + } +} diff --git a/PAPIPlugin/Impl/PAPITypeManager.cs b/PAPIPlugin/Impl/PAPITypeManager.cs new file mode 100644 index 0000000..45f9a74 --- /dev/null +++ b/PAPIPlugin/Impl/PAPITypeManager.cs @@ -0,0 +1,34 @@ +#region Usings + +using System.Collections.Generic; +using System.Linq; +using PAPIPlugin.Arrays; +using PAPIPlugin.Interfaces; + +#endregion + +namespace PAPIPlugin.Impl +{ + public class PAPITypeManager : ILightTypeManager + { + private readonly IList _papiArrays = new List(); + + #region ILightTypeManager Members + + public void Initialize(ILightGroup group) + { + foreach (var lightArray in group.LightArrays.Where(array => array is PAPIArray)) + { + _papiArrays.Add(lightArray); + } + + group.LightArrayAdded += (sender, arguments) => + { + if (arguments.Array is PAPIArray) + _papiArrays.Add(arguments.Array); + }; + } + + #endregion + } +} diff --git a/PAPIPlugin/Interfaces/ILightArray.cs b/PAPIPlugin/Interfaces/ILightArray.cs new file mode 100644 index 0000000..6c33b93 --- /dev/null +++ b/PAPIPlugin/Interfaces/ILightArray.cs @@ -0,0 +1,54 @@ +namespace PAPIPlugin.Interfaces +{ + /// + /// Represents an abstract object which contains some lights of unspecified function. This object will live as long as + /// the game is running, . + /// + public interface ILightArray + { + /// + /// Gets or sets the enabled status of the array. + /// + /// + /// Implementing classes should use this to reduce the performence impact + /// when the array is disabled. + /// + bool Enabled { get; set; } + + /// + /// Initializes the array with the given parent group. + /// + /// + /// This function called when the parent group gets created and initialized it will only be called once for a given + /// light array. + /// + /// The group that contains this array. + void Initialize(ILightGroup group); + + /// + /// Called each frame to update the display of the array. + /// + void Update(); + + /// + /// Destroyes the resources used by the light array. + /// + /// + /// Upon invocation the implementation should release any resources that were created in the invocation of + /// and return to a state in which can be called + /// again. + /// + void Destroy(); + + /// + /// Initializes the display within the given array manager. + /// + /// + /// The implementation should use this function to initialize objects and resources which are used for displaying the + /// light array. This function may be called multiple times but only when has been called + /// before. + /// + /// The array manager used for this display. + void InitializeDisplay(ILightArrayManager arrayManager); + } +} diff --git a/PAPIPlugin/Interfaces/ILightArrayConfig.cs b/PAPIPlugin/Interfaces/ILightArrayConfig.cs new file mode 100644 index 0000000..8d6fbc2 --- /dev/null +++ b/PAPIPlugin/Interfaces/ILightArrayConfig.cs @@ -0,0 +1,37 @@ +#region Usings + +using System.Collections.Generic; + +#endregion + +namespace PAPIPlugin.Interfaces +{ + /// + /// Represents a configuration of light arrays. This is used to group all known instances + /// together. + /// + /// + /// It is up to the implementation to actually chose a way of creating the light groups. + /// + public interface ILightArrayConfig + { + /// + /// The light groups in this config object. + /// + IEnumerable LightArrayGroups { get; } + + /// + /// Gets or sets the debug mode of the config object. + /// + bool DebugMode { get; set; } + + /// + /// Releases the resources of the underlying instances. + /// + /// + /// The implementation should only call for all contained light arrays it may not + /// change the state of the config object as it isn't initialized sperately later. + /// + void Destroy(); + } +} diff --git a/PAPIPlugin/Interfaces/ILightArrayManager.cs b/PAPIPlugin/Interfaces/ILightArrayManager.cs new file mode 100644 index 0000000..fc397a0 --- /dev/null +++ b/PAPIPlugin/Interfaces/ILightArrayManager.cs @@ -0,0 +1,19 @@ +#region Usings + +using System; + +#endregion + +namespace PAPIPlugin.Interfaces +{ + public interface ILightArrayManager : IDisposable + { + event EventHandler ParsingFinished; + + ILightArrayConfig LightConfig { get; set; } + + void LoadConfig(); + + void Update(); + } +} diff --git a/PAPIPlugin/Interfaces/ILightGroup.cs b/PAPIPlugin/Interfaces/ILightGroup.cs new file mode 100644 index 0000000..c3e6f93 --- /dev/null +++ b/PAPIPlugin/Interfaces/ILightGroup.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace PAPIPlugin.Interfaces +{ + public interface ILightGroup + { + event EventHandler LightArrayAdded; + + string Name { get; } + + CelestialBody ParentBody { get; } + + IEnumerable LightArrays { get; } + + void Update(); + + T GetOrAddTypeManager() where T : ILightTypeManager, new(); + + T GetOrAddTypeManager(Func creatorFunc) where T : ILightTypeManager; + } +} \ No newline at end of file diff --git a/PAPIPlugin/Interfaces/ILightTypeManager.cs b/PAPIPlugin/Interfaces/ILightTypeManager.cs new file mode 100644 index 0000000..f64df82 --- /dev/null +++ b/PAPIPlugin/Interfaces/ILightTypeManager.cs @@ -0,0 +1,7 @@ +namespace PAPIPlugin.Interfaces +{ + public interface ILightTypeManager + { + void Initialize(ILightGroup manager); + } +} diff --git a/PAPIPlugin/Internal/CelestialBodyExtensions.cs b/PAPIPlugin/Internal/CelestialBodyExtensions.cs new file mode 100644 index 0000000..d54bfe8 --- /dev/null +++ b/PAPIPlugin/Internal/CelestialBodyExtensions.cs @@ -0,0 +1,20 @@ +#region Usings + +using UnityEngine; + +#endregion + +namespace PAPIPlugin.Internal +{ + public static class CelestialBodyExtensions + { + public static double GetSurfaceHeight(this CelestialBody body, double latitude, double longitude) + { + var height = + body.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * + QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right); + + return height - body.Radius; + } + } +} diff --git a/PAPIPlugin/Internal/ConfigNodeExtensions.cs b/PAPIPlugin/Internal/ConfigNodeExtensions.cs new file mode 100644 index 0000000..fd06fc4 --- /dev/null +++ b/PAPIPlugin/Internal/ConfigNodeExtensions.cs @@ -0,0 +1,59 @@ +#region Usings + +using System; +using System.ComponentModel; + +#endregion + +namespace PAPIPlugin.Internal +{ + public static class ConfigNodeExtensions + { + public static T ConvertValue(this ConfigNode node, string key, T def = default(T)) + { + if (!node.HasValue(key)) + { + return def; + } + + var stringValue = node.GetValue(key); + + var typeConverter = TypeDescriptor.GetConverter(typeof(T)); + + try + { + return (T) typeConverter.ConvertFromInvariantString(stringValue); + } + catch (NotSupportedException) + { + Util.LogWarning(string.Format("Cannot convert value \"{0}\" to type {1}", stringValue, typeof(T).FullName)); + + return def; + } + } + + public static T ConvertValueWithException(this ConfigNode node, string key) + { + if (!node.HasValue(key)) + { + throw new FormatException(string.Format("The key \"{0}\" could not be found.", key)); + } + + var stringValue = node.GetValue(key); + + var typeConverter = TypeDescriptor.GetConverter(typeof(T)); + + try + { + return (T) typeConverter.ConvertFromInvariantString(stringValue); + } + catch (NotSupportedException) + { + Util.LogWarning(string.Format("Cannot convert value \"{0}\" to type {1}", stringValue, typeof(T).FullName)); + + throw new FormatException(string.Format("Failed to convert the value \"{0}\" for key \"{1}\" to type \"{2}\"", stringValue, key, + typeof(T).FullName)); + } + } + } +} diff --git a/PAPIPlugin/Internal/ExtensionMethods.cs b/PAPIPlugin/Internal/ExtensionMethods.cs new file mode 100644 index 0000000..d045930 --- /dev/null +++ b/PAPIPlugin/Internal/ExtensionMethods.cs @@ -0,0 +1,38 @@ +#region Usings + +using System; +using System.ComponentModel; + +#endregion + +namespace PAPIPlugin.Internal +{ + public static class ExtensionMethods + { + public static T ClampAndLog(this T val, T lower, T upper) where T : IComparable + { + if (lower.CompareTo(upper) > 0) + { + throw new ArgumentException("Lower bound is greater than upper bound!"); + } + + var outVal = val; + if (val.CompareTo(lower) < 0) + { + outVal = lower; + + Util.LogWarning(string.Format("Clamped value \"{0}\" to \"{1}\" because it is out of bounds for the range of [{2}, {3}].", val, lower, + lower, upper)); + } + else if (val.CompareTo(upper) > 0) + { + outVal = upper; + + Util.LogWarning(string.Format("Clamped value \"{0}\" to \"{1}\" because it is out of bounds for the range of [{2}, {3}].", val, upper, + lower, upper)); + } + + return outVal; + } + } +} diff --git a/PAPIPlugin/Util.cs b/PAPIPlugin/Internal/Util.cs similarity index 82% rename from PAPIPlugin/Util.cs rename to PAPIPlugin/Internal/Util.cs index 7821d6f..94b4692 100644 --- a/PAPIPlugin/Util.cs +++ b/PAPIPlugin/Internal/Util.cs @@ -1,7 +1,6 @@ -using System.Diagnostics; -using Debug = UnityEngine.Debug; +using Debug = UnityEngine.Debug; -namespace PAPIPlugin +namespace PAPIPlugin.Internal { public class Util { diff --git a/PAPIPlugin/LightArrayEventArguments.cs b/PAPIPlugin/LightArrayEventArguments.cs new file mode 100644 index 0000000..8496ca8 --- /dev/null +++ b/PAPIPlugin/LightArrayEventArguments.cs @@ -0,0 +1,15 @@ +using System; +using PAPIPlugin.Interfaces; + +namespace PAPIPlugin +{ + public class LightArrayEventArguments : EventArgs + { + public LightArrayEventArguments(ILightArray array) + { + Array = array; + } + + public ILightArray Array { get; private set; } + } +} \ No newline at end of file diff --git a/PAPIPlugin/LightArrayManager.cs b/PAPIPlugin/LightArrayManager.cs deleted file mode 100644 index 4c974f1..0000000 --- a/PAPIPlugin/LightArrayManager.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PAPIPlugin -{ - public class LightArrayManager - { - private readonly IList _lightArrays = new List(); - - public void AddArray(ILightArray array) - { - if (array==null)throw new ArgumentException("array"); - - _lightArrays.Add(array); - } - - public bool RemoveArray(ILightArray array) - { - return _lightArrays.Remove(array); - } - - public void Update() - { - foreach (var lightArray in _lightArrays) - { - lightArray.Update(); - } - } - } -} \ No newline at end of file diff --git a/PAPIPlugin/PAPIAddon.cs b/PAPIPlugin/PAPIAddon.cs index 29bfe94..ccd1367 100644 --- a/PAPIPlugin/PAPIAddon.cs +++ b/PAPIPlugin/PAPIAddon.cs @@ -1,27 +1,63 @@ #region Usings +using PAPIPlugin.Impl; +using PAPIPlugin.Interfaces; +using PAPIPlugin.Internal; +using Tac; using UnityEngine; #endregion namespace PAPIPlugin { - [KSPAddon(KSPAddon.Startup.Flight, false)] + [KSPAddon(KSPAddon.Startup.EveryScene, false)] public class PAPIAddon : MonoBehaviour { - private LightArrayManager _arrayManager; + private ILightArrayManager _arrayManager; + + private ILightArrayConfig _config; public void Awake() { - _arrayManager = new LightArrayManager(); + if (HighLogic.LoadedScene != GameScenes.FLIGHT && HighLogic.LoadedScene != GameScenes.SPACECENTER) + { + + return; + } + + Util.LogInfo("Awake!"); - _arrayManager.AddArray(new PAPIArray(-0.0468, -74.708889, 73, 270)); - _arrayManager.AddArray(new PAPIArray(-0.036, -74.50963, 73, 90)); + _arrayManager = new DefaultLightArrayManager(); + + if (_config == null) + { + _arrayManager.LoadConfig(); + _config = _arrayManager.LightConfig; + } + else + { + _arrayManager.LightConfig = _config; + } } public void Update() { - _arrayManager.Update(); + if (_arrayManager != null) + { + _arrayManager.Update(); + } + } + + public void OnDestroy() + { + if (_arrayManager == null) + { + return; + } + + Util.LogInfo("OnDestroy!"); + + _arrayManager.Dispose(); } } } diff --git a/PAPIPlugin/PAPIArray.cs b/PAPIPlugin/PAPIArray.cs deleted file mode 100644 index cb985be..0000000 --- a/PAPIPlugin/PAPIArray.cs +++ /dev/null @@ -1,165 +0,0 @@ -#region Usings - -using System; -using System.Linq; -using UnityEngine; - -#endregion - -namespace PAPIPlugin -{ - public class PAPIArray : ILightArray - { - private const int PAPIPartCount = 4; - - private const float PAPILightRadius = 10.0f; - - private const double TargetGlidePath = 6; - - /// - /// If the difference of the gliepath from the target is more than this the whole array will show either red or white. - /// - private const double BadGlidepathVariance = 1.5; - - private static readonly Vector3 PAPILightDifference = Vector3.right * PAPILightRadius * 1.5f; - - private readonly CelestialBody _kerbinBody; - - private GameObject _papiGameObject; - - private GameObject[] _partObjects; - - private Vector3d _relativeSurfacePosition; - - public PAPIArray(double lat, double lon, double alt, double heading) - { - _kerbinBody = FlightGlobals.Bodies.First(body => body.name == "Kerbin"); - - InitializePAPIParts(lat, lon, alt, heading * Math.PI / 180); - } - - /// - /// Initializes the whole array at the given latitude and longitude with the given altitude. The heading is the - /// direction to array looks to and should be in the range [0, 2 * PI). - /// - /// The latitude - /// The longitude - /// The altitude - /// The heading in radians. - private void InitializePAPIParts(double lat, double lon, double alt, double heading) - { - _relativeSurfacePosition = _kerbinBody.transform.InverseTransformPoint(_kerbinBody.GetWorldSurfacePosition(lat, lon, alt)); - - var surfaceNormal = _kerbinBody.transform.InverseTransformDirection(_kerbinBody.GetSurfaceNVector(lat, lon)); - - var pqs = _kerbinBody.pqsController; - var north = Vector3.up * (float) pqs.radius; // We are in local space so up * radius is the north pole - - var directionToNorth = (north - surfaceNormal).normalized; - - var orthogonalNorthDir = Orthonormalise(directionToNorth, surfaceNormal); - - var anotherVector = Vector3d.Cross(surfaceNormal, orthogonalNorthDir); - - var headingVector = orthogonalNorthDir * Math.Cos(heading) + anotherVector * Math.Sin(heading); - - Util.LogWarning(_relativeSurfacePosition.ToString()); - - _papiGameObject = new GameObject(); - _papiGameObject.transform.parent = _kerbinBody.transform; - _papiGameObject.transform.localPosition = _relativeSurfacePosition; - _papiGameObject.transform.localRotation = Quaternion.LookRotation(headingVector, surfaceNormal); - - _partObjects = new GameObject[PAPIPartCount]; - for (var i = 0; i < PAPIPartCount; i++) - { - var obj = new GameObject(); - - var lineRenderer = AddPAPIPart(obj); - lineRenderer.useWorldSpace = false; - - lineRenderer.transform.parent = _papiGameObject.transform; - lineRenderer.transform.localPosition = (i - (PAPIPartCount / 2)) * PAPILightDifference; - - _partObjects[i] = obj; - } - } - - private static Vector3d Orthonormalise(Vector3d direction, Vector3d firstVector) - { - // This is basically the first step of a Gram–Schmidt process - // See http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process - - return direction - Vector3d.Dot(firstVector, direction) * firstVector; - } - - private static LineRenderer AddPAPIPart(GameObject obj) - { - var lineRenderer = obj.AddComponent(); - - lineRenderer.material = new Material(Shader.Find("Particles/Additive")); - lineRenderer.SetColors(Color.red, Color.red); - lineRenderer.SetWidth(PAPILightRadius, PAPILightRadius); - lineRenderer.SetVertexCount(2); - lineRenderer.SetPosition(0, Vector3.zero); - lineRenderer.SetPosition(1, Vector3.up * PAPILightRadius); - - return lineRenderer; - } - - public void Update() - { - var currentCamera = Camera.main; - - var relativePosition = _papiGameObject.transform.InverseTransformPoint(currentCamera.transform.position); - - var normalizedPosition = relativePosition.normalized; - // As the local normal is (0, 1, 0), y is the dot of normal * normalizedPosition. - var normalDot = normalizedPosition.y; - - var directionDot = normalizedPosition.z; - - var angle = 90 - Math.Acos(normalDot) * (180 / Math.PI); - - var difference = angle - TargetGlidePath; - - for (var i = 0; i < PAPIPartCount; i++) - { - if (directionDot <= 0) - { - _partObjects[i].SetActive(false); - } - else - { - _partObjects[i].SetActive(true); - - // Use the direction dot for alpha to fade the lights out - UpdatePAPIPart(i, difference, directionDot); - } - } - } - - private void UpdatePAPIPart(int index, double difference, float alpha) - { - var gameObj = _partObjects[index]; - - var lineRenderer = gameObj.GetComponent(); - - var color = GetArrayPartColor(index, difference); - color.a = alpha; - lineRenderer.SetColors(color, color); - } - - private static Color GetArrayPartColor(int index, double difference) - { - if (difference < -BadGlidepathVariance) return Color.red; - if (difference > BadGlidepathVariance) return Color.white; - - // This should map temp into [-1, 1] - double temp = index - (PAPIPartCount / 2); - temp = temp / (PAPIPartCount / 2); - - return temp > difference ? Color.red : Color.white; - } - } -} diff --git a/PAPIPlugin/PAPIPlugin.csproj b/PAPIPlugin/PAPIPlugin.csproj index 81c2fb7..99113bf 100644 --- a/PAPIPlugin/PAPIPlugin.csproj +++ b/PAPIPlugin/PAPIPlugin.csproj @@ -30,6 +30,9 @@ prompt 4 + + Always + False @@ -43,16 +46,46 @@ - - + + TacLib\Icon.cs + + + TacLib\PopupWindow.cs + + + TacLib\Utilities.cs + + + TacLib\Window.cs + + + + + + + + + + + + + + - + + - + + - xcopy /Y /I "$(TargetPath)" "$(KSP_PATH)/GameData/PAPIPlugin/Plugins/$(TargetFileName)" + mkdir "$(SolutionDir)assets/Plugins" +xcopy /Y /I "$(TargetPath)" "$(SolutionDir)assets/Plugins" + +robocopy "$(SolutionDir)assets" "$(KSP_PATH)/GameData/PAPIPlugin" /PURGE /e + +exit 0