Last active
November 24, 2022 21:07
-
-
Save orels1/8e25df946b8b5d828dc2e5b0efba0af1 to your computer and use it in GitHub Desktop.
Converting inline material prop conditions into a boolean result (the dirty way)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
using UnityEditor; | |
using UnityEngine; | |
namespace ORL | |
{ | |
public class ORLShaderInspectorUtils | |
{ | |
/// <summary> | |
/// Parses a Material boolean condition from a string. | |
/// </summary> | |
/// <param name="condition">Condition to evaluate. Can accept material keywords, texture properties and Ints/Floats interpreted as bools. As well as direct numerical compares for floats and ints</param> | |
/// <param name="material">Target material to fetch the data from</param> | |
/// <returns>A final boolean result of the evaluation</returns> | |
public static bool ConditionEval(string condition, Material material) | |
{ | |
var processing = condition; | |
// Eval numeric compares (> / < / >= / <= / != / ==) | |
processing = ProcessCompares(processing, material); | |
// This splits the string into a list of `()` groups and replaces them with a `%<index>%` placeholders | |
var groups = new List<string>(); | |
var groupRegex = new Regex(@"(\([\w\&\s\|\>\<\=]+?\))"); | |
if (groupRegex.IsMatch(processing)) | |
{ | |
var matches = groupRegex.Matches(processing); | |
foreach (Match match in matches) | |
{ | |
groups.Add(match.Value); | |
processing = processing.Replace(match.Value, $"%{groups.Count.ToString()}%"); | |
} | |
} | |
// Eval individual `()` groups | |
groups = groups.Select(group => ProcessGroup(group, material)).ToList(); | |
groups = groups.Select(group => EvalGroup(group)).ToList(); | |
// Now that `()` groups are converted to `True/False` - we replace the `%<index>%` placeholders with the actual values | |
for (int i = 0; i < groups.Count; i++) | |
{ | |
processing = processing.Replace($"%{i + 1}%", groups[i]); | |
} | |
// Eval final string | |
processing = EvalGroup(ProcessGroup($"({processing})", material)); | |
return Convert.ToBoolean(processing); | |
} | |
private static string ProcessCompares(string source, Material material) | |
{ | |
var processed = source; | |
var compareRegexes = new Regex(@"([\w]+\s?[\<\>\=\!]+\s?\d+)"); | |
if (compareRegexes.IsMatch(processed)) | |
{ | |
var matches = compareRegexes.Matches(processed); | |
foreach (Match match in matches) | |
{ | |
var split = match.Value.Split(' '); | |
var name = split[0]; | |
var op = split[1]; | |
var value = split[2]; | |
var so = new SerializedObject(material); | |
var floats = so.FindProperty("m_SavedProperties").FindPropertyRelative("m_Floats"); | |
for (int i = 0; i < floats.arraySize; i++) | |
{ | |
var el = floats.GetArrayElementAtIndex(i); | |
if (el.FindPropertyRelative("first").stringValue == name) | |
{ | |
var converted = el.FindPropertyRelative("second").floatValue; | |
var result = false; | |
switch (op) | |
{ | |
case ">": | |
result = converted > Convert.ToSingle(value); | |
break; | |
case "<": | |
result = converted < Convert.ToSingle(value); | |
break; | |
case ">=": | |
result = converted >= Convert.ToSingle(value); | |
break; | |
case "<=": | |
result = converted <= Convert.ToSingle(value); | |
break; | |
case "==": | |
result = converted == Convert.ToSingle(value); | |
break; | |
case "!=": | |
result = converted != Convert.ToSingle(value); | |
break; | |
} | |
processed = processed.Replace(match.Value, result.ToString()); | |
break; | |
} | |
} | |
} | |
} | |
return processed; | |
} | |
private static string ProcessGroup(string source, Material material) | |
{ | |
var result = source; | |
var split = source.Replace("(", "").Replace(")", "").Split(' '); | |
foreach (var section in split) | |
{ | |
var cleaned = section.Trim(); | |
if (cleaned.Contains("&") || cleaned.Contains("|") || cleaned.Contains("(") || | |
cleaned.Contains(")")) continue; | |
var isInverted = cleaned.StartsWith("!"); | |
if (cleaned == "False" || cleaned == "True") | |
{ | |
continue; | |
} | |
var name = isInverted ? cleaned.Substring(1) : cleaned; | |
var converted = false; | |
if (material.IsKeywordEnabled(name)) | |
{ | |
converted = true; | |
result = result.Replace(cleaned, isInverted ? (!converted).ToString() : converted.ToString()); | |
continue; | |
} | |
var so = new SerializedObject(material); | |
var textures = so.FindProperty("m_SavedProperties.m_TexEnvs"); | |
for (int i = 0; i < textures.arraySize; i++) | |
{ | |
var el = textures.GetArrayElementAtIndex(i); | |
if (el.FindPropertyRelative("first").stringValue == name) | |
{ | |
converted = el.FindPropertyRelative("second.m_Texture").objectReferenceValue != null; | |
result = result.Replace(cleaned, isInverted ? (!converted).ToString() : converted.ToString()); | |
break; | |
} | |
} | |
if (converted) continue; | |
var floats = so.FindProperty("m_SavedProperties").FindPropertyRelative("m_Floats"); | |
for (int i = 0; i < floats.arraySize; i++) | |
{ | |
var el = floats.GetArrayElementAtIndex(i); | |
if (el.FindPropertyRelative("first").stringValue == name) | |
{ | |
converted = el.FindPropertyRelative("second").floatValue > 0; | |
result = result.Replace(cleaned, isInverted ? (!converted).ToString() : converted.ToString()); | |
break; | |
} | |
} | |
if (converted) continue; | |
result = result.Replace(cleaned, isInverted ? (!converted).ToString() : converted.ToString()); | |
} | |
return result; | |
} | |
public static string EvalGroup(string source) | |
{ | |
var result = source; | |
if (result.Contains("&&") && result.Contains("||")) | |
{ | |
Debug.LogWarning("ORL: Can't evaluate group with both && and ||. Please wrap all && items in (). E.g. ((A && B && C) || D), instead of (A && B && C || D)"); | |
return result; | |
} | |
var andRegex = new Regex(@"(?<=\()([\w\s]+\&\&[\w\s]+)+(?=\))"); | |
if (andRegex.IsMatch(result)) | |
{ | |
result = andRegex.Replace(result, match => EvalAnd(match.Value)); | |
} | |
var orRegex = new Regex(@"(?<=\()([\w\s]+\|\|[\w\s]+)+(?=\))"); | |
if (orRegex.IsMatch(result)) | |
{ | |
result = orRegex.Replace(result, match => EvalOr(match.Value)); | |
} | |
return result.Replace("(", "").Replace(")", ""); | |
} | |
public static string EvalAnd(string source) | |
{ | |
var split = source.Replace("(", "").Replace(")", "").Split(new[] {"&&"}, StringSplitOptions.None) | |
.Select(s => s.Trim()).ToList(); | |
if (split.Any(s => s == "False")) | |
{ | |
return "False"; | |
} | |
return "True"; | |
} | |
public static string EvalOr(string source) | |
{ | |
var split = source.Replace("(", "").Replace(")", "").Split(new[] {"||"}, StringSplitOptions.None) | |
.Select(s => s.Trim()).ToList(); | |
if (split.Any(s => s == "True")) | |
{ | |
return "True"; | |
} | |
return "False"; | |
} | |
/// <summary> | |
/// Strips all of the shader inspector internals from the property name for nice display | |
/// </summary> | |
/// <param name="originalName"></param> | |
/// <returns>The cleaned up name</returns> | |
public static string StripInternalSymbols(string originalName) | |
{ | |
// This regex matches stuff like %ShowIf(stuff) and %SetKeyword(stuff) | |
var pattern = @"(?<=\w+\s+)(?<fn>\%[\w\,\s\&\|\(\)\!\>\<\=]+]*)"; | |
var cleanedFns = Regex.Replace(originalName, pattern, ""); | |
return cleanedFns.Replace(">", ""); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment