From 79bdc03c4b7f67049613c467763cf5f4bd4ab5c2 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Sun, 7 Jun 2020 19:39:09 -0700 Subject: [PATCH] Add tiling index and size values, for easier tiling Also API changes made as I chased a bug --- .../Plugins/ConformalDecals.dll | Bin 35328 -> 35328 bytes .../MaterialPropertyCollection.cs | 98 +++++------- .../MaterialTextureProperty.cs | 143 ++++++++++-------- .../ConformalDecals/ModuleConformalDecal.cs | 42 ++--- Source/ConformalDecals/ModuleConformalFlag.cs | 7 +- Source/ConformalDecals/ProjectionTarget.cs | 2 - 6 files changed, 143 insertions(+), 149 deletions(-) diff --git a/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 5e4bdd4706347b346fbb89a90d442133a5f5fc82..557a85effbc625702d0f1818a5b0ce3f4c2defe6 100644 GIT binary patch literal 35328 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 literal 35328 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 N7XSYe|Fb;szX28C)~^5n diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs b/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs index f0b7e14..4c818df 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialPropertyCollection.cs @@ -23,7 +23,6 @@ namespace ConformalDecals.MaterialModifiers { get { if (_decalMaterial == null) { _decalMaterial = new Material(_shader); - UpdateMaterial(_decalMaterial); _decalMaterial.SetInt(DecalPropertyIDs._Cull, (int) CullMode.Off); } @@ -36,7 +35,6 @@ namespace ConformalDecals.MaterialModifiers { get { if (_previewMaterial == null) { _previewMaterial = new Material(_shader); - UpdateMaterial(_previewMaterial); _previewMaterial.EnableKeyword("DECAL_PREVIEW"); _previewMaterial.SetInt(DecalPropertyIDs._Cull, (int) CullMode.Back); @@ -79,7 +77,7 @@ namespace ConformalDecals.MaterialModifiers { Debug.Log($"insantiating {property.GetType().Name} {property.GetInstanceID()}"); _materialProperties.Add(_serializedNames[i], property); - if (property is MaterialTextureProperty textureProperty) { + if (property is MaterialTextureProperty textureProperty && textureProperty.isMain) { _mainTexture = textureProperty; } } @@ -105,7 +103,7 @@ namespace ConformalDecals.MaterialModifiers { _materialProperties.Add(property.name, property); if (property is MaterialTextureProperty textureProperty) { - if (textureProperty.isMain) _mainTexture ??= textureProperty; + if (textureProperty.isMain) _mainTexture = textureProperty; } } @@ -114,6 +112,7 @@ namespace ConformalDecals.MaterialModifiers { var newProperty = MaterialProperty.CreateInstance(); newProperty.PropertyName = propertyName; _materialProperties.Add(propertyName, newProperty); + return newProperty; } @@ -137,7 +136,8 @@ namespace ConformalDecals.MaterialModifiers { public MaterialTextureProperty AddTextureProperty(string propertyName, bool isMain = false) { var newProperty = AddProperty(propertyName); - if (isMain) MainTexture = newProperty; + if (isMain) _mainTexture = newProperty; + return newProperty; } @@ -146,41 +146,26 @@ namespace ConformalDecals.MaterialModifiers { } public MaterialTextureProperty AddOrGetTextureProperty(string propertyName, bool isMain = false) { - if (_materialProperties.ContainsKey(propertyName) && _materialProperties[propertyName] is MaterialTextureProperty property) { - return property; - } - else { - return AddTextureProperty(propertyName, isMain); - } + var newProperty = AddOrGetProperty(propertyName); + if (isMain) _mainTexture = newProperty; + + return newProperty; } - public void ParseProperty(ConfigNode node) where T : MaterialProperty { + public T ParseProperty(ConfigNode node) where T : MaterialProperty { var propertyName = node.GetValue("name"); if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("node has no name"); - Debug.Log($"Parsing material property {propertyName}"); - T newProperty; - - if (_materialProperties.ContainsKey(propertyName)) { - if (_materialProperties[propertyName] is T property) { - newProperty = property; - property.ParseNode(node); - } - else { - throw new ArgumentException("Material property already exists for {name} but it has a different type"); - } - } - else { - newProperty = MaterialProperty.CreateInstance(); - Debug.Log($"Adding new material property of type {newProperty.GetType().Name} {newProperty.GetInstanceID()}"); - newProperty.ParseNode(node); - _materialProperties.Add(propertyName, newProperty); - } + var newProperty = AddOrGetProperty(propertyName); + newProperty.ParseNode(node); if (newProperty is MaterialTextureProperty textureProperty && textureProperty.isMain) { + Debug.Log("new texture has isMain enabled"); _mainTexture = textureProperty; } + + return newProperty; } public void SetShader(string shaderName) { @@ -209,32 +194,35 @@ namespace ConformalDecals.MaterialModifiers { public void UpdateScale(Vector2 scale) { foreach (var entry in _materialProperties) { - if (entry.Value is MaterialTextureProperty textureProperty) { - textureProperty.UpdateScale(scale); - textureProperty.Modify(_decalMaterial); - textureProperty.Modify(_previewMaterial); + if (entry.Value is MaterialTextureProperty textureProperty && textureProperty.autoScale) { + textureProperty.SetScale(scale); } } } public void UpdateTile(Rect tile) { if (_mainTexture == null) throw new InvalidOperationException("UpdateTile called but no main texture is specified!"); - Vector2 scale; - Vector2 offset; + var mainTexSize = _mainTexture.Dimensions; - scale.x = tile.width / _mainTexture.texture.width; - scale.y = tile.height / _mainTexture.texture.height; + Debug.Log($"Main texture is {_mainTexture.PropertyName} and its size is {mainTexSize}"); - offset.x = tile.x / _mainTexture.texture.width; - offset.y = tile.y / _mainTexture.texture.height; - foreach (var entry in _materialProperties) { - if (entry.Value is MaterialTextureProperty textureProperty) { - textureProperty.UpdateTiling(scale, offset); - textureProperty.Modify(_decalMaterial); - textureProperty.Modify(_previewMaterial); + if (entry.Value is MaterialTextureProperty textureProperty && textureProperty.autoTile) { + textureProperty.SetTile(tile, mainTexSize); } - } + } + } + + public void UpdateTile(int index, Vector2 tileSize) { + int tileCountX = (int) (_mainTexture.Width / tileSize.x); + int tileCountY = (int) (_mainTexture.Height / tileSize.y); + + int x = index % tileCountX; + int y = index / tileCountY; + + var tile = new Rect(x * tileSize.x, y * tileSize.y, tileSize.x, tileSize.y); + + UpdateTile(tile); } public void SetOpacity(float opacity) { @@ -248,28 +236,14 @@ namespace ConformalDecals.MaterialModifiers { } public void UpdateMaterials() { - if (_decalMaterial == null) { - _decalMaterial = DecalMaterial; - } - else { - UpdateMaterial(_decalMaterial); - } - - if (_previewMaterial == null) { - _previewMaterial = PreviewMaterial; - } - else { - UpdateMaterial(_previewMaterial); - } + UpdateMaterial(DecalMaterial); + UpdateMaterial(PreviewMaterial); } public void UpdateMaterial(Material material) { if (material == null) throw new ArgumentNullException(nameof(material)); - foreach (var entry in _materialProperties) { - Debug.Log($"Applying material property {entry.Key} {entry.Value.PropertyName} {entry.Value.GetInstanceID()}"); - entry.Value.Modify(material); } } diff --git a/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs b/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs index d5c7681..b891873 100644 --- a/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs +++ b/Source/ConformalDecals/MaterialModifiers/MaterialTextureProperty.cs @@ -2,40 +2,49 @@ using System; using UnityEngine; namespace ConformalDecals.MaterialModifiers { - public class MaterialTextureProperty : MaterialProperty { - [SerializeField] public Texture2D texture; - + public class MaterialTextureProperty : MaterialProperty, ISerializationCallbackReceiver { [SerializeField] public bool isNormal; [SerializeField] public bool isMain; [SerializeField] public bool autoScale; [SerializeField] public bool autoTile; - [SerializeField] private bool _hasTile; - [SerializeField] private Rect _tileRect; - [SerializeField] private Vector2 _baseTextureScale = Vector2.one; - - [SerializeField] private Vector2 _textureOffset = Vector2.zero; - [SerializeField] private Vector2 _textureScale = Vector2.one; + [SerializeField] private string _textureUrl; + + private Texture2D _texture; - public float AspectRatio { - get { - if (texture == null) return 1; + [SerializeField] private bool _hasTile; + [SerializeField] private Rect _tileRect; - if (!_hasTile) return ((float) texture.height) / texture.width; + [SerializeField] private Vector2 _scale = Vector2.one; + [SerializeField] private Vector2 _textureOffset; + [SerializeField] private Vector2 _textureScale = Vector2.one; - return _tileRect.height / _tileRect.width; + public Texture2D Texture => _texture; + + public string TextureUrl { + get => _textureUrl; + set { + _texture = LoadTexture(value, isNormal); + _textureUrl = value; } } - public Rect TileRect { - get => _tileRect; - set { - if (autoTile) return; - _hasTile = !(Mathf.Abs(value.width) < 0.1) || !(Mathf.Abs(value.height) < 0.1); + public int Width => _texture.width; + public int Height => _texture.height; - _tileRect = value; - UpdateTiling(); - } + public int MaskedWidth => _hasTile ? (int) _tileRect.width : _texture.width; + public int MaskedHeight => _hasTile ? (int) _tileRect.height : _texture.height; + + public Vector2 Dimensions => new Vector2(_texture.width, _texture.height); + public Vector2 MaskedDimensions => _hasTile ? _tileRect.size : Dimensions; + + public float AspectRatio => MaskedHeight / (float) MaskedWidth; + + public void OnBeforeSerialize() { } + + public void OnAfterDeserialize() { + // Unity appears to be screwing up textures when deserializing them, so this is the fix? + _texture = LoadTexture(_textureUrl, isNormal); } public override void ParseNode(ConfigNode node) { @@ -46,68 +55,78 @@ namespace ConformalDecals.MaterialModifiers { autoScale = ParsePropertyBool(node, "autoScale", true, autoScale); autoTile = ParsePropertyBool(node, "autoTile", true, autoTile); - SetTexture(node.GetValue("textureUrl")); + var textureUrl = node.GetValue("textureUrl"); - if (node.HasValue("tileRect") && !autoTile) { - TileRect = ParsePropertyRect(node, "tileRect", true, _tileRect); - } - } - - public void SetTexture(string textureUrl) { - if ((textureUrl == null && isNormal) || textureUrl == "Bump") { - texture = Texture2D.normalTexture; - } - else if ((textureUrl == null && !isNormal) || textureUrl == "White") { - texture = Texture2D.whiteTexture; - } - else if (textureUrl == "Black") { - texture = Texture2D.blackTexture; + if (string.IsNullOrEmpty(textureUrl)) { + if (string.IsNullOrEmpty(_textureUrl)) { + TextureUrl = ""; + } } else { - var textureInfo = GameDatabase.Instance.GetTextureInfo(textureUrl); - - if (textureInfo == null) throw new Exception($"Cannot find texture: '{textureUrl}'"); + TextureUrl = node.GetValue("textureUrl"); + } + + Debug.Log($"parsed texture node with texture {_textureUrl}, {isMain}"); - texture = isNormal ? textureInfo.normalMap : textureInfo.texture; + if (node.HasValue("tileRect") && !autoTile) { + SetTile(ParsePropertyRect(node, "tileRect", true, _tileRect)); } - if (texture == null) throw new Exception($"Cannot get texture from texture info '{textureUrl}', isNormalMap = {isNormal}"); - UpdateTiling(); } public override void Modify(Material material) { if (material == null) throw new ArgumentNullException(nameof(material)); - if (texture == null) { - texture = Texture2D.whiteTexture; + if (_texture == null) { + _texture = Texture2D.whiteTexture; throw new NullReferenceException("texture is null, but should not be"); } - material.SetTexture(_propertyID, texture); + material.SetTexture(_propertyID, _texture); material.SetTextureOffset(_propertyID, _textureOffset); - material.SetTextureScale(_propertyID, _textureScale); + material.SetTextureScale(_propertyID, _textureScale * _scale); } - public void UpdateScale(Vector2 scale) { - if (autoScale) { - _textureScale = _baseTextureScale * scale; - } + public void SetScale(Vector2 scale) { + _scale = scale; } - public void UpdateTiling(Vector2 textureScale, Vector2 textureOffset) { - if (autoTile) { - _textureScale = textureScale; - _textureOffset = textureOffset; - } + public void SetTile(Rect tile) { + SetTile(tile, new Vector2(_texture.width, _texture.height)); } - private void UpdateTiling() { - if (_hasTile) { - _baseTextureScale.x = Mathf.Approximately(0, _tileRect.width) ? 1 : _tileRect.width / texture.width; - _baseTextureScale.y = Mathf.Approximately(0, _tileRect.height) ? 1 : _tileRect.height / texture.height; + public void SetTile(Rect tile, Vector2 mainTexDimensions) { + var scale = tile.size; + var offset = tile.position; + + // invert y axis to deal with DXT image orientation + offset.y = mainTexDimensions.y - offset.y - tile.height; + + // fit to given dimensions + scale /= mainTexDimensions; + offset /= mainTexDimensions; + _tileRect = tile; + _hasTile = true; + _textureScale = scale; + _textureOffset = offset; + } + + private static Texture2D LoadTexture(string textureUrl, bool isNormal) { + Debug.Log($"loading texture '{textureUrl}', isNormalMap = {isNormal}"); + if ((string.IsNullOrEmpty(textureUrl) && isNormal) || textureUrl == "Bump") { + return Texture2D.normalTexture; } - else { - _baseTextureScale = Vector2.one; + + if ((string.IsNullOrEmpty(textureUrl) && !isNormal) || textureUrl == "White") { + return Texture2D.whiteTexture; } + + if (textureUrl == "Black") { + return Texture2D.blackTexture; + } + + var texture = GameDatabase.Instance.GetTexture(textureUrl, isNormal); + if (texture == null) throw new Exception($"Cannot get texture '{textureUrl}', isNormalMap = {isNormal}"); + return texture; } } } \ No newline at end of file diff --git a/Source/ConformalDecals/ModuleConformalDecal.cs b/Source/ConformalDecals/ModuleConformalDecal.cs index 9c6e425..ef0dd00 100644 --- a/Source/ConformalDecals/ModuleConformalDecal.cs +++ b/Source/ConformalDecals/ModuleConformalDecal.cs @@ -44,6 +44,10 @@ namespace ConformalDecals { [KSPField] public Vector2 opacityRange = new Vector2(0, 1); [KSPField] public Vector2 cutoffRange = new Vector2(0, 1); + [KSPField] public Rect tileRect = new Rect(-1, -1, 0, 0); + [KSPField] public Vector2 tileSize; + [KSPField] public int tileIndex = -1; + [KSPField] public bool updateBackScale = true; [KSPField] public bool useBaseNormal = true; @@ -65,7 +69,6 @@ namespace ConformalDecals { private Material _decalMaterial; private Material _previewMaterial; - private int DecalQueue { get { _decalQueueCounter++; @@ -76,7 +79,7 @@ namespace ConformalDecals { return _decalQueueCounter; } } - + public override void OnAwake() { base.OnAwake(); @@ -87,12 +90,13 @@ namespace ConformalDecals { materialProperties = ScriptableObject.Instantiate(materialProperties); } } - + public override void OnLoad(ConfigNode node) { this.Log("Loading module"); + this.Log($"{node.ToString()}"); try { // SETUP TRANSFORMS - + // find front transform decalFrontTransform = part.FindModelTransform(decalFront); if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'."); @@ -147,12 +151,12 @@ namespace ConformalDecals { } } } - + // PARSE MATERIAL PROPERTIES // set shader materialProperties.SetShader(shader); - + // add texture nodes foreach (var textureNode in node.GetNodes("TEXTURE")) { materialProperties.ParseProperty(textureNode); @@ -167,15 +171,18 @@ namespace ConformalDecals { foreach (var colorNode in node.GetNodes("COLOR")) { materialProperties.ParseProperty(colorNode); } - - var tileString = node.GetValue("tile"); - if (!string.IsNullOrEmpty(tileString)) { - var tileValid = ParseExtensions.TryParseRect(tileString, out var tile); - if (!tileValid) throw new FormatException($"Improperly formatted value for tile '{tileString}"); - else { - materialProperties.UpdateTile(tile); - } + // handle texture tiling parameters + + this.Log($"TileRect {tileRect}"); + this.Log($"TileSize {tileSize}"); + this.Log($"TileIndex {tileIndex}"); + + if (tileRect.x >= 0) { + materialProperties.UpdateTile(tileRect); + } + else if (tileIndex >= 0) { + materialProperties.UpdateTile(tileIndex, tileSize); } // QUEUE PART FOR ICON FIXING IN VAB @@ -191,7 +198,7 @@ namespace ConformalDecals { UpdateProjection(); } } - + public override void OnIconCreate() { UpdateScale(); } @@ -238,6 +245,7 @@ namespace ConformalDecals { // scale or depth values have been changed, so update scale // and update projection matrices if attached UpdateScale(); + UpdateMaterials(); if (_isAttached) UpdateProjection(); } @@ -279,7 +287,6 @@ namespace ConformalDecals { this.Log($"Decal attached to {part.parent.partName}"); - // hide preview model decalFrontTransform.gameObject.SetActive(false); decalBackTransform.gameObject.SetActive(false); @@ -287,6 +294,7 @@ namespace ConformalDecals { // add to preCull delegate Camera.onPreCull += Render; + UpdateMaterials(); UpdateScale(); UpdateTargets(); UpdateProjection(); @@ -302,12 +310,12 @@ namespace ConformalDecals { // remove from preCull delegate Camera.onPreCull -= Render; + UpdateMaterials(); UpdateScale(); } protected void UpdateScale() { var aspectRatio = materialProperties.AspectRatio; - this.Log($"Aspect ratio is {aspectRatio}"); var size = new Vector2(scale, scale * aspectRatio); // update orthogonal matrix scale diff --git a/Source/ConformalDecals/ModuleConformalFlag.cs b/Source/ConformalDecals/ModuleConformalFlag.cs index 0e12811..3cbaaef 100644 --- a/Source/ConformalDecals/ModuleConformalFlag.cs +++ b/Source/ConformalDecals/ModuleConformalFlag.cs @@ -36,13 +36,8 @@ namespace ConformalDecals { private void UpdateFlag(string flagUrl) { this.Log($"Loading flag texture '{flagUrl}'."); - var flagTexture = GameDatabase.Instance.GetTexture(flagUrl, false); - if (flagTexture == null) { - this.LogWarning($"Unable to find flag texture '{flagUrl}'."); - return; - } - materialProperties.AddOrGetTextureProperty("_Decal", true).texture = flagTexture; + materialProperties.AddOrGetTextureProperty("_Decal", true).TextureUrl = flagUrl; UpdateMaterials(); } diff --git a/Source/ConformalDecals/ProjectionTarget.cs b/Source/ConformalDecals/ProjectionTarget.cs index 7f79308..232f9cf 100644 --- a/Source/ConformalDecals/ProjectionTarget.cs +++ b/Source/ConformalDecals/ProjectionTarget.cs @@ -36,7 +36,6 @@ namespace ConformalDecals { _decalMPB.SetMatrix(DecalPropertyIDs._ProjectionMatrix, projectionMatrix); _decalMPB.SetVector(DecalPropertyIDs._DecalNormal, decalNormal); _decalMPB.SetVector(DecalPropertyIDs._DecalTangent, decalTangent); - Debug.Log($"Projection enabled for {target.gameObject}"); if (useBaseNormal && targetMaterial.HasProperty(DecalPropertyIDs._BumpMap)) { var normal = targetMaterial.GetTexture(DecalPropertyIDs._BumpMap); @@ -53,7 +52,6 @@ namespace ConformalDecals { } else { _projectionEnabled = false; - Debug.Log($"Projection disabled for {target.gameObject}"); } }