Skip to content

Instantly share code, notes, and snippets.

@orels1
Last active November 24, 2022 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save orels1/8e25df946b8b5d828dc2e5b0efba0af1 to your computer and use it in GitHub Desktop.
Save orels1/8e25df946b8b5d828dc2e5b0efba0af1 to your computer and use it in GitHub Desktop.
Converting inline material prop conditions into a boolean result (the dirty way)
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