#define POV_DIAGNOSTICS
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
///
/// Improved PoV Version 2.1.2
/// By Acidbubbles
/// Possession that actually feels right.
/// Source: https://github.com/acidbubbles/vam-improved-pov
///
public class ImprovedPoV : MVRScript
{
private Atom _person;
private Camera _mainCamera;
private Possessor _possessor;
private FreeControllerV3 _headControl;
private DAZCharacterSelector _selector;
private JSONStorableFloat _cameraDepthJSON;
private JSONStorableFloat _cameraHeightJSON;
private JSONStorableFloat _cameraPitchJSON;
private JSONStorableFloat _clipDistanceJSON;
private JSONStorableBool _autoWorldScaleJSON;
private JSONStorableBool _possessedOnlyJSON;
private JSONStorableBool _hideFaceJSON;
private JSONStorableBool _hideHairJSON;
private SkinHandler _skinHandler;
private List _hairHandlers;
// For change detection purposes
private DAZCharacter _character;
private DAZHairGroup[] _hair;
// Whether the PoV effects are currently active, i.e. in possession mode
private bool _lastActive;
// Requires re-generating all shaders and materials, either because last frame was not ready or because something changed
private bool _dirty;
// To avoid spamming errors when something failed
private bool _failedOnce;
// When waiting for a model to load, how long before we abandon
private int _tryAgainAttempts;
private float _originalWorldScale;
public override void Init()
{
try
{
if (containingAtom?.type != "Person")
{
SuperController.LogError($"Please apply the ImprovedPoV plugin to the 'Person' atom you wish to possess. Currently applied on '{containingAtom.type}'.");
DestroyImmediate(this);
return;
}
_person = containingAtom;
// _mainCamera = CameraTarget.centerTarget?.targetCamera;
foreach (Camera c in UnityEngine.Object.FindObjectsOfType())
{
if (c.name == "previewCam_vrRender")
{
_mainCamera = c;
SuperController.LogMessage("previewCam_vrRender cam found");
}
}
_possessor = SuperController
.FindObjectsOfType(typeof(Possessor))
.Where(p => p.name == "CenterEye")
.Select(p => p as Possessor)
.FirstOrDefault();
_headControl = (FreeControllerV3)_person.GetStorableByID("headControl");
_selector = _person.GetComponentInChildren();
InitControls();
Camera.onPreRender += OnPreRender;
Camera.onPostRender += OnPostRender;
}
catch (Exception e)
{
SuperController.LogError("Failed to initialize Improved PoV: " + e);
DestroyImmediate(this);
}
}
private void OnPreRender(Camera cam)
{
// if (!IsPovCamera(cam)) return;
try
{
if (_skinHandler != null)
_skinHandler.BeforeRender();
if (_hairHandlers != null)
_hairHandlers.ForEach(x =>
{
if (x != null)
x.BeforeRender();
});
}
catch (Exception e)
{
if (_failedOnce) return;
_failedOnce = true;
SuperController.LogError("Failed to execute pre render Improved PoV: " + e);
}
}
private void OnPostRender(Camera cam)
{
if (!IsPovCamera(cam)) return;
try
{
if (_skinHandler != null)
_skinHandler.AfterRender();
if (_hairHandlers != null)
_hairHandlers.ForEach(x =>
{
if (x != null)
x.AfterRender();
});
}
catch (Exception e)
{
if (_failedOnce) return;
_failedOnce = true;
SuperController.LogError("Failed to execute post render Improved PoV: " + e);
}
}
private bool IsPovCamera(Camera cam)
{
return
// Oculus Rift
cam.name == "CenterEyeAnchor" ||
// Steam VR
cam.name == "Camera (eye)" ||
// Desktop
cam.name == "MonitorRig";
}
private void InitControls()
{
try
{
{
_cameraDepthJSON = new JSONStorableFloat("Camera depth", 0.054f, 0f, 0.2f, false);
RegisterFloat(_cameraDepthJSON);
var cameraDepthSlider = CreateSlider(_cameraDepthJSON, false);
cameraDepthSlider.slider.onValueChanged.AddListener(delegate (float val)
{
ApplyCameraPosition(_lastActive);
});
}
{
_cameraHeightJSON = new JSONStorableFloat("Camera height", 0f, -0.05f, 0.05f, false);
RegisterFloat(_cameraHeightJSON);
var cameraHeightSlider = CreateSlider(_cameraHeightJSON, false);
cameraHeightSlider.slider.onValueChanged.AddListener(delegate (float val)
{
ApplyCameraPosition(_lastActive);
});
}
{
_cameraPitchJSON = new JSONStorableFloat("Camera pitch", 0f, -135f, 45f, true);
RegisterFloat(_cameraPitchJSON);
var cameraPitchSlider = CreateSlider(_cameraPitchJSON, false);
cameraPitchSlider.slider.onValueChanged.AddListener(delegate (float val)
{
ApplyCameraPosition(_lastActive);
});
}
{
_clipDistanceJSON = new JSONStorableFloat("Clip distance", 0.01f, 0.01f, .2f, true);
RegisterFloat(_clipDistanceJSON);
var clipDistanceSlider = CreateSlider(_clipDistanceJSON, false);
clipDistanceSlider.slider.onValueChanged.AddListener(delegate (float val)
{
ApplyCameraPosition(_lastActive);
});
}
{
_autoWorldScaleJSON = new JSONStorableBool("Auto world scale", false);
RegisterBool(_autoWorldScaleJSON);
var autoWorldScaleToggle = CreateToggle(_autoWorldScaleJSON, true);
autoWorldScaleToggle.toggle.onValueChanged.AddListener(delegate (bool val)
{
_dirty = true;
});
}
{
var possessedOnlyDefaultValue = true;
_possessedOnlyJSON = new JSONStorableBool("Activate only when possessed", possessedOnlyDefaultValue);
RegisterBool(_possessedOnlyJSON);
var possessedOnlyCheckbox = CreateToggle(_possessedOnlyJSON, true);
possessedOnlyCheckbox.toggle.onValueChanged.AddListener(delegate (bool val)
{
_dirty = true;
});
}
{
_hideFaceJSON = new JSONStorableBool("Hide face", true);
RegisterBool(_hideFaceJSON);
var hideFaceToggle = CreateToggle(_hideFaceJSON, true);
hideFaceToggle.toggle.onValueChanged.AddListener(delegate (bool val)
{
_dirty = true;
});
}
{
_hideHairJSON = new JSONStorableBool("Hide hair", true);
RegisterBool(_hideHairJSON);
var hideHairToggle = CreateToggle(_hideHairJSON, true);
hideHairToggle.toggle.onValueChanged.AddListener(delegate (bool val)
{
_dirty = true;
});
}
}
catch (Exception e)
{
SuperController.LogError("Failed to register controls: " + e);
}
}
public void OnDisable()
{
try
{
_dirty = false;
ApplyAll(false);
_lastActive = false;
}
catch (Exception e)
{
SuperController.LogError("Failed to disable Improved PoV: " + e);
}
}
public void OnDestroy()
{
OnDisable();
Camera.onPreRender -= OnPreRender;
Camera.onPostRender -= OnPostRender;
}
public void Update()
{
try
{
var active = _headControl.possessed || !_possessedOnlyJSON.val;
if (!_lastActive && active)
{
ApplyAll(true);
_lastActive = true;
}
else if (_lastActive && !active)
{
ApplyAll(false);
_lastActive = false;
}
else if (_dirty)
{
_dirty = false;
ApplyAll(_lastActive);
}
else if (_lastActive && _selector.selectedCharacter != _character)
{
_skinHandler?.Restore();
_skinHandler = null;
ApplyAll(true);
}
else if (_lastActive && !_selector.hairItems.Where(h => h.active).SequenceEqual(_hair))
{
// Note: This only checks if the first hair changed. It'll be good enough for most purposes, but imperfect.
if (_hairHandlers != null)
{
_hairHandlers.ForEach(x =>
{
if (x != null)
x.Restore();
});
_hairHandlers = null;
}
ApplyAll(true);
}
}
catch (Exception e)
{
if (_failedOnce) return;
_failedOnce = true;
SuperController.LogError("Failed to update Improved PoV: " + e);
}
}
private void ApplyAll(bool active)
{
// Try again next frame
if (_selector.selectedCharacter?.skin == null)
{
MakeDirty("Skin not yet loaded.");
return;
}
_character = _selector.selectedCharacter;
_hair = _selector.hairItems.Where(h => h.active).ToArray();
ApplyAutoWorldScale(active);
ApplyCameraPosition(active);
ApplyPossessorMeshVisibility(active);
if (UpdateHandler(ref _skinHandler, active && _hideFaceJSON.val))
ConfigureHandler("Skin", ref _skinHandler, _skinHandler.Configure(_character.skin));
if (_hairHandlers == null)
_hairHandlers = new List(new HairHandler[_hair.Length]);
for (var i = 0; i < _hairHandlers.Count; i++)
{
var hairHandler = _hairHandlers[i];
if (UpdateHandler(ref hairHandler, active && _hideHairJSON.val))
ConfigureHandler("Hair", ref hairHandler, hairHandler.Configure(_hair[i]));
_hairHandlers[i] = hairHandler;
}
if (!_dirty) _tryAgainAttempts = 0;
}
private void MakeDirty(string reason)
{
_dirty = true;
_tryAgainAttempts++;
if (_tryAgainAttempts > 90 * 20) // Approximately 20 to 40 seconds
{
SuperController.LogError("Failed to apply ImprovedPoV. Reason: " + reason + ". Try reloading the plugin, or report the issue to @Acidbubbles.");
enabled = false;
}
}
private void ConfigureHandler(string what, ref T handler, int result)
where T : IHandler, new()
{
switch (result)
{
case HandlerConfigurationResult.Success:
break;
case HandlerConfigurationResult.CannotApply:
handler = default(T);
break;
case HandlerConfigurationResult.TryAgainLater:
handler = default(T);
MakeDirty(what + " is still waiting for assets to be ready.");
break;
}
}
private bool UpdateHandler(ref T handler, bool active)
where T : IHandler, new()
{
if (handler == null && active)
{
handler = new T();
return true;
}
if (handler != null && active)
{
handler.Restore();
handler = new T();
return true;
}
if (handler != null && !active)
{
handler.Restore();
handler = default(T);
}
return false;
}
private void ApplyCameraPosition(bool active)
{
try
{
_mainCamera.nearClipPlane = active ? _clipDistanceJSON.val : 0.01f;
var cameraDepth = active ? _cameraDepthJSON.val : 0;
var cameraHeight = active ? _cameraHeightJSON.val : 0;
var cameraPitch = active ? _cameraPitchJSON.val : 0;
var pos = _possessor.transform.position;
// _mainCamera.transform.position = pos - _mainCamera.transform.rotation * Vector3.forward * cameraDepth - _mainCamera.transform.rotation * Vector3.down * cameraHeight;
_possessor.transform.localEulerAngles = new Vector3(cameraPitch, 0f, 0f);
_possessor.transform.position = pos;
}
catch (Exception e)
{
SuperController.LogError("Failed to update camera position: " + e);
}
}
private void ApplyPossessorMeshVisibility(bool active)
{
try
{
var meshActive = !active;
_possessor.gameObject.transform.Find("Capsule")?.gameObject.SetActive(meshActive);
_possessor.gameObject.transform.Find("Sphere1")?.gameObject.SetActive(meshActive);
_possessor.gameObject.transform.Find("Sphere2")?.gameObject.SetActive(meshActive);
}
catch (Exception e)
{
SuperController.LogError("Failed to update possessor mesh visibility: " + e);
}
}
private void ApplyAutoWorldScale(bool active)
{
if (!active)
{
if (_originalWorldScale != 0f && SuperController.singleton.worldScale != _originalWorldScale)
{
SuperController.singleton.worldScale = _originalWorldScale;
_originalWorldScale = 0f;
}
return;
}
if (!_autoWorldScaleJSON.val) return;
if (_originalWorldScale == 0f)
{
_originalWorldScale = SuperController.singleton.worldScale;
}
var eyes = _person.GetComponentsInChildren();
var lEye = eyes.FirstOrDefault(eye => eye.name == "lEye");
var rEye = eyes.FirstOrDefault(eye => eye.name == "rEye");
if (lEye == null || rEye == null)
return;
var atomEyeDistance = Vector3.Distance(lEye.transform.position, rEye.transform.position);
var rig = GameObject.FindObjectOfType();
if (rig == null)
return;
var rigEyesDistance = Vector3.Distance(rig.leftEyeAnchor.transform.position, rig.rightEyeAnchor.transform.position);
var scale = atomEyeDistance / rigEyesDistance;
var worldScale = SuperController.singleton.worldScale * scale;
if (SuperController.singleton.worldScale != worldScale)
SuperController.singleton.worldScale = worldScale;
var yAdjust = _possessor.autoSnapPoint.position.y - _headControl.possessPoint.position.y;
if (yAdjust != 0)
SuperController.singleton.playerHeightAdjust = SuperController.singleton.playerHeightAdjust - yAdjust;
}
public static class HandlerConfigurationResult
{
public const int Success = 0;
public const int CannotApply = 1;
public const int TryAgainLater = 2;
}
public interface IHandler
{
void Restore();
void BeforeRender();
void AfterRender();
}
public class SkinHandler : IHandler
{
public class SkinShaderMaterialReference
{
public Material material;
public Shader originalShader;
public float originalAlphaAdjust;
public float originalColorAlpha;
public Color originalSpecColor;
public int originalRenderQueue;
public static SkinShaderMaterialReference FromMaterial(Material material)
{
return new SkinShaderMaterialReference
{
material = material,
originalShader = material.shader,
originalRenderQueue = material.renderQueue,
originalAlphaAdjust = material.GetFloat("_AlphaAdjust"),
originalColorAlpha = material.GetColor("_Color").a,
originalSpecColor = material.GetColor("_SpecColor")
};
}
}
public static readonly string[] MaterialsToHide = new[]
{
"Lacrimals",
"Pupils",
"Lips",
"Gums",
"Irises",
"Teeth",
"Face",
"Head",
"InnerMouth",
"Tongue",
"EyeReflection",
"Nostrils",
"Cornea",
"Eyelashes",
"Sclera",
"Ears",
"Tear"
};
public static IList GetMaterialsToHide(DAZSkinV2 skin)
{
var materials = new List(MaterialsToHide.Length);
foreach (var material in skin.GPUmaterials)
{
if (!MaterialsToHide.Any(materialToHide => material.name.StartsWith(materialToHide)))
continue;
materials.Add(material);
}
return materials;
}
private static readonly Dictionary ReplacementShaders = 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") },
// Transparent materials
{ "Custom/Subsurface/TransparentGlossNoCullSeparateAlphaComputeBuff", null },
{ "Custom/Subsurface/TransparentGlossComputeBuff", null },
{ "Custom/Subsurface/TransparentComputeBuff", null },
{ "Custom/Subsurface/AlphaMaskComputeBuff", null },
{ "Marmoset/Transparent/Simple Glass/Specular IBLComputeBuff", null },
};
private DAZSkinV2 _skin;
private List _materialRefs;
public int Configure(DAZSkinV2 skin)
{
_skin = skin;
_materialRefs = new List();
foreach (var material in GetMaterialsToHide(skin))
{
#if (IMPROVED_POV)
if(material == null)
throw new InvalidOperationException("Attempts to apply the shader strategy on a destroyed material.");
if (material.GetInt(SkinShaderMaterialReference.ImprovedPovEnabledShaderKey) == 1)
throw new InvalidOperationException("Attempts to apply the shader strategy on a skin that already has the plugin enabled (shader key).");
#endif
var materialInfo = SkinShaderMaterialReference.FromMaterial(material);
Shader shader;
if (!ReplacementShaders.TryGetValue(material.shader.name, out shader))
SuperController.LogError("Missing replacement shader: '" + material.shader.name + "'");
if (shader != null)
{
material.shader = shader;
material.renderQueue = materialInfo.originalRenderQueue;
}
switch (material.name)
{
// case "Pupils":
// // Looks like pupils reflection does not show up. No idea how to fix that.
// material.renderQueue = 1900;
// break;
case "Lacrimals":
material.renderQueue = 1900;
break;
}
_materialRefs.Add(materialInfo);
}
// This is a hack to force a refresh of the shaders cache
skin.BroadcastMessage("OnApplicationFocus", true);
return HandlerConfigurationResult.Success;
}
public void Restore()
{
foreach (var material in _materialRefs)
{
material.material.shader = material.originalShader;
material.material.renderQueue = material.originalRenderQueue;
}
_materialRefs = null;
// This is a hack to force a refresh of the shaders cache
_skin.BroadcastMessage("OnApplicationFocus", true);
}
public void BeforeRender()
{
foreach (var materialRef in _materialRefs)
{
// var material = materialRef.material;
// material.SetFloat("_AlphaAdjust", -1f);
// var color = material.GetColor("_Color");
// material.SetColor("_Color", new Color(color.r, color.g, color.b, 0f));
// material.SetColor("_SpecColor", new Color(0f, 0f, 0f, 0f));
var material = materialRef.material;
// if (materialRef.alphaCutoffSupport)
material.SetFloat("_Cutoff", 0.3f);
// if (materialRef.alphaAdjustSupport)
material.SetFloat("_AlphaAdjust", -1f);
var color = material.GetColor("_Color");
material.SetColor("_Color", new Color(color.r, color.g, color.b, 0f));
// if (materialRef.specColorSupport)
material.SetColor("_SpecColor", new Color(0f, 0f, 0f, 0f));
}
}
public void AfterRender()
{
foreach (var materialRef in _materialRefs)
{
var material = materialRef.material;
material.SetFloat("_AlphaAdjust", materialRef.originalAlphaAdjust);
var color = material.GetColor("_Color");
material.SetColor("_Color", new Color(color.r, color.g, color.b, materialRef.originalColorAlpha));
material.SetColor("_SpecColor", materialRef.originalSpecColor);
}
}
}
public class HairHandler : IHandler
{
public class MaterialReference
{
public Material material;
public float originalAlphaAdjust;
}
private Material _hairMaterial;
private string _hairShaderProperty;
private float _hairShaderHiddenValue;
private float _hairShaderOriginalValue;
private List _materialRefs;
public int Configure(DAZHairGroup hair)
{
if (hair == null || hair.name == "NoHair")
return HandlerConfigurationResult.CannotApply;
if (hair.name == "Sim2Hair" || hair.name == "Sim2HairMale" || hair.name == "CustomHairItem")
return ConfigureSimV2Hair(hair);
else if (hair.name == "SimHairGroup" || hair.name == "SimHairGroup2")
return ConfigureSimHair(hair);
else
return ConfigureSimpleHair(hair);
}
private int ConfigureSimV2Hair(DAZHairGroup hair)
{
var materialRefs = new List(GetScalpMaterialReferences(hair));
if (materialRefs.Count != 0) _materialRefs = materialRefs;
var hairMaterial = hair.GetComponentInChildren()?.material;
if (hairMaterial == null)
return HandlerConfigurationResult.TryAgainLater;
_hairMaterial = hairMaterial;
_hairShaderProperty = "_StandWidth";
_hairShaderHiddenValue = 0f;
_hairShaderOriginalValue = _hairMaterial.GetFloat(_hairShaderProperty);
return HandlerConfigurationResult.Success;
}
private int ConfigureSimHair(DAZHairGroup hair)
{
SuperController.LogError("Hair " + hair.name + " is not supported!");
return HandlerConfigurationResult.CannotApply;
}
private int ConfigureSimpleHair(DAZHairGroup hair)
{
var materialRefs = hair.GetComponentsInChildren()
.SelectMany(m => m.materials)
.Distinct()
.Select(m => new MaterialReference
{
material = m,
originalAlphaAdjust = m.GetFloat("_AlphaAdjust")
})
.ToList();
if (materialRefs.Count == 0)
return HandlerConfigurationResult.TryAgainLater;
materialRefs.AddRange(GetScalpMaterialReferences(hair));
_materialRefs = materialRefs;
return HandlerConfigurationResult.Success;
}
private IEnumerable GetScalpMaterialReferences(DAZHairGroup hair)
{
return hair.GetComponentsInChildren()
.SelectMany(m => m.GPUmaterials)
.Distinct()
.Select(m => new MaterialReference
{
material = m,
originalAlphaAdjust = m.GetFloat("_AlphaAdjust")
});
}
public void Restore()
{
_hairMaterial = null;
_materialRefs = null;
}
public void BeforeRender()
{
if (_hairMaterial != null)
_hairMaterial.SetFloat(_hairShaderProperty, _hairShaderHiddenValue);
if (_materialRefs != null)
foreach (var materialRef in _materialRefs)
materialRef.material.SetFloat("_AlphaAdjust", -1f);
}
public void AfterRender()
{
if (_hairMaterial != null)
_hairMaterial.SetFloat(_hairShaderProperty, _hairShaderOriginalValue);
if (_materialRefs != null)
foreach (var materialRef in _materialRefs)
materialRef.material.SetFloat("_AlphaAdjust", materialRef.originalAlphaAdjust);
}
}
}