// ***********************************************************************************
// 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;
}
}
}