mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 22:39:22 +00:00
[BetterShadowClone] Throwing on git before i completely overcomplicate and have to revert lmao
This commit is contained in:
parent
d155ea546e
commit
5ee7dca50b
18 changed files with 1697 additions and 0 deletions
30
BetterShadowClone/TransformHider/FPRExclusion.cs
Normal file
30
BetterShadowClone/TransformHider/FPRExclusion.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.BetterShadowClone;
|
||||
|
||||
/// <summary>
|
||||
/// Manual exclusion component for the TransformHider (FPR) system.
|
||||
/// Allows you to manually hide and show a transform that would otherwise be hidden.
|
||||
/// </summary>
|
||||
public class FPRExclusion : MonoBehaviour
|
||||
{
|
||||
public Transform target;
|
||||
|
||||
internal List<Transform> affectedChildren = new();
|
||||
|
||||
[NonSerialized]
|
||||
internal ITransformHider[] relevantHiders;
|
||||
|
||||
private void OnEnable()
|
||||
=> SetFPRState(true);
|
||||
|
||||
private void OnDisable()
|
||||
=> SetFPRState(false);
|
||||
|
||||
private void SetFPRState(bool state)
|
||||
{
|
||||
if (relevantHiders == null) return; // no hiders to set
|
||||
foreach (ITransformHider hider in relevantHiders)
|
||||
hider.IsActive = state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace NAK.BetterShadowClone;
|
||||
|
||||
public interface ITransformHider : IDisposable
|
||||
{
|
||||
bool IsActive { get; set; }
|
||||
bool IsValid { get; }
|
||||
bool Process();
|
||||
bool PostProcess();
|
||||
void HideTransform();
|
||||
void ShowTransform();
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.BetterShadowClone;
|
||||
|
||||
public class MeshTransformHider : ITransformHider
|
||||
{
|
||||
// lame 2 frame init stuff
|
||||
private const int FrameInitCount = 0;
|
||||
private int _frameInitCounter;
|
||||
private bool _hasInitialized;
|
||||
private bool _markedForDeath;
|
||||
|
||||
// mesh
|
||||
private readonly MeshRenderer _mainMesh;
|
||||
private bool _enabledState;
|
||||
|
||||
#region ITransformHider Methods
|
||||
|
||||
public bool IsActive { get; set; } = true; // default hide, but FPRExclusion can override
|
||||
|
||||
// anything player can touch is suspect to death
|
||||
public bool IsValid => _mainMesh != null && !_markedForDeath;
|
||||
|
||||
public MeshTransformHider(MeshRenderer renderer)
|
||||
{
|
||||
_mainMesh = renderer;
|
||||
|
||||
if (_mainMesh == null
|
||||
|| _mainMesh.sharedMaterials == null
|
||||
|| _mainMesh.sharedMaterials.Length == 0)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Process()
|
||||
{
|
||||
bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy;
|
||||
|
||||
// GraphicsBuffer becomes stale when mesh is disabled
|
||||
if (!shouldRender)
|
||||
{
|
||||
_frameInitCounter = 0;
|
||||
_hasInitialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unity is weird, so we need to wait 2 frames before we can get the graphics buffer
|
||||
if (_frameInitCounter >= FrameInitCount)
|
||||
{
|
||||
if (_hasInitialized)
|
||||
return true;
|
||||
|
||||
_hasInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
_frameInitCounter++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PostProcess()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void HideTransform()
|
||||
{
|
||||
_enabledState = _mainMesh.enabled;
|
||||
_mainMesh.enabled = false;
|
||||
}
|
||||
|
||||
public void ShowTransform()
|
||||
{
|
||||
_mainMesh.enabled = _enabledState;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_markedForDeath = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.BetterShadowClone;
|
||||
|
||||
public class SkinnedTransformHider : ITransformHider
|
||||
{
|
||||
private static readonly int s_Pos = Shader.PropertyToID("pos");
|
||||
private static readonly int s_BufferLayout = Shader.PropertyToID("bufferLayout");
|
||||
private static readonly int s_WeightedCount = Shader.PropertyToID("weightedCount");
|
||||
private static readonly int s_WeightedVertices = Shader.PropertyToID("weightedVertices");
|
||||
private static readonly int s_VertexBuffer = Shader.PropertyToID("VertexBuffer");
|
||||
|
||||
// lame 2 frame init stuff
|
||||
private const int FrameInitCount = 0;
|
||||
private int _frameInitCounter;
|
||||
private bool _hasInitialized;
|
||||
private bool _markedForDeath;
|
||||
|
||||
// mesh & bone
|
||||
private readonly Transform _shrinkBone;
|
||||
private readonly SkinnedMeshRenderer _mainMesh;
|
||||
private readonly Transform _rootBone;
|
||||
|
||||
// exclusion
|
||||
private readonly FPRExclusion _exclusion;
|
||||
|
||||
// hider stuff
|
||||
private GraphicsBuffer _graphicsBuffer;
|
||||
private int _bufferLayout;
|
||||
|
||||
private ComputeBuffer _computeBuffer;
|
||||
private int _vertexCount;
|
||||
private int _threadGroups;
|
||||
|
||||
#region ITransformHider Methods
|
||||
|
||||
public bool IsActive { get; set; } = true; // default hide, but FPRExclusion can override
|
||||
|
||||
// anything player can touch is suspect to death
|
||||
public bool IsValid => _mainMesh != null && _shrinkBone != null && !_markedForDeath;
|
||||
|
||||
public SkinnedTransformHider(SkinnedMeshRenderer renderer, FPRExclusion exclusion)
|
||||
{
|
||||
_mainMesh = renderer;
|
||||
_shrinkBone = exclusion.target;
|
||||
_exclusion = exclusion;
|
||||
|
||||
if (_exclusion == null
|
||||
|| _shrinkBone == null
|
||||
|| _mainMesh == null
|
||||
|| _mainMesh.sharedMesh == null
|
||||
|| _mainMesh.sharedMaterials == null
|
||||
|| _mainMesh.sharedMaterials.Length == 0)
|
||||
{
|
||||
Dispose();
|
||||
return; // no mesh or bone!
|
||||
}
|
||||
|
||||
// find the head vertices
|
||||
var exclusionVerts = FindExclusionVertList();
|
||||
if (exclusionVerts.Count == 0)
|
||||
{
|
||||
Dispose();
|
||||
return; // no head vertices!
|
||||
}
|
||||
|
||||
_rootBone = _mainMesh.rootBone;
|
||||
_rootBone ??= _mainMesh.transform; // fallback to transform if no root bone
|
||||
|
||||
_vertexCount = exclusionVerts.Count;
|
||||
_computeBuffer = new ComputeBuffer(_vertexCount, sizeof(int));
|
||||
_computeBuffer.SetData(exclusionVerts.ToArray());
|
||||
}
|
||||
|
||||
public bool Process()
|
||||
{
|
||||
bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy;
|
||||
|
||||
// GraphicsBuffer becomes stale when mesh is disabled
|
||||
if (!shouldRender)
|
||||
{
|
||||
_frameInitCounter = 0;
|
||||
_hasInitialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unity is weird, so we need to wait 2 frames before we can get the graphics buffer
|
||||
if (_frameInitCounter >= FrameInitCount)
|
||||
{
|
||||
if (_hasInitialized)
|
||||
return true;
|
||||
|
||||
_hasInitialized = true;
|
||||
SetupGraphicsBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
_mainMesh.forceRenderingOff = true; // force off if mesh is disabled
|
||||
|
||||
_frameInitCounter++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PostProcess()
|
||||
{
|
||||
return false; // not needed
|
||||
}
|
||||
|
||||
public void HideTransform()
|
||||
{
|
||||
_mainMesh.forceRenderingOff = false;
|
||||
|
||||
// probably fine
|
||||
Vector3 pos = _rootBone.transform.InverseTransformPoint(_shrinkBone.position) * _rootBone.lossyScale.y;
|
||||
|
||||
_graphicsBuffer = _mainMesh.GetVertexBuffer();
|
||||
TransformHiderManager.shader.SetVector(s_Pos, pos);
|
||||
TransformHiderManager.shader.SetInt(s_WeightedCount, _vertexCount);
|
||||
TransformHiderManager.shader.SetInt(s_BufferLayout, _bufferLayout);
|
||||
TransformHiderManager.shader.SetBuffer(0, s_WeightedVertices, _computeBuffer);
|
||||
TransformHiderManager.shader.SetBuffer(0, s_VertexBuffer, _graphicsBuffer);
|
||||
TransformHiderManager.shader.Dispatch(0, _threadGroups, 1, 1);
|
||||
_graphicsBuffer.Release();
|
||||
}
|
||||
|
||||
public void ShowTransform()
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_markedForDeath = true;
|
||||
_graphicsBuffer?.Dispose();
|
||||
_graphicsBuffer = null;
|
||||
_computeBuffer?.Dispose();
|
||||
_computeBuffer = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
// Unity is weird, so we need to wait 2 frames before we can get the graphics buffer
|
||||
private void SetupGraphicsBuffer()
|
||||
{
|
||||
Mesh mesh = _mainMesh.sharedMesh;
|
||||
|
||||
_bufferLayout = 0;
|
||||
if (mesh.HasVertexAttribute(VertexAttribute.Position)) _bufferLayout += 3;
|
||||
if (mesh.HasVertexAttribute(VertexAttribute.Normal)) _bufferLayout += 3;
|
||||
if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4;
|
||||
|
||||
// ComputeShader is doing bitshift so we dont need to multiply by 4
|
||||
//_bufferLayout *= 4; // 4 bytes per float
|
||||
|
||||
const float xThreadGroups = 64f;
|
||||
_threadGroups = Mathf.CeilToInt(mesh.vertexCount / xThreadGroups);
|
||||
|
||||
_mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
|
||||
}
|
||||
|
||||
private List<int> FindExclusionVertList()
|
||||
{
|
||||
var boneWeights = _mainMesh.sharedMesh.boneWeights;
|
||||
var bones = _exclusion.affectedChildren;
|
||||
|
||||
HashSet<int> weights = new(); //get indexs of child bones
|
||||
for (int i = 0; i < _mainMesh.bones.Length; i++)
|
||||
if (bones.Contains(_mainMesh.bones[i])) weights.Add(i);
|
||||
|
||||
List<int> headVertices = new();
|
||||
|
||||
for (int i = 0; i < boneWeights.Length; i++)
|
||||
{
|
||||
BoneWeight weight = boneWeights[i];
|
||||
const float minWeightThreshold = 0.2f;
|
||||
if (weights.Contains(weight.boneIndex0) && weight.weight0 > minWeightThreshold
|
||||
|| weights.Contains(weight.boneIndex1) && weight.weight1 > minWeightThreshold
|
||||
|| weights.Contains(weight.boneIndex2) && weight.weight2 > minWeightThreshold
|
||||
|| weights.Contains(weight.boneIndex3) && weight.weight3 > minWeightThreshold)
|
||||
headVertices.Add(i);
|
||||
}
|
||||
|
||||
return headVertices;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
237
BetterShadowClone/TransformHider/TransformHiderManager.cs
Normal file
237
BetterShadowClone/TransformHider/TransformHiderManager.cs
Normal file
|
@ -0,0 +1,237 @@
|
|||
using System.Collections.Generic;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.Camera;
|
||||
using MagicaCloth;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.BetterShadowClone;
|
||||
|
||||
// Built on top of Koneko's BoneHider but to mimic the ShadowCloneManager
|
||||
|
||||
public class TransformHiderManager : MonoBehaviour
|
||||
{
|
||||
public static ComputeShader shader;
|
||||
|
||||
#region Singleton Implementation
|
||||
|
||||
private static TransformHiderManager _instance;
|
||||
public static TransformHiderManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance != null) return _instance;
|
||||
_instance = new GameObject("Koneko.TransformHiderManager").AddComponent<TransformHiderManager>();
|
||||
DontDestroyOnLoad(_instance.gameObject);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Game cameras
|
||||
private static Camera s_MainCamera;
|
||||
private static Camera s_UiCamera;
|
||||
|
||||
// Settings
|
||||
internal static bool s_DebugHeadHide;
|
||||
|
||||
// Implementation
|
||||
private bool _hasRenderedThisFrame;
|
||||
|
||||
// Shadow Clones
|
||||
private readonly List<ITransformHider> s_TransformHider = new();
|
||||
public void AddTransformHider(ITransformHider clone)
|
||||
=> s_TransformHider.Add(clone);
|
||||
|
||||
// Debug
|
||||
private bool _debugHeadHiderProcessingTime;
|
||||
private readonly StopWatch _stopWatch = new();
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (Instance != null
|
||||
&& Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePlayerCameras();
|
||||
|
||||
s_DebugHeadHide = ModSettings.EntryDebugHeadHide.Value;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Camera.onPreRender += MyOnPreRender;
|
||||
Camera.onPostRender += MyOnPostRender;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Camera.onPreRender -= MyOnPreRender;
|
||||
Camera.onPostRender -= MyOnPostRender;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transform Hider Managment
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_hasRenderedThisFrame = false;
|
||||
}
|
||||
|
||||
private void MyOnPreRender(Camera cam)
|
||||
{
|
||||
if (_hasRenderedThisFrame)
|
||||
return; // can only hide head once per frame
|
||||
|
||||
if (cam != s_MainCamera // only hide in player cam, or if debug is on
|
||||
&& !s_DebugHeadHide)
|
||||
return;
|
||||
|
||||
if (!CheckPlayerCamWithinRange())
|
||||
return; // player is too far away (likely HoloPort or Sitting)
|
||||
|
||||
if (!ShadowCloneMod.CheckWantsToHideHead(cam))
|
||||
return; // listener said no (Third Person, etc)
|
||||
|
||||
_hasRenderedThisFrame = true;
|
||||
|
||||
_stopWatch.Start();
|
||||
|
||||
for (int i = s_TransformHider.Count - 1; i >= 0; i--)
|
||||
{
|
||||
ITransformHider hider = s_TransformHider[i];
|
||||
if (hider is not { IsValid: true })
|
||||
{
|
||||
hider?.Dispose();
|
||||
s_TransformHider.RemoveAt(i);
|
||||
continue; // invalid or dead
|
||||
}
|
||||
|
||||
if (!hider.Process()) continue; // not ready yet or disabled
|
||||
|
||||
if (hider.IsActive) hider.HideTransform();
|
||||
}
|
||||
|
||||
_stopWatch.Stop();
|
||||
if (_debugHeadHiderProcessingTime) Debug.Log($"TransformHiderManager.MyOnPreRender({s_DebugHeadHide}) took {_stopWatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
private void MyOnPostRender(Camera cam)
|
||||
{
|
||||
if (cam != s_UiCamera) return; // ui camera is expected to render last
|
||||
|
||||
for (int i = s_TransformHider.Count - 1; i >= 0; i--)
|
||||
{
|
||||
ITransformHider hider = s_TransformHider[i];
|
||||
if (hider is not { IsValid: true })
|
||||
{
|
||||
hider?.Dispose();
|
||||
s_TransformHider.RemoveAt(i);
|
||||
continue; // invalid or dead
|
||||
}
|
||||
|
||||
if (!hider.PostProcess()) continue; // does not need post processing
|
||||
|
||||
if (hider.IsActive) hider.ShowTransform();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Game Events
|
||||
|
||||
public void OnAvatarCleared()
|
||||
{
|
||||
// Dispose all shadow clones BEFORE game unloads avatar
|
||||
// Otherwise we memory leak the shadow clones mesh & material instances!!!
|
||||
foreach (ITransformHider hider in s_TransformHider)
|
||||
hider.Dispose();
|
||||
s_TransformHider.Clear();
|
||||
}
|
||||
|
||||
private void OnVRModeSwitchCompleted(bool _, Camera __)
|
||||
{
|
||||
UpdatePlayerCameras();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void UpdatePlayerCameras()
|
||||
{
|
||||
s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
|
||||
s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent<Camera>();
|
||||
}
|
||||
|
||||
private static bool CheckPlayerCamWithinRange()
|
||||
{
|
||||
if (PlayerSetup.Instance == null)
|
||||
return false; // hack
|
||||
|
||||
const float MinHeadHidingRange = 0.5f;
|
||||
Vector3 playerHeadPos = PlayerSetup.Instance.GetViewWorldPosition();
|
||||
Vector3 playerCamPos = s_MainCamera.transform.position;
|
||||
float scaleModifier = PlayerSetup.Instance.GetPlaySpaceScale();
|
||||
return (Vector3.Distance(playerHeadPos, playerCamPos) < (MinHeadHidingRange * scaleModifier));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Helpers
|
||||
|
||||
internal static bool IsLegacyFPRExcluded(Component renderer)
|
||||
=> renderer.gameObject.name.Contains("[FPR]");
|
||||
|
||||
internal static ITransformHider CreateTransformHider(Component renderer, Transform bone)
|
||||
{
|
||||
if (IsLegacyFPRExcluded(renderer))
|
||||
return null;
|
||||
|
||||
return renderer switch
|
||||
{
|
||||
//SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedTransformHider(skinnedMeshRenderer, bone),
|
||||
MeshRenderer meshRenderer => new MeshTransformHider(meshRenderer),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
internal static void CreateTransformHider(Component renderer, Dictionary<Transform, FPRExclusion> exclusions)
|
||||
{
|
||||
if (IsLegacyFPRExcluded(renderer))
|
||||
return;
|
||||
|
||||
if (renderer is SkinnedMeshRenderer skinnedMeshRenderer)
|
||||
{
|
||||
// get all bones for renderer
|
||||
var bones = skinnedMeshRenderer.bones;
|
||||
List<FPRExclusion> fprExclusions = new();
|
||||
|
||||
// check if any bones are excluded
|
||||
foreach (Transform bone in bones)
|
||||
{
|
||||
if (!exclusions.TryGetValue(bone, out FPRExclusion exclusion))
|
||||
continue;
|
||||
|
||||
fprExclusions.Add(exclusion);
|
||||
}
|
||||
|
||||
foreach (FPRExclusion exclusion in fprExclusions)
|
||||
{
|
||||
ITransformHider hider = new SkinnedTransformHider(skinnedMeshRenderer, exclusion);
|
||||
Instance.AddTransformHider(hider);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue