yum-archive/Tooner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum-archive/Tooner
07a94ee
master
1// !! AI ARTIFACT !! 2// This code was originally generated by Claude 3.5 Sonnet. 3// I wanted to write this tooling like I want a fucking hole in the head so I 4// kindly asked Claude to write it for me. It's shitty and poorly designed, but 5// it works well enough for my purposes. 6// It has been slightly tweaked by me, and validated on *this* codebase. It is 7// provided with no warranty. 8using UnityEngine ; 9using UnityEditor ; 10using System . IO ; 11using System . Text . RegularExpressions ; 12using System . Collections . Generic ; 13using System . Linq ; 14 15public class ShaderInliner : EditorWindow 16{ 17private string inputShaderPath ; 18private string outputShaderPath ; 19 20[ MenuItem ( "Tools/yum_food/Shader Inliner" )] 21public static void ShowWindow () 22{ 23GetWindow < ShaderInliner > ( "Shader Inliner" ); 24} 25 26private void OnGUI () 27{ 28GUILayout . Label ( "Shader Inliner" , EditorStyles . boldLabel ); 29 30inputShaderPath = EditorGUILayout . TextField ( "Input Shader Path" , inputShaderPath ); 31if ( GUILayout . Button ( "Select Input Shader" )) 32{ 33inputShaderPath = EditorUtility . OpenFilePanel ( "Select Shader" , "" , "shader" ); 34} 35 36if ( GUILayout . Button ( "Inline Shader" )) 37{ 38if ( string . IsNullOrEmpty ( inputShaderPath )) 39{ 40EditorUtility . DisplayDialog ( "Error" , "Please select an input shader." , "OK" ); 41return ; 42} 43 44InlineShader (); 45} 46} 47 48private void InlineShader () 49{ 50string shaderContent = File . ReadAllText ( inputShaderPath ); 51string inlinedShader = ProcessShader ( shaderContent , Path . GetDirectoryName ( inputShaderPath )); 52 53string fileName = Path . GetFileNameWithoutExtension ( inputShaderPath ); 54outputShaderPath = Path . Combine ( Path . GetDirectoryName ( inputShaderPath ), $" { fileName } _inlined.shader" ); 55File . WriteAllText ( outputShaderPath , inlinedShader ); 56 57AssetDatabase . Refresh (); 58//EditorUtility.DisplayDialog("Success", $"Inlined shader saved to:\n{outputShaderPath}", "OK"); 59} 60 61private string ProcessShader ( string content , string basePath ) 62{ 63// Update shader name 64content = Regex . Replace ( content , @"Shader\s+""(.+?)""" , match=> 65{ 66string shaderName = match . Groups [ 1 ]. Value ; 67return $"Shader \" { shaderName } _inlined\"" ; 68}); 69 70// Process each Pass independently 71content = Regex . Replace ( content , @"(CGPROGRAM.*?ENDCG)" , match=> 72{ 73return ProcessPass ( match . Value , basePath ); 74}, RegexOptions . Singleline ); 75 76// Check for mismatched preprocessor macros in the entire shader 77CheckMismatchedMacros ( content ); 78 79return content ; 80} 81 82private string ProcessPass ( string passContent , string basePath ) 83{ 84HashSet < string > includedFiles = new HashSet < string > (); 85 86string pattern = @"#include\s+""(.+?)""" ; 87return Regex . Replace ( passContent , pattern , match=> 88{ 89string includePath = match . Groups [ 1 ]. Value ; 90string fullPath = Path . Combine ( basePath , includePath ); 91 92if ( File . Exists ( fullPath )) 93{ 94if ( ! includedFiles . Contains ( fullPath )) 95{ 96includedFiles . Add ( fullPath ); 97string includeContent = File . ReadAllText ( fullPath ); 98return ProcessInclude ( includeContent , Path . GetDirectoryName ( fullPath ), includedFiles ); 99} 100else 101{ 102return "// Already included in this pass: " + includePath ; 103} 104} 105else 106{ 107Debug . LogWarning ( $"Include file not found: { fullPath } " ); 108return match . Value ; 109} 110}); 111} 112 113private string ProcessInclude ( string content , string basePath , HashSet < string > includedFiles ) 114{ 115string pattern = @"#include\s+""(.+?)""" ; 116return Regex . Replace ( content , pattern , match=> 117{ 118string includePath = match . Groups [ 1 ]. Value ; 119string fullPath = Path . Combine ( basePath , includePath ); 120 121if ( File . Exists ( fullPath )) 122{ 123if ( ! includedFiles . Contains ( fullPath )) 124{ 125includedFiles . Add ( fullPath ); 126string includeContent = File . ReadAllText ( fullPath ); 127return ProcessInclude ( includeContent , Path . GetDirectoryName ( fullPath ), includedFiles ); 128} 129else 130{ 131return "// Already included in this pass: " + includePath ; 132} 133} 134else 135{ 136Debug . LogWarning ( $"Include file not found: { fullPath } " ); 137return match . Value ; 138} 139}); 140} 141 142private void CheckMismatchedMacros ( string content ) 143{ 144var stack = new Stack < string > (); 145var lines = content . Split ( '\n' ); 146var macroPattern = @"^\s*#(if|ifdef|ifndef|elif|else|endif|if\s+defined)" ; 147 148for ( int i = 0 ; i < lines . Length ; i ++ ) 149{ 150var line = lines [ i ]. Trim (); 151var match = Regex . Match ( line , macroPattern ); 152 153if ( match . Success ) 154{ 155var directive = match . Groups [ 1 ]. Value ; 156 157switch ( directive ) 158{ 159case "if" : 160case "ifdef" : 161case "ifndef" : 162case "if defined" : 163stack . Push ( directive ); 164break ; 165case "elif" : 166if ( stack . Count == 0 || ( stack . Peek () != "if" && stack . Peek () != "elif" )) 167{ 168Debug . LogError ( $"Mismatched #elif at line { i + 1 } " ); 169} 170else 171{ 172stack . Pop (); 173stack . Push ( "elif" ); 174} 175break ; 176case "else" : 177if ( stack . Count == 0 || ( stack . Peek () != "if" && stack . Peek () != "elif" )) 178{ 179Debug . LogError ( $"Mismatched #else at line { i + 1 } " ); 180} 181else 182{ 183stack . Pop (); 184stack . Push ( "else" ); 185} 186break ; 187case "endif" : 188if ( stack . Count == 0 ) 189{ 190Debug . LogError ( $"Mismatched #endif at line { i + 1 } " ); 191} 192else 193{ 194stack . Pop (); 195} 196break ; 197} 198} 199} 200 201if ( stack . Count > 0 ) 202{ 203Debug . LogError ( $"Unclosed preprocessor directives: { string . Join ( ", " , stack )} " ); 204} 205} 206} 207