From 7e520f97ca573cb7fdfc9f7db51c4d840e84ee49 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Tue, 2 Jun 2020 00:53:17 -0700 Subject: [PATCH] Refactor material parsing and loading This should hopefully pave the way for usable module switching using B9PS --- .../ConformalDecals/Parts/decal-blank.cfg | 28 ++-- .../Plugins/ConformalDecals.dll | Bin 28160 -> 27136 bytes Source/ConformalDecals/ConformalDecals.csproj | 6 +- ...alProperty.cs => MaterialColorProperty.cs} | 4 +- ...alProperty.cs => MaterialFloatProperty.cs} | 4 +- .../MaterialPropertyCollection.cs | 123 +++++------------- ...Property.cs => MaterialTextureProperty.cs} | 4 +- .../ConformalDecals/ModuleConformalDecal.cs | 60 +++++++-- Source/ConformalDecals/ProjectionTarget.cs | 35 +++-- 9 files changed, 123 insertions(+), 141 deletions(-) rename Source/ConformalDecals/MaterialModifiers/{ColorMaterialProperty.cs => MaterialColorProperty.cs} (72%) rename Source/ConformalDecals/MaterialModifiers/{FloatMaterialProperty.cs => MaterialFloatProperty.cs} (72%) rename Source/ConformalDecals/MaterialModifiers/{TextureMaterialProperty.cs => MaterialTextureProperty.cs} (94%) diff --git a/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg b/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg index 289a285..fd0ddc2 100644 --- a/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg +++ b/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg @@ -47,24 +47,22 @@ PART decalFront = Decal-Front decalBack = Decal-Back + decalShader = ConformalDecals/Feature/Bumped - MATERIAL - { - shader = ConformalDecals/Feature/Bumped + useBaseNormal = false - TEXTURE - { - name = _Decal - textureURL = ConformalDecals/Assets/Tortilla-diffuse - isMain = true - } + TEXTURE + { + name = _Decal + textureURL = ConformalDecals/Assets/Tortilla-diffuse + isMain = true + } - TEXTURE - { - name = _BumpMap - textureURL = ConformalDecals/Assets/Tortilla-normal - isNormalMap = true - } + TEXTURE + { + name = _BumpMap + textureURL = ConformalDecals/Assets/Tortilla-normal + isNormalMap = true } } } diff --git a/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 0da29e539df26748b4d88c36be9ace02794d4d7a..5ec30807f97607e2eaddec899aed7ae74dd16eff 100644 GIT binary patch literal 27136 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 L|9}49^T7WAq`!-& diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index 2109274..08a2aee 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -49,11 +49,11 @@ - - + + - + diff --git a/Source/ConformalDecals/MaterialModifiers/ColorMaterialProperty.cs b/Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs similarity index 72% rename from Source/ConformalDecals/MaterialModifiers/ColorMaterialProperty.cs rename to Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs index ddd6241..ab0ff88 100644 --- a/Source/ConformalDecals/MaterialModifiers/ColorMaterialProperty.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialColorProperty.cs @@ -2,10 +2,10 @@ using System; using UnityEngine; namespace ConformalDecals.MaterialModifiers { - public class ColorMaterialProperty : MaterialProperty { + public class MaterialColorProperty : MaterialProperty { private readonly Color _color; - public ColorMaterialProperty(ConfigNode node) : base(node) { + public MaterialColorProperty(ConfigNode node) : base(node) { _color = ParsePropertyColor(node, "color", false); } diff --git a/Source/ConformalDecals/MaterialModifiers/FloatMaterialProperty.cs b/Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs similarity index 72% rename from Source/ConformalDecals/MaterialModifiers/FloatMaterialProperty.cs rename to Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs index 2154eb2..6efb83b 100644 --- a/Source/ConformalDecals/MaterialModifiers/FloatMaterialProperty.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialFloatProperty.cs @@ -2,10 +2,10 @@ using System; using UnityEngine; namespace ConformalDecals.MaterialModifiers { - public class FloatMaterialProperty : MaterialProperty { + public class MaterialFloatProperty : MaterialProperty { private readonly float _value; - public FloatMaterialProperty(ConfigNode node) : base(node) { + public MaterialFloatProperty(ConfigNode node) : base(node) { _value = ParsePropertyFloat(node, "value", false); } diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs b/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs index 360262a..0b0397e 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs @@ -1,22 +1,16 @@ using System; using System.Collections.Generic; -using Smooth.Delegates; using UnityEngine; namespace ConformalDecals.MaterialModifiers { public class MaterialPropertyCollection : ScriptableObject { - private static int _opacityID = Shader.PropertyToID("_Opacity"); - private static int _cutoffID = Shader.PropertyToID("_Cutoff"); + private static readonly int OpacityId = Shader.PropertyToID("_Opacity"); + private static readonly int CutoffId = Shader.PropertyToID("_Cutoff"); - public TextureMaterialProperty MainTextureProperty { get; set; } - - public bool UseBaseNormal { get; private set; } + public MaterialTextureProperty MainMaterialTextureProperty { get; set; } private List _materialProperties; - private List _textureMaterialProperties; - - public String BaseNormalSrc { get; private set; } - public String BaseNormalDest { get; private set; } + private List _textureMaterialProperties; public Material DecalMaterial { get { @@ -28,107 +22,60 @@ namespace ConformalDecals.MaterialModifiers { } } - private Shader _decalShader; + private Shader _decalShader; private Material _protoDecalMaterial; - private const string _normalTextureName = "_BumpMap"; - - public void Initialize(ConfigNode node, PartModule module) { - - // Initialize fields + public void Initialize() { _materialProperties = new List(); - _textureMaterialProperties = new List(); - - // Get shader - var shaderString = node.GetValue("shader") ?? throw new FormatException("Missing shader name in material"); - - _decalShader = Shabby.Shabby.FindShader(shaderString); + _textureMaterialProperties = new List(); + } - // note to self: null coalescing does not work on UnityEngine classes - if (_decalShader == null) { - throw new FormatException($"Shader not found: '{shaderString}'"); + public void AddProperty(MaterialProperty property) { + foreach (var p in _materialProperties) { + if (p.PropertyName == property.PropertyName) { + _materialProperties.Remove(property); + } } - // Get useBaseNormal value - var useBaseNormalString = node.GetValue("useBaseNormal"); + _materialProperties.Add(property); - if (useBaseNormalString != null) { - if (bool.TryParse(useBaseNormalString, out var useBaseNormalRef)) { - UseBaseNormal = useBaseNormalRef; - } - else { - throw new FormatException($"Improperly formatted bool value for 'useBaseNormal' : {useBaseNormalString}"); + if (property is MaterialTextureProperty textureProperty) { + foreach (var p in _textureMaterialProperties) { + if (p.PropertyName == textureProperty.PropertyName) { + _textureMaterialProperties.Remove(textureProperty); + } } - } - else { - UseBaseNormal = false; - } - // Get basenormal source and destination property names - BaseNormalSrc = node.GetValue("baseNormalSource") ?? _normalTextureName; - BaseNormalDest = node.GetValue("baseNormalDestination") ?? _normalTextureName; - - // Parse all materialProperties - foreach (ConfigNode propertyNode in node.nodes) { - try { - MaterialProperty property; - switch (propertyNode.name) { - case "FLOAT": - property = new FloatMaterialProperty(propertyNode); - break; - - case "COLOR": - property = new ColorMaterialProperty(propertyNode); - break; - - case "TEXTURE": - property = new TextureMaterialProperty(propertyNode); - var textureModifier = (TextureMaterialProperty) property; - if (textureModifier.IsMain) { - if (MainTextureProperty == null) { - MainTextureProperty = textureModifier; - } - else { - // multiple textures have been marked as main! - // non-fatal issue, ignore this one and keep using current main texture - module.LogWarning( - $"Material texture property {textureModifier.TextureUrl} is marked as main, but material already has a main texture! \n" + - $"Defaulting to {MainTextureProperty.TextureUrl}"); - } - } - - _textureMaterialProperties.Add(textureModifier); - break; - - default: - throw new FormatException($"Invalid property type '{propertyNode.name}' in material"); - } + _textureMaterialProperties.Add(textureProperty); - _materialProperties.Add(property); - } + if (textureProperty.IsMain) MainMaterialTextureProperty ??= textureProperty; + } + } - catch (Exception e) { - // Catch exception from parsing current material property - // And print it to the log as an Error - module.LogException("Exception while parsing material node", e); - } + public void SetShader(string shaderName) { + if (_decalShader == null && string.IsNullOrEmpty(shaderName)) { + throw new FormatException("Shader name not provided"); } - module.Log($"Parsed {_materialProperties.Count} properties"); + var shader = Shabby.Shabby.FindShader(shaderName); + + if (shader == null) throw new FormatException($"Unable to find specified shader '{shaderName}'"); + + _decalShader = shader; } - + public void SetScale(Material material, Vector2 scale) { foreach (var textureProperty in _textureMaterialProperties) { textureProperty.UpdateScale(material, scale); } } - + public void SetOpacity(Material material, float opacity) { - material.SetFloat(_opacityID, opacity); + material.SetFloat(OpacityId, opacity); } public void SetCutoff(Material material, float cutoff) { - material.SetFloat(_cutoffID, cutoff); + material.SetFloat(CutoffId, cutoff); } private Material MakeMaterial(Shader shader) { diff --git a/Source/ConformalDecals/MaterialModifiers/TextureMaterialProperty.cs b/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs similarity index 94% rename from Source/ConformalDecals/MaterialModifiers/TextureMaterialProperty.cs rename to Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs index 759f5b6..d327b65 100644 --- a/Source/ConformalDecals/MaterialModifiers/TextureMaterialProperty.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs @@ -2,7 +2,7 @@ using System; using UnityEngine; namespace ConformalDecals.MaterialModifiers { - public class TextureMaterialProperty : MaterialProperty { + public class MaterialTextureProperty : MaterialProperty { public string TextureUrl { get; } public Texture2D TextureRef { get; } @@ -17,7 +17,7 @@ namespace ConformalDecals.MaterialModifiers { private readonly Vector2 _textureOffset; private readonly Vector2 _textureScale; - public TextureMaterialProperty(ConfigNode node) : base(node) { + public MaterialTextureProperty(ConfigNode node) : base(node) { TextureUrl = node.GetValue("textureURL"); var textureInfo = GameDatabase.Instance.GetTextureInfo(TextureUrl); diff --git a/Source/ConformalDecals/ModuleConformalDecal.cs b/Source/ConformalDecals/ModuleConformalDecal.cs index 6c48831..2b341b3 100644 --- a/Source/ConformalDecals/ModuleConformalDecal.cs +++ b/Source/ConformalDecals/ModuleConformalDecal.cs @@ -28,6 +28,7 @@ namespace ConformalDecals { [KSPField] public string decalBack = string.Empty; [KSPField] public string decalModel = string.Empty; [KSPField] public string decalProjector = string.Empty; + [KSPField] public string decalShader = string.Empty; [KSPField] public Transform decalFrontTransform; [KSPField] public Transform decalBackTransform; @@ -46,9 +47,12 @@ namespace ConformalDecals { [KSPField] public Vector2 decalQueueRange = new Vector2(2100, 2400); [KSPField] public bool updateBackScale = true; + [KSPField] public bool useBaseNormal = true; [KSPField] public MaterialPropertyCollection materialProperties; - [KSPField] public Material decalMaterial; + + [KSPField] public Material decalMaterial; + [KSPField] public Material backMaterial; private static int _decalQueueCounter = -1; @@ -59,7 +63,11 @@ namespace ConformalDecals { private Bounds _decalBounds; private Vector2 _backTextureBaseScale; - private Material _backMaterial; + public ModuleConformalDecal() { + decalBackTransform = null; + decalModelTransform = null; + decalProjectorTransform = null; + } private int DecalQueue { get { @@ -76,16 +84,40 @@ namespace ConformalDecals { public override void OnLoad(ConfigNode node) { this.Log("Loading module"); try { - // parse MATERIAL node - var materialNode = node.GetNode("MATERIAL") ?? throw new FormatException("Missing MATERIAL node in module"); - materialProperties = ScriptableObject.CreateInstance(); - materialProperties.Initialize(materialNode, this); + + if (materialProperties == null) { + // materialProperties is null, so make a new one + materialProperties = ScriptableObject.CreateInstance(); + materialProperties.Initialize(); + } + else { + // materialProperties already exists, so make a copy + materialProperties = ScriptableObject.Instantiate(materialProperties); + } + + // add texture nodes + foreach (var textureNode in node.GetNodes("TEXTURE")) { + materialProperties.AddProperty(new MaterialTextureProperty(textureNode)); + } + + // add float nodes + foreach (var floatNode in node.GetNodes("FLOAT")) { + materialProperties.AddProperty(new MaterialFloatProperty(floatNode)); + } + + // add color nodes + foreach (var colorNode in node.GetNodes("COLOR")) { + materialProperties.AddProperty(new MaterialColorProperty(colorNode)); + } + + // set shader + materialProperties.SetShader(decalShader); // get decal material decalMaterial = materialProperties.DecalMaterial; // get aspect ratio from main texture, if it exists - var mainTexture = materialProperties.MainTextureProperty; + var mainTexture = materialProperties.MainMaterialTextureProperty; if (mainTexture != null) { aspectRatio = mainTexture.AspectRatio; } @@ -127,6 +159,12 @@ namespace ConformalDecals { decalProjectorTransform = part.FindModelTransform(decalProjector); if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'."); } + + // update EVERYTHING if currently attached + if (_isAttached) { + OnDetach(); + OnAttach(); + } } catch (Exception e) { this.LogException("Exception parsing partmodule", e); @@ -207,12 +245,12 @@ namespace ConformalDecals { this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false."); updateBackScale = false; } - else if ((_backMaterial = backRenderer.material) == null) { + else if ((backMaterial = backRenderer.material) == null) { this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false."); updateBackScale = false; } else { - _backTextureBaseScale = _backMaterial.GetTextureScale(PropertyIDs._MainTex); + _backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex); } } @@ -300,7 +338,7 @@ namespace ConformalDecals { this.Log($"Adding target for object {meshFilter.gameObject.name} with the mesh {mesh.name}"); // create new ProjectionTarget to represent the renderer - var target = new ProjectionTarget(renderer, mesh, materialProperties); + var target = new ProjectionTarget(renderer, mesh, useBaseNormal); this.Log("done."); @@ -349,7 +387,7 @@ namespace ConformalDecals { // update back material scale if (updateBackScale) { - _backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y)); + backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y)); } // update material scale diff --git a/Source/ConformalDecals/ProjectionTarget.cs b/Source/ConformalDecals/ProjectionTarget.cs index f94dc01..8829123 100644 --- a/Source/ConformalDecals/ProjectionTarget.cs +++ b/Source/ConformalDecals/ProjectionTarget.cs @@ -17,30 +17,29 @@ namespace ConformalDecals { private bool _projectionEnabled; // property block - public readonly MaterialPropertyBlock decalMPB; + private readonly MaterialPropertyBlock _decalMPB; - public ProjectionTarget(MeshRenderer targetRenderer, Mesh targetMesh, MaterialPropertyCollection properties) { + public ProjectionTarget(MeshRenderer targetRenderer, Mesh targetMesh, bool useBaseNormal) { target = targetRenderer.transform; _targetRenderer = targetRenderer; _targetMesh = targetMesh; var targetMaterial = targetRenderer.sharedMaterial; - decalMPB = new MaterialPropertyBlock(); + _decalMPB = new MaterialPropertyBlock(); - if (properties.UseBaseNormal) { - var normalSrcID = Shader.PropertyToID(properties.BaseNormalSrc); - var normalDestID = Shader.PropertyToID(properties.BaseNormalDest); - var normalDestIDST = Shader.PropertyToID(properties.BaseNormalDest + "_ST"); + if (useBaseNormal) { + var normalID = Shader.PropertyToID("_BumpMap"); + var normalIDST = Shader.PropertyToID("_BumpMap_ST"); - var normal = targetMaterial.GetTexture(normalSrcID); + var normal = targetMaterial.GetTexture(normalID); if (normal != null) { - decalMPB.SetTexture(normalDestID, targetMaterial.GetTexture(normalSrcID)); + _decalMPB.SetTexture(normalID, targetMaterial.GetTexture(normalID)); - var normalScale = targetMaterial.GetTextureScale(normalSrcID); - var normalOffset = targetMaterial.GetTextureOffset(normalSrcID); + var normalScale = targetMaterial.GetTextureScale(normalID); + var normalOffset = targetMaterial.GetTextureOffset(normalID); - decalMPB.SetVector(normalDestIDST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y)); + _decalMPB.SetVector(normalIDST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y)); } } } @@ -55,9 +54,9 @@ namespace ConformalDecals { var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized; var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized; - decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix); - decalMPB.SetVector(_decalNormalID, decalNormal); - decalMPB.SetVector(_decalTangentID, decalTangent); + _decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix); + _decalMPB.SetVector(_decalNormalID, decalNormal); + _decalMPB.SetVector(_decalTangentID, decalTangent); Debug.Log($"Projection enabled for {target.gameObject}"); } else { @@ -68,10 +67,10 @@ namespace ConformalDecals { public bool Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) { if (_projectionEnabled) { - decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff)); - decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor)); + _decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff)); + _decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor)); - Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, decalMPB, ShadowCastingMode.Off, true); + Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true); return true; }