mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 14:29:25 +00:00
AvatarCloneTest: push so i can reference in an email
This commit is contained in:
parent
6d30fe1f41
commit
3660b8f683
16 changed files with 1155 additions and 0 deletions
68
AvatarCloneTest/AvatarClone/AvatarClone.API.cs
Normal file
68
AvatarCloneTest/AvatarClone/AvatarClone.API.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Public API Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether the specific renderer requires additional runtime checks when copying to the clone.
|
||||||
|
/// For example, Magica Cloth modifies the sharedMesh & bones of the renderer at runtime. This is not needed
|
||||||
|
/// for most renderers, so copying for all renderers would be inefficient.
|
||||||
|
/// </summary>
|
||||||
|
public void SetRendererNeedsAdditionalChecks(Renderer rend, bool needsChecks)
|
||||||
|
{
|
||||||
|
switch (rend)
|
||||||
|
{
|
||||||
|
case MeshRenderer meshRenderer:
|
||||||
|
{
|
||||||
|
int index = _standardRenderers.IndexOf(meshRenderer);
|
||||||
|
if (index == -1) return;
|
||||||
|
|
||||||
|
if (needsChecks && !_standardRenderersNeedingChecks.Contains(index))
|
||||||
|
{
|
||||||
|
int insertIndex = _standardRenderersNeedingChecks.Count;
|
||||||
|
_standardRenderersNeedingChecks.Add(index);
|
||||||
|
_cachedSharedMeshes.Insert(insertIndex, null);
|
||||||
|
}
|
||||||
|
else if (!needsChecks)
|
||||||
|
{
|
||||||
|
int removeIndex = _standardRenderersNeedingChecks.IndexOf(index);
|
||||||
|
if (removeIndex != -1)
|
||||||
|
{
|
||||||
|
_standardRenderersNeedingChecks.RemoveAt(removeIndex);
|
||||||
|
_cachedSharedMeshes.RemoveAt(removeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case SkinnedMeshRenderer skinnedRenderer:
|
||||||
|
{
|
||||||
|
int index = _skinnedRenderers.IndexOf(skinnedRenderer);
|
||||||
|
if (index == -1) return;
|
||||||
|
|
||||||
|
if (needsChecks && !_skinnedRenderersNeedingChecks.Contains(index))
|
||||||
|
{
|
||||||
|
int insertIndex = _skinnedRenderersNeedingChecks.Count;
|
||||||
|
_skinnedRenderersNeedingChecks.Add(index);
|
||||||
|
_cachedSharedMeshes.Insert(_standardRenderersNeedingChecks.Count + insertIndex, null);
|
||||||
|
_cachedSkinnedBoneCounts.Add(0);
|
||||||
|
}
|
||||||
|
else if (!needsChecks)
|
||||||
|
{
|
||||||
|
int removeIndex = _skinnedRenderersNeedingChecks.IndexOf(index);
|
||||||
|
if (removeIndex != -1)
|
||||||
|
{
|
||||||
|
_skinnedRenderersNeedingChecks.RemoveAt(removeIndex);
|
||||||
|
_cachedSharedMeshes.RemoveAt(_standardRenderersNeedingChecks.Count + removeIndex);
|
||||||
|
_cachedSkinnedBoneCounts.RemoveAt(removeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public API Methods
|
||||||
|
}
|
103
AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs
Normal file
103
AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Clone Creation
|
||||||
|
|
||||||
|
private void CreateClones()
|
||||||
|
{
|
||||||
|
int standardCount = _standardRenderers.Count;
|
||||||
|
_standardClones = new List<MeshRenderer>(standardCount);
|
||||||
|
_standardCloneFilters = new List<MeshFilter>(standardCount);
|
||||||
|
for (int i = 0; i < standardCount; i++) CreateStandardClone(i);
|
||||||
|
|
||||||
|
int skinnedCount = _skinnedRenderers.Count;
|
||||||
|
_skinnedClones = new List<SkinnedMeshRenderer>(skinnedCount);
|
||||||
|
for (int i = 0; i < skinnedCount; i++) CreateSkinnedClone(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateStandardClone(int index)
|
||||||
|
{
|
||||||
|
MeshRenderer sourceRenderer = _standardRenderers[index];
|
||||||
|
MeshFilter sourceFilter = _standardFilters[index];
|
||||||
|
|
||||||
|
GameObject go = new(sourceRenderer.name + "_VisualClone")
|
||||||
|
{
|
||||||
|
layer = CVRLayers.PlayerClone
|
||||||
|
};
|
||||||
|
|
||||||
|
go.transform.SetParent(sourceRenderer.transform, false);
|
||||||
|
//go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||||
|
|
||||||
|
MeshRenderer cloneRenderer = go.AddComponent<MeshRenderer>();
|
||||||
|
MeshFilter cloneFilter = go.AddComponent<MeshFilter>();
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
cloneRenderer.sharedMaterials = sourceRenderer.sharedMaterials;
|
||||||
|
cloneRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||||
|
cloneRenderer.probeAnchor = sourceRenderer.probeAnchor;
|
||||||
|
cloneRenderer.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity);
|
||||||
|
cloneFilter.sharedMesh = sourceFilter.sharedMesh;
|
||||||
|
|
||||||
|
// Optimizations to enforce
|
||||||
|
cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||||
|
cloneRenderer.allowOcclusionWhenDynamic = false;
|
||||||
|
|
||||||
|
// Optimizations to enforce
|
||||||
|
sourceRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||||
|
sourceRenderer.allowOcclusionWhenDynamic = false;
|
||||||
|
|
||||||
|
_standardClones.Add(cloneRenderer);
|
||||||
|
_standardCloneFilters.Add(cloneFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSkinnedClone(int index)
|
||||||
|
{
|
||||||
|
SkinnedMeshRenderer source = _skinnedRenderers[index];
|
||||||
|
|
||||||
|
GameObject go = new(source.name + "_VisualClone")
|
||||||
|
{
|
||||||
|
layer = CVRLayers.PlayerClone
|
||||||
|
};
|
||||||
|
|
||||||
|
go.transform.SetParent(source.transform, false);
|
||||||
|
//go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||||
|
|
||||||
|
SkinnedMeshRenderer clone = go.AddComponent<SkinnedMeshRenderer>();
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
clone.sharedMaterials = source.sharedMaterials;
|
||||||
|
clone.shadowCastingMode = ShadowCastingMode.Off;
|
||||||
|
clone.probeAnchor = source.probeAnchor;
|
||||||
|
clone.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity);
|
||||||
|
clone.sharedMesh = source.sharedMesh;
|
||||||
|
clone.rootBone = source.rootBone;
|
||||||
|
clone.bones = source.bones;
|
||||||
|
clone.quality = source.quality;
|
||||||
|
clone.updateWhenOffscreen = source.updateWhenOffscreen;
|
||||||
|
|
||||||
|
// Optimizations to enforce
|
||||||
|
clone.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||||
|
clone.allowOcclusionWhenDynamic = false;
|
||||||
|
clone.updateWhenOffscreen = false;
|
||||||
|
clone.skinnedMotionVectors = false;
|
||||||
|
clone.forceMatrixRecalculationPerRender = false;
|
||||||
|
clone.quality = SkinQuality.Bone4;
|
||||||
|
|
||||||
|
// Optimizations to enforce
|
||||||
|
source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||||
|
source.allowOcclusionWhenDynamic = false;
|
||||||
|
source.updateWhenOffscreen = false;
|
||||||
|
source.skinnedMotionVectors = false;
|
||||||
|
source.forceMatrixRecalculationPerRender = false;
|
||||||
|
source.quality = SkinQuality.Bone4;
|
||||||
|
|
||||||
|
_skinnedClones.Add(clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Clone Creation
|
||||||
|
}
|
141
AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs
Normal file
141
AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Transform, HashSet<Renderer>> _exclusionDirectRenderers = new();
|
||||||
|
private readonly Dictionary<Transform, HashSet<Transform>> _exclusionControlledBones = new();
|
||||||
|
|
||||||
|
private void InitializeExclusions()
|
||||||
|
{
|
||||||
|
// Add head exclusion for humanoid avatars if not present
|
||||||
|
var animator = GetComponent<Animator>();
|
||||||
|
if (animator != null && animator.isHuman)
|
||||||
|
{
|
||||||
|
var headBone = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||||
|
if (headBone != null && headBone.GetComponent<FPRExclusion>() == null)
|
||||||
|
{
|
||||||
|
var exclusion = headBone.gameObject.AddComponent<FPRExclusion>();
|
||||||
|
exclusion.isShown = false;
|
||||||
|
exclusion.target = headBone;
|
||||||
|
exclusion.shrinkToZero = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process existing exclusions bottom-up
|
||||||
|
var exclusions = GetComponentsInChildren<FPRExclusion>(true);
|
||||||
|
|
||||||
|
for (int i = exclusions.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var exclusion = exclusions[i];
|
||||||
|
if (exclusion.target == null)
|
||||||
|
exclusion.target = exclusion.transform;
|
||||||
|
|
||||||
|
// Skip invalid exclusions or already processed targets
|
||||||
|
if (exclusion.target == null || _exclusionDirectRenderers.ContainsKey(exclusion.target))
|
||||||
|
{
|
||||||
|
Destroy(exclusion);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize data for this exclusion
|
||||||
|
_exclusionDirectRenderers[exclusion.target] = new HashSet<Renderer>();
|
||||||
|
_exclusionControlledBones[exclusion.target] = new HashSet<Transform>();
|
||||||
|
|
||||||
|
// Set up our behaviour
|
||||||
|
exclusion.behaviour = new AvatarCloneExclusion(this, exclusion.target);
|
||||||
|
|
||||||
|
// Collect affected renderers and bones
|
||||||
|
CollectExclusionData(exclusion.target);
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
exclusion.UpdateExclusions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollectExclusionData(Transform target)
|
||||||
|
{
|
||||||
|
var stack = new Stack<Transform>();
|
||||||
|
stack.Push(target);
|
||||||
|
|
||||||
|
while (stack.Count > 0)
|
||||||
|
{
|
||||||
|
var current = stack.Pop();
|
||||||
|
|
||||||
|
// Skip if this transform belongs to another exclusion
|
||||||
|
if (current != target && current.GetComponent<FPRExclusion>() != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_exclusionControlledBones[target].Add(current);
|
||||||
|
|
||||||
|
// Add renderers that will need their clone visibility toggled
|
||||||
|
foreach (var renderer in current.GetComponents<Renderer>())
|
||||||
|
{
|
||||||
|
// Find corresponding clone renderer
|
||||||
|
if (renderer is MeshRenderer meshRenderer)
|
||||||
|
{
|
||||||
|
int index = _standardRenderers.IndexOf(meshRenderer);
|
||||||
|
if (index != -1)
|
||||||
|
_exclusionDirectRenderers[target].Add(_standardClones[index]);
|
||||||
|
}
|
||||||
|
else if (renderer is SkinnedMeshRenderer skinnedRenderer)
|
||||||
|
{
|
||||||
|
int index = _skinnedRenderers.IndexOf(skinnedRenderer);
|
||||||
|
if (index != -1)
|
||||||
|
_exclusionDirectRenderers[target].Add(_skinnedClones[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add children to stack
|
||||||
|
foreach (Transform child in current)
|
||||||
|
{
|
||||||
|
stack.Push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleExclusionUpdate(Transform target, Transform shrinkBone, bool isShown)
|
||||||
|
{
|
||||||
|
if (!_exclusionDirectRenderers.TryGetValue(target, out var directCloneRenderers) ||
|
||||||
|
!_exclusionControlledBones.TryGetValue(target, out var controlledBones))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Handle direct clone renderers
|
||||||
|
foreach (var cloneRenderer in directCloneRenderers)
|
||||||
|
{
|
||||||
|
cloneRenderer.enabled = isShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update bone references in clone renderers
|
||||||
|
int cloneCount = _skinnedClones.Count;
|
||||||
|
var cloneRenderers = _skinnedClones;
|
||||||
|
var sourceRenderers = _skinnedRenderers;
|
||||||
|
|
||||||
|
for (int i = 0; i < cloneCount; i++)
|
||||||
|
{
|
||||||
|
var clone = cloneRenderers[i];
|
||||||
|
var source = sourceRenderers[i];
|
||||||
|
var sourceBones = source.bones;
|
||||||
|
var cloneBones = clone.bones;
|
||||||
|
int boneCount = cloneBones.Length;
|
||||||
|
bool needsUpdate = false;
|
||||||
|
|
||||||
|
for (int j = 0; j < boneCount; j++)
|
||||||
|
{
|
||||||
|
// Check if this bone is in our controlled set
|
||||||
|
if (controlledBones.Contains(sourceBones[j]))
|
||||||
|
{
|
||||||
|
cloneBones[j] = isShown ? sourceBones[j] : shrinkBone;
|
||||||
|
needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
clone.bones = cloneBones;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs
Normal file
67
AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Profile Markers
|
||||||
|
//#if UNITY_EDITOR
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_CopyMaterials =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMaterials");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_CopyBlendShapes =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyBlendShapes");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_CopyMeshes =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMeshes");
|
||||||
|
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_MyOnPreRender =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.MyOnPreRender");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_SetShadowsOnly =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetShadowsOnly");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_UndoShadowsOnly =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoShadowsOnly");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_SetUiCulling =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetUiCulling");
|
||||||
|
private static readonly UnityEngine.Profiling.CustomSampler s_UndoUiCulling =
|
||||||
|
UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoUiCulling");
|
||||||
|
|
||||||
|
//#endif
|
||||||
|
#endregion Profile Markers
|
||||||
|
|
||||||
|
#region Source Renderers
|
||||||
|
private List<MeshRenderer> _standardRenderers;
|
||||||
|
private List<MeshFilter> _standardFilters;
|
||||||
|
private List<SkinnedMeshRenderer> _skinnedRenderers;
|
||||||
|
private List<Renderer> _allSourceRenderers; // For shadow casting only
|
||||||
|
#endregion Source Renderers
|
||||||
|
|
||||||
|
#region Clone Renderers
|
||||||
|
private List<MeshRenderer> _standardClones;
|
||||||
|
private List<MeshFilter> _standardCloneFilters;
|
||||||
|
private List<SkinnedMeshRenderer> _skinnedClones;
|
||||||
|
#endregion Clone Renderers
|
||||||
|
|
||||||
|
#region Dynamic Check Lists
|
||||||
|
private List<int> _standardRenderersNeedingChecks; // Stores indices into _standardRenderers
|
||||||
|
private List<int> _skinnedRenderersNeedingChecks; // Stores indices into _skinnedRenderers
|
||||||
|
private List<int> _cachedSkinnedBoneCounts; // So we don't copy the bones unless they've changed
|
||||||
|
private List<Mesh> _cachedSharedMeshes; // So we don't copy the mesh unless it's changed
|
||||||
|
#endregion Dynamic Check Lists
|
||||||
|
|
||||||
|
#region Material Data
|
||||||
|
private List<Material[]> _localMaterials;
|
||||||
|
private List<Material[]> _cullingMaterials;
|
||||||
|
private List<Material> _mainMaterials;
|
||||||
|
private MaterialPropertyBlock _propertyBlock;
|
||||||
|
#endregion Material Data
|
||||||
|
|
||||||
|
#region Blend Shape Data
|
||||||
|
private List<List<float>> _blendShapeWeights;
|
||||||
|
#endregion Blend Shape Data
|
||||||
|
|
||||||
|
#region Shadow and UI Culling Settings
|
||||||
|
private bool _uiCullingActive;
|
||||||
|
private bool _shadowsOnlyActive;
|
||||||
|
private bool[] _originallyHadShadows;
|
||||||
|
private bool[] _originallyWasEnabled;
|
||||||
|
#endregion Shadow and UI Culling Settings
|
||||||
|
}
|
128
AvatarCloneTest/AvatarClone/AvatarClone.Init.cs
Normal file
128
AvatarCloneTest/AvatarClone/AvatarClone.Init.cs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
using ABI_RC.Core.Player.ShadowClone;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Initialization
|
||||||
|
|
||||||
|
private void InitializeCollections()
|
||||||
|
{
|
||||||
|
_standardRenderers = new List<MeshRenderer>();
|
||||||
|
_standardFilters = new List<MeshFilter>();
|
||||||
|
_skinnedRenderers = new List<SkinnedMeshRenderer>();
|
||||||
|
_allSourceRenderers = new List<Renderer>();
|
||||||
|
|
||||||
|
_standardClones = new List<MeshRenderer>();
|
||||||
|
_standardCloneFilters = new List<MeshFilter>();
|
||||||
|
_skinnedClones = new List<SkinnedMeshRenderer>();
|
||||||
|
|
||||||
|
_standardRenderersNeedingChecks = new List<int>();
|
||||||
|
_skinnedRenderersNeedingChecks = new List<int>();
|
||||||
|
_cachedSkinnedBoneCounts = new List<int>();
|
||||||
|
_cachedSharedMeshes = new List<Mesh>();
|
||||||
|
|
||||||
|
_localMaterials = new List<Material[]>();
|
||||||
|
_cullingMaterials = new List<Material[]>();
|
||||||
|
_mainMaterials = new List<Material>();
|
||||||
|
_propertyBlock = new MaterialPropertyBlock();
|
||||||
|
|
||||||
|
_blendShapeWeights = new List<List<float>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRenderers()
|
||||||
|
{
|
||||||
|
var renderers = GetComponentsInChildren<Renderer>(true);
|
||||||
|
|
||||||
|
// Pre-size lists based on found renderers
|
||||||
|
// _standardRenderers.Capacity = renderers.Length;
|
||||||
|
// _standardFilters.Capacity = renderers.Length;
|
||||||
|
// _skinnedRenderers.Capacity = renderers.Length;
|
||||||
|
// _allSourceRenderers.Capacity = renderers.Length;
|
||||||
|
|
||||||
|
// Sort renderers into their respective lists
|
||||||
|
foreach (Renderer render in renderers)
|
||||||
|
{
|
||||||
|
_allSourceRenderers.Add(render);
|
||||||
|
|
||||||
|
switch (render)
|
||||||
|
{
|
||||||
|
case MeshRenderer meshRenderer:
|
||||||
|
{
|
||||||
|
MeshFilter filter = meshRenderer.GetComponent<MeshFilter>();
|
||||||
|
if (filter != null && filter.sharedMesh != null)
|
||||||
|
{
|
||||||
|
_standardRenderers.Add(meshRenderer);
|
||||||
|
_standardFilters.Add(filter);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SkinnedMeshRenderer skinnedRenderer:
|
||||||
|
{
|
||||||
|
if (skinnedRenderer.sharedMesh != null) _skinnedRenderers.Add(skinnedRenderer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupMaterialsAndBlendShapes()
|
||||||
|
{
|
||||||
|
// Cache counts
|
||||||
|
int standardCount = _standardRenderers.Count;
|
||||||
|
int skinnedCount = _skinnedRenderers.Count;
|
||||||
|
var standardRenderers = _standardRenderers;
|
||||||
|
var skinnedRenderers = _skinnedRenderers;
|
||||||
|
var localMats = _localMaterials;
|
||||||
|
var cullingMats = _cullingMaterials;
|
||||||
|
var blendWeights = _blendShapeWeights;
|
||||||
|
|
||||||
|
// Setup standard renderer materials
|
||||||
|
for (int i = 0; i < standardCount; i++)
|
||||||
|
{
|
||||||
|
MeshRenderer render = standardRenderers[i];
|
||||||
|
int matCount = render.sharedMaterials.Length;
|
||||||
|
|
||||||
|
// Local materials array
|
||||||
|
var localMatArray = new Material[matCount];
|
||||||
|
for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j];
|
||||||
|
localMats.Add(localMatArray);
|
||||||
|
|
||||||
|
// Culling materials array
|
||||||
|
var cullingMatArray = new Material[matCount];
|
||||||
|
for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial;
|
||||||
|
cullingMats.Add(cullingMatArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup skinned renderer materials and blend shapes
|
||||||
|
for (int i = 0; i < skinnedCount; i++)
|
||||||
|
{
|
||||||
|
SkinnedMeshRenderer render = skinnedRenderers[i];
|
||||||
|
int matCount = render.sharedMaterials.Length;
|
||||||
|
|
||||||
|
// Local materials array
|
||||||
|
var localMatArray = new Material[matCount];
|
||||||
|
for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j];
|
||||||
|
localMats.Add(localMatArray);
|
||||||
|
|
||||||
|
// Culling materials array
|
||||||
|
var cullingMatArray = new Material[matCount];
|
||||||
|
for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial;
|
||||||
|
cullingMats.Add(cullingMatArray);
|
||||||
|
|
||||||
|
// Blend shape weights
|
||||||
|
int blendShapeCount = render.sharedMesh.blendShapeCount;
|
||||||
|
var weights = new List<float>(blendShapeCount);
|
||||||
|
for (int j = 0; j < blendShapeCount; j++) weights.Add(0f);
|
||||||
|
blendWeights.Add(weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize renderer state arrays
|
||||||
|
int totalRenderers = _allSourceRenderers.Count;
|
||||||
|
_originallyHadShadows = new bool[totalRenderers];
|
||||||
|
_originallyWasEnabled = new bool[totalRenderers];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Initialization
|
||||||
|
}
|
34
AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs
Normal file
34
AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using MagicaCloth;
|
||||||
|
using MagicaCloth2;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Magica Cloth Support
|
||||||
|
|
||||||
|
private void SetupMagicaClothSupport()
|
||||||
|
{
|
||||||
|
var magicaCloths1 = GetComponentsInChildren<MagicaRenderDeformer>(true);
|
||||||
|
foreach (MagicaRenderDeformer magicaCloth in magicaCloths1)
|
||||||
|
{
|
||||||
|
// Get the renderer on the same object
|
||||||
|
Renderer renderer = magicaCloth.gameObject.GetComponent<Renderer>();
|
||||||
|
SetRendererNeedsAdditionalChecks(renderer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var magicaCloths2 = GetComponentsInChildren<MagicaCloth2.MagicaCloth>(true);
|
||||||
|
foreach (MagicaCloth2.MagicaCloth magicaCloth in magicaCloths2)
|
||||||
|
{
|
||||||
|
if (magicaCloth.serializeData.clothType != ClothProcess.ClothType.MeshCloth)
|
||||||
|
continue; // Only matters for cloth physics
|
||||||
|
|
||||||
|
// Set the affected renderers as requiring extra checks
|
||||||
|
var renderers = magicaCloth.serializeData.sourceRenderers;
|
||||||
|
foreach (Renderer renderer in renderers) SetRendererNeedsAdditionalChecks(renderer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Magica Cloth Support
|
||||||
|
}
|
181
AvatarCloneTest/AvatarClone/AvatarClone.Update.cs
Normal file
181
AvatarCloneTest/AvatarClone/AvatarClone.Update.cs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
#region Update Methods
|
||||||
|
|
||||||
|
private void UpdateStandardRenderers()
|
||||||
|
{
|
||||||
|
int count = _standardRenderers.Count;
|
||||||
|
var sourceRenderers = _standardRenderers;
|
||||||
|
var cloneRenderers = _standardClones;
|
||||||
|
var localMats = _localMaterials;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (!IsRendererValid(sourceRenderers[i])) continue;
|
||||||
|
CopyMaterialsAndProperties(
|
||||||
|
sourceRenderers[i],
|
||||||
|
cloneRenderers[i],
|
||||||
|
_propertyBlock,
|
||||||
|
_mainMaterials,
|
||||||
|
localMats[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSkinnedRenderers()
|
||||||
|
{
|
||||||
|
int standardCount = _standardRenderers.Count;
|
||||||
|
int count = _skinnedRenderers.Count;
|
||||||
|
var sourceRenderers = _skinnedRenderers;
|
||||||
|
var cloneRenderers = _skinnedClones;
|
||||||
|
var localMats = _localMaterials;
|
||||||
|
var blendWeights = _blendShapeWeights;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
SkinnedMeshRenderer source = sourceRenderers[i];
|
||||||
|
if (!IsRendererValid(source)) continue;
|
||||||
|
|
||||||
|
SkinnedMeshRenderer clone = cloneRenderers[i];
|
||||||
|
CopyMaterialsAndProperties(
|
||||||
|
source,
|
||||||
|
clone,
|
||||||
|
_propertyBlock,
|
||||||
|
_mainMaterials,
|
||||||
|
localMats[i + standardCount]);
|
||||||
|
|
||||||
|
CopyBlendShapes(source, clone, blendWeights[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStandardRenderersWithChecks()
|
||||||
|
{
|
||||||
|
s_CopyMeshes.Begin();
|
||||||
|
|
||||||
|
var cloneFilters = _standardCloneFilters;
|
||||||
|
var sourceFilters = _standardFilters;
|
||||||
|
var cachedMeshes = _cachedSharedMeshes;
|
||||||
|
var checkIndices = _standardRenderersNeedingChecks;
|
||||||
|
int checkCount = checkIndices.Count;
|
||||||
|
|
||||||
|
while (cachedMeshes.Count < checkCount) cachedMeshes.Add(null);
|
||||||
|
|
||||||
|
for (int i = 0; i < checkCount; i++)
|
||||||
|
{
|
||||||
|
int rendererIndex = checkIndices[i];
|
||||||
|
Mesh newMesh = sourceFilters[rendererIndex].sharedMesh;
|
||||||
|
if (ReferenceEquals(newMesh, cachedMeshes[i])) continue;
|
||||||
|
cloneFilters[rendererIndex].sharedMesh = newMesh; // expensive & allocates
|
||||||
|
cachedMeshes[i] = newMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_CopyMeshes.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSkinnedRenderersWithChecks()
|
||||||
|
{
|
||||||
|
s_CopyMeshes.Begin();
|
||||||
|
|
||||||
|
var sourceRenderers = _skinnedRenderers;
|
||||||
|
var cloneRenderers = _skinnedClones;
|
||||||
|
var cachedMeshes = _cachedSharedMeshes;
|
||||||
|
var cachedBoneCounts = _cachedSkinnedBoneCounts;
|
||||||
|
var checkIndices = _skinnedRenderersNeedingChecks;
|
||||||
|
int checkCount = checkIndices.Count;
|
||||||
|
int meshOffset = _standardRenderersNeedingChecks.Count;
|
||||||
|
|
||||||
|
// Ensure cache lists are properly sized
|
||||||
|
while (cachedMeshes.Count < meshOffset + checkCount) cachedMeshes.Add(null);
|
||||||
|
while (cachedBoneCounts.Count < checkCount) cachedBoneCounts.Add(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < checkCount; i++)
|
||||||
|
{
|
||||||
|
int rendererIndex = checkIndices[i];
|
||||||
|
SkinnedMeshRenderer source = sourceRenderers[rendererIndex];
|
||||||
|
SkinnedMeshRenderer clone = cloneRenderers[rendererIndex];
|
||||||
|
|
||||||
|
// Check mesh changes
|
||||||
|
Mesh newMesh = source.sharedMesh; // expensive & allocates
|
||||||
|
if (!ReferenceEquals(newMesh, cachedMeshes[meshOffset + i]))
|
||||||
|
{
|
||||||
|
clone.sharedMesh = newMesh;
|
||||||
|
cachedMeshes[meshOffset + i] = newMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bone changes
|
||||||
|
var sourceBones = source.bones;
|
||||||
|
int newBoneCount = sourceBones.Length;
|
||||||
|
int oldBoneCount = cachedBoneCounts[i];
|
||||||
|
if (newBoneCount == oldBoneCount)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var cloneBones = clone.bones; // expensive & allocates
|
||||||
|
if (newBoneCount > oldBoneCount)
|
||||||
|
{
|
||||||
|
// Resize array and copy only the new bones (Magica Cloth appends bones when enabling Mesh Cloth)
|
||||||
|
Array.Resize(ref cloneBones, newBoneCount);
|
||||||
|
for (int boneIndex = oldBoneCount; boneIndex < newBoneCount; boneIndex++)
|
||||||
|
cloneBones[boneIndex] = sourceBones[boneIndex];
|
||||||
|
clone.bones = cloneBones;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If shrinking, just set the whole array
|
||||||
|
clone.bones = sourceBones;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedBoneCounts[i] = newBoneCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_CopyMeshes.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyMaterialsAndProperties(
|
||||||
|
Renderer source, Renderer clone,
|
||||||
|
MaterialPropertyBlock propertyBlock,
|
||||||
|
List<Material> mainMaterials,
|
||||||
|
Material[] localMaterials)
|
||||||
|
{
|
||||||
|
s_CopyMaterials.Begin();
|
||||||
|
|
||||||
|
source.GetSharedMaterials(mainMaterials);
|
||||||
|
|
||||||
|
int matCount = mainMaterials.Count;
|
||||||
|
bool hasChanged = false;
|
||||||
|
for (var i = 0; i < matCount; i++)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(mainMaterials[i], localMaterials[i])) continue;
|
||||||
|
localMaterials[i] = mainMaterials[i];
|
||||||
|
hasChanged = true;
|
||||||
|
}
|
||||||
|
if (hasChanged) clone.sharedMaterials = localMaterials;
|
||||||
|
|
||||||
|
source.GetPropertyBlock(propertyBlock);
|
||||||
|
clone.SetPropertyBlock(propertyBlock);
|
||||||
|
|
||||||
|
s_CopyMaterials.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyBlendShapes(
|
||||||
|
SkinnedMeshRenderer source,
|
||||||
|
SkinnedMeshRenderer target,
|
||||||
|
List<float> blendShapeWeights)
|
||||||
|
{
|
||||||
|
s_CopyBlendShapes.Begin();
|
||||||
|
|
||||||
|
int weightCount = blendShapeWeights.Count;
|
||||||
|
for (var i = 0; i < weightCount; i++)
|
||||||
|
{
|
||||||
|
var weight = source.GetBlendShapeWeight(i);
|
||||||
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||||
|
if (weight == blendShapeWeights[i]) continue; // Halves the work
|
||||||
|
target.SetBlendShapeWeight(i, blendShapeWeights[i] = weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_CopyBlendShapes.End();
|
||||||
|
}
|
||||||
|
#endregion Update Methods
|
||||||
|
}
|
20
AvatarCloneTest/AvatarClone/AvatarClone.Util.cs
Normal file
20
AvatarCloneTest/AvatarClone/AvatarClone.Util.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone
|
||||||
|
{
|
||||||
|
private static bool CameraRendersPlayerLocalLayer(Camera cam)
|
||||||
|
=> (cam.cullingMask & (1 << CVRLayers.PlayerLocal)) != 0;
|
||||||
|
|
||||||
|
private static bool CameraRendersPlayerCloneLayer(Camera cam)
|
||||||
|
=> (cam.cullingMask & (1 << CVRLayers.PlayerClone)) != 0;
|
||||||
|
|
||||||
|
private static bool IsUIInternalCamera(Camera cam)
|
||||||
|
=> cam == PlayerSetup.Instance.activeUiCam;
|
||||||
|
|
||||||
|
private static bool IsRendererValid(Renderer renderer)
|
||||||
|
=> renderer && renderer.gameObject.activeInHierarchy;
|
||||||
|
}
|
134
AvatarCloneTest/AvatarClone/AvatarClone.cs
Normal file
134
AvatarCloneTest/AvatarClone/AvatarClone.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public partial class AvatarClone : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Unity Events
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
InitializeCollections();
|
||||||
|
InitializeRenderers();
|
||||||
|
SetupMaterialsAndBlendShapes();
|
||||||
|
CreateClones();
|
||||||
|
|
||||||
|
InitializeExclusions();
|
||||||
|
SetupMagicaClothSupport();
|
||||||
|
|
||||||
|
Camera.onPreCull += MyOnPreRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
Camera.onPreCull -= MyOnPreRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
// Update all renderers with basic properties (Materials & BlendShapes)
|
||||||
|
UpdateStandardRenderers();
|
||||||
|
UpdateSkinnedRenderers();
|
||||||
|
|
||||||
|
// Additional pass for renderers needing extra checks (Shared Mesh & Bone Changes)
|
||||||
|
UpdateStandardRenderersWithChecks();
|
||||||
|
UpdateSkinnedRenderersWithChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MyOnPreRender(Camera cam)
|
||||||
|
{
|
||||||
|
s_MyOnPreRender.Begin();
|
||||||
|
|
||||||
|
bool isOurUiCamera = IsUIInternalCamera(cam);
|
||||||
|
bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam);
|
||||||
|
bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam);
|
||||||
|
|
||||||
|
// Renders both player layers.
|
||||||
|
// PlayerLocal will now act as a shadow caster, while PlayerClone will act as the actual head-hidden renderer.
|
||||||
|
bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer;
|
||||||
|
if (!_shadowsOnlyActive && rendersBothPlayerLayers)
|
||||||
|
{
|
||||||
|
s_SetShadowsOnly.Begin();
|
||||||
|
|
||||||
|
int sourceCount = _allSourceRenderers.Count;
|
||||||
|
var sourceRenderers = _allSourceRenderers;
|
||||||
|
for (int i = 0; i < sourceCount; i++)
|
||||||
|
{
|
||||||
|
Renderer renderer = sourceRenderers[i];
|
||||||
|
if (!IsRendererValid(renderer)) continue;
|
||||||
|
|
||||||
|
bool shouldRender = renderer.shadowCastingMode != ShadowCastingMode.Off;
|
||||||
|
_originallyWasEnabled[i] = renderer.enabled;
|
||||||
|
_originallyHadShadows[i] = shouldRender;
|
||||||
|
renderer.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||||
|
if (renderer.forceRenderingOff == shouldRender) renderer.forceRenderingOff = !shouldRender; // TODO: Eval if check is needed
|
||||||
|
}
|
||||||
|
_shadowsOnlyActive = true;
|
||||||
|
|
||||||
|
s_SetShadowsOnly.End();
|
||||||
|
}
|
||||||
|
else if (_shadowsOnlyActive && !rendersBothPlayerLayers)
|
||||||
|
{
|
||||||
|
s_UndoShadowsOnly.Begin();
|
||||||
|
|
||||||
|
int sourceCount = _allSourceRenderers.Count;
|
||||||
|
var sourceRenderers = _allSourceRenderers;
|
||||||
|
for (int i = 0; i < sourceCount; i++)
|
||||||
|
{
|
||||||
|
Renderer renderer = sourceRenderers[i];
|
||||||
|
if (!IsRendererValid(renderer)) continue;
|
||||||
|
|
||||||
|
renderer.shadowCastingMode = _originallyHadShadows[i] ? ShadowCastingMode.On : ShadowCastingMode.Off;
|
||||||
|
if (renderer.forceRenderingOff == _originallyWasEnabled[i]) renderer.forceRenderingOff = !_originallyWasEnabled[i]; // TODO: Eval if check is needed
|
||||||
|
}
|
||||||
|
_shadowsOnlyActive = false;
|
||||||
|
|
||||||
|
s_UndoShadowsOnly.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle UI culling material changes
|
||||||
|
if (isOurUiCamera && !_uiCullingActive && rendersOurCloneLayer)
|
||||||
|
{
|
||||||
|
s_SetUiCulling.Begin();
|
||||||
|
|
||||||
|
int standardCount = _standardRenderers.Count;
|
||||||
|
var standardClones = _standardClones;
|
||||||
|
var cullingMaterials = _cullingMaterials;
|
||||||
|
for (int i = 0; i < standardCount; i++)
|
||||||
|
standardClones[i].sharedMaterials = cullingMaterials[i];
|
||||||
|
|
||||||
|
int skinnedCount = _skinnedRenderers.Count;
|
||||||
|
var skinnedClones = _skinnedClones;
|
||||||
|
for (int i = 0; i < skinnedCount; i++)
|
||||||
|
skinnedClones[i].sharedMaterials = cullingMaterials[i + standardCount];
|
||||||
|
|
||||||
|
_uiCullingActive = true;
|
||||||
|
|
||||||
|
s_SetUiCulling.End();
|
||||||
|
}
|
||||||
|
else if (!isOurUiCamera && _uiCullingActive)
|
||||||
|
{
|
||||||
|
s_UndoUiCulling.Begin();
|
||||||
|
|
||||||
|
int standardCount = _standardRenderers.Count;
|
||||||
|
var standardClones = _standardClones;
|
||||||
|
var localMaterials = _localMaterials;
|
||||||
|
for (int i = 0; i < standardCount; i++)
|
||||||
|
standardClones[i].sharedMaterials = localMaterials[i];
|
||||||
|
|
||||||
|
int skinnedCount = _skinnedRenderers.Count;
|
||||||
|
var skinnedClones = _skinnedClones;
|
||||||
|
for (int i = 0; i < skinnedCount; i++)
|
||||||
|
skinnedClones[i].sharedMaterials = localMaterials[i + standardCount];
|
||||||
|
|
||||||
|
_uiCullingActive = false;
|
||||||
|
|
||||||
|
s_UndoUiCulling.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_MyOnPreRender.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Unity Events
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public class AvatarCloneExclusion : IExclusionBehaviour
|
||||||
|
{
|
||||||
|
private readonly AvatarClone _cloneSystem;
|
||||||
|
private readonly Transform _target;
|
||||||
|
private Transform _shrinkBone;
|
||||||
|
|
||||||
|
public bool isImmuneToGlobalState { get; set; }
|
||||||
|
|
||||||
|
public AvatarCloneExclusion(AvatarClone cloneSystem, Transform target)
|
||||||
|
{
|
||||||
|
_cloneSystem = cloneSystem;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateExclusions(bool isShown, bool shrinkToZero)
|
||||||
|
{
|
||||||
|
Debug.Log($"[AvatarClone2] Updating exclusion for {_target.name}: isShown={isShown}, shrinkToZero={shrinkToZero}");
|
||||||
|
|
||||||
|
if (_shrinkBone == null)
|
||||||
|
{
|
||||||
|
// Create shrink bone parented directly to target
|
||||||
|
_shrinkBone = new GameObject($"{_target.name}_Shrink").transform;
|
||||||
|
_shrinkBone.SetParent(_target, false);
|
||||||
|
Debug.Log($"[AvatarClone2] Created shrink bone for {_target.name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scale based on shrink mode
|
||||||
|
_shrinkBone.localScale = shrinkToZero ? Vector3.zero : Vector3.positiveInfinity;
|
||||||
|
|
||||||
|
// Let the clone system handle the update
|
||||||
|
_cloneSystem.HandleExclusionUpdate(_target, _shrinkBone, isShown);
|
||||||
|
}
|
||||||
|
}
|
9
AvatarCloneTest/AvatarCloneTest.csproj
Normal file
9
AvatarCloneTest/AvatarCloneTest.csproj
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>LocalCloneFix</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DefineConstants>TRACE;TRACE;</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
72
AvatarCloneTest/Main.cs
Normal file
72
AvatarCloneTest/Main.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public class AvatarCloneTestMod : MelonMod
|
||||||
|
{
|
||||||
|
#region Melon Preferences
|
||||||
|
|
||||||
|
private static readonly MelonPreferences_Category Category =
|
||||||
|
MelonPreferences.CreateCategory(nameof(AvatarCloneTest));
|
||||||
|
|
||||||
|
internal static readonly MelonPreferences_Entry<bool> EntryUseAvatarCloneTest =
|
||||||
|
Category.CreateEntry("use_avatar_clone_test", true,
|
||||||
|
"Use Avatar Clone", description: "Uses the Avatar Clone setup for the local avatar.");
|
||||||
|
|
||||||
|
// internal static readonly MelonPreferences_Entry<bool> EntryCopyBlendShapes =
|
||||||
|
// Category.CreateEntry("copy_blend_shapes", true,
|
||||||
|
// "Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone.");
|
||||||
|
//
|
||||||
|
// internal static readonly MelonPreferences_Entry<bool> EntryCopyMaterials =
|
||||||
|
// Category.CreateEntry("copy_materials", true,
|
||||||
|
// "Copy Materials", description: "Copies the materials from the original avatar to the clone.");
|
||||||
|
//
|
||||||
|
// internal static readonly MelonPreferences_Entry<bool> EntryCopyMeshes =
|
||||||
|
// Category.CreateEntry("copy_meshes", true,
|
||||||
|
// "Copy Meshes", description: "Copies the meshes from the original avatar to the clone.");
|
||||||
|
|
||||||
|
#endregion Melon Preferences
|
||||||
|
|
||||||
|
#region Melon Events
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason?
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
// press f1 to find all cameras that arent tagged main and set them tno not render CVRLayers.PlayerClone
|
||||||
|
if (Input.GetKeyDown(KeyCode.F1))
|
||||||
|
{
|
||||||
|
foreach (var camera in UnityEngine.Object.FindObjectsOfType<UnityEngine.Camera>())
|
||||||
|
{
|
||||||
|
if (camera.tag != "MainCamera")
|
||||||
|
{
|
||||||
|
camera.cullingMask &= ~(1 << CVRLayers.PlayerClone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Melon Events
|
||||||
|
|
||||||
|
#region Melon Mod Utilities
|
||||||
|
|
||||||
|
private void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||||
|
LoggerInstance.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Melon Mod Utilities
|
||||||
|
}
|
87
AvatarCloneTest/Patches.cs
Normal file
87
AvatarCloneTest/Patches.cs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Player.TransformHider;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
|
using ABI_RC.Systems.Camera;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest;
|
||||||
|
|
||||||
|
public static class Patches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))]
|
||||||
|
private static bool OnSetupAvatar(GameObject avatar)
|
||||||
|
{
|
||||||
|
if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) return true;
|
||||||
|
avatar.AddComponent<AvatarClone>();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [HarmonyPostfix]
|
||||||
|
// [HarmonyPatch(typeof(FPRExclusion), nameof(FPRExclusion.UpdateExclusions))]
|
||||||
|
// private static void OnUpdateExclusions(ref FPRExclusion __instance)
|
||||||
|
// {
|
||||||
|
// AvatarClone clone = PlayerSetup.Instance._avatar.GetComponent<AvatarClone>();
|
||||||
|
// if (clone == null) return;
|
||||||
|
// clone.SetBoneChainVisibility(__instance.target, !__instance.isShown, !__instance.shrinkToZero);
|
||||||
|
// }
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(CVRMirror), nameof(CVRMirror.Start))]
|
||||||
|
private static void OnMirrorStart(CVRMirror __instance)
|
||||||
|
{
|
||||||
|
if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't reflect the player clone layer
|
||||||
|
__instance.m_ReflectLayers &= ~(1 << CVRLayers.PlayerClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Update))]
|
||||||
|
private static void OnTransformHiderManagerUpdate(PlayerSetup __instance)
|
||||||
|
{
|
||||||
|
if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (MetaPort.Instance.settings.GetSettingsBool("ExperimentalAvatarOverrenderUI"))
|
||||||
|
__instance.activeUiCam.cullingMask |= 1 << CVRLayers.PlayerClone;
|
||||||
|
else
|
||||||
|
__instance.activeUiCam.cullingMask &= ~(1 << CVRLayers.PlayerClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool _wasDebugInPortableCamera;
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.Update))]
|
||||||
|
private static void OnPortableCameraUpdate(ref PortableCamera __instance)
|
||||||
|
{
|
||||||
|
if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value)
|
||||||
|
{
|
||||||
|
// Show both PlayerLocal and PlayerClone
|
||||||
|
__instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal;
|
||||||
|
__instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TransformHiderManager.s_DebugInPortableCamera == _wasDebugInPortableCamera)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TransformHiderManager.s_DebugInPortableCamera)
|
||||||
|
{
|
||||||
|
// Hide PlayerLocal, show PlayerClone
|
||||||
|
__instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerLocal);
|
||||||
|
__instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Show PlayerLocal, hide PlayerClone
|
||||||
|
__instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal;
|
||||||
|
__instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
_wasDebugInPortableCamera = TransformHiderManager.s_DebugInPortableCamera;
|
||||||
|
}
|
||||||
|
}
|
32
AvatarCloneTest/Properties/AssemblyInfo.cs
Normal file
32
AvatarCloneTest/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.AvatarCloneTest.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.AvatarCloneTest))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.AvatarCloneTest))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.AvatarCloneTest.AvatarCloneTestMod),
|
||||||
|
nameof(NAK.AvatarCloneTest),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarCloneTest"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||||
|
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.AvatarCloneTest.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
18
AvatarCloneTest/README.md
Normal file
18
AvatarCloneTest/README.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# VisualCloneFix
|
||||||
|
|
||||||
|
Fixes the Visual Clone system and allows you to use it again.
|
||||||
|
|
||||||
|
Using the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.
|
||||||
|
|
||||||
|
**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||||
|
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||||
|
|
||||||
|
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||||
|
|
||||||
|
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||||
|
|
||||||
|
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
23
AvatarCloneTest/format.json
Normal file
23
AvatarCloneTest/format.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"_id": 221,
|
||||||
|
"name": "VisualCloneFix",
|
||||||
|
"modversion": "1.0.1",
|
||||||
|
"gameversion": "2024r175",
|
||||||
|
"loaderversion": "0.6.1",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"author": "NotAKidoS",
|
||||||
|
"description": "Fixes the Visual Clone system and allows you to use it again.\n\nUsing the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.\n\n**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.",
|
||||||
|
"searchtags": [
|
||||||
|
"visual",
|
||||||
|
"clone",
|
||||||
|
"head",
|
||||||
|
"hiding"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"None"
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/",
|
||||||
|
"changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.",
|
||||||
|
"embedcolor": "#f61963"
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue