diff --git a/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 5e4bdd4..557a85e 100644 Binary files a/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ 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}"); } }