| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957 | using System;using System.Collections.Generic;using System.IO;using System.Reflection;using System.Text;using System.Text.RegularExpressions;using FlatKit;using FlatKit.StylizedSurface;using JetBrains.Annotations;using UnityEditor;using UnityEngine;using UnityEngine.Rendering;using Object = UnityEngine.Object;// ReSharper disable Unity.PreferAddressByIdToGraphicsParams// ReSharper disable StringLiteralTypo[HelpURL("https://flatkit.dustyroom.com/")]public class StylizedSurfaceEditor : BaseShaderGUI {    private Material _target;    private MaterialProperty[] _properties;    private int _celShadingNumSteps = 0;    private AnimationCurve _gradient = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));    private bool? _smoothNormalsEnabled;    private static GUIStyle _boxStyle;    private static GUIStyle _richHelpBoxStyle;    private static GUIStyle _foldoutStyle;    private static readonly Dictionary<string, bool> FoldoutStates = new() { { RenderingOptionsName, false } };    private static readonly Color HashColor = new Color(0.85023f, 0.85034f, 0.85045f, 0.85056f);    private static readonly int ColorPropertyName = Shader.PropertyToID("_BaseColor");    private static readonly int LightmapDirection = Shader.PropertyToID("_LightmapDirection");    private static readonly int LightmapDirectionPitch = Shader.PropertyToID("_LightmapDirectionPitch");    private static readonly int LightmapDirectionYaw = Shader.PropertyToID("_LightmapDirectionYaw");    private const string OutlineSmoothNormalsKeyword = "DR_OUTLINE_SMOOTH_NORMALS";    private const string RenderingOptionsName = "Rendering Options";    private const string UnityVersion = "JLE8GP";    private void DrawStandard(MaterialProperty property) {        // Remove everything in square brackets.        var cleanName = Regex.Replace(property.displayName, @" ?\[.*?\]", string.Empty);        if (!Tooltips.Map.TryGetValue(property.displayName, out string tooltip)) {            Tooltips.Map.TryGetValue(cleanName, out tooltip);        }        var guiContent = new GUIContent(cleanName, tooltip);        if (property.type == MaterialProperty.PropType.Texture) {            if (!property.name.Contains("_BaseMap")) {                EditorGUILayout.Space(10);            }            materialEditor.TexturePropertySingleLine(guiContent, property);        } else {            materialEditor.ShaderProperty(property, guiContent);        }    }    private MaterialProperty FindProperty(string name) {        return FindProperty(name, _properties);    }    private bool HasProperty(string name) {        return _target != null && _target.HasProperty(name);    }#if UNITY_2021_2_OR_NEWER    [Obsolete("MaterialChanged has been renamed ValidateMaterial", false)]#endif    public override void MaterialChanged(Material material) { }    public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties) {        materialEditor = editor;        _properties = properties;        _target = editor.target as Material;        Debug.Assert(_target != null, "_target != null");        if (_target.shader.name.Equals("FlatKit/Stylized Surface With Outline")) {            EditorGUILayout.HelpBox(                "'Stylized Surface with Outline' shader has been deprecated. Please use the outline section in the 'Stylized Surface' shader.",                MessageType.Warning);        }        if (!Application.unityVersion.Contains(Rev(UnityVersion)) &&            !Application.unityVersion.Contains('b') &&            !Application.unityVersion.Contains('a') &&            !Application.unityVersion.Replace('.', '^').Contains("23^2")) {            EditorGUILayout.BeginVertical(EditorStyles.helpBox);            EditorGUILayout.BeginHorizontal();            // Icon.            {                EditorGUILayout.BeginVertical();                GUILayout.FlexibleSpace();                var icon = EditorGUIUtility.IconContent("console.erroricon@2x").image;                var iconSize = Mathf.Min(Mathf.Min(60, icon.width), EditorGUIUtility.currentViewWidth - 100);                GUILayout.Label(icon, new GUIStyle {                    alignment = TextAnchor.MiddleCenter,                    imagePosition = ImagePosition.ImageLeft,                    fixedWidth = iconSize,                    fixedHeight = iconSize,                    padding = new RectOffset(0, 0, 5, 5),                    margin = new RectOffset(0, 0, 0, 0),                });                GUILayout.FlexibleSpace();                EditorGUILayout.EndVertical();            }            var unityMajorVersion = Application.unityVersion.Substring(0, Application.unityVersion.LastIndexOf('.'));            var m = $"This version of <b>Flat Kit</b> is designed for <b>Unity {Rev(UnityVersion)}</b>. " +                    $"You are currently using <b>Unity {unityMajorVersion}</b>.\n" +                    "<i>The shader and the UI below may not work correctly.</i>\n" +                    "Please <b>re-download Flat Kit</b> to get the compatible version.";            var style = new GUIStyle(EditorStyles.wordWrappedLabel) {                alignment = TextAnchor.MiddleLeft,                richText = true,                fontSize = 12,                padding = new RectOffset(0, 5, 5, 5),                margin = new RectOffset(0, 0, 0, 0),            };            EditorGUILayout.LabelField(m, style);            EditorGUILayout.EndHorizontal();            // Unity version help buttons.            {                EditorGUILayout.BeginHorizontal();                GUILayout.FlexibleSpace();                const float buttonWidth = 120;                if (GUILayout.Button("Package Manager", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {                    const string packageName = "Flat Kit: Toon Shading and Water";                    var type = typeof(UnityEditor.PackageManager.UI.Window);                    var method = type.GetMethod("OpenFilter", BindingFlags.NonPublic | BindingFlags.Static);                    if (method != null) {                        method.Invoke(null, new object[] { $"AssetStore/{packageName}" });                    } else {                        UnityEditor.PackageManager.UI.Window.Open(packageName);                    }                }                if (GUILayout.Button("Asset Store", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {                    const string assetStoreUrl = "https://u3d.as/1uVy";                    Application.OpenURL(assetStoreUrl);                }                if (GUILayout.Button("Support", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {                    const string contactUrl = "https://flatkit.dustyroom.com/contact-details/";                    Application.OpenURL(contactUrl);                }                EditorGUILayout.EndHorizontal();                EditorGUILayout.Space(2);            }            EditorGUILayout.EndVertical();            EditorGUILayout.Space();            foreach (var property in properties) {                // materialEditor.ShaderProperty(property, property.displayName);                DrawStandard(property);            }            return;        }        if (_target.IsKeywordEnabled("DR_OUTLINE_ON") && _target.IsKeywordEnabled("_ALPHATEST_ON")) {            EditorGUILayout.HelpBox(                "The 'Outline' and 'Alpha Clip' features are usually incompatible. The outline shader pass will not be using alpha clipping.",                MessageType.Warning);        }        int originalIntentLevel = EditorGUI.indentLevel;        string foldoutName = string.Empty;        int foldoutRemainingItems = 0;        bool latestFoldoutState = false;        _foldoutStyle ??= new GUIStyle(EditorStyles.foldout) {            margin = {                left = -5            },            padding = {                left = 20            },        };        _boxStyle ??= new GUIStyle(EditorStyles.helpBox);        _richHelpBoxStyle ??= new GUIStyle(EditorStyles.helpBox) {            richText = true        };        bool vGroupStarted = false;        foreach (MaterialProperty property in properties) {            string displayName = property.displayName;            if (displayName.Contains("[") && !displayName.Contains("FOLDOUT")) {                EditorGUI.indentLevel += 1;            }            bool skipProperty = false;            skipProperty |= displayName.Contains("[_CELPRIMARYMODE_SINGLE]") &&                            !_target.IsKeywordEnabled("_CELPRIMARYMODE_SINGLE");            skipProperty |= displayName.Contains("[_CELPRIMARYMODE_STEPS]") &&                            !_target.IsKeywordEnabled("_CELPRIMARYMODE_STEPS");            skipProperty |= displayName.Contains("[_CELPRIMARYMODE_CURVE]") &&                            !_target.IsKeywordEnabled("_CELPRIMARYMODE_CURVE");            skipProperty |= displayName.Contains("[DR_CEL_EXTRA_ON]") &&                            !property.name.Equals("_CelExtraEnabled") &&                            !_target.IsKeywordEnabled("DR_CEL_EXTRA_ON");            skipProperty |= displayName.Contains("[DR_SPECULAR_ON]") &&                            !property.name.Equals("_SpecularEnabled") &&                            !_target.IsKeywordEnabled("DR_SPECULAR_ON");            skipProperty |= displayName.Contains("[DR_RIM_ON]") &&                            !property.name.Equals("_RimEnabled") &&                            !_target.IsKeywordEnabled("DR_RIM_ON");            skipProperty |= displayName.Contains("[DR_GRADIENT_ON]") &&                            !property.name.Equals("_GradientEnabled") &&                            !_target.IsKeywordEnabled("DR_GRADIENT_ON");            skipProperty |= displayName.Contains("[_UNITYSHADOWMODE_MULTIPLY]") &&                            !_target.IsKeywordEnabled("_UNITYSHADOWMODE_MULTIPLY");            skipProperty |= displayName.Contains("[_UNITYSHADOWMODE_COLOR]") &&                            !_target.IsKeywordEnabled("_UNITYSHADOWMODE_COLOR");            skipProperty |= displayName.Contains("[DR_ENABLE_LIGHTMAP_DIR]") &&                            !_target.IsKeywordEnabled("DR_ENABLE_LIGHTMAP_DIR");            skipProperty |= displayName.Contains("[DR_OUTLINE_ON]") &&                            !_target.IsKeywordEnabled("DR_OUTLINE_ON");            skipProperty |= displayName.Contains("[_EMISSION]") &&                            !_target.IsKeywordEnabled("_EMISSION");            skipProperty |= displayName.Contains("[_OUTLINESPACE_SCREEN]") &&                            !_target.IsKeywordEnabled("_OUTLINESPACE_SCREEN");            bool isOverrideLightDirectionToggle = displayName.ToLower().Contains("override light direction");            if (Lightmapping.lightingDataAsset != null)            {                // Lightmapping is enabled.                _target.EnableKeyword("DR_ENABLE_LIGHTMAP_DIR");                if (isOverrideLightDirectionToggle)                {                    skipProperty = true;                    if (latestFoldoutState && foldoutName.Contains("Advanced Lighting"))                    {                        const string m = "<b>Lightmap Direction Override</b> is required because the scene is using baked lighting.";                        EditorGUILayout.LabelField(m, new GUIStyle(EditorStyles.label)                        {                            richText = true,                            wordWrap = true,                            padding = new RectOffset(16, 0, 8, 0)                        });                    }                }            }            if (_target.IsKeywordEnabled("DR_ENABLE_LIGHTMAP_DIR") && isOverrideLightDirectionToggle) {                var dirPitch = _target.GetFloat(LightmapDirectionPitch);                var dirYaw = _target.GetFloat(LightmapDirectionYaw);                var dirPitchRad = dirPitch * Mathf.Deg2Rad;                var dirYawRad = dirYaw * Mathf.Deg2Rad;                var direction = new Vector4(Mathf.Sin(dirPitchRad) * Mathf.Sin(dirYawRad), Mathf.Cos(dirPitchRad),                    Mathf.Sin(dirPitchRad) * Mathf.Cos(dirYawRad), 0.0f);                _target.SetVector(LightmapDirection, direction);            }            if (displayName.Contains("FOLDOUT")) {                foldoutName = displayName.Split('(', ')')[1];                string foldoutItemCount = displayName.Split('{', '}')[1];                foldoutRemainingItems = Convert.ToInt32(foldoutItemCount);                FoldoutStates.TryAdd(property.name, false);                EditorGUILayout.Space(10);                FoldoutStates[property.name] =                    EditorGUILayout.Foldout(FoldoutStates[property.name], foldoutName, _foldoutStyle);                latestFoldoutState = FoldoutStates[property.name];                if (latestFoldoutState) {                    BeginBox();                }            }            if (foldoutRemainingItems > 0) {                skipProperty = skipProperty || !latestFoldoutState;                EditorGUI.indentLevel += 1;                --foldoutRemainingItems;            } else if (!skipProperty) {                if (EditorGUI.indentLevel > 0 && !vGroupStarted) {                    BeginBox();                    vGroupStarted = true;                }                if (EditorGUI.indentLevel <= 0 && vGroupStarted) {                    EndBox();                    vGroupStarted = false;                }            }            if (_target.IsKeywordEnabled("_CELPRIMARYMODE_STEPS") && displayName.Contains("[LAST_PROP_STEPS]")) {                EditorGUILayout.HelpBox(                    "This mode creates a step texture that control the light/shadow transition. To use:\n" +                    "1. Set the number of steps (e.g. 3 means three steps between lit and shaded regions), \n" +                    "2. Save the steps as a texture - 'Save Ramp Texture' button",                    MessageType.Info);                int currentNumSteps = _target.GetInt("_CelNumSteps");                if (currentNumSteps != _celShadingNumSteps) {                    if (GUILayout.Button("Save Ramp Texture")) {                        _celShadingNumSteps = currentNumSteps;                        PromptTextureSave(editor, GenerateStepTexture, "_CelStepTexture", FilterMode.Point);                    }                }            }            if (_target.IsKeywordEnabled("_CELPRIMARYMODE_CURVE") && displayName.Contains("[LAST_PROP_CURVE]")) {                EditorGUILayout.HelpBox(                    "This mode uses arbitrary curves to control the light/shadow transition. How to use:\n" +                    "1. Set shading curve (generally from 0.0 to 1.0)\n" +                    "2. [Optional] Save the curve preset\n" +                    "3. Save the curve as a texture.",                    MessageType.Info);                _gradient = EditorGUILayout.CurveField("Shading curve", _gradient);                if (GUILayout.Button("Save Ramp Texture")) {                    PromptTextureSave(editor, GenerateCurveTexture, "_CelCurveTexture",                        FilterMode.Trilinear);                }            }            if (!skipProperty &&                property.type == MaterialProperty.PropType.Color &&                property.colorValue == HashColor) {                property.colorValue = _target.GetColor(ColorPropertyName);            }            if (!skipProperty && property.name.Contains("_EmissionMap")) {                EditorGUILayout.Space(10);                bool emission = editor.EmissionEnabledProperty();                EditorGUILayout.Space(-10);                EditorGUI.indentLevel += 1;                if (emission) {                    _target.EnableKeyword("_EMISSION");                } else {                    _target.DisableKeyword("_EMISSION");                }            }            if (!skipProperty && property.name.Contains("_EmissionColor")) {                EditorGUI.indentLevel += 1;            }            bool hideInInspector = (property.flags & MaterialProperty.PropFlags.HideInInspector) != 0;            if (!hideInInspector && !skipProperty) {                EditorGUI.BeginChangeCheck();                DrawStandard(property);                // Toggle per-object outlines.                if (property.name.Equals("_OutlineEnabled")) {                    var outlineEnabled = _target.IsKeywordEnabled("DR_OUTLINE_ON");                    if (_target.GetShaderPassEnabled("SRPDEFAULTUNLIT")) {                        // Legacy per-object outlines.                        const string m = "Using legacy per-object outlines. Please update to Unity 2022.3+ to use " +                                         "the new Renderer Feature outlines.";                        EditorGUILayout.HelpBox(m, MessageType.Info);                    } else {                        // Per-object outlines are now handled by a Renderer Feature.                        var renderer = ObjectOutlineEditorUtils.GetRendererData();                        if (renderer != null) {                            GUILayout.Space(-EditorGUIUtility.standardVerticalSpacing -                                            EditorGUIUtility.singleLineHeight);                            GUILayout.BeginHorizontal();                            GUILayout.FlexibleSpace();                            if (GUILayout.Button("Select Renderer Feature", EditorStyles.miniButtonRight)) {                                Selection.activeObject = renderer;                            }                            GUILayout.EndHorizontal();                        }                    }                    if (EditorGUI.EndChangeCheck()) {                        // Outline toggle changed state.#if UNITY_2022_3_OR_NEWER                        ObjectOutlineEditorUtils.SetActive(_target, outlineEnabled);#else                        _target.SetShaderPassEnabled("SRPDEFAULTUNLIT", outlineEnabled);#endif                    }#if UNITY_2022_3_OR_NEWER                    // Switch from legacy shader pass per-object outlines (pre-4.7.0) to new Renderer Feature outlines.                    const string legacyPassName = "SRPDEFAULTUNLIT";                    if (_target.GetShaderPassEnabled(legacyPassName) && outlineEnabled) {                        _target.SetShaderPassEnabled(legacyPassName, false);                        var m = $"<color=grey>[Flat Kit]</color> <b>Per-object outlines</b> are now handled by a " +                                $"<b>Renderer Feature</b>. The material <color=green><b>{_target.name}</b></color> " +                                $"and the <b>URP Renderer</b> currently in use will be updated now.";                        Debug.Log(m);                        ObjectOutlineEditorUtils.SetActive(_target, true);                    }#endif                }            }            if (!skipProperty && displayName.Contains("Detail Impact")) {                DrawTileOffset(editor, FindProperty("_DetailMap"));            }            // Horizontal line separators.            {                if (FoldoutStates.TryGetValue("_BaseMap", out bool baseMapFoldoutState) && baseMapFoldoutState) {                    if (displayName.Contains("Texture Impact") ||                        displayName.Contains("Detail Impact") ||                        displayName.Contains("Normal Map") ||                        displayName.Contains("Emission Color")) {                        var indent = EditorGUI.indentLevel;                        EditorGUI.indentLevel = originalIntentLevel + 1;                        EditorGUILayout.Space(5);                        EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);                        EditorGUILayout.Space(-5);                        EditorGUI.indentLevel = indent;                    }                }            }            if (!skipProperty && property.name.Contains("_EmissionColor")) {                EditorGUILayout.Space(10);                EditorGUI.indentLevel -= 1;                EditorGUILayout.LabelField("Applied to All Maps", EditorStyles.boldLabel);                DrawTileOffset(editor, FindProperty("_BaseMap"));            }            if (!skipProperty && property.displayName.Contains("Smooth Normals")) {                SmoothNormalsUI();            }            EditorGUI.indentLevel = originalIntentLevel;            if (foldoutRemainingItems == 0 && latestFoldoutState) {                EndBox();                latestFoldoutState = false;                if (foldoutName.Contains("Advanced Lighting")) {                    const string m = "<b>Advanced Lighting</b> features have significantly different impact in real-time and baked lighting.";                    EditorGUILayout.LabelField(m, _richHelpBoxStyle);                }            }        }        EditorGUILayout.Space(10);        var renderingOptionsName = RenderingOptionsName;        FoldoutStates[renderingOptionsName] =            EditorGUILayout.Foldout(FoldoutStates[renderingOptionsName], renderingOptionsName, _foldoutStyle);        if (FoldoutStates[renderingOptionsName]) {            EditorGUI.indentLevel += 1;            BeginBox();            HandleUrpSettings(_target, editor);            EndBox();        }        if (_target.IsKeywordEnabled("_UNITYSHADOWMODE_NONE")) {            _target.EnableKeyword("_RECEIVE_SHADOWS_OFF");        } else {            _target.DisableKeyword("_RECEIVE_SHADOWS_OFF");        }        // Toggle the outline pass. Disabling by name `Outline` doesn't work.        // _target.SetShaderPassEnabled("SRPDEFAULTUNLIT", _target.IsKeywordEnabled("DR_OUTLINE_ON"));        /*        if (HasProperty("_MainTex")) {            TransferToBaseMap();        }        */    }    private void BeginBox() {        EditorGUILayout.BeginVertical(_boxStyle);        EditorGUILayout.Space(3);    }    private void EndBox() {        EditorGUILayout.Space(3);        EditorGUILayout.EndVertical();    }    private void SmoothNormalsUI() {        var keywordEnabled = _target.IsKeywordEnabled(OutlineSmoothNormalsKeyword);        _smoothNormalsEnabled ??= keywordEnabled;        if (keywordEnabled != _smoothNormalsEnabled) {            var mesh = GetMeshFromSelection();            if (mesh == null) {                return;            }            if (keywordEnabled) {                if (!MeshSmoother.HasSmoothNormals(mesh)) {                    SmoothNormals(mesh);                }            }        } else if (keywordEnabled) {            var mesh = GetMeshFromSelection();            if (mesh == null) {                return;            }            if (!MeshSmoother.HasSmoothNormals(mesh)) {                EditorGUILayout.HelpBox(                    "Mesh does not have smooth normals. Please use the 'Smooth Normals' button to generate them.",                    MessageType.Warning);                if (mesh.subMeshCount > 1) {                    EditorGUILayout.HelpBox(                        "Mesh smoothing is not supported for meshes with multiple sub-meshes. " +                        "Please combine sub-meshes into a single sub-mesh before smoothing.",                        MessageType.Warning);                }                GUILayout.BeginHorizontal();                GUILayout.FlexibleSpace();                if (GUILayout.Button("Smooth Normals", GUILayout.ExpandWidth(false))) {                    SmoothNormals(mesh);                }                GUILayout.EndHorizontal();            }        }        _smoothNormalsEnabled = keywordEnabled;    }    private void SmoothNormals(Mesh mesh) {        if (mesh.isReadable) {            MeshSmoother.SmoothNormals(mesh);            var path = AssetDatabase.GetAssetPath(mesh);            var pathSmoothed =                $"{Path.GetDirectoryName(path)}\\{Path.GetFileNameWithoutExtension(path)} Smooth Normals.asset";            var fileExists = File.Exists(pathSmoothed);            if (fileExists) {                var action1 = EditorUtility.DisplayDialogComplex("Smoothing normals",                    "Asset already exists. Do you want to overwrite it?",                    "Overwrite", "Cancel", "Open asset");                switch (action1) {                    case 0: {                        // Overwrite                        AssetDatabase.DeleteAsset(pathSmoothed);                        break;                    }                    case 1: {                        // Cancel                        _target.DisableKeyword(OutlineSmoothNormalsKeyword);                        return;                    }                    case 2: {                        // Open asset                        AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<Object>(pathSmoothed));                        return;                    }                }            }            var newMesh = Object.Instantiate(mesh);            try {                AssetDatabase.CreateAsset(newMesh, pathSmoothed);                Debug.Log($"<b>[Flat Kit]</b> Created asset <i>{pathSmoothed}</i>.");            }            catch (Exception) {                Debug.Log("<b>[Flat Kit]</b> Please ignore the error above. It is caused by a Unity bug.");                var action2 = EditorUtility.DisplayDialogComplex("Error",                    $"Could not create asset at path '{pathSmoothed}'. Please check the Console for more information.",                    "OK", "Show Console", "Save to 'Assets' folder");                switch (action2) {                    case 0: {                        // OK                        _target.DisableKeyword(OutlineSmoothNormalsKeyword);                        return;                    }                    case 1: {                        // Show Console                        EditorApplication.ExecuteMenuItem("Window/General/Console");                        return;                    }                    case 2: {                        // Save to Assets folder                        pathSmoothed = $"Assets/{Path.GetFileName(pathSmoothed)}";                        AssetDatabase.CreateAsset(newMesh, pathSmoothed);                        break;                    }                }            }            AssetDatabase.SaveAssets();            AssetDatabase.Refresh();            var newAsset = AssetDatabase.LoadAssetAtPath<Mesh>(pathSmoothed);            if (newAsset != null) {                SetMeshToSelection(newAsset);            }        } else {            var action = EditorUtility.DisplayDialogComplex("Smoothing normals",                "Mesh is not readable. Please enable 'Read/Write Enabled' in the mesh import settings.",                "Set readable", "Open import settings", "Cancel");            switch (action) {                case 0: {                    // Set readable                    var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh));                    if (importer != null) {                        var modelImporter = importer as ModelImporter;                        if (modelImporter != null) {                            modelImporter.isReadable = true;                            modelImporter.SaveAndReimport();                        }                    }                    break;                }                case 1: {                    // Open import settings                    var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh));                    if (importer != null) {                        AssetDatabase.OpenAsset(importer);                    }                    break;                }                case 2: {                    // Cancel                    _target.DisableKeyword(OutlineSmoothNormalsKeyword);                    break;                }            }        }    }    private static Mesh GetMeshFromSelection() {        var go = Selection.activeGameObject;        if (go == null) {            EditorGUILayout.HelpBox(                "All meshes using smooth normals need processing. To process a mesh, select it in a scene and " +                "re-enable 'Smooth Normals' in the inspector.",                MessageType.Info);            return null;        }        var meshFilter = go.GetComponent<MeshFilter>();        if (meshFilter != null) {            return meshFilter.sharedMesh;        }        var skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();        if (skinnedMeshRenderer != null) {            return skinnedMeshRenderer.sharedMesh;        }        return null;    }    private static void SetMeshToSelection(Mesh mesh) {        var go = Selection.activeGameObject;        if (go == null) {            return;        }        var meshFilter = go.GetComponent<MeshFilter>();        if (meshFilter != null) {            meshFilter.sharedMesh = mesh;            return;        }        var skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();        if (skinnedMeshRenderer != null) {            skinnedMeshRenderer.sharedMesh = mesh;        }    }// Adapted from BaseShaderGUI.cs.    private void HandleUrpSettings(Material material, MaterialEditor editor) {        queueOffsetProp = FindProperty("_QueueOffset");        bool alphaClip = false;        if (material.HasProperty("_AlphaClip")) {            alphaClip = material.GetFloat("_AlphaClip") >= 0.5;        }        if (alphaClip) {            material.EnableKeyword("_ALPHATEST_ON");        } else {            material.DisableKeyword("_ALPHATEST_ON");        }        if (HasProperty("_Surface")) {            EditorGUI.BeginChangeCheck();            var surfaceProp = FindProperty("_Surface");            EditorGUI.showMixedValue = surfaceProp.hasMixedValue;            var surfaceType = (SurfaceType)surfaceProp.floatValue;            surfaceType = (SurfaceType)EditorGUILayout.EnumPopup("Surface Type", surfaceType);            if (EditorGUI.EndChangeCheck()) {                editor.RegisterPropertyChangeUndo("Surface Type");                surfaceProp.floatValue = (float)surfaceType;            }            if (surfaceType == SurfaceType.Opaque) {                if (alphaClip) {                    material.renderQueue = (int)RenderQueue.AlphaTest;                    material.SetOverrideTag("RenderType", "TransparentCutout");                } else {                    material.renderQueue = (int)RenderQueue.Geometry;                    material.SetOverrideTag("RenderType", "Opaque");                }                material.renderQueue += queueOffsetProp != null ? (int)queueOffsetProp.floatValue : 0;                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);                material.SetInt("_ZWrite", 1);                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");                material.SetShaderPassEnabled("ShadowCaster", true);            } else // Transparent            {                BlendMode blendMode = (BlendMode)material.GetFloat("_Blend");                // Specific Transparent Mode Settings                switch (blendMode) {                    case BlendMode.Alpha:                        material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);                        material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);                        material.DisableKeyword("_ALPHAPREMULTIPLY_ON");                        break;                    case BlendMode.Premultiply:                        material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);                        material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);                        material.EnableKeyword("_ALPHAPREMULTIPLY_ON");                        break;                    case BlendMode.Additive:                        material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);                        material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);                        material.DisableKeyword("_ALPHAPREMULTIPLY_ON");                        break;                    case BlendMode.Multiply:                        material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstColor);                        material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);                        material.DisableKeyword("_ALPHAPREMULTIPLY_ON");                        material.EnableKeyword("_ALPHAMODULATE_ON");                        break;                }                // General Transparent Material Settings                material.SetOverrideTag("RenderType", "Transparent");                material.SetInt("_ZWrite", 0);                material.renderQueue = (int)RenderQueue.Transparent;                material.renderQueue += queueOffsetProp != null ? (int)queueOffsetProp.floatValue : 0;                material.SetShaderPassEnabled("ShadowCaster", false);            }            // DR: draw popup.            if (surfaceType == SurfaceType.Transparent && HasProperty("_Blend")) {                EditorGUI.BeginChangeCheck();                var blendModeProperty = FindProperty("_Blend");                EditorGUI.showMixedValue = blendModeProperty.hasMixedValue;                var blendMode = (BlendMode)blendModeProperty.floatValue;                blendMode = (BlendMode)EditorGUILayout.EnumPopup("Blend Mode", blendMode);                if (EditorGUI.EndChangeCheck()) {                    editor.RegisterPropertyChangeUndo("Blend Mode");                    blendModeProperty.floatValue = (float)blendMode;                }            }        }        DrawQueueOffsetField();        // DR: draw popup.        if (HasProperty("_Cull")) {            EditorGUI.BeginChangeCheck();            var cullProp = FindProperty("_Cull");            EditorGUI.showMixedValue = cullProp.hasMixedValue;            var culling = (RenderFace)cullProp.floatValue;            culling = (RenderFace)EditorGUILayout.EnumPopup("Render Faces", culling);            if (EditorGUI.EndChangeCheck()) {                editor.RegisterPropertyChangeUndo("Render Faces");                cullProp.floatValue = (float)culling;                material.doubleSidedGI = (RenderFace)cullProp.floatValue != RenderFace.Front;            }        }        if (HasProperty("_AlphaClip")) {            EditorGUI.BeginChangeCheck();            var clipProp = FindProperty("_AlphaClip");            EditorGUI.showMixedValue = clipProp.hasMixedValue;            // ReSharper disable once CompareOfFloatsByEqualityOperator            var alphaClipEnabled = EditorGUILayout.Toggle("Alpha Clipping", clipProp.floatValue == 1);            if (EditorGUI.EndChangeCheck())                clipProp.floatValue = alphaClipEnabled ? 1 : 0;            EditorGUI.showMixedValue = false;            // ReSharper disable once CompareOfFloatsByEqualityOperator            if (clipProp.floatValue == 1 && HasProperty("_Cutoff")) {                var cutoffProp = FindProperty("_Cutoff");                editor.ShaderProperty(cutoffProp, "Threshold", 1);            }        }        editor.EnableInstancingField();    }    private void PromptTextureSave(MaterialEditor editor, Func<Texture2D> generate, string propertyName,        FilterMode filterMode) {        var rampTexture = generate();        var pngNameNoExtension = $"{editor.target.name}{propertyName}-ramp";        var fullPath =            EditorUtility.SaveFilePanel("Save Ramp Texture", "Assets", pngNameNoExtension, "png");        if (fullPath.Length > 0) {            SaveTextureAsPng(rampTexture, fullPath, filterMode);            var loadedTexture = LoadTexture(fullPath);            if (loadedTexture != null) {                _target.SetTexture(propertyName, loadedTexture);            } else {                Debug.LogWarning("Could not save the texture. Make sure the destination is in the Assets folder.");            }        }    }    private Texture2D GenerateStepTexture() {        int numSteps = _celShadingNumSteps;        var t2d = new Texture2D(numSteps + 1, /*height=*/1, TextureFormat.R8, /*mipChain=*/false) {            filterMode = FilterMode.Point,            wrapMode = TextureWrapMode.Clamp        };        for (int i = 0; i < numSteps + 1; i++) {            var color = Color.white * i / numSteps;            t2d.SetPixel(i, 0, color);        }        t2d.Apply();        return t2d;    }    private Texture2D GenerateCurveTexture() {        const int width = 256;        const int height = 1;        var lut = new Texture2D(width, height, TextureFormat.R8, /*mipChain=*/false) {            alphaIsTransparency = false,            wrapMode = TextureWrapMode.Clamp,            hideFlags = HideFlags.HideAndDontSave,            filterMode = FilterMode.Trilinear        };        for (float x = 0; x < width; x++) {            float value = _gradient.Evaluate(x / width);            for (float y = 0; y < height; y++) {                var color = Color.white * value;                lut.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);            }        }        return lut;    }    private static void SaveTextureAsPng(Texture2D texture, string fullPath, FilterMode filterMode) {        byte[] bytes = texture.EncodeToPNG();        File.WriteAllBytes(fullPath, bytes);        AssetDatabase.Refresh();        Debug.Log($"<b>[Flat Kit]</b> Texture saved as: {fullPath}");        string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);        TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(pathRelativeToAssets);        if (importer != null) {            importer.filterMode = filterMode;            importer.textureType = TextureImporterType.SingleChannel;            importer.textureCompression = TextureImporterCompression.Uncompressed;            importer.mipmapEnabled = false;            var textureSettings = new TextureImporterPlatformSettings {                format = TextureImporterFormat.R8            };            importer.SetPlatformTextureSettings(textureSettings);            EditorUtility.SetDirty(importer);            importer.SaveAndReimport();        }        // 22b5f7ed-989d-49d1-90d9-c62d76c3081a        Debug.Assert(importer,            string.Format("[FlatKit] Could not change import settings of {0} [{1}]",                fullPath, pathRelativeToAssets));    }    private static Texture2D LoadTexture(string fullPath) {        string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);        if (pathRelativeToAssets.Length == 0) {            return null;        }        var loadedTexture = AssetDatabase.LoadAssetAtPath(pathRelativeToAssets, typeof(Texture2D)) as Texture2D;        if (loadedTexture == null) {            Debug.LogError(string.Format("[FlatKit] Could not load texture from {0} [{1}].", fullPath,                pathRelativeToAssets));            return null;        }        loadedTexture.filterMode = FilterMode.Point;        loadedTexture.wrapMode = TextureWrapMode.Clamp;        return loadedTexture;    }    private static string ConvertFullPathToAssetPath(string fullPath) {        int count = (Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar).Length;        return fullPath.Remove(0, count);    }    private static string Rev(string a) {        StringBuilder b = new StringBuilder(a.Length);        int i = 0;        foreach (char c in a) {            b.Append((char)(c - "8<3F9="[i] % 32));            i = (i + 1) % 6;        }        return b.ToString();    }#if !UNITY_2020_3_OR_NEWER    private new void DrawQueueOffsetField() {        GUIContent queueSlider = new GUIContent("     Priority",            "Determines the chronological rendering order for a Material. High values are rendered first.");        const int queueOffsetRange = 50;        MaterialProperty queueOffsetProp = FindProperty("_QueueOffset", _properties, false);        if (queueOffsetProp == null) return;        EditorGUI.BeginChangeCheck();        EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue;        var queue = EditorGUILayout.IntSlider(queueSlider, (int) queueOffsetProp.floatValue, -queueOffsetRange,            queueOffsetRange);        if (EditorGUI.EndChangeCheck())            queueOffsetProp.floatValue = queue;        EditorGUI.showMixedValue = false;        _target.renderQueue = (int)RenderQueue.Transparent + queue;    }#endif    [UsedImplicitly]    private void TransferToBaseMap() {        var baseMapProperty = FindProperty("_MainTex");        var baseColorProperty = FindProperty("_Color");        _target.SetTexture("_BaseMap", baseMapProperty.textureValue);        var baseMapTiling = baseMapProperty.textureScaleAndOffset;        _target.SetTextureScale("_BaseMap", new Vector2(baseMapTiling.x, baseMapTiling.y));        _target.SetTextureOffset("_BaseMap", new Vector2(baseMapTiling.z, baseMapTiling.w));        _target.SetColor("_BaseColor", baseColorProperty.colorValue);    }    [UsedImplicitly]    private void TransferToMainTex() {        var baseMapProperty = FindProperty("_BaseMap");        var baseColorProperty = FindProperty("_BaseColor");        _target.SetTexture("_MainTex", baseMapProperty.textureValue);        var baseMapTiling = baseMapProperty.textureScaleAndOffset;        _target.SetTextureScale("_MainTex", new Vector2(baseMapTiling.x, baseMapTiling.y));        _target.SetTextureOffset("_MainTex", new Vector2(baseMapTiling.z, baseMapTiling.w));        _target.SetColor("_Color", baseColorProperty.colorValue);    }}
 |