diff --git a/VisualCloneFix/Main.cs b/VisualCloneFix/Main.cs index 2787b55..9e5634d 100644 --- a/VisualCloneFix/Main.cs +++ b/VisualCloneFix/Main.cs @@ -1,4 +1,5 @@ -using MelonLoader; +using System; +using MelonLoader; namespace NAK.VisualCloneFix; diff --git a/VisualCloneFix/Patches.cs b/VisualCloneFix/Patches.cs index 50356c8..3da1b35 100644 --- a/VisualCloneFix/Patches.cs +++ b/VisualCloneFix/Patches.cs @@ -1,8 +1,12 @@ -using ABI_RC.Core.Player.LocalClone; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ABI_RC.Core.Player.LocalClone; using ABI_RC.Core.Player.TransformHider; using ABI.CCK.Components; using HarmonyLib; using UnityEngine; +using Debug = UnityEngine.Debug; namespace NAK.VisualCloneFix; @@ -36,28 +40,20 @@ public static class Patches MeshHiderExclusion headExclusionBehaviour = new(); headExclusion.behaviour = headExclusionBehaviour; headExclusionBehaviour.id = 1; // head bone is always 1 - - // body is 0, ensure it is not masked away - //LocalCloneManager.cullingMask &= ~(1 << 0); - + // get all FPRExclusions - var fprExclusions = root.GetComponentsInChildren(true).ToList(); + var fprExclusions = root.GetComponentsInChildren(true); // get all valid exclusion targets, and destroy invalid exclusions Dictionary exclusionTargets = new(); int nextId = 2; - for (int i = fprExclusions.Count - 1; i >= 0; i--) + foreach (FPRExclusion exclusion in fprExclusions) { - FPRExclusion exclusion = fprExclusions[i]; if (exclusion.target == null || exclusionTargets.ContainsKey(exclusion.target) || !exclusion.target.gameObject.scene.IsValid()) - { - UnityEngine.Object.Destroy(exclusion); - fprExclusions.RemoveAt(i); - continue; - } + continue; // invalid exclusion if (exclusion.behaviour == null) // head exclusion is already created { @@ -71,25 +67,23 @@ public static class Patches } // process each FPRExclusion (recursive) - foreach (FPRExclusion exclusion in fprExclusions) + int exclusionCount = exclusionTargets.Values.Count; + for (var index = 0; index < exclusionCount; index++) { + FPRExclusion exclusion = exclusionTargets.Values.ElementAt(index); ProcessExclusion(exclusion, exclusion.target); exclusion.UpdateExclusions(); // initial state } - - // log totals - //LocalCloneMod.Logger.Msg($"Exclusions: {fprExclusions.Count}"); + __result = exclusionTargets; return false; - + void ProcessExclusion(FPRExclusion exclusion, Transform transform) { if (exclusionTargets.ContainsKey(transform) && exclusionTargets[transform] != exclusion) return; // found other exclusion root - //exclusion.affectedChildren.Add(transform); // associate with the exclusion exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) - foreach (Transform child in transform) ProcessExclusion(exclusion, child); // process children } @@ -97,39 +91,55 @@ public static class Patches [HarmonyPrefix] [HarmonyPatch(typeof(SkinnedLocalClone), nameof(SkinnedLocalClone.FindExclusionVertList))] - public static bool FindExclusionVertList( + private static bool FindExclusionVertList( SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, - ref int[] __result) + ref int[] __result) { - Mesh sharedMesh = renderer.sharedMesh; - var boneWeights = sharedMesh.boneWeights; - int[] vertexIndices = new int[sharedMesh.vertexCount]; - - // Pre-map bone transforms to their exclusion ids if applicable + // Start the stopwatch + Stopwatch stopwatch = new(); + stopwatch.Start(); + + var boneWeights = renderer.sharedMesh.boneWeights; var bones = renderer.bones; - int[] boneIndexToExclusionId = new int[bones.Length]; - for (int i = 0; i < bones.Length; i++) - { - Transform bone = bones[i]; - if (bone != null && exclusions.TryGetValue(bone, out FPRExclusion exclusion)) - boneIndexToExclusionId[i] = ((MeshHiderExclusion)(exclusion.behaviour)).id; - } + int boneCount = bones.Length; + bool[] boneHasExclusion = new bool[boneCount]; + + // Populate the weights array + for (int i = 0; i < boneCount; i++) + if (exclusions.ContainsKey(bones[i])) + boneHasExclusion[i] = true; + const float minWeightThreshold = 0.2f; - for (int i = 0; i < boneWeights.Length; i++) + + int[] vertexIndices = new int[renderer.sharedMesh.vertexCount]; + + // Check bone weights and add vertex to exclusion list if needed + for (int i = 0; i < boneWeights.Length; i++) { BoneWeight weight = boneWeights[i]; - - if (weight.weight0 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex0]; - else if (weight.weight1 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex1]; - else if (weight.weight2 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex2]; - else if (weight.weight3 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex3]; + Transform bone; + + if (boneHasExclusion[weight.boneIndex0] && weight.weight0 > minWeightThreshold) + bone = bones[weight.boneIndex0]; + else if (boneHasExclusion[weight.boneIndex1] && weight.weight1 > minWeightThreshold) + bone = bones[weight.boneIndex1]; + else if (boneHasExclusion[weight.boneIndex2] && weight.weight2 > minWeightThreshold) + bone = bones[weight.boneIndex2]; + else if (boneHasExclusion[weight.boneIndex3] && weight.weight3 > minWeightThreshold) + bone = bones[weight.boneIndex3]; + else continue; + + if (exclusions.TryGetValue(bone, out FPRExclusion exclusion)) + vertexIndices[i] = ((MeshHiderExclusion)(exclusion.behaviour)).id; } + // Stop the stopwatch + stopwatch.Stop(); + + // Log the execution time + Debug.Log($"FindExclusionVertList execution time: {stopwatch.ElapsedMilliseconds} ms"); + __result = vertexIndices; return false; } diff --git a/VisualCloneFix/format.json b/VisualCloneFix/format.json index ed16de1..6634e9f 100644 --- a/VisualCloneFix/format.json +++ b/VisualCloneFix/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/", - "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.", + "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.", "embedcolor": "#f61963" } \ No newline at end of file