using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; // Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/ // Website & Documentation - https://acegikmo.com/shapes/ namespace Shapes { public static class CodegenMpbs { static CodeWriter code; static Dictionary propToVarName; const string SIMPLE_TRANSFER = "protected override void TransferShapeProperties() => _ = 0; // :>"; static string[] excludedShaders = { "Texture Core" }; // manually set up public static void Generate() { // load core files TextAsset[] coreShaders = ShapesIO.LoadCoreShaders() .Where( t => excludedShaders.Contains( t.name ) == false ) .OrderBy( x => x.name ) .ToArray(); // Dictionary for (_X) to (ShapesMaterialUtils.propX) propToVarName = LoadPropertyNames(); // generate code code = new CodeWriter(); using( code.MainScope( typeof(CodegenMpbs), "System.Collections.Generic", "UnityEngine" ) ) { // main shaders coreShaders.ForEach( GenerateMpbClass ); // text is a special case code.Spacer(); using( code.Scope( "internal class MpbText : MetaMpb" ) ) code.Append( SIMPLE_TRANSFER ); } // save string path = ShapesIO.RootFolder + "/Scripts/Runtime/Immediate Mode/MetaMpbShapes.cs"; code.WriteTo( path ); } static Dictionary LoadPropertyNames() { var dict = new Dictionary(); string path = ShapesIO.RootFolder + "/Scripts/Runtime/Utils/ShapesMaterialUtils.cs"; // manually parse this out I guess~ foreach( string s in File.ReadAllLines( path ) ) { if( s.TrimStart().StartsWith( "public static readonly int prop" ) ) { string propNameVar = s.Between( "readonly int ", " = Shader.PropertyToID" ); string propNameShader = s.Between( "ToID( \"", "\" );" ); dict[propNameShader] = propNameVar; } } return dict; } // scuffed regex~ static string Between( this string s, string pre, string post ) => s.Split( new[] { pre }, StringSplitOptions.None )[1].Split( new[] { post }, StringSplitOptions.None )[0].Trim(); struct PropertyVar { public PropertyVar( string type, string nameShaderProp ) { this.type = type; this.nameShaderProp = nameShaderProp; this.nameCsVar = char.ToLower( nameShaderProp[1] ) + nameShaderProp.Substring( 2 ); this.nameCsVarProp = propToVarName[nameShaderProp]; } public string type; // Vector4 public string nameShaderProp; // _Rect public string nameCsVar; // rect public string nameCsVarProp; // ShapesMaterialUtils.propRect } struct Property { public Property( string type, string name ) { this.type = type; this.name = name; if( type == "Single" ) this.type = "float"; } public string type; // Vector4 public string name; // _Rect } static List GetInterfaceListProperties() { return typeof(T).GetProperties() .Select( p => new Property( p.PropertyType.GenericTypeArguments[0].Name, p.Name ) ) .OrderBy( p => p.name ).ToList(); } static void GenerateMpbClass( TextAsset coreFile ) { string path = AssetDatabase.GetAssetPath( coreFile ); string[] lines = File.ReadAllLines( path ); bool filled = false; bool dashed = false; List properties = new List(); List propertiesFill = GetInterfaceListProperties(); List propertiesDash = GetInterfaceListProperties(); // find all properties from the core shader files bool propStart = false; foreach( var s in lines ) { string sTrimmed = s.TrimStart(); if( propStart ) { if( sTrimmed.StartsWith( "SHAPES_FILL_PROPERTIES" ) ) { filled = true; } else if( sTrimmed.StartsWith( "SHAPES_DASH_PROPERTIES" ) ) { dashed = true; } else if( sTrimmed.StartsWith( "PROP_DEF(" ) ) { string contents = s.Between( "_DEF(", ")" ); string[] typeAndName = contents.Split( ',' ); string type = ShaderTypeToCsType( typeAndName[0].Trim() ); string name = typeAndName[1].Trim(); if( name != "_Color" ) // skip global properties properties.Add( new PropertyVar( type, name ) ); } else if( sTrimmed.StartsWith( "UNITY_INSTANCING_BUFFER_END" ) ) break; // done } else if( sTrimmed.StartsWith( "UNITY_INSTANCING_BUFFER_START" ) ) propStart = true; } string shapeName = coreFile.name.Substring( 0, coreFile.name.Length - " Core".Length ); string className = "Mpb" + shapeName.Replace( " ", "" ); string baseTypes = "MetaMpb"; if( filled ) baseTypes += $", {nameof(IFillableMpb)}"; if( dashed ) baseTypes += $", {nameof(IDashableMpb)}"; properties = properties.OrderBy( p => p.nameCsVar ).ToList(); code.Spacer(); using( code.Scope( $"internal class {className} : {baseTypes}" ) ) { // shape vars if( properties.Count > 0 ) { code.Spacer(); foreach( PropertyVar p in properties ) code.Append( $"internal readonly List<{p.type}> {p.nameCsVar} = InitList<{p.type}>();" ); } // fill/dash properties if( filled ) { code.Spacer(); code.Comment( "fill boilerplate" ); foreach( Property p in propertiesFill ) code.Append( $"List<{p.type}> {nameof(IFillableMpb)}.{p.name} {{ get; }} = InitList<{p.type}>();" ); } if( dashed ) { code.Spacer(); code.Comment( "dash boilerplate" ); foreach( Property p in propertiesDash ) code.Append( $"List<{p.type}> {nameof(IDashableMpb)}.{p.name} {{ get; }} = InitList<{p.type}>();" ); } // transfer function code.Spacer(); if( properties.Count == 0 ) { code.Append( "protected override void TransferShapeProperties() => _ = 0; // :>" ); } else { using( code.Scope( "protected override void TransferShapeProperties()" ) ) { foreach( PropertyVar p in properties ) code.Append( $"Transfer( {nameof(ShapesMaterialUtils)}.{p.nameCsVarProp}, {p.nameCsVar} );" ); } } code.Spacer(); } } static string ShaderTypeToCsType( string t ) { switch( t ) { case "int": case "half": case "fixed": case "float": return "float"; // SetFloat() case "int2": case "half2": case "fixed2": case "float2": case "int3": case "half3": case "fixed3": case "float3": case "int4": case "half4": case "fixed4": case "float4": return "Vector4"; // SetVector() } throw new ArgumentException( "INVALID TYPE: " + t ); } } }