diff --git a/GameData/ConformalDecals/Assets/decal-blank.mu b/GameData/ConformalDecals/Assets/decal-blank.mu index a2e687d..3c13c11 100644 Binary files a/GameData/ConformalDecals/Assets/decal-blank.mu and b/GameData/ConformalDecals/Assets/decal-blank.mu differ diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 76e71ba..a42d00a 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/GameData/ConformalDecals/Resources/ConformalDecals.cfg b/GameData/ConformalDecals/Resources/ConformalDecals.cfg index 72c6e59..bed0e9c 100644 --- a/GameData/ConformalDecals/Resources/ConformalDecals.cfg +++ b/GameData/ConformalDecals/Resources/ConformalDecals.cfg @@ -1,4 +1,7 @@ CONFORMALDECALS { + decalLayer = 31 + selectableInFlight = false + SHADERBLACKLIST { shader = DepthMask shader = KSP/Alpha/Cutoff diff --git a/GameData/ConformalDecals/Versioning/ConformalDecals.version b/GameData/ConformalDecals/Versioning/ConformalDecals.version index 7b3078a..7289185 100644 --- a/GameData/ConformalDecals/Versioning/ConformalDecals.version +++ b/GameData/ConformalDecals/Versioning/ConformalDecals.version @@ -6,7 +6,7 @@ { "MAJOR":0, "MINOR":1, - "PATCH":2, + "PATCH":3, "BUILD":0 }, "KSP_VERSION": diff --git a/README.md b/README.md index 02cde9f..18e5179 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Conformal Decals v0.1.2 +# Conformal Decals v0.1.3 [![Build Status](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals.svg?branch=release)](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals) [![Art: CC BY-SA 4.0](https://img.shields.io/badge/Art%20License-CC%20BY--SA%204.0-orange.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Code: GPL v3](https://img.shields.io/badge/Code%20License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) ![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png) diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index 19d5ed4..fd1d246 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -54,17 +54,19 @@ - - - - - + + + + + + + diff --git a/Source/ConformalDecals/DecalConfig.cs b/Source/ConformalDecals/DecalConfig.cs index e446b94..ca35d8d 100644 --- a/Source/ConformalDecals/DecalConfig.cs +++ b/Source/ConformalDecals/DecalConfig.cs @@ -1,14 +1,20 @@ using System.Collections.Generic; +using ConformalDecals.Util; using UnityEngine; -using UnityEngine.Experimental.Rendering; namespace ConformalDecals { public static class DecalConfig { - private static Texture2D _blankNormal; + private static Texture2D _blankNormal; private static List _shaderBlacklist; - + private static int _decalLayer = 31; + private static bool _selectableInFlight = false; + public static Texture2D BlankNormal => _blankNormal; + public static int DecalLayer => _decalLayer; + + public static bool SelectableInFlight => _selectableInFlight; + public static bool IsBlacklisted(Shader shader) { return IsBlacklisted(shader.name); } @@ -22,6 +28,9 @@ namespace ConformalDecals { foreach (var shaderName in blacklist.GetValuesList("shader")) { _shaderBlacklist.Add(shaderName); } + + ParseUtil.ParseIntIndirect(ref _decalLayer, node, "decalLayer"); + ParseUtil.ParseBoolIndirect(ref _selectableInFlight, node, "selectableInFlight"); } } @@ -30,7 +39,7 @@ namespace ConformalDecals { var width = 2; var height = 2; var color = new Color32(255, 128, 128, 128); - var colors = new Color32[] { color, color, color, color }; + var colors = new[] {color, color, color, color}; var tex = new Texture2D(width, height, TextureFormat.RGBA32, false); for (var x = 0; x <= width; x++) { @@ -38,11 +47,13 @@ namespace ConformalDecals { tex.SetPixels32(colors); } } + tex.Apply(); return tex; } + // ReSharper disable once UnusedMember.Global public static void ModuleManagerPostLoad() { _shaderBlacklist = new List(); @@ -55,6 +66,14 @@ namespace ConformalDecals { } } + // setup physics for decals, ignore collision with everything + Physics.IgnoreLayerCollision(_decalLayer, 1, true); // default + Physics.IgnoreLayerCollision(_decalLayer, 17, true); // EVA + Physics.IgnoreLayerCollision(_decalLayer, 19, true); // PhysicalObjects + Physics.IgnoreLayerCollision(_decalLayer, 23, true); // AeroFXIgnore + Physics.IgnoreLayerCollision(_decalLayer, 26, true); // wheelCollidersIgnore + Physics.IgnoreLayerCollision(_decalLayer, 27, true); // wheelColliders + _blankNormal = MakeBlankNormal(); } } diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialProperty.cs b/Source/ConformalDecals/MaterialModifiers/MaterialProperty.cs deleted file mode 100644 index 0bce511..0000000 --- a/Source/ConformalDecals/MaterialModifiers/MaterialProperty.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using UnityEngine; - -namespace ConformalDecals.MaterialModifiers { - public abstract class MaterialProperty : ScriptableObject { - public string PropertyName { - get => _propertyName; - set { - _propertyName = value; - _propertyID = Shader.PropertyToID(_propertyName); - } - } - - [SerializeField] protected int _propertyID; - [SerializeField] protected string _propertyName; - - public virtual void ParseNode(ConfigNode node) { - if (node == null) throw new ArgumentNullException(nameof(node)); - - PropertyName = node.GetValue("name"); - Debug.Log($"Parsing material property {_propertyName}"); - } - - public abstract void Modify(Material material); - - private delegate bool TryParseDelegate(string valueString, out T value); - - protected bool ParsePropertyBool(ConfigNode node, string valueName, bool isOptional = false, bool defaultValue = false) { - return ParsePropertyValue(node, valueName, bool.TryParse, isOptional, defaultValue); - } - - protected float ParsePropertyFloat(ConfigNode node, string valueName, bool isOptional = false, float defaultValue = 0.0f) { - return ParsePropertyValue(node, valueName, float.TryParse, isOptional, defaultValue); - } - - protected int ParsePropertyInt(ConfigNode node, string valueName, bool isOptional = false, int defaultValue = 0) { - return ParsePropertyValue(node, valueName, int.TryParse, isOptional, defaultValue); - } - - protected Color ParsePropertyColor(ConfigNode node, string valueName, bool isOptional = false, Color defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseColor, isOptional, defaultValue); - } - - protected Rect ParsePropertyRect(ConfigNode node, string valueName, bool isOptional = false, Rect defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseRect, isOptional, defaultValue); - } - - protected Vector2 ParsePropertyVector2(ConfigNode node, string valueName, bool isOptional = false, Vector2 defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseVector2, isOptional, defaultValue); - } - - private T ParsePropertyValue(ConfigNode node, string valueName, TryParseDelegate tryParse, bool isOptional = false, T defaultValue = default) { - string valueString = node.GetValue(valueName); - - if (isOptional) { - if (string.IsNullOrEmpty(valueString)) return defaultValue; - } - else { - if (valueString == null) - throw new FormatException($"Missing {typeof(T)} value for {valueName} in property '{PropertyName}'"); - - if (valueString == string.Empty) - throw new FormatException($"Empty {typeof(T)} value for {valueName} in property '{PropertyName}'"); - } - - if (tryParse(valueString, out var value)) { - return value; - } - - if (isOptional) { - return defaultValue; - } - - else { - throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} in property '{PropertyName}' : '{valueString}"); - } - } - } -} \ No newline at end of file diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs similarity index 60% rename from Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs rename to Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs index f60d388..5b770e6 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs @@ -1,18 +1,19 @@ using System; +using ConformalDecals.Util; using UnityEngine; -namespace ConformalDecals.MaterialModifiers { +namespace ConformalDecals.MaterialProperties { public class MaterialColorProperty : MaterialProperty { - [SerializeField] public Color color; + [SerializeField] public Color32 color = new Color32(0, 0, 0, byte.MaxValue); public override void ParseNode(ConfigNode node) { base.ParseNode(node); - color = ParsePropertyColor(node, "color", true, color); + ParseUtil.ParseColor32Indirect(ref color, node, "color"); } public override void Modify(Material material) { - if (material == null) throw new ArgumentNullException("material cannot be null"); + if (material == null) throw new ArgumentNullException(nameof(material)); material.SetColor(_propertyID, color); } diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs similarity index 72% rename from Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs rename to Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs index 1dad8df..6745dfc 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs @@ -1,18 +1,19 @@ using System; +using ConformalDecals.Util; using UnityEngine; -namespace ConformalDecals.MaterialModifiers { +namespace ConformalDecals.MaterialProperties { public class MaterialFloatProperty : MaterialProperty { [SerializeField] public float value; public override void ParseNode(ConfigNode node) { base.ParseNode(node); - value = ParsePropertyFloat(node, "value", true, value); + ParseUtil.ParseFloatIndirect(ref value, node, "value"); } public override void Modify(Material material) { - if (material == null) throw new ArgumentNullException("material cannot be null"); + if (material == null) throw new ArgumentNullException(nameof(material)); material.SetFloat(_propertyID, value); } diff --git a/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs new file mode 100644 index 0000000..6d792ab --- /dev/null +++ b/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs @@ -0,0 +1,26 @@ +using System; +using UnityEngine; + +namespace ConformalDecals.MaterialProperties { + public abstract class MaterialProperty : ScriptableObject { + public string PropertyName { + get => _propertyName; + set { + _propertyName = value; + _propertyID = Shader.PropertyToID(_propertyName); + } + } + + [SerializeField] protected int _propertyID; + [SerializeField] protected string _propertyName; + + public virtual void ParseNode(ConfigNode node) { + if (node == null) throw new ArgumentNullException(nameof(node)); + + PropertyName = node.GetValue("name"); + Debug.Log($"Parsing material property {_propertyName}"); + } + + public abstract void Modify(Material material); + } +} \ No newline at end of file diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs similarity index 97% rename from Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs rename to Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs index 8698f88..b61912b 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using ConformalDecals.Util; using UniLinq; using UnityEngine; using UnityEngine.Rendering; -namespace ConformalDecals.MaterialModifiers { +namespace ConformalDecals.MaterialProperties { public class MaterialPropertyCollection : ScriptableObject, ISerializationCallbackReceiver { public int RenderQueue { get => _renderQueue; @@ -163,8 +164,8 @@ namespace ConformalDecals.MaterialModifiers { } public T ParseProperty(ConfigNode node) where T : MaterialProperty { - var propertyName = node.GetValue("name"); - if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("node has no name"); + string propertyName = ""; + if (!ParseUtil.ParseStringIndirect(ref propertyName, node, "name")) throw new ArgumentException("node has no name"); var newProperty = AddOrGetProperty(propertyName); newProperty.ParseNode(node); diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs similarity index 79% rename from Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs rename to Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs index e6004bb..fbbeacf 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs @@ -1,7 +1,8 @@ using System; +using ConformalDecals.Util; using UnityEngine; -namespace ConformalDecals.MaterialModifiers { +namespace ConformalDecals.MaterialProperties { public class MaterialTextureProperty : MaterialProperty { [SerializeField] public bool isNormal; [SerializeField] public bool isMain; @@ -9,7 +10,7 @@ namespace ConformalDecals.MaterialModifiers { [SerializeField] public bool autoTile; [SerializeField] private string _textureUrl; - [SerializeField] private Texture2D _texture; + [SerializeField] private Texture2D _texture = Texture2D.whiteTexture; [SerializeField] private bool _hasTile; [SerializeField] private Rect _tileRect; @@ -42,24 +43,17 @@ namespace ConformalDecals.MaterialModifiers { public override void ParseNode(ConfigNode node) { base.ParseNode(node); - isNormal = ParsePropertyBool(node, "isNormalMap", true, (PropertyName == "_BumpMap") || (PropertyName == "_DecalBumpMap") || isNormal); - isMain = ParsePropertyBool(node, "isMain", true, isMain); - autoScale = ParsePropertyBool(node, "autoScale", true, autoScale); - autoTile = ParsePropertyBool(node, "autoTile", true, autoTile); + ParseUtil.ParseBoolIndirect(ref isMain, node, "isMain"); + ParseUtil.ParseBoolIndirect(ref isNormal, node, "isNormalMap"); + ParseUtil.ParseBoolIndirect(ref autoScale, node, "autoScale"); + ParseUtil.ParseBoolIndirect(ref autoTile, node, "autoTile"); - var textureUrl = node.GetValue("textureUrl"); - - if (string.IsNullOrEmpty(textureUrl)) { - if (string.IsNullOrEmpty(_textureUrl)) { - TextureUrl = ""; - } - } - else { - TextureUrl = node.GetValue("textureUrl"); + if (!autoTile) { + ParseUtil.ParseRectIndirect(ref _tileRect, node, "tile"); } - if (node.HasValue("tile") && !autoTile) { - SetTile(ParsePropertyRect(node, "tile", true, _tileRect)); + if (ParseUtil.ParseStringIndirect(ref _textureUrl, node, "textureUrl")) { + _texture = LoadTexture(_textureUrl, isNormal); } } diff --git a/Source/ConformalDecals/ModuleConformalDecal.cs b/Source/ConformalDecals/ModuleConformalDecal.cs index 863eea7..c5b06e2 100644 --- a/Source/ConformalDecals/ModuleConformalDecal.cs +++ b/Source/ConformalDecals/ModuleConformalDecal.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; -using ConformalDecals.MaterialModifiers; +using System.Diagnostics.CodeAnalysis; +using ConformalDecals.MaterialProperties; using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals { public class ModuleConformalDecal : PartModule { + [SuppressMessage("ReSharper", "InconsistentNaming")] public enum DecalScaleMode { HEIGHT, WIDTH, @@ -17,43 +19,21 @@ namespace ConformalDecals { // CONFIGURABLE VALUES - /// - /// Shader name. Should be one that supports decal projection. - /// [KSPField] public string shader = "ConformalDecals/Paint/Diffuse"; - /// - /// Decal front transform name. Required - /// - [KSPField] public string decalFront = "Decal-Front"; - - /// - /// Decal back transform name. Required if is true. - /// - [KSPField] public string decalBack = "Decal-Back"; - - /// - /// Decal model transform name. Is rescaled to preview the decal scale when unattached. - /// - /// - /// If unspecified, the decal front transform is used instead. - /// - [KSPField] public string decalModel = "Decal-Model"; - - /// - /// Decal projector transform name. The decal will project along the +Z axis of this transform. - /// - /// - /// if unspecified, the part "model" transform will be used instead. - /// + [KSPField] public string decalFront = "Decal-Front"; + [KSPField] public string decalBack = "Decal-Back"; + [KSPField] public string decalModel = "Decal-Model"; [KSPField] public string decalProjector = "Decal-Projector"; + [KSPField] public string decalCollider = "Decal-Collider"; // Parameters - [KSPField] public bool scaleAdjustable = true; - [KSPField] public float defaultScale = 1; - [KSPField] public Vector2 scaleRange = new Vector2(0, 4); - [KSPField] public DecalScaleMode scaleMode = DecalScaleMode.HEIGHT; + [KSPField] public bool scaleAdjustable = true; + [KSPField] public float defaultScale = 1; + [KSPField] public Vector2 scaleRange = new Vector2(0, 4); + + [KSPField] public DecalScaleMode scaleMode = DecalScaleMode.HEIGHT; [KSPField] public bool depthAdjustable = true; [KSPField] public float defaultDepth = 0.1f; @@ -75,56 +55,40 @@ namespace ConformalDecals { [KSPField] public Vector2 tileSize; [KSPField] public int tileIndex = -1; - /// - /// Should the back material scale be updated automatically? - /// [KSPField] public bool updateBackScale = true; - + [KSPField] public bool selectableInFlight; // INTERNAL VALUES - - /// - /// Decal scale factor, in meters. - /// [KSPField(guiName = "#LOC_ConformalDecals_gui-scale", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"), UI_FloatRange(stepIncrement = 0.05f)] public float scale = 1.0f; - /// - /// Projection depth value for the decal projector, in meters. - /// [KSPField(guiName = "#LOC_ConformalDecals_gui-depth", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"), UI_FloatRange(stepIncrement = 0.02f)] public float depth = 0.2f; - /// - /// Opacity value for the decal shader. - /// [KSPField(guiName = "#LOC_ConformalDecals_gui-opacity", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"), UI_FloatRange(stepIncrement = 0.05f)] public float opacity = 1.0f; - /// - /// Alpha cutoff value for the decal shader. - /// [KSPField(guiName = "#LOC_ConformalDecals_gui-cutoff", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"), UI_FloatRange(stepIncrement = 0.05f)] public float cutoff = 0.5f; - /// - /// Edge wear value for the decal shader. Only relevent when useBaseNormal is true and the shader is a paint shader - /// [KSPField(guiName = "#LOC_ConformalDecals_gui-wear", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F0"), UI_FloatRange()] public float wear = 100; + [KSPField(isPersistant = true)] public bool projectMultiple; // reserved for future features. do not modify + [KSPField] public MaterialPropertyCollection materialProperties; [KSPField] public Transform decalFrontTransform; [KSPField] public Transform decalBackTransform; [KSPField] public Transform decalModelTransform; [KSPField] public Transform decalProjectorTransform; - + [KSPField] public Transform decalColliderTransform; + [KSPField] public Material backMaterial; [KSPField] public Vector2 backTextureBaseScale; @@ -137,10 +101,10 @@ namespace ConformalDecals { private bool _isAttached; private Matrix4x4 _orthoMatrix; - private Material _decalMaterial; - private Material _previewMaterial; - private BoxCollider _boundsCollider; - + private Material _decalMaterial; + private Material _previewMaterial; + private MeshRenderer _boundsRenderer; + private int DecalQueue { get { _decalQueueCounter++; @@ -169,43 +133,22 @@ namespace ConformalDecals { this.Log("Loading module"); try { // SETUP TRANSFORMS - - // find front transform decalFrontTransform = part.FindModelTransform(decalFront); if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'."); - // find back transform - if (string.IsNullOrEmpty(decalBack)) { - if (updateBackScale) { - this.LogWarning("updateBackScale is true but has no specified decalBack transform!"); - this.LogWarning("Setting updateBackScale to false."); - updateBackScale = false; - } - } - else { - decalBackTransform = part.FindModelTransform(decalBack); - if (decalBackTransform == null) throw new FormatException($"Could not find decalBack transform: '{decalBack}'."); - } + decalBackTransform = part.FindModelTransform(decalBack); + if (decalBackTransform == null) throw new FormatException($"Could not find decalBack transform: '{decalBack}'."); - // find model transform - if (string.IsNullOrEmpty(decalModel)) { - decalModelTransform = decalFrontTransform; - } - else { - decalModelTransform = part.FindModelTransform(decalModel); - if (decalModelTransform == null) throw new FormatException($"Could not find decalModel transform: '{decalModel}'."); - } + decalModelTransform = part.FindModelTransform(decalModel); + if (decalModelTransform == null) throw new FormatException($"Could not find decalModel transform: '{decalModel}'."); - // find projector transform - if (string.IsNullOrEmpty(decalProjector)) { - decalProjectorTransform = part.transform; - } - else { - decalProjectorTransform = part.FindModelTransform(decalProjector); - if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'."); - } + decalProjectorTransform = part.FindModelTransform(decalProjector); + if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'."); + + decalColliderTransform = part.FindModelTransform(decalCollider); + if (decalColliderTransform == null) throw new FormatException($"Could not find decalCollider transform: '{decalCollider}'."); - // get back material if necessary + // SETUP BACK MATERIAL if (updateBackScale) { this.Log("Getting material and base scale for back material"); var backRenderer = decalBackTransform.GetComponent(); @@ -294,17 +237,11 @@ namespace ConformalDecals { public override void OnStart(StartState state) { this.Log("Starting module"); - // handle tweakables - if (HighLogic.LoadedSceneIsEditor) { - GameEvents.onEditorPartEvent.Add(OnEditorEvent); - GameEvents.onVariantApplied.Add(OnVariantApplied); - UpdateTweakables(); - } materialProperties.RenderQueue = DecalQueue; - _boundsCollider = decalProjectorTransform.GetComponent(); + _boundsRenderer = decalProjectorTransform.GetComponent(); UpdateMaterials(); @@ -317,12 +254,40 @@ namespace ConformalDecals { OnAttach(); } } + + // handle tweakables + if (HighLogic.LoadedSceneIsEditor) { + GameEvents.onEditorPartEvent.Add(OnEditorEvent); + GameEvents.onVariantApplied.Add(OnVariantApplied); + + UpdateTweakables(); + } + + // handle flight events + if (HighLogic.LoadedSceneIsFlight) { + GameEvents.onPartWillDie.Add(OnPartWillDie); + + Part.layerMask |= 1 << DecalConfig.DecalLayer; + decalColliderTransform.gameObject.layer = DecalConfig.DecalLayer; + + if (!selectableInFlight || !DecalConfig.SelectableInFlight) { + decalColliderTransform.GetComponent().enabled = false; + } + + if (part.parent == null) part.explode(); + } } public virtual void OnDestroy() { // remove GameEvents - GameEvents.onEditorPartEvent.Remove(OnEditorEvent); - GameEvents.onVariantApplied.Remove(OnVariantApplied); + if (HighLogic.LoadedSceneIsEditor) { + GameEvents.onEditorPartEvent.Remove(OnEditorEvent); + GameEvents.onVariantApplied.Remove(OnVariantApplied); + } + + if (HighLogic.LoadedSceneIsFlight) { + GameEvents.onPartWillDie.Remove(OnPartWillDie); + } // remove from preCull delegate Camera.onPreCull -= Render; @@ -381,6 +346,13 @@ namespace ConformalDecals { } } + protected void OnPartWillDie(Part willDie) { + if (willDie == part.parent) { + this.Log("Parent part about to be destroyed! Killing decal part."); + part.Die(); + } + } + protected void OnAttach() { if (part.parent == null) { this.LogError("Attach function called but part has no parent!"); @@ -423,6 +395,8 @@ namespace ConformalDecals { } protected void UpdateScale() { + scale = Mathf.Max(0.01f, scale); + depth = Mathf.Max(0.01f, depth); var aspectRatio = materialProperties.AspectRatio; Vector2 size; @@ -463,7 +437,7 @@ namespace ConformalDecals { // update projection foreach (var target in _targets) { - target.Project(_orthoMatrix, decalProjectorTransform, _boundsCollider.bounds, useBaseNormal); + target.Project(_orthoMatrix, decalProjectorTransform, _boundsRenderer.bounds, useBaseNormal); } } else { @@ -597,7 +571,7 @@ namespace ConformalDecals { public void Render(Camera camera) { if (!_isAttached) return; - + // render on each target object foreach (var target in _targets) { target.Render(_decalMaterial, part.mpb, camera); diff --git a/Source/ConformalDecals/Test/TestLayers.cs b/Source/ConformalDecals/Test/TestLayers.cs new file mode 100644 index 0000000..50e413a --- /dev/null +++ b/Source/ConformalDecals/Test/TestLayers.cs @@ -0,0 +1,34 @@ +using System; +using UnityEngine; + +namespace ConformalDecals.Test { + public class TestLayers : PartModule { + + [KSPField(guiActive = true)] + public int layer = 2; + + public override void OnStart(StartState state) { + base.OnStart(state); + + + Part.layerMask.value |= (1 << 3); + } + + public void Update() { + foreach (var collider in GameObject.FindObjectsOfType()) { + if (collider.gameObject.layer == 3) { + Debug.Log($"Has layer 3: {collider.gameObject.name}"); + } + } + } + + [KSPEvent(guiActive = true, guiActiveEditor = true, guiName = "switch layers")] + public void Switch() { + Debug.Log(Part.layerMask.value); + + var cube = part.FindModelTransform("test"); + layer = (layer + 1) % 32; + cube.gameObject.layer = layer; + } + } +} \ No newline at end of file diff --git a/Source/ConformalDecals/Util/ParseUtil.cs b/Source/ConformalDecals/Util/ParseUtil.cs new file mode 100644 index 0000000..fcb1ecd --- /dev/null +++ b/Source/ConformalDecals/Util/ParseUtil.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace ConformalDecals.Util { + public static class ParseUtil { + private static readonly Dictionary NamedColors = new Dictionary(); + private static readonly char[] Separator = {',', ' ', '\t'}; + + public delegate bool TryParseDelegate(string valueString, out T value); + + static ParseUtil() { + // setup named colors + foreach (var propertyInfo in typeof(Color).GetProperties(BindingFlags.Static | BindingFlags.Public)) { + if (!propertyInfo.CanRead) continue; + if (propertyInfo.PropertyType != typeof(Color)) continue; + + NamedColors.Add(propertyInfo.Name, (Color) propertyInfo.GetValue(null, null)); + } + + foreach (var propertyInfo in typeof(XKCDColors).GetProperties(BindingFlags.Static | BindingFlags.Public)) { + if (!propertyInfo.CanRead) continue; + if (propertyInfo.PropertyType != typeof(Color)) continue; + + if (NamedColors.ContainsKey(propertyInfo.Name)) throw new Exception("duplicate key " + propertyInfo.Name); + + NamedColors.Add(propertyInfo.Name, (Color) propertyInfo.GetValue(null, null)); + } + } + + public static string ParseString(ConfigNode node, string valueName, bool isOptional = false, string defaultValue = "") { + if (!node.HasValue(valueName)) throw new FormatException($"Missing value for {valueName}"); + + return node.GetValue(valueName); + } + + public static bool ParseStringIndirect(ref string value, ConfigNode node, string valueName) { + if (node.HasValue(valueName)) { + value = node.GetValue(valueName); + return true; + } + + return false; + } + + public static bool ParseBool(ConfigNode node, string valueName, bool isOptional = false, bool defaultValue = false) { + return ParseValue(node, valueName, bool.TryParse, isOptional, defaultValue); + } + + public static bool ParseBoolIndirect(ref bool value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, bool.TryParse); + } + + public static float ParseFloat(ConfigNode node, string valueName, bool isOptional = false, float defaultValue = 0.0f) { + return ParseValue(node, valueName, float.TryParse, isOptional, defaultValue); + } + + public static bool ParseFloatIndirect(ref float value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, float.TryParse); + } + + public static int ParseInt(ConfigNode node, string valueName, bool isOptional = false, int defaultValue = 0) { + return ParseValue(node, valueName, int.TryParse, isOptional, defaultValue); + } + + public static bool ParseIntIndirect(ref int value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, int.TryParse); + } + + public static Color32 ParseColor32(ConfigNode node, string valueName, bool isOptional = false, Color32 defaultValue = default) { + return ParseValue(node, valueName, TryParseColor32, isOptional, defaultValue); + } + + public static bool ParseColor32Indirect(ref Color32 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, TryParseColor32); + } + + public static Rect ParseRect(ConfigNode node, string valueName, bool isOptional = false, Rect defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseRect, isOptional, defaultValue); + } + + public static bool ParseRectIndirect(ref Rect value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseRect); + } + + public static Vector2 ParseVector2(ConfigNode node, string valueName, bool isOptional = false, Vector2 defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseVector2, isOptional, defaultValue); + } + + public static bool ParseVector2Indirect(ref Vector2 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector2); + } + + public static Vector3 ParseVector3(ConfigNode node, string valueName, bool isOptional = false, Vector3 defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseVector3, isOptional, defaultValue); + } + + public static bool ParseVector3Indirect(ref Vector3 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector3); + } + + public static T ParseValue(ConfigNode node, string valueName, TryParseDelegate tryParse, bool isOptional = false, T defaultValue = default) { + string valueString = node.GetValue(valueName); + + if (isOptional) { + if (string.IsNullOrEmpty(valueString)) return defaultValue; + } + else { + if (valueString == null) + throw new FormatException($"Missing {typeof(T)} value for {valueName}"); + + if (valueString == string.Empty) + throw new FormatException($"Empty {typeof(T)} value for {valueName}"); + } + + if (tryParse(valueString, out var value)) { + return value; + } + + if (isOptional) { + return defaultValue; + } + + else { + throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}"); + } + } + + public static bool ParseValueIndirect(ref T value, ConfigNode node, string valueName, TryParseDelegate tryParse) { + if (!node.HasValue(valueName)) return false; + + var valueString = node.GetValue(valueName); + if (tryParse(valueString, out var newValue)) { + value = newValue; + return true; + } + + throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}"); + } + + public static bool TryParseColor32(string valueString, out Color32 value) { + value = new Color32(0, 0, 0, byte.MaxValue); + + // HTML-style hex color + if (valueString[0] == '#') { + var hexColorString = valueString.Substring(1); + + if (!int.TryParse(hexColorString, System.Globalization.NumberStyles.HexNumber, null, out var hexColor)) return false; + + switch (hexColorString.Length) { + case 8: // RRGGBBAA + value.a = (byte) (hexColor & 0xFF); + hexColor >>= 8; + goto case 6; + + case 6: // RRGGBB + value.b = (byte) (hexColor & 0xFF); + hexColor >>= 8; + value.g = (byte) (hexColor & 0xFF); + hexColor >>= 8; + value.r = (byte) (hexColor & 0xFF); + return true; + + case 4: // RGBA + value.a = (byte) ((hexColor & 0xF) << 4); + hexColor >>= 4; + goto case 3; + + case 3: // RGB + value.b = (byte) (hexColor & 0xF << 4); + hexColor >>= 4; + value.g = (byte) (hexColor & 0xF << 4); + hexColor >>= 4; + value.r = (byte) (hexColor & 0xF << 4); + return true; + + default: + return false; + } + } + + // named color + if (NamedColors.TryGetValue(valueString, out var namedColor)) { + value = namedColor; + return true; + } + + // float color + var split = valueString.Split(Separator, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < split.Length; i++) { + split[i] = split[i].Trim(); + } + + switch (split.Length) { + case 4: + if (!float.TryParse(split[4], out var alpha)) return false; + value.a = (byte) (alpha * 0xFF); + goto case 3; + + case 3: + if (!float.TryParse(split[0], out var red)) return false; + if (!float.TryParse(split[1], out var green)) return false; + if (!float.TryParse(split[2], out var blue)) return false; + + value.r = (byte) (red * 0xFF); + value.g = (byte) (green * 0xFF); + value.b = (byte) (blue * 0xFF); + return true; + + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 7cbaa7c..3d730e5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,12 +1,24 @@ -v0.1.2 +v0.1.3 ------ +Fixes: +- Fixed decals being able to be scaled down to 0 + +Changes: +- Made decal bounds no longer collide in flight, this is done by repurposing layer 31 (which is configurable in the ConformalDecals.cfg file) +- Decals will now be unselectable in flight by default. This can be disabled with the `selectableInFlight` value in ConformalDecals.cfg, or in the module config itself. +- Decal parts will now destroy themselves automatically when the parent part is destroyed +- Small refactor of node parsing code + - Colors can now be specified in hex (#RGB, #RGBA, #RRGGBB, or #RRGGBBAA) or using the colors specified in the XKCDColors class + + +v0.1.2 +------ Fixes: - Disabled writing to the zbuffer in the decal bounds shader. Should fix any issues with Scatterer or EVE. v0.1.1 ------ - Fixes: - Fixed flag decal not adjusting to new texture sizes immediately. - Fixed decal bounds being visible on launch. @@ -14,7 +26,6 @@ Fixes: v0.1.0 ------ - Initial release! New parts: