// *********************************************************************************** // CUAshaderMod // *********************************************************************************** // Created by Sally Whitemane on VAM Hub // https://www.patreon.com/sallyw // Licensed under CC-BY // Special thanks to Everlaster for help in Discord #scripting channel! using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; namespace Sally { public class CUAshaderMod : MVRScript { private readonly bool DEBUG_DEFAULT = false; private readonly string SHADER_DEFAULT_CHOICE = "Sprites/Default"; private readonly string VERSION = "2 "; //"2 (experimental)" private bool initialized = false; private CustomUnityAssetLoader cuaLoader; private GameObject CUArescaleObject; // has actual CUA // UI private JSONStorableStringChooser jShaderChoices; private UIDynamicPopup popupFilterable; //private UIDynamicPopup popupScrollable; private JSONStorableColor jColor; private JSONStorableBool jPopupOpen; private JSONStorableFloat jAlpha; private JSONStorableBool jDebug; // Shaders private List shaderChoices = new List(); private Dictionary snapshot = new Dictionary(); public override void Init() { try { Log("Init()"); Atom atom = GetContainingAtom(); if (!atom.type.Equals("CustomUnityAsset")) { SuperController.LogError("CUAshaderMod: Atom is not a Custom Unity Asset (CUA)."); return; } SuperController.singleton.onSceneLoadedHandlers += OnSceneLoaded; cuaLoader = atom.GetComponentInChildren() ?? GetLoaderFromParentSubscene(atom); if (cuaLoader == null) { SuperController.LogError("CUAshaderMod: Unable to find CustomUnityAssetLoader to register callback to."); return; } cuaLoader.RegisterAssetLoadedCallback(OnCUALoaded); CUArescaleObject = cuaLoader.gameObject; //Log($"cuaAssetLoader: {cuaLoader} {cuaLoader.gameObject.name}"); // UI UIDynamicTextField textField; UIDynamicButton button; LayoutElement le; // Shader chooser shaderChoices = SallyUtil.GetLoadedShaderNameList(); jShaderChoices = new JSONStorableStringChooser("Shader", shaderChoices, SHADER_DEFAULT_CHOICE, " "); RegisterStringChooser(jShaderChoices); jShaderChoices.setCallbackFunction = (v) => { ReplaceWithShader(v); }; // Filterable popup style popupFilterable = CreateFilterablePopup(jShaderChoices); //popupFilterable.popup.alwaysOpen = true; popupFilterable.popup.popupPanelHeight = 450; RectTransform rt = popupFilterable.popup.GetComponent(); rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 520f); le = popupFilterable.GetComponent(); le.minWidth = 1060f; popupFilterable.labelWidth = 0f; // Scrollable popup style (V1) //popupScrollable = CreateScrollablePopup(jShaderChoices); //le = popupScrollable.GetComponent(); //le.minWidth = 1060f; //popupScrollable.labelWidth = 0f; // Text CUAshaderMod textField = CreateTextField(new JSONStorableString("Text", "CUAshaderMod V" + VERSION +" by Sally Whitemane (CC-BY)\n" + "The popup above lists ALL currectly loaded shaders.\n" + "Changing the shader also applies the current color or alpha if supported.\n"+ "If the VaM session previously loaded other content, additional shader may be available now, but not after a restart!\n\n" + "A snapshot of all materials gets created when:\n" + "-an asset from an assetbundle has fully loaded (OnCUALoaded event)\n" + "-a scene has fully loaded (OnSceneLoaded event)\n" + "-the plugin reloads and the shader slider is moved\n" + "The snapshot is used to copy material properties to the new shader. It should contain the original material. Reload CUA in [Asset] tab if needed.\n" + "Color and alpha not working for many shaders is not a bug." ), false); textField.height = 420f; textField.GetComponent().minWidth = 1060f; // Colorpicker jColor = new JSONStorableColor("Color", HSVColorPicker.RGBToHSV(1f, 1f, 1f), SetColor); RegisterColor(jColor); UIDynamicColorPicker colorPicker = CreateColorPicker(jColor, false); // UI right - Spacer for wide shader popup CreateSpacer(true).height = 555f; // Slider Alpha jAlpha = new JSONStorableFloat("Alpha", 1f, 0f, 1f); RegisterFloat(jAlpha); jAlpha.setCallbackFunction = (v) => { SetColor(jColor.val.H, jColor.val.S, jColor.val.V); }; CreateSlider(jAlpha, true); // Button restore snapshot button = CreateButton("Load material snapshot", true); button.button.onClick.AddListener(LoadMaterialSnapshot); // Button store snapshot button = CreateButton("Save material snapshot", true); button.button.onClick.AddListener(SaveMaterialSnapshot); // Popup style toggle jPopupOpen = new JSONStorableBool("Popup always open", false); CreateToggle(jPopupOpen, true); jPopupOpen.setCallbackFunction = (b) => { popupFilterable.popup.alwaysOpen = b; popupFilterable.popup.Toggle(); }; // Button debug messages jDebug = new JSONStorableBool("Print debug messages", DEBUG_DEFAULT); CreateToggle(jDebug, true); // Patreon button button = CreateButton("Patreon.com/SallyW", true); button.button.onClick.AddListener(OnClickSupportButton); initialized = true; } catch (Exception e) { SuperController.LogError("Exception: " + e); } } static CustomUnityAssetLoader GetLoaderFromParentSubscene(Atom atom) { var parentAtom = atom.parentAtom; if (parentAtom == null || !parentAtom.isSubSceneType) { return null; } return parentAtom.GetComponentsInChildren() .Where(loader => loader.containingAtom == atom).FirstOrDefault(); } private void SaveMaterialSnapshot() { Log("SaveMaterialSnapshot()"); snapshot.Clear(); Renderer[] renderers = CUArescaleObject.GetComponentsInChildren(); foreach (Renderer renderer in renderers) { // Store a copy of the array linked to this renderer snapshot.Add(renderer, renderer.materials); } } private void PrintlMaterials() { Log("PrintMaterials()"); foreach (KeyValuePair entry in snapshot) { StringBuilder sbMats = new StringBuilder(); foreach (Material material in entry.Value) { sbMats.Append(material.name); } SuperController.LogMessage($"Renderer: {entry.Key} Mats: {sbMats}"); } } private void LoadMaterialSnapshot() { try { Log("LoadMaterialSnapshot()"); Renderer[] renderers = CUArescaleObject.GetComponentsInChildren(); foreach (Renderer renderer in renderers) { Material[] materials; if (snapshot.ContainsKey(renderer)) { materials = snapshot[renderer]; int i = 0; foreach (Material material in materials) { material.shader = snapshot[renderer][i].shader; i++; } renderer.materials = materials; } } } catch (Exception e) { SuperController.LogError("Exception: " + e); } } private void ReplaceWithShader(string shaderName) { if (!initialized) return; try { Log($"ReplaceWithShader({shaderName} materials: {snapshot.Count})"); if (snapshot.Count == 0) { SuperController.LogMessage("CUAshaderMod: material snapshot empty, taking snapshot now"); SaveMaterialSnapshot(); } Color color = Color.HSVToRGB(jColor.val.H, jColor.val.S, jColor.val.V); color.a = jAlpha.val; Shader newShader = SallyUtil.FindShaderFromAll(shaderName); if (newShader == null) { SuperController.LogError($"Could not find shader: {shaderName} (not loaded)"); return; } Material[] materials; Renderer[] renderers = CUArescaleObject.GetComponentsInChildren(); foreach (Renderer renderer in renderers) { if (snapshot.ContainsKey(renderer)) { materials = renderer.materials; for (int i = 0; i < materials.Length; i++) { // create new material from shader, not a reference materials[i] = new Material(newShader); // copy properties from snapshot material materials[i].CopyPropertiesFromMaterial(snapshot[renderer][i]); // set color & alpha from colorpicker and alpha slider materials[i].color = color; } // set back full array at once renderer.materials = materials; } } } catch (Exception e) { SuperController.LogError("Exception: " + e); } } private void SetColor(float h, float s, float v) { Log("SetColor()"); Color color = Color.HSVToRGB(h, s, v); color.a = jAlpha.val; Material[] materials; Renderer[] renderers = CUArescaleObject.GetComponentsInChildren(); foreach (Renderer r in renderers) { materials = r.materials; int numberOfMaterials = r.materials.Length; for (int i = 0; i < numberOfMaterials; i++) { materials[i].color = color; } // set back full array at once r.materials = materials; } } void OnDestroy() { Log("OnDestroy()"); cuaLoader.DeregisterAssetLoadedCallback(OnCUALoaded); SuperController.singleton.onSceneLoadedHandlers -= OnSceneLoaded; } public void Log(string s) { // Was debug toggle created already and is it's default state on? if (jDebug == null) { if (DEBUG_DEFAULT) { SuperController.LogMessage(s); } } // print debug message based on toggle state else if (jDebug.val) // debug toggle on? { SuperController.LogMessage(s); } } // Callbacks private void OnClickSupportButton() { Application.OpenURL("https://www.patreon.com/sallyw/about"); } private void OnCUALoaded() { Log("OnCUALoaded()"); SaveMaterialSnapshot(); // ReplaceWithShader(jShaderChoices.val); // DISABLED } private void OnSceneLoaded() { Log("OnSceneLoaded()"); SaveMaterialSnapshot(); //PrintMaterials(); //SallyUtil.PrintAllLoadedShaders(); ReplaceWithShader(jShaderChoices.val); } } public class SallyUtil { public static Shader FindShaderFromAll(string name) { Shader[] shaders = Resources.FindObjectsOfTypeAll(); foreach (Shader shader in shaders) if (shader.name.Equals(name)) return shader; return null; } public static void PrintAllLoadedShaders() { Shader[] shaders = Resources.FindObjectsOfTypeAll(); foreach (Shader shader in shaders) SuperController.LogMessage(shader.name); SuperController.LogMessage("Total number loaded shader: " + shaders.Length); } public static List GetLoadedShaderNameList() { List shaderNames = new List(); Shader[] shaders = Resources.FindObjectsOfTypeAll(); foreach (Shader shader in shaders) shaderNames.Add(shader.name); return shaderNames; } } }