// Material/Shader Inspector for Unity 2017/2018 // Copyright (C) 2019 Thryrallo using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; namespace Thry { #region Constants public class PATH { public const string TEXTURES_DIR = "Assets/textures"; public const string RSP_NEEDED_PATH = "Assets/"; public const string DELETING_DIR = "Thry/trash"; public const string PERSISTENT_DATA = "Thry/persistent_data"; public const string GRADIENT_INFO_FILE = "Thry/gradients"; public const string LINKED_MATERIALS_FILE = "Thry/linked_materials.json"; } public class URL { public const string MODULE_COLLECTION = "https://raw.githubusercontent.com/Thryrallo/ThryEditorStreamedResources/main/packages.json"; public const string SETTINGS_MESSAGE_URL = "https://raw.githubusercontent.com/Thryrallo/ThryEditorStreamedResources/main/Messages/settingsWindow.json"; public const string COUNT_PROJECT = "http://thryeditor.thryrallo.de/count_project.php"; public const string COUNT_USER = "http://thryeditor.thryrallo.de/count_user.php"; } public class DEFINE_SYMBOLS { public const string IMAGING_EXISTS = "IMAGING_DLL_EXISTS"; } public class RESOURCE_GUID { public const string RECT = "2329f8696fd09a743a5baf2a5f4986af"; public const string ICON_LINK = "e85fd0a0e4e4fea46bb3fdeab5c3fb07"; public const string ICON_THRY = "693aa4c2cdc578346a196469a06ddbba"; } #endregion public class DrawingData { public static TextureProperty CurrentTextureProperty; public static Rect LastGuiObjectRect; public static Rect LastGuiObjectHeaderRect; public static Rect TooltipCheckRect; public static bool LastPropertyUsedCustomDrawer; public static bool LastPropertyDoesntAllowAnimation; public static DrawerType LastPropertyDrawerType; public static MaterialPropertyDrawer LastPropertyDrawer; public static List LastPropertyDecorators = new List(); public static bool IsEnabled = true; public static bool IsCollectingProperties = false; public static ShaderPart LastInitiatedPart; public static void ResetLastDrawerData() { LastPropertyUsedCustomDrawer = false; LastPropertyDoesntAllowAnimation = false; LastPropertyDrawer = null; LastPropertyDrawerType = DrawerType.None; LastPropertyDecorators.Clear(); } public static void RegisterDecorator(MaterialPropertyDrawer drawer) { if(IsCollectingProperties) { LastPropertyDecorators.Add(drawer); } } } public enum DrawerType { None } public class GradientData { public Texture PreviewTexture; public Gradient Gradient; } public enum TextureDisplayType { small, big, big_basic } //--------------Shader Data Structs-------------------- #region In Shader Data public class PropertyOptions { public string tooltip = ""; public DefineableAction altClick; public DefineableAction onClick; public DefineableCondition condition_show = new DefineableCondition(); public string condition_showS; public DefineableCondition condition_enable = null; public PropertyValueAction[] on_value_actions; public string on_value; public DefineableAction[] actions; public ButtonData button_help; public TextureData texture; public string[] reference_properties; public string reference_property; public bool force_texture_options = false; public bool is_visible_simple = false; public string file_name; public string remote_version_url; public string generic_string; public bool never_lock; public static PropertyOptions Deserialize(string s) { if(s == null) return new PropertyOptions(); s = s.Replace("''", "\""); PropertyOptions options = Parser.Deserialize(s); if (options == null) return new PropertyOptions(); // The following could be removed since the parser can now handle it. leaving it in for now /shrug if (options.condition_showS != null) { options.condition_show = DefineableCondition.Parse(options.condition_showS); } if (options.on_value != null) { options.on_value_actions = PropertyValueAction.ParseToArray(options.on_value); } return options; } } public class ButtonData { public string text = ""; public TextureData texture = null; public DefineableAction action = new DefineableAction(); public string hover = ""; public bool center_position = false; public DefineableCondition condition_show = new DefineableCondition(); } public class TextureData { public string name = null; public string guid = null; public int width = 128; public int height = 128; public char channel = 'r'; public int ansioLevel = 1; public FilterMode filterMode = FilterMode.Bilinear; public TextureWrapMode wrapMode = TextureWrapMode.Repeat; public bool center_position = false; bool _isLoading; public void ApplyModes(Texture texture) { texture.filterMode = filterMode; texture.wrapMode = wrapMode; texture.anisoLevel = ansioLevel; } public void ApplyModes(string path) { TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(path); importer.filterMode = filterMode; importer.wrapMode = wrapMode; importer.anisoLevel = ansioLevel; importer.SaveAndReimport(); } static Dictionary s_loaded_textures = new Dictionary(); public Texture loaded_texture { get { if(guid != null) { if(!s_loaded_textures.ContainsKey(guid) || s_loaded_textures[guid] == null) { string path = AssetDatabase.GUIDToAssetPath(guid); if (path != null) s_loaded_textures[guid] = AssetDatabase.LoadAssetAtPath(path); else s_loaded_textures[guid] = Texture2D.whiteTexture; } return s_loaded_textures[guid]; }else if(name != null) { if(!s_loaded_textures.ContainsKey(name) || s_loaded_textures[name] == null) { if(IsUrl()) { if(!_isLoading) { s_loaded_textures[name] = Texture2D.whiteTexture; WebHelper.DownloadBytesASync(name, (byte[] b) => { _isLoading = false; Texture2D tex = new Texture2D(1,1, TextureFormat.ARGB32, false); ImageConversion.LoadImage(tex, b, false); s_loaded_textures[name] = tex; }); _isLoading = true; } }else { string path = FileHelper.FindFile(name, "texture"); if (path != null) s_loaded_textures[name] = AssetDatabase.LoadAssetAtPath(path); else s_loaded_textures[name] = Texture2D.whiteTexture; } } return s_loaded_textures[name]; } return Texture2D.whiteTexture; } } private static TextureData ParseForThryParser(string s) { if(s.StartsWith("{") == false) { return new TextureData() { name = s }; } return Parser.Deserialize(s); } bool IsUrl() { return name.StartsWith("http") && (name.EndsWith(".jpg") || name.EndsWith(".png")); } } public class PropertyValueAction { public string value; public DefineableAction[] actions; public bool Execute(MaterialProperty p, Material[] targets) { if( (p.type == MaterialProperty.PropType.Float && p.floatValue.ToString() == value) || (p.type == MaterialProperty.PropType.Range && p.floatValue.ToString() == value) || (p.type == MaterialProperty.PropType.Color && p.colorValue.ToString() == value) || (p.type == MaterialProperty.PropType.Vector && p.vectorValue.ToString() == value) || (p.type == MaterialProperty.PropType.Texture && ((p.textureValue == null) == (value == "0"))) || (p.type == MaterialProperty.PropType.Texture && ((p.textureValue != null) == (value == "1"))) || (p.type == MaterialProperty.PropType.Texture && (p.textureValue != null && p.textureValue.name == value)) ) {; foreach (DefineableAction a in actions) a.Perform(targets); return true; } return false; } private static PropertyValueAction ParseForThryParser(string s) { return Parse(s); } // value,property1=value1,property2=value2 public static PropertyValueAction Parse(string s) { s = s.Trim(); string[] valueAndActions = s.Split(new string[]{"=>"}, System.StringSplitOptions.RemoveEmptyEntries); if (valueAndActions.Length > 1) { PropertyValueAction propaction = new PropertyValueAction(); propaction.value = valueAndActions[0]; List actions = new List(); string[] actionStrings = valueAndActions[1].Split(';'); for (int i = 0; i < actionStrings.Length; i++) { if(string.IsNullOrWhiteSpace(actionStrings[i])) continue; actions.Add(DefineableAction.Parse(actionStrings[i])); } propaction.actions = actions.ToArray(); return propaction; } return null; } private static PropertyValueAction[] ParseToArrayForThryParser(string s) { return ParseToArray(s); } public static PropertyValueAction[] ParseToArray(string s) { //s := 0=>p1=v1;p2=v2;1=>p1=v3... List propactions = new List(); string[] valueAndActionMatches = Regex.Matches(s, @"[^;]+=>.+?(?=(;[^;]+=>)|$)", RegexOptions.Multiline).Cast().Select(m => m.Value).ToArray(); foreach (string p in valueAndActionMatches) { PropertyValueAction propertyValueAction = PropertyValueAction.Parse(p); if (propertyValueAction != null) propactions.Add(propertyValueAction); } return propactions.ToArray(); } } public class DefineableAction { public DefineableActionType type = DefineableActionType.NONE; public string data = ""; public void Perform(Material[] targets) { switch (type) { case DefineableActionType.URL: Application.OpenURL(data); break; case DefineableActionType.SET_PROPERTY: string[] set = Regex.Split(data, @"="); if (set.Length > 1) MaterialHelper.SetMaterialValue(set[0].Trim(), set[1].Trim()); break; case DefineableActionType.SET_TAG: string[] keyValue = Regex.Split(data, @"="); foreach (Material m in targets) m.SetOverrideTag(keyValue[0].Trim(), keyValue[1].Trim()); break; case DefineableActionType.SET_SHADER: Shader shader = Shader.Find(data); if (shader != null) { foreach (Material m in targets) m.shader = shader; } break; case DefineableActionType.OPEN_EDITOR: System.Type t = Helper.FindTypeByFullName(data); if (t != null) { try { EditorWindow window = EditorWindow.GetWindow(t); window.titleContent = new GUIContent("TPS Setup Wizard"); window.Show(); }catch(System.Exception e) { Debug.LogError("[Thry] Couldn't open Editor Window of type" + data); Debug.LogException(e); } } break; } } private static DefineableAction ParseForThryParser(string s) { return Parse(s); } public static DefineableAction Parse(string s) { s = s.Trim(); DefineableAction action = new DefineableAction(); if (s.StartsWith("http", StringComparison.Ordinal) || s.StartsWith("www", StringComparison.Ordinal)) { action.type = DefineableActionType.URL; action.data = s; } else if (s.StartsWith("tag::", StringComparison.Ordinal)) { action.type = DefineableActionType.SET_TAG; action.data = s.Replace("tag::", ""); } else if (s.StartsWith("shader=", StringComparison.Ordinal)) { action.type = DefineableActionType.SET_SHADER; action.data = s.Replace("shader=", ""); } else if (s.Contains("=")) { action.type = DefineableActionType.SET_PROPERTY; action.data = s; } else if (Regex.IsMatch(s, @"\w+(\.\w+)")) { action.type = DefineableActionType.OPEN_EDITOR; action.data = s; } return action; } public static DefineableAction ParseDrawerParameter(string s) { s = s.Trim(); DefineableAction action = new DefineableAction(); if (s.StartsWith("youtube#", StringComparison.Ordinal)) { action.type = DefineableActionType.URL; action.data = "https://www.youtube.com/watch?v="+s.Substring(8); } return action; } public override string ToString() { return $"{{{type},{data}}}"; } } public enum DefineableActionType { NONE, URL, SET_PROPERTY, SET_SHADER, SET_TAG, OPEN_EDITOR, } public class DefineableCondition { public DefineableConditionType type = DefineableConditionType.NONE; public string data = ""; public DefineableCondition condition1; public DefineableCondition condition2; CompareType _compareType; string _obj; ShaderProperty _propertyObj; Material _materialInsteadOfEditor; string _value; float _floatValue; bool _hasConstantValue; bool _constantValue; bool _isInit = false; public void Init() { if (_isInit) return; _hasConstantValue = true; if (type == DefineableConditionType.NONE) { _constantValue = true; } else if (type == DefineableConditionType.TRUE) { _constantValue = true; } else if (type == DefineableConditionType.FALSE) { _constantValue = false; } else { var (compareType, compareString) = GetComparetor(); _compareType = compareType; string[] parts = Regex.Split(data, compareString); _obj = parts[0].Trim(); _value = parts[parts.Length - 1].Trim(); _floatValue = Parser.ParseFloat(_value); if (ShaderEditor.Active != null && ShaderEditor.Active.PropertyDictionary.ContainsKey(_obj)) _propertyObj = ShaderEditor.Active.PropertyDictionary[_obj]; if (type == DefineableConditionType.EDITOR_VERSION) InitEditorVersion(); else if (type == DefineableConditionType.VRC_SDK_VERSION) InitVRCSDKVersion(); else _hasConstantValue = false; } _isInit = true; } void InitEditorVersion() { int c_ev = Helper.CompareVersions(Config.Singleton.verion, _value); if (_compareType == CompareType.EQUAL) _constantValue = c_ev == 0; if (_compareType == CompareType.NOT_EQUAL) _constantValue = c_ev != 0; if (_compareType == CompareType.SMALLER) _constantValue = c_ev == 1; if (_compareType == CompareType.BIGGER) _constantValue = c_ev == -1; if (_compareType == CompareType.BIGGER_EQ) _constantValue = c_ev == -1 || c_ev == 0; if (_compareType == CompareType.SMALLER_EQ) _constantValue = c_ev == 1 || c_ev == 0; } void InitVRCSDKVersion() { if (VRCInterface.Get().Sdk_information.type == VRCInterface.VRC_SDK_Type.NONE) { _constantValue = false; return; } int c_vrc = Helper.CompareVersions(VRCInterface.Get().Sdk_information.installed_version, _value); if (_compareType == CompareType.EQUAL) _constantValue = c_vrc == 0; if (_compareType == CompareType.NOT_EQUAL) _constantValue = c_vrc != 0; if (_compareType == CompareType.SMALLER) _constantValue = c_vrc == 1; if (_compareType == CompareType.BIGGER) _constantValue = c_vrc == -1; if (_compareType == CompareType.BIGGER_EQ) _constantValue = c_vrc == -1 || c_vrc == 0; if (_compareType == CompareType.SMALLER_EQ) _constantValue = c_vrc == 1 || c_vrc == 0; } public bool Test() { Init(); if (_hasConstantValue) return _constantValue; MaterialProperty materialProperty = null; switch (type) { case DefineableConditionType.PROPERTY_BOOL: materialProperty = GetMaterialProperty(); if (materialProperty == null) return false; if (_compareType == CompareType.NONE) return materialProperty.floatValue == 1; if (_compareType == CompareType.EQUAL) return materialProperty.floatValue == _floatValue; if (_compareType == CompareType.NOT_EQUAL) return materialProperty.floatValue != _floatValue; if (_compareType == CompareType.SMALLER) return materialProperty.floatValue < _floatValue; if (_compareType == CompareType.BIGGER) return materialProperty.floatValue > _floatValue; if (_compareType == CompareType.BIGGER_EQ) return materialProperty.floatValue >= _floatValue; if (_compareType == CompareType.SMALLER_EQ) return materialProperty.floatValue <= _floatValue; break; case DefineableConditionType.TEXTURE_SET: materialProperty = GetMaterialProperty(); if (materialProperty == null) return false; return (materialProperty.textureValue == null) == (_compareType == CompareType.EQUAL); case DefineableConditionType.DROPDOWN: materialProperty = GetMaterialProperty(); if (materialProperty == null) return false; if (_compareType == CompareType.NONE) return materialProperty.floatValue == 1; if (_compareType == CompareType.EQUAL) return "" + materialProperty.floatValue == _value; if (_compareType == CompareType.NOT_EQUAL) return "" + materialProperty.floatValue != _value; break; case DefineableConditionType.PROPERTY_IS_ANIMATED: return ShaderOptimizer.IsAnimated(_materialInsteadOfEditor, _obj); case DefineableConditionType.PROPERTY_IS_NOT_ANIMATED: return !ShaderOptimizer.IsAnimated(_materialInsteadOfEditor, _obj); case DefineableConditionType.AND: if(condition1!=null&&condition2!=null) return condition1.Test() && condition2.Test(); break; case DefineableConditionType.OR: if(condition1 != null && condition2 != null) return condition1.Test() || condition2.Test(); break; case DefineableConditionType.NOT: if(condition1 != null) return !condition1.Test(); break; } return true; } private MaterialProperty GetMaterialProperty() { if(_materialInsteadOfEditor) return MaterialEditor.GetMaterialProperty(new Material[]{_materialInsteadOfEditor}, _obj); if(_propertyObj != null) return _propertyObj.MaterialProperty; return null; } private (CompareType,string) GetComparetor() { if (data.Contains("==")) return (CompareType.EQUAL,"=="); if (data.Contains("!=")) return (CompareType.NOT_EQUAL,"!="); if (data.Contains(">=")) return (CompareType.BIGGER_EQ,">="); if (data.Contains("<=")) return (CompareType.SMALLER_EQ,"<="); if (data.Contains(">")) return (CompareType.BIGGER,">"); if (data.Contains("<")) return (CompareType.SMALLER,"<"); return (CompareType.NONE,"##"); } public override string ToString() { switch (type) { case DefineableConditionType.PROPERTY_BOOL: return data; case DefineableConditionType.EDITOR_VERSION: return "EDITOR_VERSION" + data; case DefineableConditionType.VRC_SDK_VERSION: return "VRC_SDK_VERSION" + data; case DefineableConditionType.TEXTURE_SET: return "TEXTURE_SET" + data; case DefineableConditionType.DROPDOWN: return "DROPDOWN" + data; case DefineableConditionType.PROPERTY_IS_ANIMATED: return $"isAnimated({data})"; case DefineableConditionType.PROPERTY_IS_NOT_ANIMATED: return $"isNotAnimated({data})"; case DefineableConditionType.AND: if (condition1 != null && condition2 != null) return "("+condition1.ToString() + "&&" + condition2.ToString()+")"; break; case DefineableConditionType.OR: if (condition1 != null && condition2 != null) return "("+condition1.ToString()+"||"+condition2.ToString()+")"; break; case DefineableConditionType.NOT: if (condition1 != null) return "!"+condition1.ToString(); break; } return ""; } private static DefineableCondition ParseForThryParser(string s) { return Parse(s); } private static readonly char[] ComparissionLiteralsToCheckFor = "*><=".ToCharArray(); public static DefineableCondition Parse(string s, Material useThisMaterialInsteadOfOpenEditor = null, int start = 0, int end = -1) { if(end == -1) end = s.Length; DefineableCondition con; // Debug.Log("Parsing: " + s.Substring(start, end - start)); int depth = 0; int bracketStart = -1; int bracketEnd = -1; for(int i = start; i < end; i++) { char c = s[i]; if(c == '(') { depth += 1; if(depth == 1) { bracketStart = i; } }else if(c == ')') { if(depth == 1) { bracketEnd = i; } depth -= 1; }else if(depth == 0) { if(c == '&') { con = new DefineableCondition(); con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; con.type = DefineableConditionType.AND; con.condition1 = Parse(s, useThisMaterialInsteadOfOpenEditor, start, i); con.condition2 = Parse(s, useThisMaterialInsteadOfOpenEditor, i + (s[i+1] == '&' ? 2 : 1), end); return con; }else if(c == '|') { con = new DefineableCondition(); con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; con.type = DefineableConditionType.OR; con.condition1 = Parse(s, useThisMaterialInsteadOfOpenEditor, start, i); con.condition2 = Parse(s, useThisMaterialInsteadOfOpenEditor, i + (s[i + 1] == '|' ? 2 : 1), end); return con; } } } bool isInverted = IsInverted(s, ref start); // if no AND or OR was found, check for brackets if(bracketStart != -1 && bracketEnd != -1) { con = Parse(s, useThisMaterialInsteadOfOpenEditor, bracketStart + 1, bracketEnd); }else { con = ParseSingle(s.Substring(start, end - start), useThisMaterialInsteadOfOpenEditor); } if(isInverted) { DefineableCondition inverted = new DefineableCondition(); inverted._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; inverted.type = DefineableConditionType.NOT; inverted.condition1 = con; return inverted; } return con; } static bool IsInverted(string s, ref int start) { for(int i = start; i < s.Length; i++) { if(s[i] == '!') { start += 1; return true; } if(s[i] != ' ') return false; } return false; } static DefineableCondition ParseSingle(string s, Material useThisMaterialInsteadOfOpenEditor = null) { // Debug.Log("Parsing single: " + s); DefineableCondition con = new DefineableCondition(); con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; if(s.IndexOfAny(ComparissionLiteralsToCheckFor) != -1) { //is a comparission con.data = s; con.type = DefineableConditionType.PROPERTY_BOOL; if (s.StartsWith("VRCSDK", StringComparison.Ordinal)) { con.type = DefineableConditionType.VRC_SDK_VERSION; con.data = s.Replace("VRCSDK", ""); }else if (s.StartsWith("ThryEditor", StringComparison.Ordinal)) { con.type = DefineableConditionType.EDITOR_VERSION; con.data = s.Replace("ThryEditor", ""); }else if(IsTextureNullComparission(s, useThisMaterialInsteadOfOpenEditor)) { con.type = DefineableConditionType.TEXTURE_SET; con.data = s.Replace("TEXTURE_SET", ""); } return con; } if(s.StartsWith("isNotAnimated(", StringComparison.Ordinal)) { con.type = DefineableConditionType.PROPERTY_IS_NOT_ANIMATED; con.data = s.Replace("isNotAnimated(", "").TrimEnd(')'); return con; } if(s.StartsWith("isAnimated(", StringComparison.Ordinal)) { con.type = DefineableConditionType.PROPERTY_IS_ANIMATED; con.data = s.Replace("isAnimated(", "").TrimEnd(')'); return con; } return con; } static bool IsTextureNullComparission(string data, Material useThisMaterialInsteadOfOpenEditor = null) { // Check if property is a texture property && is checking for null Material m = GetReferencedMaterial(useThisMaterialInsteadOfOpenEditor); if( m == null) return false; if(data.Length < 7) return false; if(data.EndsWith("null") == false) return false; string propertyName = data.Substring(0, data.Length - 6); if(m.HasProperty(propertyName) == false) return false; MaterialProperty p = MaterialEditor.GetMaterialProperty(new Material[]{m}, propertyName); return p.type == MaterialProperty.PropType.Texture; } static Material GetReferencedMaterial(Material useThisMaterialInsteadOfOpenEditor = null) { if( useThisMaterialInsteadOfOpenEditor != null ) return useThisMaterialInsteadOfOpenEditor; if( ShaderEditor.Active != null ) return ShaderEditor.Active.Materials[0]; return null; } } enum CompareType { NONE,BIGGER,SMALLER,EQUAL,NOT_EQUAL,BIGGER_EQ,SMALLER_EQ } public enum DefineableConditionType { NONE, TRUE, FALSE, PROPERTY_BOOL, PROPERTY_IS_ANIMATED, PROPERTY_IS_NOT_ANIMATED, EDITOR_VERSION, VRC_SDK_VERSION, TEXTURE_SET, DROPDOWN, AND, OR, NOT } #endregion }