diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 76e71ba..bcdc259 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index b6b940e..fd1d246 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -66,6 +66,7 @@ + diff --git a/Source/ConformalDecals/DecalConfig.cs b/Source/ConformalDecals/DecalConfig.cs index e446b94..b0b8e78 100644 --- a/Source/ConformalDecals/DecalConfig.cs +++ b/Source/ConformalDecals/DecalConfig.cs @@ -1,13 +1,15 @@ 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 = 3; + public static Texture2D BlankNormal => _blankNormal; + public static int DecalLayer => _decalLayer; public static bool IsBlacklisted(Shader shader) { return IsBlacklisted(shader.name); @@ -22,6 +24,8 @@ namespace ConformalDecals { foreach (var shaderName in blacklist.GetValuesList("shader")) { _shaderBlacklist.Add(shaderName); } + + ParseUtil.ParseIntIndirect(ref _decalLayer, node, "decalLayer"); } } @@ -30,7 +34,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 +42,13 @@ namespace ConformalDecals { tex.SetPixels32(colors); } } + tex.Apply(); return tex; } + // ReSharper disable once UnusedMember.Global public static void ModuleManagerPostLoad() { _shaderBlacklist = new List(); @@ -55,6 +61,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/MaterialProperties/MaterialColorProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs index 7a665e3..5b770e6 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs @@ -1,18 +1,19 @@ using System; +using ConformalDecals.Util; using UnityEngine; 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/MaterialProperties/MaterialFloatProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs index db5c987..6745dfc 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs @@ -1,4 +1,5 @@ using System; +using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals.MaterialProperties { @@ -8,11 +9,11 @@ namespace ConformalDecals.MaterialProperties { 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 index 6416d22..6d792ab 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs @@ -22,58 +22,5 @@ namespace ConformalDecals.MaterialProperties { } 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/MaterialProperties/MaterialPropertyCollection.cs b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs index ce464eb..b61912b 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using ConformalDecals.Util; using UniLinq; using UnityEngine; using UnityEngine.Rendering; @@ -163,8 +164,8 @@ namespace ConformalDecals.MaterialProperties { } 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/MaterialProperties/MaterialTextureProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs index e1b229c..fbbeacf 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs @@ -1,4 +1,5 @@ using System; +using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals.MaterialProperties { @@ -9,7 +10,7 @@ namespace ConformalDecals.MaterialProperties { [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.MaterialProperties { 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/Util/ParseUtil.cs b/Source/ConformalDecals/Util/ParseUtil.cs new file mode 100644 index 0000000..4cbddd3 --- /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[0], out var green)) return false; + if (!float.TryParse(split[0], 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