// CustomShaderLoader.cs // Author: Regguise // Version: 1.72 using System; using UnityEngine; using UnityEngine.UI; using UnityEngine.XR; using System.Collections; using System.Collections.Generic; using MeshVR; using MVR.FileManagementSecure; using SimpleJSON; using System.Text.RegularExpressions; using MacGruber; using System.Linq; namespace Regguise { public class CustomShaderLoader : MVRScript { private const string LEVEL_NONE = "NONE"; private const string LEVEL_LOW = "LOW"; private const string LEVEL_MID = "MID"; private const string LEVEL_HIGH = "HIGH"; private string _currentLogLevel = LEVEL_NONE; private string _logFilePath, _logFileName; private bool _enableFileLogging = false; private string accumulatedLog = ""; private void InitializeLogging() { try { string pluginPath = GetPluginPath(); _logFileName = this.storeId + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".log"; if(!FileManagerSecure.IsDirectoryInPackage(pluginPath)) { _logFilePath = FileManagerSecure.NormalizePath(pluginPath + "/"); } else { int colonIndex = pluginPath.IndexOf(':'); if (colonIndex != -1) { _logFilePath = pluginPath.Substring(colonIndex + 2); } _logFilePath = FileManagerSecure.NormalizePath(_logFilePath + "/"); } if (!FileManagerSecure.DirectoryExists(_logFilePath)) { FileManagerSecure.CreateDirectory(_logFilePath); } _logFilePath = FileManagerSecure.NormalizePath(_logFilePath + _logFileName); LogMessage("Log file initialized at: " + _logFilePath, LEVEL_LOW); } catch (Exception e) { SuperController.LogError($"[{this.storeId}] Failed to initialize logging: {e}"); } } private void LogToFile(string message) { UserActionCallback confirmCallback = null; UserActionCallback denyCallback = null; ExceptionCallback exceptionCallback = null; if (!_enableFileLogging) return; if (accumulatedLog == "") return; try { // Read existing content if file exists string existingContent = ""; if (FileManagerSecure.FileExists(_logFilePath)) { existingContent = FileManagerSecure.ReadAllText(_logFilePath); } // Combine existing content with new message string combinedContent = existingContent + message; // Write the combined content back to file FileManagerSecure.WriteAllText(_logFilePath, combinedContent, confirmCallback, denyCallback, exceptionCallback); accumulatedLog = ""; } catch (Exception e) { SuperController.LogError($"[{this.storeId}] Failed to write to log file: {e}"); } } private void LogMessage(string message, string level = LEVEL_NONE) { if (level == LEVEL_NONE || (level == LEVEL_LOW && _currentLogLevel != LEVEL_NONE) || (level == LEVEL_MID && (_currentLogLevel == LEVEL_MID || _currentLogLevel == LEVEL_HIGH)) || (level == LEVEL_HIGH && _currentLogLevel == LEVEL_HIGH)) { string formattedMessage = $"[{this.storeId}] [{level}] {message}"; SuperController.LogMessage(formattedMessage); if (_enableFileLogging) { accumulatedLog += formattedMessage + "\n"; } } } private void LogError(string message) { string formattedMessage = $"[{this.storeId}] [ERROR] {message}"; SuperController.LogError(formattedMessage); if (_enableFileLogging) { accumulatedLog += formattedMessage + "\n"; } } HashSet excludedMatNames = new HashSet { "Gums", "Gums-1", "EyeReflection-1", "EyeReflection", "Irises", "Irises-1", "Eyelashes", "Eyelashes-1", "Cornea-1", "Cornea", "Pupils-1", "Pupils", "Tear", "Tear-1", "Teeth-1", "Teeth", "Tongue", "Tongue-1", "InnerMouth-1", "InnerMouth"}; private static readonly string defaultDir = "Custom/Scripts/Regguise/CustomShaderLoader/"; private static readonly string defaultAssetDir = "Custom/Assets/"; JSONStorableUrl shaderBundleUrl = new JSONStorableUrl("shaderBundle", ""); JSONStorableUrl shaderPropertiesUrl = new JSONStorableUrl("shaderProperties", "", "json"); string shaderPropertiesFileRestored = ""; JSONStorableString currentBundlePath = new JSONStorableString("currentBundlePath", "None"); JSONStorableStringChooser selectedShaderName = new JSONStorableStringChooser("selectedShader", new List(), "", ""); JSONStorableStringChooser selectedClothingItem = new JSONStorableStringChooser("selectedClothingItem", new List(), "", ""); List availableShaders = new List(); List clothingItems = new List(); UnityEngine.Object[] allAssets; string fileExt = "customshaderloader"; Dictionary materialSlotEnabled = new Dictionary(); List materialSlotStorables = new List(); List skinMaterialSlotStorables = new List(); JSONStorableBool showSkinMaterialSlots = new JSONStorableBool("showSkinMaterialSlots", false); JSONStorableFloat renderQueueStorable = new JSONStorableFloat("Render Queue", 2000, 0, 5000); List CUAmaterials = new List(); Dictionary positionCoroutines = new Dictionary(); Coroutine updateTimeCoroutine; bool isLoadingPrevUpdate = false; bool nowStarted = false; bool loadedAssetBundle; int retryAttempts; private ComputeBuffer _vertexBuffer; private RenderTexture _outputTexture; private ComputeShader _computeShader; protected List defaultShaders = new List(); Shader clothingDefaultShader; private Shader loadedShader; private DAZCharacter _character; private DAZCharacterSelector _selector; private DAZCharacterRun _dcr; private Atom _person; private Material _material; Material[] SavedMaterials; Material[] SavedMaterialsTemp; private DAZSkinV2 skin; DAZClothingItem dazClothingItem; DAZDynamic dazDynamic; DAZMesh dazMesh; DAZSkinWrap dazSkinWrap; DAZBone anchorBone; Material[] skinOriginalMaterials; Material[] clothingOriginalMaterials; JSONStorableString pluginMode = new JSONStorableString("pluginMode", ""); bool restoreDone = false; bool shaderPropertyInitDone = false; bool assetBundleLoaded = false; bool hideSkinMatToggleDone = false; bool customShaderLoaderPresent = false; bool skinLoaded = false; bool personPluginInitDone = false; bool cuaPluginInitDone = false; Dictionary propertyStorables = new Dictionary(); List textureStorables = new List(); List skinMaterialSlotUI = new List(); Dictionary shaderProperties = new Dictionary(); string overrideStoreId; public override void Init() { try { InitializeLogging(); LogMessage("Initializing CustomShaderLoader", LEVEL_NONE); // Setup shader bundle selection UI SetupShaderBundleUI(); // Check if we're on a person atom or a clothing item WalkAndGetParentItems(); SetupShaderSelectionUI(); CreateInfoText("Shader Properties", 50, true, new Color(0.7f, 0.7f, 0.7f)); LogMessage("Init completed successfully", LEVEL_LOW); clothingDefaultShader = Shader.Find("Custom/Subsurface/TransparentGlossNMNoCullSeparateAlphaComputeBuff"); // Create material slot storables // 10 should be plenty for a clothing item!! for(int i = 0; i < 10; i++) { var storable = new JSONStorableBool($"materialSlot_{i}", true); RegisterBool(storable); materialSlotStorables.Add(storable); LogMessage($"Created material slot storable {i}", LEVEL_HIGH); } // Create skin material slot storables for(int i = 0; i < 30; i++) { var storable = new JSONStorableBool($"skinMaterialSlot_{i}", false); RegisterBool(storable); skinMaterialSlotStorables.Add(storable); LogMessage($"Created skin material slot storable {i}", LEVEL_HIGH); } // RegisterStringChooser(selectedShaderName); if (_person.type == "CustomUnityAsset") { LogMessage("Entering CUA mode", LEVEL_MID); pluginMode.val = "cua"; CreateInfoText("CUA", 50, false, new Color(1f, 0.5f, 0.5f)); Utils.SetupButton(this, "Rescan CUA Materials", () => { LogMessage ("Rescanning CUA materials", LEVEL_MID); StartCoroutine(GetAllMaterialsFromCUA(_person)); }, false); Utils.SetupAction(this, "RescanCUAMaterials", () => { LogMessage ("Rescanning CUA materials", LEVEL_MID); StartCoroutine(GetAllMaterialsFromCUA(_person)); }); renderQueueStorable = Utils.SetupSliderInt(this, "Render Queue", 2000, 0, 5000, false); renderQueueStorable.setCallbackFunction = (val) => { LogMessage("Setting Render Queue: " + val, LEVEL_MID); int intVal = (int)val; if (CUAmaterials.Count > 0) { for (int i = 0; i < CUAmaterials.Count; i++) { CUAmaterials[i].renderQueue = intVal; } } }; StartCoroutine(GetAllMaterialsFromCUA(_person)); } else if (dazClothingItem == null && dazSkinWrap == null) { LogMessage("Entering person atom mode", LEVEL_MID); pluginMode.val = "person"; if(_character == null) { LogError("Could not find character. Is this a person atom?"); return; } StartCoroutine(WaitSkinAndSetup()); } else { LogMessage("Entering clothing item mode", LEVEL_MID); pluginMode.val = "clothing"; if(dazClothingItem == null) { LogError("Could not find clothing item. Is this a clothing item?"); return; } if(dazSkinWrap == null) { LogError("Could not find skinwrap. Is this a clothing item?"); return; } UpdateMaterialSlotCheckboxes(); clothingOriginalMaterials = new Material[dazSkinWrap.dazMesh.materials.Length]; for(int i = 0; i < dazSkinWrap.dazMesh.materials.Length; i++){ clothingOriginalMaterials[i] = new Material(dazSkinWrap.dazMesh.materials[i]); LogMessage($"Created original material copy for clothing material {i}: {dazSkinWrap.dazMesh.materials[i].name}", LEVEL_HIGH); } } } catch (Exception e) { LogError($"Exception caught in Init: {e}"); if (skin == null) LogError("skin is null"); if (dazClothingItem == null) LogError("dazClothingItem is null"); if (dazSkinWrap == null) LogError("dazSkinWrap is null"); if (containingAtom == null) LogError("containingAtom is null"); if (defaultShaders == null) LogError("defaultShaders is null"); if (skinOriginalMaterials == null) LogError("skinOriginalMaterials is null"); if (clothingOriginalMaterials == null) LogError("clothingOriginalMaterials is null"); if (clothingItems == null) LogError("clothingItems is null"); Cleanup(); } } private IEnumerator WaitSkinAndSetup() { LogMessage("Waiting for skin to load", LEVEL_LOW); yield return new WaitUntil(() => _character.skin != null); skin = _character.skin; skinLoaded = true; LogMessage("Skin loaded", LEVEL_LOW); MVRPluginManager pluginManager = _person.GetStorableByID("PluginManager") as MVRPluginManager; foreach(MVRScript script in pluginManager.GetComponentsInChildren(true)) { LogMessage("Active script: " + script.GetType().ToString().ToLower(), LEVEL_HIGH); if(script.GetType().ToString().ToLower().Contains("customshaderloader") && script.storeId != this.storeId) { LogMessage("Found CustomShaderLoader in PluginManager", LEVEL_HIGH); customShaderLoaderPresent = true; } } if (!customShaderLoaderPresent) { UpdateSkinMaterialSlotCheckboxes(); } else { LogMessage("CustomShaderLoader found in PluginManager, skipping skin material slot checkboxes", LEVEL_HIGH); CreateInfoText("Skin slots disabled due to another CustomShaderLoader present", 80, false, new Color(1f, 0.5f, 0.5f), 30); Utils.SetupSpacer(this, 50, false); } ShowHideSkinMaterialSlotUI(false); for (int i = 0; i < skin.GPUmaterials.Length; i++) { defaultShaders.Add(new Material(skin.GPUmaterials[i]).shader); LogMessage($"Added default shader for material {i}: {skin.GPUmaterials[i].name}", LEVEL_HIGH); } skinOriginalMaterials = new Material[skin.GPUmaterials.Length]; for(int i = 0; i < skin.GPUmaterials.Length; i++){ skinOriginalMaterials[i] = new Material(skin.GPUmaterials[i]); LogMessage($"Created original material copy for {skin.GPUmaterials[i].name}", LEVEL_HIGH); } FindClothingItems(); // Setup clothing selection UI SetupClothingSelectionUI(); if (clothingItems.Count == 0) { LogMessage("No clothing items found on the person atom.", LEVEL_MID); } personPluginInitDone = true; } private void FindClothingItems() { LogMessage("Finding clothing items", LEVEL_LOW); clothingItems.Clear(); // Find all DAZClothingItem components in the person atom DAZSkinWrap[] items = containingAtom.GetComponentsInChildren(true); foreach (var item in items) { // Only add active clothing items if (item.gameObject.activeInHierarchy) { clothingItems.Add(item); LogMessage($"Found clothing item: {item.name}", LEVEL_HIGH); } } LogMessage($"Found {clothingItems.Count} clothing items on the person atom.", LEVEL_MID); } private void SetupShaderSelectionUI(){ selectedShaderName = Utils.SetupStringChooser(this, "Select Shader", selectedShaderName.choices, -1, false); selectedShaderName.setCallbackFunction = (string value) => { if (!string.IsNullOrEmpty(value)) { StartCoroutine(LoadSelectedShader(value)); } }; // Add a button to load shader properties from a file UIDynamicButton shaderPropertiesButton = Utils.SetupButton(this, "Load Shader Properties", () => { LogMessage("Loading shader properties from file..."); }, false); shaderPropertiesUrl.shortCuts = FileManagerSecure.GetShortCutsForDirectory(defaultAssetDir); UIDynamicTextField textfield = CreateTextField(shaderPropertiesUrl, false); textfield.UItext.alignment = TextAnchor.MiddleRight; textfield.UItext.horizontalOverflow = HorizontalWrapMode.Overflow; textfield.UItext.verticalOverflow = VerticalWrapMode.Truncate; LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = 35; textfield.height = 35; RegisterUrl(shaderPropertiesUrl); // shaderPropertiesUrl.SetFilePath(defaultAssetDir + "shaderProperties.json"); shaderPropertiesUrl.RegisterFileBrowseButton(shaderPropertiesButton.button); shaderPropertiesUrl.setCallbackFunction = (string url) => { // SuperController.LogMessage("shaderPropertiesUrl: " + url); DefineShaderProperties(); }; Utils.SetupSpacer(this, 20, false); } private void CreateInfoText(string text, float height, bool rightSide, Color color, int fontSize = 40) { JSONStorableString storable = new JSONStorableString("Info", text); UIDynamicTextField textfield = CreateTextField(storable, rightSide); LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = height; // textfield.height = 20; textfield.UItext.alignment = TextAnchor.MiddleCenter; textfield.UItext.fontSize = fontSize; textfield.UItext.fontStyle = FontStyle.Bold; textfield.backgroundColor = color; } private void RescanClothingItems() { LogMessage("Rescanning clothing items..."); FindClothingItems(); // Update the clothing selection popup List updatedClothingNames = new List(); foreach (var item in clothingItems) { updatedClothingNames.Add(item.name); } // Try to keep the same selection if possible string currentSelection = selectedClothingItem.val; selectedClothingItem.choices = updatedClothingNames; // If the current selection still exists, keep it if (!string.IsNullOrEmpty(currentSelection) && updatedClothingNames.Contains(currentSelection)) { selectedClothingItem.val = currentSelection; } else { selectedClothingItem.val = ""; // SuperController.LogMessage("No clothing items found after reload."); } } private void SetupClothingSelectionUI() { CreateInfoText("Clothing", 50, false, Color.green); Utils.SetupButton(this, "Rescan Clothing Items", () => { RescanClothingItems(); }, false); Utils.SetupAction(this, "RescanClothingItems", () => { RescanClothingItems(); }); // Create list of clothing item names for the popup List clothingNames = new List(); foreach (var item in clothingItems) { clothingNames.Add(item.name); } // Setup the clothing selection popup selectedClothingItem = Utils.SetupStringChooser(this, "Select Clothing Item", clothingNames, -1, false); selectedClothingItem.setCallbackFunction = (string name) => { if (!string.IsNullOrEmpty(name)) { // Find the selected clothing item if (dazSkinWrap != null) { RestoreMaterialsShader(); } StartCoroutine(ApplyShaderToMaterialsPersonCoroutine(name)); } }; } private IEnumerator ApplyShaderToMaterialsPersonCoroutine(string name) { LogMessage("ApplyShaderToMaterialsPersonCoroutine: " + name, LEVEL_LOW); yield return new WaitUntil(() => !SuperController.singleton.isLoading && shaderPropertyInitDone && personPluginInitDone); // if (waitForShader) { // yield return new WaitUntil(() => shaderPropertyInitDone); // } int retryCount = 0; while (clothingItems.Count == 0) { FindClothingItems(); // upon exiting from load, find clothing items again retryCount++; if (retryCount > 20) { LogError("ApplyShaderToMaterialsPersonCoroutine: Could not find clothing items after 20 retries"); yield break; } yield return new WaitForSeconds(0.1f); } selectedClothingItem.choices = clothingItems.Select(item => item.name).ToList(); selectedClothingItem.valNoCallback = name; dazSkinWrap = clothingItems.Find(item => item.name == name); if (dazSkinWrap == null) { LogError("ApplyShaderToMaterialsPersonCoroutine: Could not find clothing item: " + name); yield break; } if (dazSkinWrap != null) { // Store original materials clothingOriginalMaterials = new Material[dazSkinWrap.dazMesh.materials.Length]; for(int i = 0; i < dazSkinWrap.dazMesh.materials.Length; i++){ clothingOriginalMaterials[i] = dazSkinWrap.dazMesh.materials[i]; } // Update material slot checkboxes // SuperController.LogMessage("SetupClothingSelectionUI: Updating material slot checkboxes"); UpdateMaterialSlotCheckboxes(); // Apply shader if one is loaded if (loadedShader != null) { ApplyShaderToMaterials(); ApplyAllProperties(); } } else { LogError($"Could not find skinwrap on clothing item: {name}"); } } private void SetupShaderBundleUI() { Utils.OnInitUI(CreateUIElement); shaderBundleUrl = Utils.SetupAssetBundleChooser(this, "Load Shader Bundle", defaultAssetDir, false, "assetbundle"); shaderBundleUrl.shortCuts = FileManagerSecure.GetShortCutsForDirectory(defaultAssetDir); shaderBundleUrl.setCallbackFunction = (string url) => { if (!string.IsNullOrEmpty(url)) { StartCoroutine(AttemptLoadAssetBundle(url)); } }; Utils.SetupSpacer(this, 50, false); } private void Cleanup() { // Restore original materials RestoreMaterialsShader(); // Release asset bundle if one was loaded if (!string.IsNullOrEmpty(shaderBundleUrl.val)) { AssetLoader.DoneWithAssetBundleFromFile(shaderBundleUrl.val); } // Clear material slot checkboxes // Reset UI state currentBundlePath.val = "None"; availableShaders.Clear(); selectedShaderName.choices = new List(); selectedShaderName.val = ""; // Clear clothing selection (only in person atom mode) if (containingAtom.type == "Person" && selectedClothingItem != null) { selectedClothingItem.choices = new List(); selectedClothingItem.val = ""; } } private void OnEnable() { // No need to load anything on enable // Shaders will be loaded when selected from the dropdown or from a preset // SuperController.LogMessage("CustomShaderLoader OnEnable"); if (loadedShader != null) { ApplyShaderToMaterials(); if (pluginMode.val == "person") { ApplySkinShaderToMaterials(); } // ApplyAllProperties(); } } void OnDisable() { LogToFile(accumulatedLog); RestoreMaterialsShader(); } private void RestoreMaterialsShader() { if (pluginMode.val == "cua") { for (int i = 0; i < CUAmaterials.Count; i++) { LogMessage("RestoreMaterialsShader: CUAmaterials[i].name: " + CUAmaterials[i].name + " defaultShaders[i].name: " + defaultShaders[i].name, LEVEL_HIGH); CUAmaterials[i].shader = defaultShaders[i]; } return; } if (dazSkinWrap != null) { if(clothingOriginalMaterials != null) { foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { int originalRenderQueue = dazSkinWrap.GPUmaterials[i].renderQueue; LogMessage("RestoreMaterialsShader: originalRenderQueue: " + originalRenderQueue, LEVEL_HIGH); dazSkinWrap.GPUmaterials[i].shader = clothingDefaultShader; dazSkinWrap.GPUmaterials[i].renderQueue = originalRenderQueue; } dazSkinWrap.BroadcastMessage("OnApplicationFocus", true); } } } if (skin != null) { for(int i = 0; i < skin.GPUmaterials.Length; i++) { if (skin.GPUmaterials[i] == null) { LogError("RestoreMaterialsShader: skin.GPUmaterials[i] is null, is the person using dummie skin?"); continue; } LogMessage("RestoreMaterialsShader: skin.GPUmaterials[i].name: " + skin.GPUmaterials[i].name, LEVEL_HIGH); skin.GPUmaterials[i] = skinOriginalMaterials[i]; skin.GPUmaterials[i].shader = defaultShaders[i]; } skin.BroadcastMessage("OnApplicationFocus", true); } } void OnDestroy() { OnDisable(); if (!string.IsNullOrEmpty(shaderBundleUrl.val)) { AssetLoader.DoneWithAssetBundleFromFile(shaderBundleUrl.val); } foreach (var coroutine in positionCoroutines) { StopCoroutine(coroutine.Value); } if (updateTimeCoroutine != null) { StopCoroutine(updateTimeCoroutine); updateTimeCoroutine = null; } positionCoroutines.Clear(); } class LoadAssetBundle : CustomYieldInstruction { AssetLoader.AssetBundleFromFileRequest req; private bool done; public bool success => req.assetBundle != null; public T GetAsset(string name) where T : UnityEngine.Object { return req.assetBundle?.LoadAsset(name); } public UnityEngine.Object[] GetAllAssets() { return req.assetBundle?.LoadAllAssets(); } public LoadAssetBundle(string url) { req = new AssetLoader.AssetBundleFromFileRequest { path = url, callback = _ => { done = true; if (req.assetBundle == null) AssetLoader.DoneWithAssetBundleFromFile(req.path); } }; AssetLoader.QueueLoadAssetBundleFromFile(req); } public override bool keepWaiting => !done; } private IEnumerator LoadSelectedShader(string shaderName) { // SuperController.LogMessage("id: " + this.storeId + " LoadSelectedShader: " + shaderName); int loadSelectedShaderTryCount = 0; // if (string.IsNullOrEmpty(shaderBundleUrl.val)) { while (!assetBundleLoaded && loadSelectedShaderTryCount < 1500) { //max wait for 30 seconds for asset bundle to load loadSelectedShaderTryCount++; // SuperController.LogMessage("id: " + this.storeId + " LoadSelectedShader: Retrying Count: " + loadSelectedShaderTryCount); yield return new WaitForSeconds(0.02f); } if (!assetBundleLoaded) { LogError("No asset bundle loaded"); yield break; } } //call this when selectedShaderName.choices is set selectedShaderName.valNoCallback = shaderName; // AttemptLoadAssetBundle will clear the value, reset it here selectedShaderName.popup.currentValueNoCallback = shaderName; if (allAssets == null) { LogError("No assets loaded"); yield break; } // Find the shader with matching name Shader foundShader = null; foreach (var asset in allAssets) { if (asset is Shader && asset.name == shaderName) { foundShader = asset as Shader; break; } } try { if (foundShader != null) { loadedShader = foundShader; SuperController.LogMessage($"id: {this.storeId} Successfully loaded shader: {shaderName}"); DefineShaderProperties(); ApplyShaderToMaterials(); if (pluginMode.val == "person") { ApplySkinShaderToMaterials(); } ApplyAllProperties(); shaderPropertyInitDone = true; } else { LogError($"Shader {shaderName} not found in asset bundle"); AssetLoader.DoneWithAssetBundleFromFile(shaderBundleUrl.val); } } catch (Exception e) { LogError($"Exception caught: {e}"); AssetLoader.DoneWithAssetBundleFromFile(shaderBundleUrl.val); } } private void ApplyShaderToMaterials() { if (loadedShader == null) return; if (pluginMode.val == "cua") { for (int i = 0; i < CUAmaterials.Count; i++) { if (materialSlotStorables[i].val) { CUAmaterials[i].shader = loadedShader; LogMessage("Applied shader to: " + CUAmaterials[i].name + " shader: " + loadedShader.name + " renderQueue: " + CUAmaterials[i].renderQueue, LEVEL_HIGH); } else { CUAmaterials[i].shader = defaultShaders[i]; LogMessage("Applied shader to: " + CUAmaterials[i].name + " shader: " + defaultShaders[i].name + " renderQueue: " + CUAmaterials[i].renderQueue, LEVEL_HIGH); } } } else if (dazSkinWrap != null) { foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { if (materialSlotStorables[i].val) { LogMessage("Applying shader to: " + dazSkinWrap.GPUmaterials[i].name + " shader: " + loadedShader.name + " renderQueue: " + dazSkinWrap.GPUmaterials[i].renderQueue, LEVEL_HIGH); int maxRenderQueue = Mathf.Max(dazSkinWrap.GPUmaterials[i].renderQueue, loadedShader.renderQueue); dazSkinWrap.GPUmaterials[i].shader = loadedShader; dazSkinWrap.GPUmaterials[i].renderQueue = maxRenderQueue; LogMessage("Applied shader to: " + dazSkinWrap.GPUmaterials[i].name + " shader: " + loadedShader.name + " renderQueue: " + dazSkinWrap.GPUmaterials[i].renderQueue, LEVEL_HIGH); } else { dazSkinWrap.GPUmaterials[i].shader = clothingDefaultShader; LogMessage("Applied shader to: " + dazSkinWrap.GPUmaterials[i].name + " shader: " + clothingDefaultShader.name + " renderQueue: " + dazSkinWrap.GPUmaterials[i].renderQueue, LEVEL_HIGH); } } dazSkinWrap.BroadcastMessage("OnApplicationFocus", true); } } } private void ApplySkinShaderToMaterials() { if (skin == null || loadedShader == null || pluginMode.val != "person" || customShaderLoaderPresent) return; for(int i = 0; i < skin.GPUmaterials.Length; i++) { if (excludedMatNames.Contains(skin.GPUmaterials[i].name)) continue; if (skinMaterialSlotStorables[i].val) { skin.GPUmaterials[i].shader = loadedShader; LogMessage("ApplySkinShaderToMaterials: " + skin.GPUmaterials[i].name + ": " + skin.GPUmaterials[i].shader.name + " renderQueue: " + skin.GPUmaterials[i].renderQueue, LEVEL_HIGH); } else { skin.GPUmaterials[i] = skinOriginalMaterials[i]; skin.GPUmaterials[i].shader = defaultShaders[i]; LogMessage("ApplySkinShaderToMaterials: " + skin.GPUmaterials[i].name + ": " + skin.GPUmaterials[i].shader.name + " renderQueue: " + skin.GPUmaterials[i].renderQueue, LEVEL_HIGH); } } skin.BroadcastMessage("OnApplicationFocus", true); } private IEnumerator SkinApplyShaderWaitForLoading() { yield return new WaitUntil(() => !SuperController.singleton.isLoading && personPluginInitDone); ApplySkinShaderToMaterials(); } private void ApplyAllProperties() { var xValue = 0f; var yValue = 0f; foreach (var kvp in propertyStorables) { var storable = kvp.Value; if (storable is JSONStorableFloat) { var propertyname = kvp.Key; if (propertyname.EndsWith("_X")) { xValue = ((JSONStorableFloat)kvp.Value).val; } else if (propertyname.EndsWith("_Y")) { yValue = ((JSONStorableFloat)kvp.Value).val; } else if (propertyname.EndsWith("_Z")) { propertyname = propertyname.Substring(0, propertyname.Length - 2); UpdateShaderProperty(propertyname, new Vector3(xValue, yValue, ((JSONStorableFloat)kvp.Value).val)); xValue = 0f; yValue = 0f; } else { UpdateShaderProperty(propertyname, ((JSONStorableFloat)kvp.Value).val); } } else if (storable is JSONStorableColor) { var propertyname = kvp.Key; UpdateShaderProperty(propertyname, HSVColorPicker.HSVToRGB(((JSONStorableColor)kvp.Value).val.H, ((JSONStorableColor)kvp.Value).val.S, ((JSONStorableColor)kvp.Value).val.V)); } else if (storable is JSONStorableBool) { var propertyname = kvp.Key; UpdateShaderProperty(propertyname, ((JSONStorableBool)kvp.Value).val); } else if (storable is JSONStorableUrl) { var propertyname = kvp.Key; // UpdateShaderProperty(propertyname, ((JSONStorableUrl)kvp.Value).val); TexturePropertyCallback(((JSONStorableUrl)kvp.Value).val); } else if (storable is JSONStorableStringChooser) { var propertyname = kvp.Key; UpdateShaderProperty(propertyname, ((JSONStorableStringChooser)kvp.Value).val); } else { LogError("Unknown property type: " + storable.GetType()); } } } private void UpdateMaterialSlotCheckboxes() { Utils.RemoveUIElements(this, materialSlotStorables.Select(x => (object)x).ToList()); if (pluginMode.val == "cua") { // SuperController.LogMessage("UpdateMaterialSlotCheckboxes: materials: " + CUAmaterials.Count); if (CUAmaterials.Count == 0) return; for (int i = 0; i < CUAmaterials.Count; i++) { materialSlotStorables[i] = Utils.SetupToggle(this, CUAmaterials[i].name, materialSlotStorables[i].val, false); materialSlotStorables[i].setCallbackFunction = (bool val) => { ApplyShaderToMaterials(); }; } } else if (pluginMode.val == "clothing" || pluginMode.val == "person") { if (dazSkinWrap == null) return; // SuperController.LogMessage("UpdateMaterialSlotCheckboxes: materialSlotStorables[1].val: " + materialSlotStorables[1].val); // Create new checkboxes for each material slot foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { // SuperController.LogMessage("UpdateMaterialSlotCheckboxes: dazSkinWrap.GPUmaterials[i].name: " + dazSkinWrap.GPUmaterials[i].name); materialSlotStorables[i] = Utils.SetupToggle(this, dazSkinWrap.GPUmaterials[i].name, materialSlotStorables[i].val, false); // Add callback to reapply shader when toggle changes materialSlotStorables[i].setCallbackFunction = (bool val) => { ApplyShaderToMaterials(); }; } } } } private IEnumerator GetAllMaterialsFromCUA(Atom atom, bool wait = false) { int attempts = 0; cuaPluginInitDone = false; CUAmaterials.Clear(); if (wait) { while(CUAmaterials.Count == 0 && attempts < 30) { foreach (Transform child in atom.transform) { if (child != null && child.gameObject != null) { CUAmaterials.AddRange(child.GetComponentsInChildren(true).SelectMany(r => r.materials)); } } attempts++; yield return new WaitForSeconds(0.1f); } } else { foreach (Transform child in atom.transform) { if (child != null && child.gameObject != null) { CUAmaterials.AddRange(child.GetComponentsInChildren(true).SelectMany(r => r.materials)); } } } for (int i = 0; i < CUAmaterials.Count; i++) { defaultShaders.Add(CUAmaterials[i].shader); } UpdateMaterialSlotCheckboxes(); ApplyShaderToMaterials(); cuaPluginInitDone = true; yield break; } private void UpdateSkinMaterialSlotCheckboxes() { CreateInfoText("Skin", 50, false, Color.yellow); showSkinMaterialSlots = Utils.SetupToggle(this, "Show Skin Material Slots", false, false); showSkinMaterialSlots.setCallbackFunction = (bool val) => { ShowHideSkinMaterialSlotUI(val); }; if (skin == null) return; Utils.RemoveUIElements(this, skinMaterialSlotStorables.Select(x => (object)x).ToList()); for(int i = 0; i < skin.GPUmaterials.Length; i++) { if (skin.GPUmaterials[i] == null) { LogError("UpdateSkinMaterialSlotCheckboxes: skin.GPUmaterials[i] is null, is the person using dummie skin?"); continue; } if (excludedMatNames.Contains(skin.GPUmaterials[i].name)) continue; string matName = skin.GPUmaterials[i].name.EndsWith("-1") ? skin.GPUmaterials[i].name.Substring(0, skin.GPUmaterials[i].name.Length - 2) : skin.GPUmaterials[i].name; skinMaterialSlotStorables[i] = SetupSkinMaterialSlotToggle(matName, skinMaterialSlotStorables[i].val, false); skinMaterialSlotStorables[i].setCallbackFunction = (bool val) => { ApplySkinShaderToMaterials(); ApplyAllProperties(); }; } Utils.SetupSpacer(this, 20, false); } private void ShowHideSkinMaterialSlotUI(bool show) { if (pluginMode.val == "person") { foreach (var ui in skinMaterialSlotUI) { ui.gameObject.SetActive(show); } } } private IEnumerator AttemptLoadAssetBundle(string url) { SuperController.LogMessage("id: " + this.storeId + " AttemptLoadAssetBundle: " + url); assetBundleLoaded = false; // Clean up existing resources availableShaders.Clear(); selectedShaderName.choices = new List(); selectedShaderName.val = ""; shaderPropertiesUrl.valNoCallback = ""; shaderPropertyInitDone = false; // Utils.RemoveUIElements(this, new List {selectedShaderName}); RestoreMaterialsShader(); allAssets = null; ClearShaderProperties(); var request = new LoadAssetBundle(url); yield return StartCoroutine(request); if (!request.success) { LogError("Failed to load asset bundle"); yield break; } // Get all shader assets from the bundle allAssets = request.GetAllAssets(); if (allAssets != null) { foreach (var asset in allAssets) { if (asset is Shader) { availableShaders.Add(asset.name); } } } // Update UI with available shaders if (availableShaders.Count > 0) { selectedShaderName.choices = availableShaders; currentBundlePath.val = url; LogMessage($"Found {availableShaders.Count} shaders in bundle"); assetBundleLoaded = true; } else { LogError("No shaders found in asset bundle"); AssetLoader.DoneWithAssetBundleFromFile(url); } yield break; } string GetPluginPath() { string pluginId = this.storeId.Split('_')[0]; string pathToScriptFile = manager.GetJSON(true, true)["plugins"][pluginId].Value; string pathToScriptFolder = pathToScriptFile.Substring(0, pathToScriptFile.LastIndexOfAny(new char[] { '/', '\\' })); return pathToScriptFolder; } string GetLatestVarPath(string inputPath) { const int MAX_VERSION = 100; // Wake me if there is a var with 100 versions bool findLatest = false; string varName = Regex.Match(inputPath, "^[^:]*(?=:)").Value; string varPath = Regex.Replace(inputPath, "^[^:]*:", string.Empty); LogMessage("GetLatestVarPath: inputPath: " + inputPath, LEVEL_LOW); LogMessage("GetLatestVarPath: varName: " + varName, LEVEL_LOW); LogMessage("GetLatestVarPath: varPath: " + varPath, LEVEL_LOW); if (string.IsNullOrEmpty(varName)) { return inputPath; } if (varName.Contains("latest")) { varName = varName.Replace(".latest", ""); findLatest = true; } else { Match match = Regex.Match(varName, @"\d+$"); if (match.Success) { int version = int.Parse(match.Value); if (FileManagerSecure.PackageExists(varName)) { LogMessage("GetLatestVarPath: Found version: " + varName, LEVEL_LOW); return inputPath; // return as is } else { varName = varName.Replace("." + version, ""); findLatest = true; } } } if (findLatest) { int version = MAX_VERSION; while (version > 0) { if (FileManagerSecure.PackageExists(varName + "." + version)) { LogMessage("GetLatestVarPath: Found latest version: " + varName + "." + version, LEVEL_LOW); return varName + '.' + version + ":" + varPath; } version--; } // If we get here, no valid version was found LogMessage("GetLatestVarPath: No latest version found for " + varName, LEVEL_LOW); return inputPath; } LogMessage("GetLatestVarPath: No valid version found for " + varName, LEVEL_LOW); return inputPath; } private void Start() { if (!hideSkinMatToggleDone) { hideSkinMatToggleDone = true; ShowHideSkinMaterialSlotUI(false); } } private void Update() { //somehow calling this during Init() doesn't work, so we do it once here if (isLoadingPrevUpdate != SuperController.singleton.isLoading && SuperController.singleton.isLoading) { LogMessage("SuperController.singleton.isLoading Set: " + SuperController.singleton.isLoading); isLoadingPrevUpdate = SuperController.singleton.isLoading; } if (isLoadingPrevUpdate != SuperController.singleton.isLoading && !SuperController.singleton.isLoading) { LogMessage("SuperController.singleton.isLoading Cleared: " + SuperController.singleton.isLoading); isLoadingPrevUpdate = SuperController.singleton.isLoading; } } private void WalkAndGetParentItems(){ GameObject parent = this.gameObject; // SuperController.LogMessage("WalkAndGetParentItems: containingAtom: " + containingAtom.name); _person = containingAtom.GetComponent(); if (_person.name.ToLower().Contains("_debug")) { if (_person.name.ToLower().Contains("high")) { _currentLogLevel = LEVEL_HIGH; } else if (_person.name.ToLower().Contains("mid")) { _currentLogLevel = LEVEL_MID; } else if (_person.name.ToLower().Contains("low")) { _currentLogLevel = LEVEL_LOW; } _enableFileLogging = true; } if (_person.type == "CustomUnityAsset") { return; } _selector = _person.GetComponentInChildren(); _character = _selector.selectedCharacter; while(containingAtom == null || parent != containingAtom.gameObject){ if(parent.transform.parent == null){ return; } // LogMessage(parent.name); if(dazClothingItem == null){ dazClothingItem = parent.GetComponent(); } if(dazMesh == null){ dazMesh = parent.GetComponent(); } if(dazDynamic == null){ dazDynamic = parent.GetComponent(); } if(dazSkinWrap == null){ dazSkinWrap = parent.GetComponent(); } containingAtom = parent.GetComponent(); parent = parent.transform.parent.gameObject; } } private void LoadPreset(string path) { if (string.IsNullOrEmpty(path)) return; var presetJson = (JSONClass)LoadJSON(path); RestoreFromJSON(presetJson, true, true, null, false); } public override void RestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, JSONArray presetAtoms = null, bool setMissingToDefault = true) { LogMessage("RestoreFromJSON: " + jc.ToString(), LEVEL_MID); if (jc == null || jc.Count == 0) { // LogError("RestoreFromJSON: jc is null or empty"); LogMessage("RestoreFromJSON: [WARNING] jc is null or empty", LEVEL_NONE); return; } if (jc.HasKey("pluginMode")) { pluginMode.val = jc["pluginMode"].Value; } // First Restore selected shader name - this will be used after bundle loads // The late restore is mainly needed to ensure shader properties are applied after everything is loaded if (jc.HasKey("shaderPropertiesFile")) { shaderPropertiesFileRestored = GetLatestVarPath(jc["shaderPropertiesFile"].Value); //don't trigger the callback, LoadSelectedShader will do that } // Restore selected clothing item (only in person atom mode) if (pluginMode.val == "person" && jc.HasKey("selectedClothingItem")) { string clothingName = jc["selectedClothingItem"].Value; // Find the clothing item in our list if (!string.IsNullOrEmpty(clothingName)) { StartCoroutine(ApplyShaderToMaterialsPersonCoroutine(clothingName)); //we are calling callback explicitly here because it may have not been setup yet } } else if (pluginMode.val == "cua") { StartCoroutine(GetAllMaterialsFromCUA(_person, true)); if (jc.HasKey("Render Queue")) { float renderQueue = jc["Render Queue"].AsFloat; renderQueueStorable.val = renderQueue; } } // Restore material slot toggles if (jc.HasKey("materialSlots")) { var slots = jc["materialSlots"].AsArray; for (int i = 0; i < slots.Count && i < materialSlotStorables.Count; i++) { materialSlotStorables[i].valNoCallback = slots[i].AsBool; ApplyShaderToMaterials(); LogMessage("RestoreFromJSON: materialSlotStorables[" + i + "].val: " + materialSlotStorables[i].val, LEVEL_HIGH); } } // Restore skin material slot toggles if (jc.HasKey("skinMaterialSlots")) { var skinSlots = jc["skinMaterialSlots"].AsArray; for (int i = 0; i < skinSlots.Count && i < skinMaterialSlotStorables.Count; i++) { skinMaterialSlotStorables[i].valNoCallback = skinSlots[i].AsBool; LogMessage("RestoreFromJSON: skinMaterialSlotStorables[" + i + "].val: " + skinMaterialSlotStorables[i].val, LEVEL_HIGH); } StartCoroutine(SkinApplyShaderWaitForLoading()); //skin shader needs to be applied after loading is done } // Then restore the shader bundle path and trigger loading if (jc.HasKey("shaderBundle")) { shaderBundleUrl.valNoCallback = jc["shaderBundle"].Value; LogMessage("RestoreFromJSON: shaderBundleUrl.valNoCallback: " + shaderBundleUrl.valNoCallback, LEVEL_LOW); shaderBundleUrl.valNoCallback = GetLatestVarPath(shaderBundleUrl.valNoCallback); if (!string.IsNullOrEmpty(shaderBundleUrl.valNoCallback)) { LogMessage("RestoreFromJSON: Start AttemptLoadAssetBundle", LEVEL_HIGH); StartCoroutine(AttemptLoadAssetBundle(shaderBundleUrl.valNoCallback)); } } if (jc.HasKey("selectedShader")) { selectedShaderName.val = jc["selectedShader"].Value; } // Restore shader property values StartCoroutine(RestoreShaderPropertiesFromJSON(jc)); } public override void LateRestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, bool setMissingToDefault = true) { if (loadedShader != null) { ApplyShaderToMaterials(); foreach (var kvp in propertyStorables) { var storable = kvp.Value; if (storable is JSONStorableFloat) { LogMessage("LateRestoreFromJSON: storable.name: " + storable.name + " val: " + ((JSONStorableFloat)storable).val, LEVEL_HIGH); UpdateShaderProperty(storable.name, ((JSONStorableFloat)storable).val); } else if (storable is JSONStorableColor) { var color = ((JSONStorableColor)storable).val; UpdateShaderProperty(storable.name, HSVColorPicker.HSVToRGB(color.H, color.S, color.V)); } else if (storable is JSONStorableVector3) { UpdateShaderProperty(storable.name, ((JSONStorableVector3)storable).val); } } } restoreDone = true; } public override JSONClass GetJSON(bool includePhysical = true, bool includeAppearance = true, bool forceStore = false) { // var jc = new JSONClass(); var jc = base.GetJSON(includePhysical, includeAppearance, forceStore); jc["id"] = this.storeId; // Save shader bundle path jc["shaderBundle"] = shaderBundleUrl.val; jc["pluginMode"] = pluginMode.val; // Save selected shader name jc["selectedShader"] = selectedShaderName.val; // Save selected clothing item (only in person atom mode) if (containingAtom.type == "Person" && dazSkinWrap != null) { jc["selectedClothingItem"] = dazSkinWrap.name; } // Save material slot toggles var slots = new JSONArray(); foreach (var slot in materialSlotStorables) { slots.Add(new JSONData(slot.val)); } jc["materialSlots"] = slots; if (pluginMode.val == "person") { var skinSlots = new JSONArray(); foreach (var slot in skinMaterialSlotStorables) { skinSlots.Add(new JSONData(slot.val)); } jc["skinMaterialSlots"] = skinSlots; } if (!string.IsNullOrEmpty(shaderPropertiesUrl.val)) { jc["shaderPropertiesFile"] = GetLatestVarPath(shaderPropertiesUrl.val); } // Save shader property values var props = new JSONClass(); foreach (var kvp in propertyStorables) { var storable = kvp.Value; if (storable is JSONStorableFloat) { props[kvp.Key] = new JSONData(((JSONStorableFloat)storable).val); } else if (storable is JSONStorableColor) { var color = ((JSONStorableColor)storable).val; var colorObj = new JSONClass(); colorObj["H"] = new JSONData(color.H); colorObj["S"] = new JSONData(color.S); colorObj["V"] = new JSONData(color.V); props[kvp.Key] = colorObj; } else if (storable is JSONStorableVector3) { var vec = ((JSONStorableVector3)storable).val; var vecObj = new JSONClass(); vecObj["x"] = new JSONData(vec.x); vecObj["y"] = new JSONData(vec.y); vecObj["z"] = new JSONData(vec.z); props[kvp.Key] = vecObj; } else if (storable is JSONStorableUrl) { props[kvp.Key] = ((JSONStorableUrl)storable).val; } else if (storable is JSONStorableBool) { props[kvp.Key] = new JSONData(((JSONStorableBool)storable).val); } } jc["shaderProperties"] = props; return jc; } private void SavePreset(string path) { SuperController.singleton.fileBrowserUI.fileFormat = null; if (string.IsNullOrEmpty(path)) { return; } if (!path.ToLower().EndsWith($".{fileExt}")) { path += $".{fileExt}"; } SaveJSON(GetJSON(true,true,true), path); } private void ClearShaderProperties() { LogMessage("ClearShaderProperties: Clearing existing properties", LEVEL_LOW); Utils.RemoveUIElements(this, propertyStorables.Values.Select(x => (object)x).ToList()); Utils.RemoveUIElements(this, textureStorables.Select(x => (object)x).ToList()); textureStorables.Clear(); propertyStorables.Clear(); shaderProperties.Clear(); } private void DefineShaderProperties() { LogMessage("Defining shader properties", LEVEL_LOW); // Clear existing properties ClearShaderProperties(); if (loadedShader == null) { LogMessage("No shader loaded", LEVEL_NONE); return; } // Create a temporary material to read properties Material tempMaterial = new Material(loadedShader); // Define shader properties based on shader name string shaderName = loadedShader.name; List properties = GetShaderProperties(shaderName); if (properties == null) { LogMessage($"No properties found for shader: {shaderName}", LEVEL_NONE); return; } foreach (var property in properties) { LogMessage($"Processing property: {property.name} (Type: {property.type})", LEVEL_HIGH); string propertyName = property.name; string displayName = property.displayName; // Create appropriate JSONStorable based on property type switch (property.type) { case "color": // var defaultColorRGB = property.defaultColor; var colorStorable = Utils.SetupColor(this, displayName, property.defaultColor, true); colorStorable.setCallbackFunction = (h, s, v) => UpdateShaderProperty(propertyName, HSVColorPicker.HSVToRGB(h,s,v)); propertyStorables[propertyName] = colorStorable; LogMessage($"Created color property: {propertyName}", LEVEL_MID); break; case "float": case "range": var floatStorable = Utils.SetupSliderFloat(this, displayName, property.defaultValue, property.minValue, property.maxValue, true); floatStorable.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, val); propertyStorables[propertyName] = floatStorable; LogMessage($"Created float/range property: {propertyName}", LEVEL_MID); break; case "texture": var defaultUrl = (property.defaultUrl != null) ? GetLatestVarPath(property.defaultUrl) : ""; LogMessage($"Created texture property: {propertyName} with defaultUrl: {defaultUrl}", LEVEL_MID); propertyStorables[propertyName] = SetupTexture2DChooser(displayName, defaultUrl, true, new TextureSettings(), (Texture2D tex) => TexturePropertyCallback(tex.name)); break; case "vector": var vectorStorable_x = Utils.SetupSliderFloat(this, $"{displayName}_X", property.defaultVector.x, property.minVector.x, property.maxVector.x, true); var vectorStorable_y = Utils.SetupSliderFloat(this, $"{displayName}_Y", property.defaultVector.y, property.minVector.y, property.maxVector.y, true); var vectorStorable_z = Utils.SetupSliderFloat(this, $"{displayName}_Z", property.defaultVector.z, property.minVector.z, property.maxVector.z, true); vectorStorable_x.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, new Vector3(val, vectorStorable_y.val, vectorStorable_z.val)); vectorStorable_y.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, new Vector3(vectorStorable_x.val, val, vectorStorable_z.val) ); vectorStorable_z.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, new Vector3(vectorStorable_x.val, vectorStorable_y.val, val)); propertyStorables[$"{propertyName}_X"] = vectorStorable_x; propertyStorables[$"{propertyName}_Y"] = vectorStorable_y; propertyStorables[$"{propertyName}_Z"] = vectorStorable_z; LogMessage($"Created vector property: {propertyName}", LEVEL_MID); break; case "toggle": var toggleStorable = Utils.SetupToggle(this, displayName, property.defaultValueBool, true); toggleStorable.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, val); propertyStorables[propertyName] = toggleStorable; LogMessage($"Created toggle property: {propertyName}, defaultValue: {property.defaultValueBool}", LEVEL_MID); break; case "position": List boneNames = new List(); foreach(var storableId in containingAtom.GetStorableIDs()){ var storable = containingAtom.GetStorableByID(storableId); if(storable is DAZBone){ boneNames.Add(storableId); } } var bonePositionStorable = Utils.SetupStringChooser(this, displayName, boneNames, true); bonePositionStorable.setCallbackFunction = (val) => UpdateShaderProperty(propertyName, val); propertyStorables[propertyName] = bonePositionStorable; bonePositionStorable.val = property.defaultValuePosition; LogMessage($"Created position property: {propertyName}", LEVEL_MID); break; case "time": updateTimeCoroutine = StartCoroutine(UpdateTime()); LogMessage($"Created time property: {propertyName}", LEVEL_MID); break; } } // shaderPropertyInitDone = true; Destroy(tempMaterial); LogMessage("Shader properties definition completed", LEVEL_LOW); } private class ShaderProperty { public string name; public string displayName; public string type; public float defaultValue; public float minValue; public float maxValue; public string defaultUrl; public Color defaultColor; public Vector3 defaultVector; public Vector3 minVector; public Vector3 maxVector; public bool defaultValueBool; public string defaultValuePosition; } private List GetShaderProperties(string shaderName) { var properties = new List(); LogMessage("GetShaderProperties: shaderName: " + shaderName, LEVEL_LOW); try { // Get the directory of the asset bundle string bundlePath = shaderBundleUrl.val; string bundleDir = bundlePath.Substring(0, bundlePath.LastIndexOf('/')); if (string.IsNullOrEmpty(shaderName)) { LogError("Choose a shader first"); return null; } string propertiesFile = bundleDir + "/" + shaderName + ".json"; LogMessage("GetShaderProperties: propertiesFile: " + propertiesFile, LEVEL_LOW); if (!string.IsNullOrEmpty(shaderPropertiesFileRestored)) { propertiesFile = shaderPropertiesFileRestored; shaderPropertiesUrl.valNoCallback = propertiesFile; shaderPropertiesFileRestored = ""; // reset after use } else if (!string.IsNullOrEmpty(shaderPropertiesUrl.val)) { propertiesFile = shaderPropertiesUrl.val; } else { shaderPropertiesUrl.valNoCallback = propertiesFile; } if (FileManagerSecure.FileExists(propertiesFile)) { LogMessage("GetShaderProperties: propertiesFile exists", LEVEL_HIGH); var jsonData = LoadJSON(propertiesFile); if (jsonData != null && jsonData.AsArray != null) { var array = jsonData.AsArray; for (int i = 0; i < array.Count; i++) { var propertyJson = array[i]; var property = new ShaderProperty { name = propertyJson["name"].Value, displayName = propertyJson["displayName"].Value, type = propertyJson["type"].Value.ToLower() }; // Set type-specific properties switch (property.type) { case "color": property.defaultColor = ParseColor(propertyJson["defaultColor"].Value); break; case "float": case "range": property.defaultValue = propertyJson["defaultValue"].AsFloat; property.minValue = propertyJson["minValue"].AsFloat; property.maxValue = propertyJson["maxValue"].AsFloat; break; case "texture": property.defaultUrl = propertyJson["defaultUrl"].Value; break; case "vector": var vectorJson = propertyJson["defaultVector"]; property.defaultVector = new Vector3( vectorJson["x"].AsFloat, vectorJson["y"].AsFloat, vectorJson["z"].AsFloat ); var minVectorJson = propertyJson["minVector"]; property.minVector = new Vector3( minVectorJson["x"].AsFloat, minVectorJson["y"].AsFloat, minVectorJson["z"].AsFloat ); var maxVectorJson = propertyJson["maxVector"]; property.maxVector = new Vector3( maxVectorJson["x"].AsFloat, maxVectorJson["y"].AsFloat, maxVectorJson["z"].AsFloat ); break; case "toggle": property.defaultValueBool = bool.Parse(propertyJson["defaultValueBool"].Value); break; case "position": var positionJson = propertyJson["defaultValue"]; property.defaultValuePosition = positionJson.Value.ToString(); break; } LogMessage("GetShaderProperties: property: " + property.name + " " + property.type + " " + property.defaultValue + " " + property.minValue + " " + property.maxValue + " " + property.defaultVector + " " + property.minVector + " " + property.maxVector, LEVEL_HIGH); properties.Add(property); } } else { LogError($"Invalid JSON format in properties file: {propertiesFile}"); } } else { LogMessage($"No properties file found for shader: {shaderName}"); return null; } } catch (Exception e) { LogError($"Error loading shader properties: {e}"); return null; } return properties; } private IEnumerator RestoreShaderPropertiesFromJSON(JSONClass jc) { yield return new WaitUntil(() => shaderPropertyInitDone && (dazSkinWrap != null || pluginMode.val == "cua" || pluginMode.val == "person")); if (pluginMode.val == "person") { yield return new WaitUntil(() => personPluginInitDone); } if (pluginMode.val == "cua") { yield return new WaitUntil(() => cuaPluginInitDone); } if (jc.HasKey("shaderProperties")) { var props = jc["shaderProperties"].AsObject; foreach (string key in props.Keys) { LogMessage("RestoreFromJSON: key: " + key, LEVEL_HIGH); if (propertyStorables.ContainsKey(key)) { LogMessage("RestoreFromJSON: propertyStorables contains key: " + key, LEVEL_HIGH); var storable = propertyStorables[key]; if (storable is JSONStorableFloat) { LogMessage("RestoreFromJSON: storable is JSONStorableFloat: " + key + " " + props[key].AsFloat, LEVEL_HIGH); ((JSONStorableFloat)storable).val = props[key].AsFloat; // UpdateShaderProperty(key, props[key].AsFloat); } else if (storable is JSONStorableColor) { var color = props[key].AsObject; LogMessage("RestoreFromJSON: storable is JSONStorableColor: " + key + " " + color["H"].AsFloat + " " + color["S"].AsFloat + " " + color["V"].AsFloat, LEVEL_HIGH); ((JSONStorableColor)storable).val = new HSVColor { H = color["H"].AsFloat, S = color["S"].AsFloat, V = color["V"].AsFloat }; } else if (storable is JSONStorableVector3) { var vec = props[key].AsObject; LogMessage("RestoreFromJSON: storable is JSONStorableVector3: " + key + " " + vec["x"].AsFloat + " " + vec["y"].AsFloat + " " + vec["z"].AsFloat, LEVEL_HIGH); ((JSONStorableVector3)storable).val = new Vector3( vec["x"].AsFloat, vec["y"].AsFloat, vec["z"].AsFloat ); } else if (storable is JSONStorableUrl) { LogMessage("RestoreFromJSON: storable is JSONStorableUrl: " + key + " " + props[key].Value, LEVEL_HIGH); ((JSONStorableUrl)storable).val = GetLatestVarPath(props[key].Value); } else if (storable is JSONStorableBool) { LogMessage("RestoreFromJSON: storable is JSONStorableBool: " + key + " " + props[key].AsBool, LEVEL_HIGH); ((JSONStorableBool)storable).val = props[key].AsBool; } //TODO: Bone position is not handled here } } } } private void CreateColorPicker(JSONStorableColor colorStorable, string displayName) { Utils.SetupColor(this, displayName, HSVColorPicker.HSVToRGB(colorStorable.val.H, colorStorable.val.S, colorStorable.val.V), false); } private void CreateSlider(JSONStorableFloat floatStorable, string displayName) { Utils.SetupSliderFloat(this, displayName, floatStorable.val, floatStorable.min, floatStorable.max, false); } private void UpdateShaderProperty(string propertyName, object value) { LogMessage("UpdateShaderProperty: Updating shader property: " + propertyName + " with value: " + value, LEVEL_HIGH); if (dazSkinWrap != null) { foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { if (materialSlotStorables[i].val) { if (value is Color) { dazSkinWrap.GPUmaterials[i].SetColor(propertyName, (Color)value); } else if (value is float) { dazSkinWrap.GPUmaterials[i].SetFloat(propertyName, (float)value); } else if (value is Vector3) { dazSkinWrap.GPUmaterials[i].SetVector(propertyName, (Vector3)value); } else if (value is bool) { dazSkinWrap.GPUmaterials[i].SetFloat(propertyName, (bool)value ? 1 : 0); } else if (value is Texture2D) { dazSkinWrap.GPUmaterials[i].SetTexture(propertyName, (Texture2D)value); } else if (value is string) { if (positionCoroutines.ContainsKey(propertyName)) { StopCoroutine(positionCoroutines[propertyName]); } positionCoroutines[propertyName] = StartCoroutine(MonitorUpdatePosition(propertyName, (string)value)); } } } dazSkinWrap.BroadcastMessage("OnApplicationFocus", true); } } else { LogMessage("UpdateShaderProperty: No dazSkinWrap found", LEVEL_HIGH); } if (pluginMode.val == "cua") { for (int i = 0; i < CUAmaterials.Count; i++) { if (materialSlotStorables[i].val) { if (value is Color) { CUAmaterials[i].SetColor(propertyName, (Color)value); } else if (value is float) { CUAmaterials[i].SetFloat(propertyName, (float)value); } else if (value is Vector3) { CUAmaterials[i].SetVector(propertyName, (Vector3)value); } else if (value is bool) { CUAmaterials[i].SetFloat(propertyName, (bool)value ? 1 : 0); } else if (value is Texture2D) { // LogMessage("IsKeywordEnabled: " + CUAmaterials[i].IsKeywordEnabled(propertyName), LEVEL_HIGH); // CUAmaterials[i].EnableKeyword(propertyName); // LogMessage("IsKeywordEnabled: " + CUAmaterials[i].IsKeywordEnabled(propertyName), LEVEL_HIGH); LogMessage("UpdateShaderProperty: Setting texture: " + propertyName + " with value: " + value, LEVEL_HIGH); CUAmaterials[i].SetTexture(propertyName, (Texture2D)value); } } } } if (pluginMode.val == "person" && skin != null) { for (int i = 0; i < skin.GPUmaterials.Length; i++) { if (skinMaterialSlotStorables[i].val) { if (value is Color) { skin.GPUmaterials[i].SetColor(propertyName, (Color)value); } else if (value is float) { skin.GPUmaterials[i].SetFloat(propertyName, (float)value); } else if (value is Vector3) { skin.GPUmaterials[i].SetVector(propertyName, (Vector3)value); } else if (value is bool) { skin.GPUmaterials[i].SetFloat(propertyName, (bool)value ? 1 : 0); } else if (value is Texture2D) { skin.GPUmaterials[i].SetTexture(propertyName, (Texture2D)value); } else if (value is string) { if (positionCoroutines.ContainsKey(propertyName)) { StopCoroutine(positionCoroutines[propertyName]); } positionCoroutines[propertyName] = StartCoroutine(MonitorUpdatePosition(propertyName, (string)value)); } } } } } private IEnumerator UpdateTime() { float elapsedTime = 0; while (true) { yield return new WaitForEndOfFrame(); elapsedTime += Time.deltaTime; if (elapsedTime >= 2) { elapsedTime = 0; } if (dazSkinWrap != null) { foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { if (materialSlotStorables[i].val) { // LogMessage("UpdateTime: Setting _CustomTime with value: " + elapsedTime, LEVEL_HIGH); dazSkinWrap.GPUmaterials[i].SetFloat("_CustomTime", elapsedTime); } } dazSkinWrap.BroadcastMessage("OnApplicationFocus", true); } } if (pluginMode.val == "person" && skin != null) { for (int i = 0; i < skin.GPUmaterials.Length; i++) { if (skinMaterialSlotStorables[i].val) { skin.GPUmaterials[i].SetFloat("_CustomTime", elapsedTime); } } skin.BroadcastMessage("OnApplicationFocus", true); } if (pluginMode.val == "cua") { for (int i = 0; i < CUAmaterials.Count; i++) { if (materialSlotStorables[i].val) { CUAmaterials[i].SetFloat("_CustomTime", elapsedTime); } } } } } private IEnumerator MonitorUpdatePosition(string propertyName, string value) { if (string.IsNullOrEmpty(value)) { yield break; } DAZBone bone = containingAtom.GetStorableByID(value) as DAZBone; if (bone == null) { yield break; } Vector3 position = new Vector3(0, 0, 0); Vector4 rotation = new Vector4(0, 0, 0, 1); while (true) { yield return new WaitForEndOfFrame(); position.x = bone.transform.position.x; position.y = bone.transform.position.y; position.z = bone.transform.position.z; rotation.x = bone.transform.rotation.x; rotation.y = bone.transform.rotation.y; rotation.z = bone.transform.rotation.z; rotation.w = bone.transform.rotation.w; if (dazSkinWrap != null) { foreach (var mo in dazSkinWrap.GetComponents()) { foreach (var i in mo.paramMaterialSlots) { if (materialSlotStorables[i].val) { // LogMessage("MonitorUpdatePosition: Setting position: " + propertyName + " with value: " + position, LEVEL_HIGH); // LogMessage("MonitorUpdatePosition: Setting rotation: " + propertyName + "Rotation with value: " + rotation, LEVEL_HIGH); dazSkinWrap.GPUmaterials[i].SetVector(propertyName + "Pos", position); dazSkinWrap.GPUmaterials[i].SetVector(propertyName + "Rot", rotation); if (XRSettings.eyeTextureWidth > 0) { dazSkinWrap.GPUmaterials[i].SetFloat("_ScreenResolutionX", XRSettings.eyeTextureWidth); dazSkinWrap.GPUmaterials[i].SetFloat("_ScreenResolutionY", XRSettings.eyeTextureHeight); } else { dazSkinWrap.GPUmaterials[i].SetFloat("_ScreenResolutionX", Screen.width); dazSkinWrap.GPUmaterials[i].SetFloat("_ScreenResolutionY", Screen.height); } } } dazSkinWrap.BroadcastMessage("OnApplicationFocus", true); } } if (pluginMode.val == "person" && skin != null) { for (int i = 0; i < skin.GPUmaterials.Length; i++) { if (skinMaterialSlotStorables[i].val) { skin.GPUmaterials[i].SetVector(propertyName, position); skin.GPUmaterials[i].SetVector(propertyName + "Rotation", rotation); } } skin.BroadcastMessage("OnApplicationFocus", true); } if (pluginMode.val == "cua") { for (int i = 0; i < CUAmaterials.Count; i++) { if (materialSlotStorables[i].val) { CUAmaterials[i].SetVector(propertyName, position); } } } } } private void TexturePropertyCallback(string inpath) { if (string.IsNullOrEmpty(inpath)) return; inpath = inpath.Replace(":L", ""); inpath = inpath.Replace("SELF:/", ""); //not sure why this is needed string filename = FileManagerSecure.GetFileName(inpath); string path = FileManagerSecure.GetDirectoryName(inpath) + "/" + filename; if (!FileManagerSecure.FileExists(path)) { LogError("TexturePropertyCallback: File does not exist: " + path); return; } LogMessage("TexturePropertyCallback: path: " + path, LEVEL_HIGH); var settings = new TextureSettings { compress = true, createMipMaps = true, linearColor = true, wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Trilinear, anisoLevel = 5 }; var queuedImage = new ImageLoaderThreaded.QueuedImage { imgPath = path, createMipMaps = settings.createMipMaps, compress = settings.compress, linear = settings.linearColor, callback = (ImageLoaderThreaded.QueuedImage qi) => { if (!qi.hadError && qi.tex != null) { qi.tex.wrapMode = settings.wrapMode; qi.tex.filterMode = settings.filterMode; qi.tex.anisoLevel = settings.anisoLevel; // Find the property name that corresponds to this texture path string propertyName = propertyStorables.FirstOrDefault(x => x.Value is JSONStorableUrl && ((JSONStorableUrl)x.Value).val == inpath).Key; if (!string.IsNullOrEmpty(propertyName)) { UpdateShaderProperty(propertyName, qi.tex); } else { LogError($"Could not find property name for texture path: {inpath}"); } } else { LogError($"Failed to load texture: {qi.errorText}"); } } }; ImageLoaderThreaded.singleton.QueueImage(queuedImage); } //from MacGruber_Utils.cs private JSONStorableUrl SetupTexture2DChooser(string label, string defaultValue, bool rightSide, TextureSettings settings, TextureSetCallback callback) { JSONStorableUrl storable = new JSONStorableUrl(label, string.Empty, (string url) => {QueueLoadTexture(url, settings, callback); }, "jpg|png|tif|tiff"); RegisterUrl(storable); UIDynamicButton button = CreateButton("Browse " + label, rightSide); UIDynamicTextField textfield = CreateTextField(storable, rightSide); textfield.UItext.alignment = TextAnchor.MiddleLeft; textfield.UItext.horizontalOverflow = HorizontalWrapMode.Wrap; textfield.UItext.verticalOverflow = VerticalWrapMode.Truncate; LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = 35; textfield.height = 60; if (!string.IsNullOrEmpty(defaultValue)) { storable.SetFilePath(defaultValue); textfield.UItext.text = defaultValue; } storable.RegisterFileBrowseButton(button.button); textureStorables.Add(button); textureStorables.Add(textfield); // textureStorables.Add(layout); return storable; } //from MacGruber_Utils.cs private void QueueLoadTexture(string url, TextureSettings settings, TextureSetCallback callback) { if (ImageLoaderThreaded.singleton == null) return; if (string.IsNullOrEmpty(url)) return; ImageLoaderThreaded.QueuedImage queuedImage = new ImageLoaderThreaded.QueuedImage(); queuedImage.imgPath = url; queuedImage.forceReload = true; queuedImage.skipCache = true; queuedImage.compress = settings.compress; queuedImage.createMipMaps = settings.createMipMaps; queuedImage.isNormalMap = settings.isNormalMap; queuedImage.linear = settings.linearColor; queuedImage.createAlphaFromGrayscale = settings.createAlphaFromGrayscale; queuedImage.createNormalFromBump = settings.createNormalFromBump; queuedImage.bumpStrength = settings.bumpStrength; queuedImage.isThumbnail = false; queuedImage.fillBackground = false; queuedImage.invert = false; queuedImage.callback = (ImageLoaderThreaded.QueuedImage qi) => { Texture2D tex = qi.tex; if (tex != null) { tex.wrapMode = settings.wrapMode; tex.filterMode = settings.filterMode; tex.anisoLevel = settings.anisoLevel; } callback(tex); }; ImageLoaderThreaded.singleton.QueueImage(queuedImage); } //from MacGruber_Utils.cs private JSONStorableBool SetupSkinMaterialSlotToggle(string label, bool defaultValue, bool rightSide) { LogMessage("SetupSkinMaterialSlotToggle: label: " + label + " defaultValue: " + defaultValue, LEVEL_HIGH); JSONStorableBool storable = new JSONStorableBool(label, defaultValue); storable.storeType = JSONStorableParam.StoreType.Full; UIDynamic ui = CreateToggle(storable, rightSide); skinMaterialSlotUI.Add(ui); RegisterBool(storable); return storable; } private Color ParseColor(string colorText) { if (string.IsNullOrEmpty(colorText)) return Color.white; // Handle named colors switch (colorText.ToLower()) { case "white": return Color.white; case "black": return Color.black; case "red": return Color.red; case "green": return Color.green; case "blue": return Color.blue; case "yellow": return Color.yellow; case "cyan": return Color.cyan; case "magenta": return Color.magenta; case "gray": return Color.gray; case "grey": return Color.grey; case "clear": return Color.clear; } // Default to white if parsing fails return Color.white; } } }