mirror of
https://github.com/drewcassidy/KSP-Conformal-Decals.git
synced 2024-09-01 18:23:54 +00:00
Rename MaterialModifiers namespace
This commit is contained in:
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ConformalDecals.MaterialProperties {
|
||||
public class MaterialColorProperty : MaterialProperty {
|
||||
[SerializeField] public Color color;
|
||||
|
||||
public override void ParseNode(ConfigNode node) {
|
||||
base.ParseNode(node);
|
||||
|
||||
color = ParsePropertyColor(node, "color", true, color);
|
||||
}
|
||||
|
||||
public override void Modify(Material material) {
|
||||
if (material == null) throw new ArgumentNullException("material cannot be null");
|
||||
|
||||
material.SetColor(_propertyID, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public override void Modify(Material material) {
|
||||
if (material == null) throw new ArgumentNullException("material cannot be null");
|
||||
|
||||
material.SetFloat(_propertyID, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
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);
|
||||
|
||||
private delegate bool TryParseDelegate<T>(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<T>(ConfigNode node, string valueName, TryParseDelegate<T> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using UniLinq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace ConformalDecals.MaterialProperties {
|
||||
public class MaterialPropertyCollection : ScriptableObject, ISerializationCallbackReceiver {
|
||||
public int RenderQueue {
|
||||
get => _renderQueue;
|
||||
set {
|
||||
_renderQueue = value;
|
||||
if (_decalMaterial != null) _decalMaterial.renderQueue = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private Shader _shader;
|
||||
[SerializeField] private MaterialTextureProperty _mainTexture;
|
||||
[SerializeField] private string[] _serializedNames;
|
||||
[SerializeField] private MaterialProperty[] _serializedProperties;
|
||||
|
||||
|
||||
private Dictionary<string, MaterialProperty> _materialProperties;
|
||||
|
||||
private Material _decalMaterial;
|
||||
private Material _previewMaterial;
|
||||
private int _renderQueue = 2100;
|
||||
|
||||
public Shader DecalShader => _shader;
|
||||
|
||||
public Material DecalMaterial {
|
||||
get {
|
||||
if (_decalMaterial == null) {
|
||||
_decalMaterial = new Material(_shader);
|
||||
|
||||
_decalMaterial.SetInt(DecalPropertyIDs._Cull, (int) CullMode.Off);
|
||||
_decalMaterial.renderQueue = RenderQueue;
|
||||
}
|
||||
|
||||
return _decalMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
public Material PreviewMaterial {
|
||||
get {
|
||||
if (_previewMaterial == null) {
|
||||
_previewMaterial = new Material(_shader);
|
||||
|
||||
_previewMaterial.EnableKeyword("DECAL_PREVIEW");
|
||||
_previewMaterial.SetInt(DecalPropertyIDs._Cull, (int) CullMode.Back);
|
||||
}
|
||||
|
||||
return _previewMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
public MaterialTextureProperty MainTexture {
|
||||
get => _mainTexture;
|
||||
set {
|
||||
if (!_materialProperties.ContainsValue(value))
|
||||
throw new ArgumentException($"Texture property {value.name} is not part of this property collection.");
|
||||
|
||||
_mainTexture = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float AspectRatio => MainTexture == null ? 1 : MainTexture.AspectRatio;
|
||||
|
||||
public void OnBeforeSerialize() {
|
||||
Debug.Log($"Serializing MaterialPropertyCollection {this.GetInstanceID()}");
|
||||
if (_materialProperties == null) throw new SerializationException("Tried to serialize an uninitialized MaterialPropertyCollection");
|
||||
|
||||
_serializedNames = _materialProperties.Keys.ToArray();
|
||||
_serializedProperties = _materialProperties.Values.ToArray();
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize() {
|
||||
Debug.Log($"Deserializing MaterialPropertyCollection {this.GetInstanceID()}");
|
||||
if (_serializedNames == null) throw new SerializationException("ID array is null");
|
||||
if (_serializedProperties == null) throw new SerializationException("Property array is null");
|
||||
if (_serializedProperties.Length != _serializedNames.Length) throw new SerializationException("Material property arrays are different lengths.");
|
||||
|
||||
_materialProperties ??= new Dictionary<string, MaterialProperty>();
|
||||
|
||||
for (var i = 0; i < _serializedNames.Length; i++) {
|
||||
var property = MaterialProperty.Instantiate(_serializedProperties[i]);
|
||||
_materialProperties.Add(_serializedNames[i], property);
|
||||
|
||||
if (property is MaterialTextureProperty textureProperty && textureProperty.isMain) {
|
||||
_mainTexture = textureProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Awake() {
|
||||
Debug.Log($"MaterialPropertyCollection {this.GetInstanceID()} onAwake");
|
||||
_materialProperties ??= new Dictionary<string, MaterialProperty>();
|
||||
}
|
||||
|
||||
public void OnDestroy() {
|
||||
if (_decalMaterial != null) Destroy(_decalMaterial);
|
||||
if (_previewMaterial != null) Destroy(_previewMaterial);
|
||||
|
||||
foreach (var entry in _materialProperties) {
|
||||
Destroy(entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddProperty(MaterialProperty property) {
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
_materialProperties.Add(property.name, property);
|
||||
|
||||
if (property is MaterialTextureProperty textureProperty) {
|
||||
if (textureProperty.isMain) _mainTexture = textureProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public T AddProperty<T>(string propertyName) where T : MaterialProperty {
|
||||
if (_materialProperties.ContainsKey(propertyName)) throw new ArgumentException("property with that name already exists!");
|
||||
var newProperty = MaterialProperty.CreateInstance<T>();
|
||||
newProperty.PropertyName = propertyName;
|
||||
_materialProperties.Add(propertyName, newProperty);
|
||||
|
||||
return newProperty;
|
||||
}
|
||||
|
||||
public T GetProperty<T>(string propertyName) where T : MaterialProperty {
|
||||
if (_materialProperties.ContainsKey(propertyName) && _materialProperties[propertyName] is T property) {
|
||||
return property;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public T AddOrGetProperty<T>(string propertyName) where T : MaterialProperty {
|
||||
if (_materialProperties.ContainsKey(propertyName) && _materialProperties[propertyName] is T property) {
|
||||
return property;
|
||||
}
|
||||
else {
|
||||
return AddProperty<T>(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
public MaterialTextureProperty AddTextureProperty(string propertyName, bool isMain = false) {
|
||||
var newProperty = AddProperty<MaterialTextureProperty>(propertyName);
|
||||
if (isMain) _mainTexture = newProperty;
|
||||
|
||||
return newProperty;
|
||||
}
|
||||
|
||||
public MaterialTextureProperty GetTextureProperty(string propertyName) {
|
||||
return GetProperty<MaterialTextureProperty>(propertyName);
|
||||
}
|
||||
|
||||
public MaterialTextureProperty AddOrGetTextureProperty(string propertyName, bool isMain = false) {
|
||||
var newProperty = AddOrGetProperty<MaterialTextureProperty>(propertyName);
|
||||
if (isMain) _mainTexture = newProperty;
|
||||
|
||||
return newProperty;
|
||||
}
|
||||
|
||||
public T ParseProperty<T>(ConfigNode node) where T : MaterialProperty {
|
||||
var propertyName = node.GetValue("name");
|
||||
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("node has no name");
|
||||
|
||||
var newProperty = AddOrGetProperty<T>(propertyName);
|
||||
newProperty.ParseNode(node);
|
||||
|
||||
if (newProperty is MaterialTextureProperty textureProperty && textureProperty.isMain) {
|
||||
_mainTexture = textureProperty;
|
||||
}
|
||||
|
||||
return newProperty;
|
||||
}
|
||||
|
||||
public void SetShader(string shaderName) {
|
||||
if (string.IsNullOrEmpty(shaderName)) {
|
||||
if (_shader == null) {
|
||||
Debug.Log("Using default decal shader");
|
||||
shaderName = "ConformalDecals/Paint/Diffuse";
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var shader = Shabby.Shabby.FindShader(shaderName);
|
||||
|
||||
if (shader == null) throw new FormatException($"Unable to find specified shader '{shaderName}'");
|
||||
|
||||
_shader = shader;
|
||||
_decalMaterial = null;
|
||||
_previewMaterial = null;
|
||||
}
|
||||
|
||||
public void UpdateScale(Vector2 scale) {
|
||||
foreach (var entry in _materialProperties) {
|
||||
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!");
|
||||
var mainTexSize = _mainTexture.Dimensions;
|
||||
|
||||
Debug.Log($"Main texture is {_mainTexture.PropertyName} and its size is {mainTexSize}");
|
||||
|
||||
foreach (var entry in _materialProperties) {
|
||||
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 x = index % tileCountX;
|
||||
int y = index / tileCountX;
|
||||
|
||||
var tile = new Rect(x * tileSize.x, y * tileSize.y, tileSize.x, tileSize.y);
|
||||
|
||||
UpdateTile(tile);
|
||||
}
|
||||
|
||||
public void SetOpacity(float opacity) {
|
||||
DecalMaterial.SetFloat(DecalPropertyIDs._DecalOpacity, opacity);
|
||||
PreviewMaterial.SetFloat(DecalPropertyIDs._DecalOpacity, opacity);
|
||||
}
|
||||
|
||||
public void SetCutoff(float cutoff) {
|
||||
DecalMaterial.SetFloat(DecalPropertyIDs._Cutoff, cutoff);
|
||||
PreviewMaterial.SetFloat(DecalPropertyIDs._Cutoff, cutoff);
|
||||
}
|
||||
|
||||
public void SetWear(float wear) {
|
||||
DecalMaterial.SetFloat(DecalPropertyIDs._EdgeWearStrength, wear);
|
||||
}
|
||||
|
||||
public void UpdateMaterials() {
|
||||
UpdateMaterial(DecalMaterial);
|
||||
UpdateMaterial(PreviewMaterial);
|
||||
}
|
||||
|
||||
public void UpdateMaterial(Material material) {
|
||||
if (material == null) throw new ArgumentNullException(nameof(material));
|
||||
|
||||
foreach (var entry in _materialProperties) {
|
||||
entry.Value.Modify(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ConformalDecals.MaterialProperties {
|
||||
public class MaterialTextureProperty : MaterialProperty {
|
||||
[SerializeField] public bool isNormal;
|
||||
[SerializeField] public bool isMain;
|
||||
[SerializeField] public bool autoScale;
|
||||
[SerializeField] public bool autoTile;
|
||||
|
||||
[SerializeField] private string _textureUrl;
|
||||
[SerializeField] private Texture2D _texture;
|
||||
|
||||
[SerializeField] private bool _hasTile;
|
||||
[SerializeField] private Rect _tileRect;
|
||||
|
||||
[SerializeField] private Vector2 _scale = Vector2.one;
|
||||
[SerializeField] private Vector2 _textureOffset;
|
||||
[SerializeField] private Vector2 _textureScale = Vector2.one;
|
||||
|
||||
public Texture2D Texture => _texture;
|
||||
|
||||
public string TextureUrl {
|
||||
get => _textureUrl;
|
||||
set {
|
||||
_texture = LoadTexture(value, isNormal);
|
||||
_textureUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Width => _texture.width;
|
||||
public int Height => _texture.height;
|
||||
|
||||
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 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);
|
||||
|
||||
var textureUrl = node.GetValue("textureUrl");
|
||||
|
||||
if (string.IsNullOrEmpty(textureUrl)) {
|
||||
if (string.IsNullOrEmpty(_textureUrl)) {
|
||||
TextureUrl = "";
|
||||
}
|
||||
}
|
||||
else {
|
||||
TextureUrl = node.GetValue("textureUrl");
|
||||
}
|
||||
|
||||
if (node.HasValue("tile") && !autoTile) {
|
||||
SetTile(ParsePropertyRect(node, "tile", true, _tileRect));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Modify(Material material) {
|
||||
if (material == null) throw new ArgumentNullException(nameof(material));
|
||||
if (_texture == null) {
|
||||
_texture = Texture2D.whiteTexture;
|
||||
throw new NullReferenceException("texture is null, but should not be");
|
||||
}
|
||||
|
||||
material.SetTexture(_propertyID, _texture);
|
||||
material.SetTextureOffset(_propertyID, _textureOffset);
|
||||
material.SetTextureScale(_propertyID, _textureScale * _scale);
|
||||
}
|
||||
|
||||
public void SetScale(Vector2 scale) {
|
||||
_scale = scale;
|
||||
}
|
||||
|
||||
public void SetTile(Rect tile) {
|
||||
SetTile(tile, Dimensions);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user