Move many mods to Deprecated folder, fix spelling

This commit is contained in:
NotAKidoS 2025-04-03 02:57:35 -05:00
parent 5e822cec8d
commit 0042590aa6
539 changed files with 7475 additions and 3120 deletions

View file

@ -1,68 +0,0 @@
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
}

View file

@ -1,103 +0,0 @@
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
}

View file

@ -1,141 +0,0 @@
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;
}
}
}
}

View file

@ -0,0 +1,229 @@
using ABI.CCK.Components;
using UnityEngine;
namespace NAK.AvatarCloneTest;
public partial class AvatarClone
{
#region Exclusions
private FPRExclusion[] _exclusions;
private void AddExclusionToHeadIfNeeded()
{
if (!TryGetComponent(out Animator animator)
|| !animator.isHuman
|| !animator.avatar
|| !animator.avatar.isValid)
return;
Transform head = animator.GetBoneTransform(HumanBodyBones.Head);
if (!head)
return;
GameObject headGo = head.gameObject;
if (headGo.TryGetComponent(out FPRExclusion exclusion))
return;
exclusion = headGo.AddComponent<FPRExclusion>();
exclusion.target = head;
exclusion.isShown = false;
}
private void InitializeExclusions()
{
_exclusions = GetComponentsInChildren<FPRExclusion>(true);
var exclusionRoots = new Dictionary<Transform, AvatarCloneExclusion>(_exclusions.Length);
// **1. Precompute Exclusions**
foreach (FPRExclusion exclusion in _exclusions)
{
Transform target = exclusion.target ??= exclusion.transform;
if (exclusionRoots.ContainsKey(target) || !target.gameObject.scene.IsValid())
continue;
AvatarCloneExclusion behaviour = new AvatarCloneExclusion(this, target);
exclusion.behaviour = behaviour;
exclusionRoots.Add(target, behaviour);
}
// Process Exclusion Transforms
Renderer ourRenderer;
void ProcessTransformHierarchy(Transform current, Transform root, AvatarCloneExclusion behaviour)
{
if (exclusionRoots.ContainsKey(current) && current != root) return;
behaviour.affectedTransforms.Add(current);
if (current.TryGetComponent(out ourRenderer))
behaviour.affectedRenderers.Add(ourRenderer);
for (int i = 0; i < current.childCount; i++)
{
Transform child = current.GetChild(i);
if (!exclusionRoots.ContainsKey(child))
ProcessTransformHierarchy(child, root, behaviour);
}
}
foreach (var entry in exclusionRoots)
{
Transform rootTransform = entry.Key;
AvatarCloneExclusion behaviour = entry.Value;
ProcessTransformHierarchy(rootTransform, rootTransform, behaviour);
behaviour.affectedTransformSet = new HashSet<Transform>(behaviour.affectedTransforms);
}
// ------------------------------
// **OPTIMIZED EXCLUSION BONE MAPPING**
// ------------------------------
Dictionary<Transform, AvatarCloneExclusion>.ValueCollection exclusionBehaviours = exclusionRoots.Values;
int skinnedCount = _skinnedClones.Count;
// **2. Precompute Bone-to-Exclusion Mapping**
int estimatedBoneCount = skinnedCount * 20; // Estimated bones per skinned mesh
var boneToExclusion = new Dictionary<Transform, List<AvatarCloneExclusion>>(estimatedBoneCount);
foreach (AvatarCloneExclusion behaviour in exclusionBehaviours)
{
foreach (Transform bone in behaviour.affectedTransformSet)
{
if (!boneToExclusion.TryGetValue(bone, out var list))
{
list = new List<AvatarCloneExclusion>(2);
boneToExclusion[bone] = list;
}
list.Add(behaviour);
}
}
// **3. Process Skinned Mesh Renderers**
for (int s = 0; s < skinnedCount; s++)
{
SkinnedMeshRenderer source = _skinnedRenderers[s];
var bones = source.bones; // Cache bones array
SkinnedMeshRenderer smr = _skinnedClones[s];
int boneCount = bones.Length;
for (int i = 0; i < boneCount; i++)
{
Transform bone = bones[i];
// **Skip if the bone isn't mapped to exclusions**
if (!bone // Skip null bones
|| !boneToExclusion.TryGetValue(bone, out var behaviours))
continue;
// **Avoid redundant dictionary lookups**
for (int j = 0; j < behaviours.Count; j++)
{
AvatarCloneExclusion behaviour = behaviours[j];
if (!behaviour.skinnedToBoneIndex.TryGetValue(smr, out var indices))
{
indices = new List<int>(4);
behaviour.skinnedToBoneIndex[smr] = indices;
}
indices.Add(i);
}
}
}
ApplyInitialExclusionState();
}
public void ApplyInitialExclusionState()
{
foreach (FPRExclusion exclusion in _exclusions)
{
exclusion._wasShown = exclusion.isShown;
if (!exclusion.isShown) exclusion.UpdateExclusions();
}
}
public void HandleExclusionUpdate(AvatarCloneExclusion exclusion, bool isShown)
{
#if ENABLE_PROFILER
s_UpdateExclusions.Begin();
#endif
// **1. Update Renderer Visibility**
foreach (Renderer renderer in exclusion.affectedRenderers)
{
if (renderer is SkinnedMeshRenderer skinned)
{
int index = _skinnedRenderers.IndexOf(skinned);
if (index >= 0) _skinnedClones[index].gameObject.SetActive(isShown);
}
else if (renderer is MeshRenderer mesh)
{
int index = _meshRenderers.IndexOf(mesh);
if (index >= 0)
{
if (Setting_CloneMeshRenderers)
{
_meshClones[index].gameObject.SetActive(isShown);
}
else
{
// Other renderer (never cloned) - update shadow casting state
_sourceShouldBeHiddenFromFPR[index] = !isShown; // When hidden, use for shadows
}
}
}
else if (renderer)
{
int index = _otherRenderers.IndexOf(renderer);
if (index >= 0)
{
int shadowIndex = index + (Setting_CloneMeshRenderers ? _meshRenderers.Count : 0);
_sourceShouldBeHiddenFromFPR[shadowIndex] = !isShown; // When hidden, use for shadows
}
}
}
// **2. Update Bone References in Skinned Mesh Renderers**
UpdateSkinnedMeshBones(exclusion, exclusion._shrinkBone, isShown);
#if ENABLE_PROFILER
s_UpdateExclusions.End();
#endif
}
private void UpdateSkinnedMeshBones(AvatarCloneExclusion exclusion, Transform shrinkBone, bool isShown)
{
#if ENABLE_PROFILER
s_HandleBoneUpdates.Begin();
#endif
foreach (var smrEntry in exclusion.skinnedToBoneIndex)
{
SkinnedMeshRenderer smr = smrEntry.Key;
var indices = smrEntry.Value;
bool needsUpdate = false;
var parentBones = smr.transform.parent.GetComponent<SkinnedMeshRenderer>().bones;
var cloneBones = smr.bones;
Array.Resize(ref cloneBones, parentBones.Length);
// Only modify our bones, other exclusions may have modified others
for (int i = 0; i < indices.Count; i++)
{
int index = indices[i];
if (!isShown) cloneBones[index] = shrinkBone;
else cloneBones[index] = parentBones[index];
needsUpdate = true;
}
if (needsUpdate) smr.bones = cloneBones;
}
#if ENABLE_PROFILER
s_HandleBoneUpdates.End();
#endif
}
#endregion Exclusions
}

View file

@ -1,67 +0,0 @@
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
}

View file

@ -1,5 +1,6 @@
using ABI_RC.Core.Player.ShadowClone;
using UnityEngine;
using UnityEngine.Rendering;
namespace NAK.AvatarCloneTest;
@ -9,120 +10,295 @@ public partial class AvatarClone
private void InitializeCollections()
{
_standardRenderers = new List<MeshRenderer>();
_standardFilters = new List<MeshFilter>();
#if ENABLE_PROFILER
s_InitializeData.Begin();
#endif
// Initialize source collections
_skinnedRenderers = new List<SkinnedMeshRenderer>();
_allSourceRenderers = new List<Renderer>();
_blendShapeWeights = new List<List<float>>();
_standardClones = new List<MeshRenderer>();
_standardCloneFilters = new List<MeshFilter>();
_meshRenderers = new List<MeshRenderer>();
_meshFilters = new List<MeshFilter>();
_otherRenderers = new List<Renderer>();
// Initialize clone collections
_skinnedClones = new List<SkinnedMeshRenderer>();
_skinnedCloneMaterials = new List<Material[]>();
_skinnedCloneCullingMaterials = new List<Material[]>();
_standardRenderersNeedingChecks = new List<int>();
_skinnedRenderersNeedingChecks = new List<int>();
_cachedSkinnedBoneCounts = new List<int>();
_cachedSharedMeshes = new List<Mesh>();
if (Setting_CloneMeshRenderers)
{
_meshClones = new List<MeshRenderer>();
_meshCloneFilters = new List<MeshFilter>();
_meshCloneMaterials = new List<Material[]>();
_meshCloneCullingMaterials = new List<Material[]>();
}
_localMaterials = new List<Material[]>();
_cullingMaterials = new List<Material[]>();
_mainMaterials = new List<Material>();
// Initialize shared resources
_materialWorkingList = new List<Material>();
_propertyBlock = new MaterialPropertyBlock();
_blendShapeWeights = new List<List<float>>();
#if ENABLE_PROFILER
s_InitializeData.End();
#endif
}
private void InitializeRenderers()
private void CollectRenderers()
{
var renderers = GetComponentsInChildren<Renderer>(true);
#if ENABLE_PROFILER
s_InitializeData.Begin();
#endif
// 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)
var renderers = GetComponentsInChildren<Renderer>(true);
var currentIndex = 0;
var nonCloned = 0;
// Single pass: directly categorize renderers
foreach (Renderer renderer in renderers)
{
_allSourceRenderers.Add(render);
switch (render)
switch (renderer)
{
case MeshRenderer meshRenderer:
{
MeshFilter filter = meshRenderer.GetComponent<MeshFilter>();
case SkinnedMeshRenderer skinned when skinned.sharedMesh != null:
AddSkinnedRenderer(skinned);
currentIndex++;
break;
case MeshRenderer mesh:
MeshFilter filter = mesh.GetComponent<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
_standardRenderers.Add(meshRenderer);
_standardFilters.Add(filter);
if (Setting_CloneMeshRenderers)
{
AddMeshRenderer(mesh, filter);
}
else
{
AddMeshRenderer(mesh, filter);
nonCloned++;
}
currentIndex++;
}
break;
}
case SkinnedMeshRenderer skinnedRenderer:
{
if (skinnedRenderer.sharedMesh != null) _skinnedRenderers.Add(skinnedRenderer);
default:
AddOtherRenderer(renderer);
currentIndex++;
nonCloned++;
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];
_rendererActiveStates = new bool[currentIndex];
_originalShadowCastingMode = new ShadowCastingMode[currentIndex];
_sourceShouldBeHiddenFromFPR = new bool[nonCloned];
#if ENABLE_PROFILER
s_InitializeData.End();
#endif
}
private void AddSkinnedRenderer(SkinnedMeshRenderer renderer)
{
#if ENABLE_PROFILER
s_AddRenderer.Begin();
#endif
_skinnedRenderers.Add(renderer);
// Clone materials array for clone renderer
var materials = renderer.sharedMaterials;
var cloneMaterials = new Material[materials.Length];
for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i];
_skinnedCloneMaterials.Add(cloneMaterials);
// Cache culling materials
var cullingMaterialArray = new Material[materials.Length];
#if !UNITY_EDITOR
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial;
#else
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial;
#endif
_skinnedCloneCullingMaterials.Add(cullingMaterialArray);
// Cache blend shape weights
var weights = new List<float>(renderer.sharedMesh.blendShapeCount);
for (int i = 0; i < renderer.sharedMesh.blendShapeCount; i++) weights.Add(0f);
_blendShapeWeights.Add(weights);
#if ENABLE_PROFILER
s_AddRenderer.End();
#endif
}
private void AddMeshRenderer(MeshRenderer renderer, MeshFilter filter)
{
#if ENABLE_PROFILER
s_AddRenderer.Begin();
#endif
_meshRenderers.Add(renderer);
_meshFilters.Add(filter);
if (!Setting_CloneMeshRenderers) return;
// Clone materials array for clone renderer
var materials = renderer.sharedMaterials;
var cloneMaterials = new Material[materials.Length];
for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i];
_meshCloneMaterials.Add(cloneMaterials);
// Cache culling materials
var cullingMaterialArray = new Material[materials.Length];
#if !UNITY_EDITOR
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial;
#else
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial;
#endif
_meshCloneCullingMaterials.Add(cullingMaterialArray);
#if ENABLE_PROFILER
s_AddRenderer.End();
#endif
}
private void AddOtherRenderer(Renderer renderer)
{
#if ENABLE_PROFILER
s_AddRenderer.Begin();
#endif
_otherRenderers.Add(renderer);
#if ENABLE_PROFILER
s_AddRenderer.End();
#endif
}
private void CreateClones()
{
#if ENABLE_PROFILER
s_InitializeData.Begin();
#endif
// Always create skinned mesh clones
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++)
{
CreateSkinnedClone(i);
}
// Optionally create mesh clones
if (Setting_CloneMeshRenderers)
{
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++)
{
CreateMeshClone(i);
}
}
#if ENABLE_PROFILER
s_InitializeData.End();
#endif
}
private void CreateSkinnedClone(int index)
{
#if ENABLE_PROFILER
s_CreateClone.Begin();
#endif
SkinnedMeshRenderer source = _skinnedRenderers[index];
GameObject clone = new(source.name + "_Clone")
{
layer = CLONE_LAYER
};
clone.transform.SetParent(source.transform, false);
SkinnedMeshRenderer cloneRenderer = clone.AddComponent<SkinnedMeshRenderer>();
// Basic setup
cloneRenderer.sharedMaterials = _skinnedCloneMaterials[index];
cloneRenderer.shadowCastingMode = ShadowCastingMode.Off;
cloneRenderer.probeAnchor = source.probeAnchor;
cloneRenderer.sharedMesh = source.sharedMesh;
cloneRenderer.rootBone = source.rootBone;
cloneRenderer.bones = source.bones;
#if !UNITY_EDITOR
cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f);
#endif
// Quality settings
cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
cloneRenderer.allowOcclusionWhenDynamic = false;
cloneRenderer.updateWhenOffscreen = false;
cloneRenderer.skinnedMotionVectors = false;
cloneRenderer.forceMatrixRecalculationPerRender = false;
cloneRenderer.quality = SkinQuality.Bone4;
source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
source.allowOcclusionWhenDynamic = false;
source.updateWhenOffscreen = false;
source.skinnedMotionVectors = false;
source.forceMatrixRecalculationPerRender = false;
source.quality = SkinQuality.Bone4;
// Add to clone list
_skinnedClones.Add(cloneRenderer);
#if ENABLE_PROFILER
s_CreateClone.End();
#endif
}
private void CreateMeshClone(int index)
{
#if ENABLE_PROFILER
s_CreateClone.Begin();
#endif
MeshRenderer source = _meshRenderers[index];
MeshFilter sourceFilter = _meshFilters[index];
GameObject clone = new(source.name + "_Clone")
{
layer = CLONE_LAYER
};
clone.transform.SetParent(source.transform, false);
MeshRenderer cloneRenderer = clone.AddComponent<MeshRenderer>();
MeshFilter cloneFilter = clone.AddComponent<MeshFilter>();
// Basic setup
cloneRenderer.sharedMaterials = _meshCloneMaterials[index];
cloneRenderer.shadowCastingMode = ShadowCastingMode.Off;
cloneRenderer.probeAnchor = source.probeAnchor;
#if !UNITY_EDITOR
cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f);
#endif
cloneFilter.sharedMesh = sourceFilter.sharedMesh;
// Quality settings
cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
cloneRenderer.allowOcclusionWhenDynamic = false;
source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
source.allowOcclusionWhenDynamic = false;
// Add to clone lists
_meshClones.Add(cloneRenderer);
_meshCloneFilters.Add(cloneFilter);
#if ENABLE_PROFILER
s_CreateClone.End();
#endif
}
#endregion Initialization
}

View file

@ -1,34 +0,0 @@
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
}

View file

@ -0,0 +1,212 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace NAK.AvatarCloneTest;
public partial class AvatarClone
{
#region Render State Management
private void MyOnPreCull(Camera cam)
{
#if UNITY_EDITOR
// Scene & Preview cameras are not needed
if (cam.cameraType != CameraType.Game)
return;
#endif
#if ENABLE_PROFILER
s_PreCullUpdate.Begin();
#endif
bool isOurUiCamera = IsUIInternalCamera(cam);
bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam);
bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam);
bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer;
// Handle shadow casting when camera renders both layers
if (!_sourcesSetForShadowCasting
&& rendersBothPlayerLayers)
{
ConfigureSourceShadowCasting(true);
_sourcesSetForShadowCasting = true;
}
else if (_sourcesSetForShadowCasting && !rendersBothPlayerLayers)
{
ConfigureSourceShadowCasting(false);
_sourcesSetForShadowCasting = false;
}
// Handle UI culling for clone layer
if (!_clonesSetForUiCulling
&& isOurUiCamera && rendersOurCloneLayer)
{
ConfigureCloneUICulling(true);
_clonesSetForUiCulling = true;
}
else if (_clonesSetForUiCulling)
{
ConfigureCloneUICulling(false);
_clonesSetForUiCulling = false;
}
#if ENABLE_PROFILER
s_PreCullUpdate.End();
#endif
}
private void ConfigureSourceShadowCasting(bool setSourcesToShadowCast)
{
#if ENABLE_PROFILER
s_ConfigureShadowCasting.Begin();
#endif
int currentIndex = 0;
int shadowArrayIndex = 0;
// Handle skinned mesh renderers (always have clones)
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex]) continue;
SkinnedMeshRenderer source = _skinnedRenderers[i];
if (setSourcesToShadowCast)
{
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
if (originalMode == ShadowCastingMode.Off)
source.forceRenderingOff = true;
else
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
}
else
{
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
source.forceRenderingOff = false;
}
}
// Handle mesh renderers based on clone setting
if (Setting_CloneMeshRenderers)
{
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex]) continue;
MeshRenderer source = _meshRenderers[i];
if (setSourcesToShadowCast)
{
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
if (originalMode == ShadowCastingMode.Off)
source.forceRenderingOff = true;
else
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
}
else
{
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
source.forceRenderingOff = false;
}
}
}
else
{
// When not cloned, mesh renderers use the shadow casting array
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++, shadowArrayIndex++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex]) continue;
if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue;
MeshRenderer source = _meshRenderers[i];
if (setSourcesToShadowCast)
{
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
if (originalMode == ShadowCastingMode.Off)
source.forceRenderingOff = true;
else
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
}
else
{
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
source.forceRenderingOff = false;
}
}
}
// Handle other renderers (never cloned)
int otherCount = _otherRenderers.Count;
for (int i = 0; i < otherCount; i++, shadowArrayIndex++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex]) continue;
if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue;
Renderer source = _otherRenderers[i];
if (setSourcesToShadowCast)
{
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
if (originalMode == ShadowCastingMode.Off)
source.forceRenderingOff = true;
else
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
}
else
{
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
source.forceRenderingOff = false;
}
}
#if ENABLE_PROFILER
s_ConfigureShadowCasting.End();
#endif
}
private void ConfigureCloneUICulling(bool enableCulling)
{
#if ENABLE_PROFILER
s_ConfigureUICulling.Begin();
#endif
// Set the materials to our culling materials
int currentIndex = 0;
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex])
continue;
_skinnedClones[i].sharedMaterials = enableCulling ?
_skinnedCloneCullingMaterials[i] :
_skinnedCloneMaterials[i];
}
if (Setting_CloneMeshRenderers)
{
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex])
continue;
_meshClones[i].sharedMaterials = enableCulling ?
_meshCloneCullingMaterials[i] :
_meshCloneMaterials[i];
}
}
#if ENABLE_PROFILER
s_ConfigureUICulling.End();
#endif
}
#endregion Render State Management
}

View file

@ -0,0 +1,156 @@
using UnityEngine;
namespace NAK.AvatarCloneTest;
public partial class AvatarClone
{
#region State Syncing
private void SyncEnabledState()
{
#if ENABLE_PROFILER
s_CopyEnabledState.Begin();
#endif
int currentIndex = 0;
// Update skinned mesh renderers
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++, currentIndex++)
{
SkinnedMeshRenderer source = _skinnedRenderers[i];
_skinnedClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source);
}
// Update mesh renderers
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++, currentIndex++)
{
MeshRenderer source = _meshRenderers[i];
if (Setting_CloneMeshRenderers) _meshClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source);
else _rendererActiveStates[currentIndex] = IsRendererActive(source);
}
// Update other renderers
int otherCount = _otherRenderers.Count;
for (int i = 0; i < otherCount; i++, currentIndex++)
{
Renderer source = _otherRenderers[i];
_rendererActiveStates[currentIndex] = IsRendererActive(source);
}
#if ENABLE_PROFILER
s_CopyEnabledState.End();
#endif
}
private void SyncMaterials()
{
#if ENABLE_PROFILER
s_CopyMaterials.Begin();
#endif
int currentIndex = 0;
// Sync skinned mesh materials
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex])
continue;
CopyMaterialsAndProperties(
_skinnedRenderers[i],
_skinnedClones[i],
_propertyBlock,
_materialWorkingList,
_skinnedCloneMaterials[i]);
}
// Sync mesh materials if enabled
if (Setting_CloneMeshRenderers)
{
int meshCount = _meshRenderers.Count;
for (int i = 0; i < meshCount; i++, currentIndex++)
{
if (!_rendererActiveStates[currentIndex])
continue;
CopyMaterialsAndProperties(
_meshRenderers[i],
_meshClones[i],
_propertyBlock,
_materialWorkingList,
_meshCloneMaterials[i]);
}
}
#if ENABLE_PROFILER
s_CopyMaterials.End();
#endif
}
private void SyncBlendShapes()
{
#if ENABLE_PROFILER
s_CopyBlendShapes.Begin();
#endif
int skinnedCount = _skinnedRenderers.Count;
for (int i = 0; i < skinnedCount; i++)
{
SkinnedMeshRenderer source = _skinnedRenderers[i];
if (!_rendererActiveStates[i])
continue;
CopyBlendShapes(
source,
_skinnedClones[i],
_blendShapeWeights[i]);
}
#if ENABLE_PROFILER
s_CopyBlendShapes.End();
#endif
}
private static void CopyMaterialsAndProperties(
Renderer source,
Renderer clone,
MaterialPropertyBlock propertyBlock,
List<Material> workingList,
Material[] cloneMaterials)
{
source.GetSharedMaterials(workingList);
int matCount = workingList.Count;
bool hasChanged = false;
for (int i = 0; i < matCount; i++)
{
if (ReferenceEquals(workingList[i], cloneMaterials[i])) continue;
cloneMaterials[i] = workingList[i];
hasChanged = true;
}
if (hasChanged) clone.sharedMaterials = cloneMaterials;
source.GetPropertyBlock(propertyBlock);
clone.SetPropertyBlock(propertyBlock);
}
private static void CopyBlendShapes(
SkinnedMeshRenderer source,
SkinnedMeshRenderer clone,
List<float> weights)
{
int weightCount = weights.Count;
for (int i = 0; i < weightCount; i++)
{
float weight = source.GetBlendShapeWeight(i);
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (weight == weights[i]) continue; // Halves the work
clone.SetBlendShapeWeight(i, weights[i] = weight);
}
}
#endregion State Syncing
}

View file

@ -1,181 +0,0 @@
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
}

View file

@ -1,20 +1,81 @@
using ABI_RC.Core;
using ABI_RC.Core.Player;
using ABI_RC.Core.Player;
using MagicaCloth;
using MagicaCloth2;
using UnityEngine;
namespace NAK.AvatarCloneTest;
public partial class AvatarClone
{
#region Utilities
private static bool IsRendererActive(Renderer renderer)
=> renderer && renderer.enabled && renderer.gameObject.activeInHierarchy;
private static bool CameraRendersPlayerLocalLayer(Camera cam)
=> (cam.cullingMask & (1 << CVRLayers.PlayerLocal)) != 0;
=> (cam.cullingMask & (1 << LOCAL_LAYER)) != 0;
private static bool CameraRendersPlayerCloneLayer(Camera cam)
=> (cam.cullingMask & (1 << CVRLayers.PlayerClone)) != 0;
=> (cam.cullingMask & (1 << CLONE_LAYER)) != 0;
private static bool IsUIInternalCamera(Camera cam)
#if !UNITY_EDITOR
=> cam == PlayerSetup.Instance.activeUiCam;
#else
=> cam.gameObject.layer == 15;
#endif
private static bool IsRendererValid(Renderer renderer)
=> renderer && renderer.gameObject.activeInHierarchy;
#endregion Utilities
#region Magica Cloth Support
private void SetupMagicaClothSupport()
{
var magicaCloths1 = GetComponentsInChildren<BaseCloth>(true);
foreach (BaseCloth magicaCloth1 in magicaCloths1)
magicaCloth1.SetCullingMode(PhysicsTeam.TeamCullingMode.Off);
var magicaCloths2 = base.GetComponentsInChildren<MagicaCloth2.MagicaCloth>(true);
foreach (MagicaCloth2.MagicaCloth magicaCloth2 in magicaCloths2)
magicaCloth2.serializeData.cullingSettings.cameraCullingMode = CullingSettings.CameraCullingMode.AnimatorLinkage;
}
public void OnMagicaClothMeshSwapped(Renderer render, Mesh newMesh)
{
switch (render)
{
case MeshRenderer mesh:
{
int index = _meshRenderers.IndexOf(mesh);
if (index != -1) _meshCloneFilters[index].sharedMesh = newMesh;
break;
}
case SkinnedMeshRenderer skinned:
{
int index = _skinnedRenderers.IndexOf(skinned);
if (index != -1)
{
// Copy the mesh
_skinnedClones[index].sharedMesh = newMesh;
// Copy appended bones if count is different
var cloneBones = _skinnedClones[index].bones; // alloc
var sourceBones = skinned.bones; // alloc
int cloneBoneCount = cloneBones.Length;
int sourceBoneCount = sourceBones.Length;
if (cloneBoneCount != sourceBoneCount)
{
// Copy the new bones only
Array.Resize(ref cloneBones, sourceBoneCount);
for (int i = cloneBoneCount; i < sourceBoneCount; i++) cloneBones[i] = sourceBones[i];
_skinnedClones[index].bones = cloneBones;
}
}
break;
}
}
}
#endregion Magica Cloth Support
}

View file

@ -1,3 +1,4 @@
using NAK.AvatarCloneTest;
using UnityEngine;
using UnityEngine.Rendering;
@ -5,130 +6,142 @@ namespace NAK.AvatarCloneTest;
public partial class AvatarClone : MonoBehaviour
{
#region Constants
private const int LOCAL_LAYER = 8;
private const int CLONE_LAYER = 9;
#endregion Constants
#region Profiler Markers
#if ENABLE_PROFILER
private static readonly ProfilerMarker s_CopyEnabledState = new($"{nameof(AvatarClone)}.{nameof(SyncEnabledState)}");
private static readonly ProfilerMarker s_CopyMaterials = new($"{nameof(AvatarClone)}.{nameof(CopyMaterialsAndProperties)}");
private static readonly ProfilerMarker s_CopyBlendShapes = new($"{nameof(AvatarClone)}.{nameof(CopyBlendShapes)}");
private static readonly ProfilerMarker s_InitializeData = new($"{nameof(AvatarClone)}.Initialize");
private static readonly ProfilerMarker s_UpdateExclusions = new($"{nameof(AvatarClone)}.{nameof(HandleExclusionUpdate)}");
private static readonly ProfilerMarker s_CollectExclusionData = new($"{nameof(AvatarClone)}.{nameof(CollectExclusionData)}");
private static readonly ProfilerMarker s_HandleBoneUpdates = new($"{nameof(AvatarClone)}.{nameof(UpdateSkinnedMeshBones)}");
private static readonly ProfilerMarker s_PreCullUpdate = new($"{nameof(AvatarClone)}.{nameof(MyOnPreCull)}");
private static readonly ProfilerMarker s_ConfigureShadowCasting = new($"{nameof(AvatarClone)}.{nameof(ConfigureSourceShadowCasting)}");
private static readonly ProfilerMarker s_ConfigureUICulling = new($"{nameof(AvatarClone)}.{nameof(ConfigureCloneUICulling)}");
private static readonly ProfilerMarker s_AddRenderer = new($"{nameof(AvatarClone)}.AddRenderer");
private static readonly ProfilerMarker s_CreateClone = new($"{nameof(AvatarClone)}.CreateClone");
#endif
#endregion Profiler Markers
#region Settings
public bool Setting_CloneMeshRenderers;
public bool Setting_CopyMaterials = true;
public bool Setting_CopyBlendShapes = true;
#endregion Settings
#region Source Collections - Cloned Renderers
// Skinned mesh renderers (always cloned)
private List<SkinnedMeshRenderer> _skinnedRenderers;
private List<List<float>> _blendShapeWeights;
// Mesh renderers (optionally cloned)
private List<MeshRenderer> _meshRenderers;
private List<MeshFilter> _meshFilters;
#endregion Source Collections - Cloned Renderers
#region Source Collections - Non-Cloned Renderers
// All other renderers (never cloned)
private List<Renderer> _otherRenderers;
// True if source renderer should hide. False if source renderer should show.
// Only used for non-cloned renderers (MeshRenderers and other Renderers).
private bool[] _sourceShouldBeHiddenFromFPR;
// Three states: On, ShadowsOnly, Off
private ShadowCastingMode[] _originalShadowCastingMode;
#endregion Source Collections - Non-Cloned Renderers
#region Clone Collections
// Skinned mesh clones
private List<SkinnedMeshRenderer> _skinnedClones;
private List<Material[]> _skinnedCloneMaterials;
private List<Material[]> _skinnedCloneCullingMaterials;
// Mesh clones (optional)
private List<MeshRenderer> _meshClones;
private List<MeshFilter> _meshCloneFilters;
private List<Material[]> _meshCloneMaterials;
private List<Material[]> _meshCloneCullingMaterials;
#endregion Clone Collections
#region Shared Resources
private List<Material> _materialWorkingList; // Used for GetSharedMaterials
private MaterialPropertyBlock _propertyBlock;
#endregion Shared Resources
#region State
private bool _sourcesSetForShadowCasting;
private bool _clonesSetForUiCulling;
private bool[] _rendererActiveStates;
#endregion State
#region Unity Events
private void Start()
{
InitializeCollections();
InitializeRenderers();
SetupMaterialsAndBlendShapes();
CreateClones();
Setting_CloneMeshRenderers = AvatarCloneTestMod.EntryCloneMeshRenderers.Value;
InitializeCollections();
CollectRenderers();
CreateClones();
AddExclusionToHeadIfNeeded();
InitializeExclusions();
SetupMagicaClothSupport();
Camera.onPreCull += MyOnPreRender;
}
// bool animatesClone = transform.Find("[ExplicitlyAnimatesVisualClones]") != null;
// Setting_CopyMaterials = !animatesClone;
// Setting_CopyBlendShapes = !animatesClone;
// Animator animator = GetComponent<Animator>();
// if (animator && animatesClone) animator.Rebind();
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();
// Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff
#if !UNITY_EDITOR
Camera.onPreCull += MyOnPreCull;
#else
Camera.onPreRender += MyOnPreCull;
#endif
}
private void MyOnPreRender(Camera cam)
private void LateUpdate()
{
s_MyOnPreRender.Begin();
SyncEnabledState();
bool isOurUiCamera = IsUIInternalCamera(cam);
bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam);
bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam);
if (Setting_CopyMaterials && AvatarCloneTestMod.EntryCopyMaterials.Value)
SyncMaterials();
// 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();
if (Setting_CopyBlendShapes && AvatarCloneTestMod.EntryCopyBlendShapes.Value)
SyncBlendShapes();
}
private void OnDestroy()
{
// Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff
#if !UNITY_EDITOR
Camera.onPreCull -= MyOnPreCull;
#else
Camera.onPreRender -= MyOnPreCull;
#endif
}
#endregion Unity Events
}

View file

@ -5,9 +5,14 @@ namespace NAK.AvatarCloneTest;
public class AvatarCloneExclusion : IExclusionBehaviour
{
public readonly Dictionary<SkinnedMeshRenderer, List<int>> skinnedToBoneIndex = new();
public readonly List<Transform> affectedTransforms = new();
public readonly List<Renderer> affectedRenderers = new();
public HashSet<Transform> affectedTransformSet = new();
private readonly AvatarClone _cloneSystem;
private readonly Transform _target;
private Transform _shrinkBone;
internal Transform _shrinkBone;
public bool isImmuneToGlobalState { get; set; }
@ -19,20 +24,16 @@ public class AvatarCloneExclusion : IExclusionBehaviour
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);
// Replace the bone references with the shrink bone for the indicies we modify
_cloneSystem.HandleExclusionUpdate(this, isShown);
}
}

View file

@ -1,4 +1,6 @@
using ABI_RC.Core;
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.Savior;
using MelonLoader;
using UnityEngine;
@ -15,17 +17,17 @@ public class AvatarCloneTestMod : MelonMod
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.");
internal static readonly MelonPreferences_Entry<bool> EntryCloneMeshRenderers =
Category.CreateEntry("clone_mesh_renderers", false,
"Clone Mesh Renderers", description: "Clones the mesh renderers from the original avatar to the clone.");
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.");
#endregion Melon Preferences
@ -49,6 +51,13 @@ public class AvatarCloneTestMod : MelonMod
}
}
}
// if pressing ctrl + r, reload avatar
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.R))
{
var player = MetaPort.Instance.currentAvatarGuid;
AssetManagement.Instance.LoadLocalAvatar(player);
}
}
#endregion Melon Events

View file

@ -18,70 +18,5 @@ public static class Patches
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;
}
}
}

View file

@ -27,6 +27,6 @@ using System.Reflection;
namespace NAK.AvatarCloneTest.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Version = "1.0.3";
public const string Author = "NotAKidoS";
}