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);
}
}
}