mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 22:39:22 +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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue