using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; /// /// This plugin is about body transparency and hiding body parts, while also maintaining functionality of other plugins that manipulate skin material shaders (e.g., tesselation). /// According to legend, there once was a plugin Character Transparency by ZRSX based on Doofenschmalphys edit of Seamonkey's "Hide character" plugin, which itself is a modification of Acidbubbles' "Improved POV". /// /// Version history: /// - 0.1 - trying to figure out how this works /// namespace HAL9001 { public class GoingGoingGone : MVRScript { #region PluginInfo public string pluginAuthor = "HAL9001"; public string pluginName = "Going Going Gone"; public string pluginVersion = "0.1"; public string pluginDate = "01/04/2023"; public string pluginDescription = "Makes body parts transparent or hides them completely"; #endregion #region Vars protected Atom m_person; protected DAZCharacter m_character; protected DAZCharacterSelector m_characterSelector; protected DAZSkinV2 m_activeSkin; protected Dictionary m_controls; protected JSONStorableFloat m_globalTransparency; protected JSONStorableFloat m_globalRenderQueue; protected JSONStorableBool m_globalDebugOutput; #endregion protected static readonly string[] BodyMaterialNames = new[] { "G:Head:Back of the head, face, ears, nostrils & mouth", "Head", "Face", "Ears", "Eyelashes", "Irises", "Cornea", "Pupils", "Sclera", "EyeReflection", "Lacrimals", "Tear", "Nostrils", "Lips", "Gums", "Teeth", "Tongue", "InnerMouth", "G:Upper Body:Neck, shoulder, torso & arms", "Neck", "Shoulders", "Torso", "Nipples", "Forearms", "Hands", "Fingernails", "G:Lower Body:Hips, genitals & legs", "Hips", "defaultMat|Genitals Female", "Genitalia|Genitals Male", "Genitals", "Anus|Anus Male", "Legs", "Feet", "Toenails", // "Hidden", }; protected static Dictionary TransparencyReplacementShaders = new Dictionary { // Opaque materials { "Custom/Subsurface/GlossCullComputeBuff", Shader.Find("Custom/Subsurface/TransparentGlossSeparateAlphaComputeBuff") }, { "Custom/Subsurface/GlossNMCullComputeBuff", Shader.Find("Custom/Subsurface/TransparentGlossNMSeparateAlphaComputeBuff") }, { "Custom/Subsurface/GlossNMDetailCullComputeBuff", Shader.Find("Custom/Subsurface/TransparentGlossNMDetailNoCullSeparateAlphaComputeBuff") }, { "Custom/Subsurface/CullComputeBuff", Shader.Find("Custom/Subsurface/TransparentSeparateAlphaComputeBuff") }, { "Custom/Subsurface/GlossNMTessMappedFixedComputeBuff", Shader.Find("Custom/Subsurface/TransparentGlossNMSeparateAlphaComputeBuff") }, // Transparent materials (don't need a replacement, but need to be "driven" with "AlphaAdjust") { "Custom/Subsurface/TransparentComputeBuff", null }, { "Custom/Subsurface/TransparentSeparateAlphaComputeBuff", null }, { "Custom/Subsurface/TransparentGlossComputeBuff", null }, { "Custom/Subsurface/TransparentGlossSeparateAlphaComputeBuff", null }, { "Custom/Subsurface/TransparentGlossNMSeparateAlphaComputeBuff", null }, { "Custom/Subsurface/TransparentGlossNMDetailNoCullSeparateAlphaComputeBuff", null }, { "Custom/Subsurface/TransparentGlossNoCullSeparateAlphaComputeBuff", null }, { "Custom/Subsurface/AlphaMaskComputeBuff", null }, { "Marmoset/Transparent/Simple Glass/Specular IBLComputeBuff", null }, }; protected class SkinPartControl { protected GoingGoingGone parentScript; protected JSONStorableFloat currTransparency; protected JSONStorableFloat currRenderQueue; protected float oldTransparency; protected int oldRenderQueue; protected Material material; protected Shader originalShader; protected float originalAlphaAdjust; // protected Color originalColor; // protected Color originalSpecular; protected int originalQueue; public SkinPartControl(GoingGoingGone _parent, JSONStorableFloat _transparency, JSONStorableFloat _renderQueue) { parentScript = _parent; currTransparency = _transparency; currRenderQueue = _renderQueue; oldTransparency = -1.0f; // force update ASAP material = null; originalShader = null; } public bool ApplyTransparency() { bool shaderChanged = false; float transparency = currTransparency.val; int renderQueue = (int) currRenderQueue.val; if ((material != null) && ((transparency != oldTransparency) || (renderQueue != oldRenderQueue))) { if (transparency <= 0.0f) { shaderChanged |= RestoreShader(); } else { shaderChanged |= ReplaceShader(); } if (originalShader != null) { material.renderQueue = renderQueue; material.SetFloat("_AlphaAdjust", Mathf.Min(oldTransparency, -transparency)); // Color col = originalColor; // col.a = 1.0f - transparency; // material.SetColor("_Color", col); // col = originalSpecular; // col.a = 1.0f - transparency; // material.SetColor("_SpecColor", col); } oldTransparency = transparency; oldRenderQueue = renderQueue; } return shaderChanged; } public bool ReplaceShader() { bool shaderChanged = false; if (material != null && originalShader == null) { Shader replacementShader; if (TransparencyReplacementShaders.TryGetValue(material.shader.name, out replacementShader)) { originalShader = material.shader; originalAlphaAdjust = material.GetFloat("_AlphaAdjust"); // originalColor = material.GetColor("_Color"); // originalSpecular = material.GetColor("_SpecColor"); originalQueue = material.renderQueue; if (replacementShader != null) { material.shader = replacementShader; shaderChanged = true; if (parentScript.m_globalDebugOutput.val) { SuperController.LogMessage("Replaced shader for " + material.name + " > " + replacementShader.name); } } else { // "null" as replacement shader means that the material already has a transparent capable shader } } } return shaderChanged; } public bool RestoreShader() { bool shaderChanged = false; if (material != null && originalShader != null) { material.renderQueue = originalQueue; material.shader = originalShader; material.SetFloat("_AlphaAdjust", originalAlphaAdjust); // material.SetColor("_Color", originalColor); // material.SetColor("_SpecColor", originalSpecular); if (parentScript.m_globalDebugOutput.val) { SuperController.LogMessage("Restored shader for " + material.name + " > " + originalShader.name); } originalShader = null; // restore has happened shaderChanged = true; } return shaderChanged; } public void SetMaterial(Material _material) { if (material == null) { material = _material; // SuperController.LogMessage(String.Format("Skin part '{0}' controlled", material.name)); } else { // happens, e.g., with hip, but not critical // nothing else to do // SuperController.LogMessage(String.Format("Skin part '{0}' already controlled", material.name)); } } public void SetTransparency(float _transparency) { currTransparency.val = _transparency; } public void SetRenderQueue(int _renderQueue) { currRenderQueue.val = _renderQueue; } public void ForceUpdate() { oldTransparency = -1; } } public override void Init() { try { if (containingAtom?.type != "Person") { SuperController.LogError($"This plugin only works on Person atoms"); DestroyImmediate(this); return; } m_person = containingAtom; m_characterSelector = m_person.GetComponentInChildren(); m_character = null; m_activeSkin = null; // Help text JSONStorableString helpText = new JSONStorableString("Help", "Help\n\n" + "" + "This plugin changes the transparency of body parts.\n\n" + "Moving the sliders will change the transparency of the related body part.\n\n" + "For applying quickly the same transparency to all body parts, use the 'Global Transparency' slider (be aware that it will update all other sliders accordingly).\n\n" + ""); UIDynamic helpTextfield = CreateTextField(helpText, true); helpTextfield.height = 600.0f; m_globalTransparency = new JSONStorableFloat("Global Transparency", 0f, 0f, 1f, true); RegisterFloat(m_globalTransparency); var sliderT = CreateSlider(m_globalTransparency, true); sliderT.slider.onValueChanged.AddListener(delegate {ApplyTransparencyToAll();}); m_globalRenderQueue = new JSONStorableFloat("Render Queue", 2450, 0, 5000, true); RegisterFloat(m_globalRenderQueue); var sliderRQ = CreateSlider(m_globalRenderQueue, true); sliderRQ.slider.onValueChanged.AddListener(delegate {ApplyRenderQueueToAll();}); m_globalDebugOutput = new JSONStorableBool("Debug Output", false); var tglDebug = CreateToggle(m_globalDebugOutput, true); m_controls = new Dictionary(); foreach (string bodyMaterialName in BodyMaterialNames) { if (bodyMaterialName.StartsWith("G:")) { // body section > make header string[] parts = bodyMaterialName.Split(':'); string title = parts[1]; string text = parts[2]; JSONStorableString helpTextFace = new JSONStorableString(title, "" + title + "\n" + text); UIDynamic helpTextfieldFace = CreateTextField(helpTextFace, false); helpTextfieldFace.height = 50.0f; } else { // "normal" part < make slider string[] parts = bodyMaterialName.Split('|'); string storableName = (parts.Count() > 1) ? parts[1] : parts[0]; string materialName = parts[0]; var storableT = new JSONStorableFloat(storableName + " Transparency", 0f, 0f, 1f, true); RegisterFloat(storableT); var storableRQ = new JSONStorableFloat(storableName + " Render Queue", 2540f, 0f, 5000f, true); RegisterFloat(storableRQ); var slider = CreateSlider(storableT, false); slider.label = storableName; slider.rangeAdjustEnabled = false; m_controls[materialName] = new SkinPartControl(this, storableT, storableRQ); } } } catch (Exception e) { SuperController.LogError("something failed: " + e); DestroyImmediate(this); } } public void ApplyTransparencyToAll() { foreach (var control in m_controls) { control.Value.SetTransparency(m_globalTransparency.val); } } public void ApplyRenderQueueToAll() { foreach (var control in m_controls) { control.Value.SetRenderQueue((int) m_globalRenderQueue.val); } } public void OnEnable() { ForceUpdate(); } public void OnDisable() { RestoreShaders(); } public void OnDestroy() { OnDisable(); } public void Update() { if (m_characterSelector.selectedCharacter != m_character) { RestoreShaders(); m_activeSkin = null; m_character = m_characterSelector.selectedCharacter; } if (m_character != null) { if (m_activeSkin == null) { m_activeSkin = m_character.skin; foreach (var material in m_activeSkin.GPUmaterials) { // SuperController.LogError(material.name + " > " + material.shader.name); string partName = material.name.Split('-')[0]; // remove "-1" or similar SkinPartControl control; if (m_controls.TryGetValue(partName, out control)) { control.SetMaterial(material); } else { SuperController.LogError(String.Format("No skin part controller for material '{0}'", material.name)); } } } } bool shaderChanged = false; foreach (var control in m_controls) { shaderChanged |= control.Value.ApplyTransparency(); } if (shaderChanged) { m_activeSkin?.BroadcastMessage("OnApplicationFocus", true); } } protected void ForceUpdate() { foreach (var control in m_controls) { control.Value.ForceUpdate(); } } protected void RestoreShaders() { foreach (var control in m_controls) { control.Value.RestoreShader(); } // This is a hack to force a refresh of the shaders cache m_activeSkin?.BroadcastMessage("OnApplicationFocus", true); } } }