mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
fix: ScaleAdjuster breaks scene view selection (#718)
... fixed by reimplementing ScaleAdjuster (again!)
This commit is contained in:
parent
a9141a6cfe
commit
f7b12d7f82
55
Editor/HarmonyPatches/HandleUtilityPatches.cs
Normal file
55
Editor/HarmonyPatches/HandleUtilityPatches.cs
Normal file
@ -0,0 +1,55 @@
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
internal static class HandleUtilityPatches
|
||||
{
|
||||
internal static void Patch_FilterInstanceIDs(Harmony h)
|
||||
{
|
||||
var t_HandleUtility = AccessTools.TypeByName("UnityEditor.HandleUtility");
|
||||
var m_orig = AccessTools.Method(t_HandleUtility, "FilterInstanceIDs");
|
||||
|
||||
var m_prefix = AccessTools.Method(typeof(HandleUtilityPatches), "Prefix_FilterInstanceIDs");
|
||||
|
||||
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_prefix));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static bool Prefix_FilterInstanceIDs(
|
||||
ref IEnumerable<GameObject> gameObjects,
|
||||
out int[] parentInstanceIDs,
|
||||
out int[] childInstanceIDs
|
||||
)
|
||||
{
|
||||
gameObjects = RemapObjects(gameObjects);
|
||||
parentInstanceIDs = childInstanceIDs = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<GameObject> RemapObjects(IEnumerable<GameObject> objs)
|
||||
{
|
||||
return objs.Select(
|
||||
obj =>
|
||||
{
|
||||
if (obj == null) return obj;
|
||||
if (ScaleAdjusterRenderer.originalObjects.TryGetValue(obj, out var proxy) && proxy != null)
|
||||
{
|
||||
return proxy.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/HarmonyPatches/HandleUtilityPatches.cs.meta
Normal file
3
Editor/HarmonyPatches/HandleUtilityPatches.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 807736f252df4b1b8402827257dcbea3
|
||||
timeCreated: 1709354699
|
@ -39,20 +39,17 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
[UsedImplicitly]
|
||||
private static void Postfix(GameObject prefabInstance, object __result)
|
||||
{
|
||||
var ignoredObjects = prefabInstance.GetComponentsInChildren<ScaleAdjusterRenderer>()
|
||||
.Select(sar => sar.gameObject)
|
||||
.ToImmutableHashSet();
|
||||
List<AddedGameObject> added = p_AddedGameObjects.GetValue(__result) as List<AddedGameObject>;
|
||||
|
||||
if (added == null) return;
|
||||
added.RemoveAll(obj => ignoredObjects.Contains(obj.instanceGameObject));
|
||||
added.RemoveAll(obj => ScaleAdjusterRenderer.proxyObjects.ContainsKey(obj.instanceGameObject));
|
||||
|
||||
List<ObjectOverride> objectOverrides = p_ObjectOverrides.GetValue(__result) as List<ObjectOverride>;
|
||||
if (objectOverrides == null) return;
|
||||
objectOverrides.RemoveAll(oo =>
|
||||
{
|
||||
var c = oo.instanceObject as Component;
|
||||
return c != null && ignoredObjects.Contains(c.gameObject);
|
||||
return c != null && ScaleAdjusterRenderer.proxyObjects.ContainsKey(c.gameObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
175
Editor/HarmonyPatches/HierarchyViewPatches.cs
Normal file
175
Editor/HarmonyPatches/HierarchyViewPatches.cs
Normal file
@ -0,0 +1,175 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
internal static class HierarchyViewPatches
|
||||
{
|
||||
private static readonly Type t_HierarchyProperty = AccessTools.TypeByName("UnityEditor.HierarchyProperty");
|
||||
private static readonly PropertyInfo p_pptrValue = AccessTools.Property(t_HierarchyProperty, "pptrValue");
|
||||
|
||||
private static FieldInfo f_m_Rows; // List<TreeViewItem>
|
||||
private static FieldInfo f_m_RowCount; // int
|
||||
private static PropertyInfo p_objectPPTR;
|
||||
|
||||
internal static void Patch(Harmony h)
|
||||
{
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
return;
|
||||
#endif
|
||||
var t_GameObjectTreeViewDataSource = AccessTools.TypeByName("UnityEditor.GameObjectTreeViewDataSource");
|
||||
var t_GameObjectTreeViewItem = AccessTools.TypeByName("UnityEditor.GameObjectTreeViewItem");
|
||||
|
||||
f_m_Rows = t_GameObjectTreeViewDataSource.GetField("m_Rows",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
f_m_RowCount =
|
||||
t_GameObjectTreeViewDataSource.GetField("m_RowCount", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
p_objectPPTR = t_GameObjectTreeViewItem.GetProperty("objectPPTR");
|
||||
|
||||
var m_orig = AccessTools.Method(t_GameObjectTreeViewDataSource, "InitTreeViewItem",
|
||||
new[]
|
||||
{
|
||||
t_GameObjectTreeViewItem,
|
||||
typeof(int),
|
||||
typeof(Scene),
|
||||
typeof(bool),
|
||||
typeof(int),
|
||||
typeof(Object),
|
||||
typeof(bool),
|
||||
typeof(int)
|
||||
});
|
||||
var m_patch = AccessTools.Method(typeof(HierarchyViewPatches), "Prefix_InitTreeViewItem");
|
||||
|
||||
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_patch));
|
||||
|
||||
var m_InitRows = AccessTools.Method(t_GameObjectTreeViewDataSource, "InitializeRows");
|
||||
var m_transpiler = AccessTools.Method(typeof(HierarchyViewPatches), "Transpile_InitializeRows");
|
||||
|
||||
h.Patch(original: m_InitRows,
|
||||
transpiler: new HarmonyMethod(m_transpiler),
|
||||
postfix: new HarmonyMethod(AccessTools.Method(typeof(HierarchyViewPatches), "Postfix_InitializeRows")),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HierarchyViewPatches), "Prefix_InitializeRows"))
|
||||
);
|
||||
}
|
||||
|
||||
private static int skipped = 0;
|
||||
|
||||
private static void Prefix_InitializeRows()
|
||||
{
|
||||
skipped = 0;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Postfix_InitializeRows(object __instance)
|
||||
{
|
||||
var rows = (IList<TreeViewItem>)f_m_Rows.GetValue(__instance);
|
||||
|
||||
var rowCount = (int)f_m_RowCount.GetValue(__instance);
|
||||
|
||||
f_m_RowCount.SetValue(__instance, rowCount - skipped);
|
||||
|
||||
for (int i = 0; i < skipped; i++)
|
||||
{
|
||||
rows.RemoveAt(rows.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static IEnumerable<CodeInstruction> Transpile_InitializeRows(IEnumerable<CodeInstruction> instructions,
|
||||
ILGenerator generator)
|
||||
{
|
||||
foreach (var c in Transpile_InitializeRows0(instructions, generator))
|
||||
{
|
||||
//Debug.Log(c);
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static IEnumerable<CodeInstruction> Transpile_InitializeRows0(IEnumerable<CodeInstruction> instructions,
|
||||
ILGenerator generator)
|
||||
{
|
||||
var m_shouldLoop = AccessTools.Method(typeof(HierarchyViewPatches), "ShouldLoop");
|
||||
|
||||
var m_Next = AccessTools.Method(t_HierarchyProperty, "Next", new[] { typeof(int[]) });
|
||||
|
||||
foreach (var c in instructions)
|
||||
{
|
||||
if (c.Is(OpCodes.Callvirt, m_Next))
|
||||
{
|
||||
var loopLabel = generator.DefineLabel();
|
||||
var stash_arg = generator.DeclareLocal(typeof(int[]));
|
||||
var stash_obj = generator.DeclareLocal(t_HierarchyProperty);
|
||||
|
||||
yield return new CodeInstruction(OpCodes.Stloc, stash_arg);
|
||||
yield return new CodeInstruction(OpCodes.Stloc, stash_obj);
|
||||
|
||||
var tmp = new CodeInstruction(OpCodes.Ldloc, stash_obj);
|
||||
tmp.labels.Add(loopLabel);
|
||||
yield return tmp;
|
||||
|
||||
yield return new CodeInstruction(OpCodes.Ldloc, stash_arg);
|
||||
yield return new CodeInstruction(OpCodes.Call, m_Next);
|
||||
|
||||
// Check if this item should be ignored.
|
||||
yield return new CodeInstruction(OpCodes.Ldloc, stash_obj);
|
||||
yield return new CodeInstruction(OpCodes.Call, m_shouldLoop);
|
||||
yield return new CodeInstruction(OpCodes.Brtrue_S, loopLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static bool ShouldLoop(object hierarchyProperty)
|
||||
{
|
||||
if (hierarchyProperty == null) return false;
|
||||
|
||||
var pptrValue = p_pptrValue.GetValue(hierarchyProperty);
|
||||
if (pptrValue == null) return false;
|
||||
|
||||
var skip = ScaleAdjusterRenderer.proxyObjects.ContainsKey((GameObject)pptrValue);
|
||||
if (skip) skipped++;
|
||||
|
||||
return skip;
|
||||
}
|
||||
|
||||
private static bool Prefix_InitTreeViewItem(
|
||||
object __instance,
|
||||
ref object item,
|
||||
int itemID,
|
||||
Scene scene,
|
||||
bool isSceneHeader,
|
||||
int colorCode,
|
||||
Object pptrObject,
|
||||
ref bool hasChildren,
|
||||
int depth
|
||||
)
|
||||
{
|
||||
if (pptrObject == null || isSceneHeader) return true;
|
||||
|
||||
if (hasChildren && ScaleAdjusterRenderer.originalObjects.ContainsKey((GameObject)pptrObject))
|
||||
{
|
||||
// See if there are any other children...
|
||||
hasChildren = ((GameObject)pptrObject).transform.childCount > 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/HarmonyPatches/HierarchyViewPatches.cs.meta
Normal file
3
Editor/HarmonyPatches/HierarchyViewPatches.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42f70698a5df48c0908400c425a2f6ee
|
||||
timeCreated: 1709356304
|
@ -13,9 +13,12 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
private static readonly Action<Harmony>[] patches = new Action<Harmony>[]
|
||||
{
|
||||
SnoopHeaderRendering.Patch1,
|
||||
SnoopHeaderRendering.Patch2,
|
||||
HideScaleAdjusterFromPrefabOverrideView.Patch
|
||||
HideScaleAdjusterFromPrefabOverrideView.Patch,
|
||||
HierarchyViewPatches.Patch,
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
HandleUtilityPatches.Patch_FilterInstanceIDs,
|
||||
PickingObjectPatch.Patch,
|
||||
#endif
|
||||
};
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
|
78
Editor/HarmonyPatches/PickingObjectPatch.cs
Normal file
78
Editor/HarmonyPatches/PickingObjectPatch.cs
Normal file
@ -0,0 +1,78 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
internal static class PickingObjectPatch
|
||||
{
|
||||
private static Type t_PickingObject = AccessTools.TypeByName("UnityEditor.PickingObject");
|
||||
|
||||
private static Type l_PickingObject =
|
||||
typeof(List<>).MakeGenericType(new[] { t_PickingObject });
|
||||
|
||||
private static ConstructorInfo ctor_l = AccessTools.Constructor(l_PickingObject);
|
||||
|
||||
private static ConstructorInfo ctor_PickingObject =
|
||||
AccessTools.Constructor(t_PickingObject, new[] { typeof(Object), typeof(int) });
|
||||
|
||||
private static PropertyInfo p_materialIndex = AccessTools.Property(t_PickingObject, "materialIndex");
|
||||
|
||||
private static MethodInfo m_TryGetGameObject = AccessTools.Method(t_PickingObject, "TryGetGameObject");
|
||||
|
||||
internal static void Patch(Harmony h)
|
||||
{
|
||||
var t_PickingObject = AccessTools.TypeByName("UnityEditor.PickingObject");
|
||||
var ctor_PickingObject = AccessTools.Constructor(t_PickingObject, new[] { typeof(Object), typeof(int) });
|
||||
|
||||
var t_SceneViewPicking = AccessTools.TypeByName("UnityEditor.SceneViewPicking");
|
||||
var m_GetAllOverlapping = AccessTools.Method(t_SceneViewPicking, "GetAllOverlapping");
|
||||
|
||||
var m_postfix = AccessTools.Method(typeof(PickingObjectPatch), nameof(Postfix_GetAllOverlapping));
|
||||
|
||||
h.Patch(original: m_GetAllOverlapping, postfix: new HarmonyMethod(m_postfix));
|
||||
}
|
||||
|
||||
private static void Postfix_GetAllOverlapping(ref object __result)
|
||||
{
|
||||
var erased = (IEnumerable)__result;
|
||||
var list = (IList)ctor_l.Invoke(new object[0]);
|
||||
|
||||
foreach (var obj in erased)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
list.Add(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
var args = new object[] { null };
|
||||
if ((bool)m_TryGetGameObject.Invoke(obj, args))
|
||||
{
|
||||
var go = args[0] as GameObject;
|
||||
if (go != null && ScaleAdjusterRenderer.proxyObjects.ContainsKey(go))
|
||||
{
|
||||
list.Add(ctor_PickingObject.Invoke(new[]
|
||||
{
|
||||
go.transform.parent.gameObject,
|
||||
p_materialIndex.GetValue(obj)
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(obj);
|
||||
}
|
||||
|
||||
__result = list;
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/HarmonyPatches/PickingObjectPatch.cs.meta
Normal file
3
Editor/HarmonyPatches/PickingObjectPatch.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf06818f1c0c436fbae7f755d7110aba
|
||||
timeCreated: 1709359553
|
@ -1,45 +0,0 @@
|
||||
#region
|
||||
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// ScaleAdjusterRenderer toggles off the enabled state of the original mesh just before rendering,
|
||||
/// in order to allow us to effectively replace it at rendering time. We restore this in OnPostRender,
|
||||
/// but GUI rendering can happen before this; as such, snoop GUI events and re-enable the original
|
||||
/// at that time.
|
||||
/// </summary>
|
||||
internal class SnoopHeaderRendering
|
||||
{
|
||||
internal static void Patch1(Harmony harmony)
|
||||
{
|
||||
var t_orig = AccessTools.TypeByName("UnityEditor.UIElements.EditorElement");
|
||||
var m_orig = AccessTools.Method(t_orig, "HeaderOnGUI");
|
||||
|
||||
var m_prefix = AccessTools.Method(typeof(SnoopHeaderRendering), "Prefix");
|
||||
|
||||
harmony.Patch(original: m_orig, prefix: new HarmonyMethod(m_prefix));
|
||||
}
|
||||
|
||||
internal static void Patch2(Harmony harmony)
|
||||
{
|
||||
var t_GUIUtility = typeof(GUIUtility);
|
||||
var m_ProcessEvent = AccessTools.Method(t_GUIUtility, "ProcessEvent");
|
||||
|
||||
var m_prefix = AccessTools.Method(typeof(SnoopHeaderRendering), "Prefix");
|
||||
|
||||
harmony.Patch(original: m_ProcessEvent, prefix: new HarmonyMethod(m_prefix));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Prefix()
|
||||
{
|
||||
ScaleAdjusterRenderer.ClearAllOverrides();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cafb5b7e681644cbbeafbeb12d833f6e
|
||||
timeCreated: 1708235926
|
3
Editor/ScaleAdjuster.meta
Normal file
3
Editor/ScaleAdjuster.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45b2b4957674444fa96bc0b6e221425e
|
||||
timeCreated: 1709361970
|
28
Editor/ScaleAdjuster/SelectionHack.cs
Normal file
28
Editor/ScaleAdjuster/SelectionHack.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.ScaleAdjuster
|
||||
{
|
||||
#if !UNITY_2022_3_OR_NEWER
|
||||
internal static class SelectionHack
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void Init()
|
||||
{
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
|
||||
}
|
||||
|
||||
static void OnSelectionChanged()
|
||||
{
|
||||
var gameObject = Selection.activeGameObject;
|
||||
if (gameObject != null && gameObject.GetComponent<ScaleAdjusterRenderer>() != null)
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
Selection.activeGameObject = gameObject.transform.parent.gameObject;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
3
Editor/ScaleAdjuster/SelectionHack.cs.meta
Normal file
3
Editor/ScaleAdjuster/SelectionHack.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfa3ba0c82bc4439aa86228715f61831
|
||||
timeCreated: 1709376243
|
@ -10,8 +10,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
protected override void Execute(ndmf.BuildContext context)
|
||||
{
|
||||
ScaleAdjusterRenderer.ClearAllOverrides();
|
||||
|
||||
Dictionary<Transform, Transform> boneMappings = new Dictionary<Transform, Transform>();
|
||||
foreach (var component in context.AvatarRootObject.GetComponentsInChildren<ScaleProxy>())
|
||||
{
|
||||
|
86
Runtime/ScaleAdjuster/CameraHooks.cs
Normal file
86
Runtime/ScaleAdjuster/CameraHooks.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.modular_avatar.JacksonDunstan.NativeCollections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
internal static class CameraHooks
|
||||
{
|
||||
private static Dictionary<SkinnedMeshRenderer, SkinnedMeshRenderer> originalToProxy
|
||||
= new Dictionary<SkinnedMeshRenderer, SkinnedMeshRenderer>(
|
||||
new ObjectIdentityComparer<SkinnedMeshRenderer>()
|
||||
);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
private static void Init()
|
||||
{
|
||||
Camera.onPreCull += OnPreCull;
|
||||
Camera.onPostRender += OnPostRender;
|
||||
UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += ClearStates;
|
||||
UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += (scene, path) => ClearStates();
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void RegisterProxy(SkinnedMeshRenderer original, SkinnedMeshRenderer proxy)
|
||||
{
|
||||
originalToProxy[original] = proxy;
|
||||
}
|
||||
|
||||
internal static void UnregisterProxy(SkinnedMeshRenderer original)
|
||||
{
|
||||
originalToProxy.Remove(original);
|
||||
}
|
||||
|
||||
private static List<(SkinnedMeshRenderer, bool)> statesToRestore = new List<(SkinnedMeshRenderer, bool)>();
|
||||
|
||||
private static List<SkinnedMeshRenderer> toDeregister = new List<SkinnedMeshRenderer>();
|
||||
|
||||
|
||||
private static void OnPreCull(Camera camera)
|
||||
{
|
||||
ClearStates();
|
||||
toDeregister.Clear();
|
||||
|
||||
foreach (var kvp in originalToProxy)
|
||||
{
|
||||
var original = kvp.Key;
|
||||
var proxy = kvp.Value;
|
||||
|
||||
if (original == null || proxy == null)
|
||||
{
|
||||
toDeregister.Add(original);
|
||||
continue;
|
||||
}
|
||||
|
||||
proxy.enabled = original.enabled;
|
||||
if (original.enabled && original.gameObject.activeInHierarchy)
|
||||
{
|
||||
statesToRestore.Add((original, original.enabled));
|
||||
original.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var original in toDeregister)
|
||||
{
|
||||
originalToProxy.Remove(original);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPostRender(Camera camera)
|
||||
{
|
||||
ClearStates();
|
||||
}
|
||||
|
||||
|
||||
private static void ClearStates()
|
||||
{
|
||||
foreach (var (original, state) in statesToRestore)
|
||||
{
|
||||
original.enabled = state;
|
||||
}
|
||||
|
||||
statesToRestore.Clear();
|
||||
}
|
||||
}
|
||||
}
|
3
Runtime/ScaleAdjuster/CameraHooks.cs.meta
Normal file
3
Runtime/ScaleAdjuster/CameraHooks.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 117b3ad981cb487aa5029043f7482a94
|
||||
timeCreated: 1709447257
|
@ -101,15 +101,17 @@ namespace nadena.dev.modular_avatar.core
|
||||
if (child == null)
|
||||
{
|
||||
var childObj = new GameObject(ADJUSTER_OBJECT);
|
||||
Undo.RegisterCreatedObjectUndo(childObj, "");
|
||||
|
||||
var childSmr = childObj.AddComponent<SkinnedMeshRenderer>();
|
||||
EditorUtility.CopySerialized(smr, childSmr);
|
||||
|
||||
childObj.transform.SetParent(smr.transform, false);
|
||||
childObj.transform.localPosition = Vector3.zero;
|
||||
childObj.transform.localRotation = Quaternion.identity;
|
||||
childObj.transform.localScale = Vector3.one;
|
||||
|
||||
child = childObj.AddComponent<ScaleAdjusterRenderer>();
|
||||
child.transform.SetParent(smr.transform, false);
|
||||
child.transform.localPosition = Vector3.zero;
|
||||
child.transform.localRotation = Quaternion.identity;
|
||||
child.transform.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
child.BoneMappings[transform] = scaleProxy;
|
||||
|
@ -3,8 +3,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.JacksonDunstan.NativeCollections;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using Object = System.Object;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -15,16 +20,24 @@ namespace nadena.dev.modular_avatar.core
|
||||
[RequireComponent(typeof(SkinnedMeshRenderer))]
|
||||
internal class ScaleAdjusterRenderer : MonoBehaviour, IEditorOnly
|
||||
{
|
||||
private static event Action OnClearAllOverrides;
|
||||
internal static Dictionary<ScaleAdjusterRenderer, GameObject> originalParent =
|
||||
new Dictionary<ScaleAdjusterRenderer, GameObject>(new ObjectIdentityComparer<ScaleAdjusterRenderer>());
|
||||
|
||||
internal static Dictionary<GameObject, GameObject> proxyObjects = new Dictionary<GameObject, GameObject>(
|
||||
new ObjectIdentityComparer<GameObject>());
|
||||
|
||||
internal static Dictionary<GameObject, ScaleAdjusterRenderer> originalObjects =
|
||||
new Dictionary<GameObject, ScaleAdjusterRenderer>(
|
||||
new ObjectIdentityComparer<GameObject>()
|
||||
);
|
||||
|
||||
private static int RecreateHierarchyIndexCount = 0;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
[InitializeOnLoadMethod]
|
||||
static void Setup()
|
||||
{
|
||||
UnityEditor.EditorApplication.hierarchyChanged += InvalidateAll;
|
||||
UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += ClearAllOverrides;
|
||||
UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += (scene, path) => ClearAllOverrides();
|
||||
EditorApplication.hierarchyChanged += InvalidateAll;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -32,7 +45,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
RecreateHierarchyIndexCount++;
|
||||
}
|
||||
|
||||
|
||||
private SkinnedMeshRenderer myRenderer;
|
||||
private SkinnedMeshRenderer parentRenderer;
|
||||
|
||||
@ -40,28 +53,42 @@ namespace nadena.dev.modular_avatar.core
|
||||
private bool redoBoneMappings = true;
|
||||
private int lastRecreateHierarchyIndex = -1;
|
||||
|
||||
internal Dictionary<Transform, Transform> BoneMappings = new Dictionary<Transform, Transform>();
|
||||
internal Dictionary<Transform, Transform> BoneMappings = new Dictionary<Transform, Transform>(
|
||||
new ObjectIdentityComparer<Transform>()
|
||||
);
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
redoBoneMappings = true;
|
||||
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
gameObject.hideFlags = HideFlags.None;
|
||||
#else
|
||||
gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSaveInBuild;
|
||||
#endif
|
||||
// We hide this in Harmony, not here, so it is eligible for click-to-select.
|
||||
gameObject.hideFlags = HideFlags.DontSaveInBuild;
|
||||
|
||||
if (BoneMappings == null)
|
||||
{
|
||||
BoneMappings = new Dictionary<Transform, Transform>();
|
||||
}
|
||||
|
||||
Configure();
|
||||
};
|
||||
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
redoBoneMappings = true;
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange change)
|
||||
{
|
||||
if (change == PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
ClearHooks();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -74,12 +101,76 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearAllOverrides();
|
||||
ClearHooks();
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Configure()
|
||||
{
|
||||
if (originalParent.TryGetValue(this, out var prevParent) && transform.parent?.gameObject == prevParent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevParent != null)
|
||||
{
|
||||
ClearHooks();
|
||||
}
|
||||
|
||||
if (transform.parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AssemblyReloadEvents.beforeAssemblyReload += ClearHooks;
|
||||
#endif
|
||||
|
||||
var parent = transform.parent.gameObject;
|
||||
|
||||
proxyObjects[gameObject] = parent;
|
||||
originalObjects[parent] = this;
|
||||
originalParent[this] = parent;
|
||||
}
|
||||
|
||||
private void ClearHooks()
|
||||
{
|
||||
if (originalParent.TryGetValue(this, out var prevParent))
|
||||
{
|
||||
if (parentRenderer != null)
|
||||
{
|
||||
CameraHooks.UnregisterProxy(parentRenderer);
|
||||
}
|
||||
|
||||
if ((Object)prevParent != null)
|
||||
{
|
||||
originalObjects.Remove(prevParent);
|
||||
}
|
||||
|
||||
originalParent.Remove(this);
|
||||
if (gameObject != null)
|
||||
{
|
||||
proxyObjects.Remove(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanDeadObjects();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
if (transform.parent == null)
|
||||
{
|
||||
DestroyImmediate(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (myRenderer == null)
|
||||
{
|
||||
myRenderer = GetComponent<SkinnedMeshRenderer>();
|
||||
@ -89,20 +180,24 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
parentRenderer = transform.parent.GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
CameraHooks.RegisterProxy(parentRenderer, myRenderer);
|
||||
|
||||
Configure();
|
||||
|
||||
myRenderer.sharedMaterials = parentRenderer.sharedMaterials;
|
||||
myRenderer.sharedMesh = parentRenderer.sharedMesh;
|
||||
myRenderer.localBounds = parentRenderer.localBounds;
|
||||
if (redoBoneMappings || lastRecreateHierarchyIndex != RecreateHierarchyIndexCount)
|
||||
{
|
||||
var deadBones = BoneMappings.Keys.Where(k => BoneMappings[k] == null)
|
||||
.ToList();
|
||||
deadBones.ForEach(k => { BoneMappings.Remove(k); });
|
||||
CleanDeadObjects(BoneMappings);
|
||||
|
||||
if (BoneMappings.Count == 0)
|
||||
{
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
DestroyImmediate(gameObject);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
myRenderer.rootBone = MapBone(parentRenderer.rootBone);
|
||||
@ -129,58 +224,37 @@ namespace nadena.dev.modular_avatar.core
|
||||
myRenderer.SetBlendShapeWeight(i, parentRenderer.GetBlendShapeWeight(i));
|
||||
}
|
||||
}
|
||||
|
||||
ClearAllOverrides();
|
||||
|
||||
myRenderer.enabled = parentRenderer.enabled;
|
||||
}
|
||||
|
||||
public void OnWillRenderObject()
|
||||
{
|
||||
if (myRenderer == null || parentRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearAllOverrides();
|
||||
|
||||
if (!parentRenderer.enabled || !parentRenderer.gameObject.activeInHierarchy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parentRenderer.enabled = false;
|
||||
wasActive = true;
|
||||
var objName = parentRenderer.gameObject.name;
|
||||
OnClearAllOverrides += ClearLocalOverride;
|
||||
// Sometimes - e.g. around domain reloads or undo operations - the parent renderer's enabled field might get
|
||||
// re-disabled; re-enabler it in delayCall in this case.
|
||||
UnityEditor.EditorApplication.delayCall += ClearLocalOverride;
|
||||
}
|
||||
#endif
|
||||
|
||||
private void ClearLocalOverride()
|
||||
{
|
||||
if (parentRenderer != null)
|
||||
{
|
||||
parentRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPostRender()
|
||||
{
|
||||
ClearAllOverrides();
|
||||
}
|
||||
|
||||
public void ClearBoneCache()
|
||||
{
|
||||
redoBoneMappings = true;
|
||||
}
|
||||
|
||||
internal static void ClearAllOverrides()
|
||||
private static void CleanDeadObjects()
|
||||
{
|
||||
OnClearAllOverrides?.Invoke();
|
||||
OnClearAllOverrides = null;
|
||||
CleanDeadObjects(originalParent);
|
||||
CleanDeadObjects(originalObjects);
|
||||
CleanDeadObjects(proxyObjects);
|
||||
}
|
||||
|
||||
private static int lastCleanedFrame = 0;
|
||||
private static void CleanDeadObjects<K, V>(IDictionary<K, V> dict)
|
||||
where K: UnityEngine.Object
|
||||
where V: UnityEngine.Object
|
||||
{
|
||||
// Avoid any O(n^2) behavior if we have lots of cleanup calls happening at the same instant
|
||||
if (Time.frameCount == lastCleanedFrame) return;
|
||||
lastCleanedFrame = Time.frameCount;
|
||||
|
||||
var dead = dict.Where(kvp => kvp.Key == null || kvp.Value == null).ToList();
|
||||
|
||||
foreach (var kvp in dead)
|
||||
{
|
||||
dict.Remove(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
Runtime/Util/ObjectIdentityComparer.cs
Normal file
19
Runtime/Util/ObjectIdentityComparer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace nadena.dev.modular_avatar.JacksonDunstan.NativeCollections
|
||||
{
|
||||
internal class ObjectIdentityComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return (object)x == (object)y;
|
||||
}
|
||||
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
if (obj == null) return 0;
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
3
Runtime/Util/ObjectIdentityComparer.cs.meta
Normal file
3
Runtime/Util/ObjectIdentityComparer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e674cbd75db24fb2b238674cd7010edb
|
||||
timeCreated: 1709448428
|
@ -26,18 +26,19 @@ Transform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3825275463613500755}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.023681391, y: 1.0559628, z: -0.6872994}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 3646968714803193661}
|
||||
- {fileID: 3646968713996568948}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!95 &3825275463613500750
|
||||
Animator:
|
||||
serializedVersion: 3
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
@ -50,10 +51,12 @@ Animator:
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorControllerStateOnDisable: 0
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
--- !u!114 &3825275463613500753
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -322,40 +325,12 @@ MonoBehaviour:
|
||||
contentType: 0
|
||||
assetBundleUnityVersion:
|
||||
fallbackStatus: 0
|
||||
--- !u!114 &3825275463971368602
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4167925416990528462}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6fd7cab7d93b403280f2f9da978d8a4f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Bindings:
|
||||
- ReferenceMesh:
|
||||
referencePath: BaseMesh
|
||||
Blendshape: shape_0
|
||||
LocalBlendshape: shape_0_local
|
||||
- ReferenceMesh:
|
||||
referencePath: BaseMesh
|
||||
Blendshape: shape_1
|
||||
LocalBlendshape: shape_1
|
||||
- ReferenceMesh:
|
||||
referencePath: MissingMesh
|
||||
Blendshape: missing_mesh_shape
|
||||
LocalBlendshape: missing_mesh_shape
|
||||
- ReferenceMesh:
|
||||
referencePath:
|
||||
Blendshape: missing_mesh_shape_2
|
||||
LocalBlendshape: missing_mesh_shape_2
|
||||
--- !u!1001 &3825275463173128406
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 3825275463613500751}
|
||||
m_Modifications:
|
||||
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
@ -454,6 +429,9 @@ PrefabInstance:
|
||||
value: BaseMesh
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 14ac2ad30c5d3444ca37f76cea5a7047, type: 3}
|
||||
--- !u!4 &3646968714803193661 stripped
|
||||
Transform:
|
||||
@ -466,6 +444,7 @@ PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 3825275463613500751}
|
||||
m_Modifications:
|
||||
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
@ -569,16 +548,52 @@ PrefabInstance:
|
||||
value: SyncedMesh
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 3825275463971368602}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 14ac2ad30c5d3444ca37f76cea5a7047, type: 3}
|
||||
--- !u!1 &4167925416990528462 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 3825275463971368607}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!4 &3646968713996568948 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 3825275463971368607}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &4167925416990528462 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 3825275463971368607}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &3825275463971368602
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4167925416990528462}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6fd7cab7d93b403280f2f9da978d8a4f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Bindings:
|
||||
- ReferenceMesh:
|
||||
referencePath: BaseMesh
|
||||
Blendshape: shape_0
|
||||
LocalBlendshape: shape_0_local
|
||||
- ReferenceMesh:
|
||||
referencePath: BaseMesh
|
||||
Blendshape: shape_1
|
||||
LocalBlendshape: shape_1
|
||||
- ReferenceMesh:
|
||||
referencePath: MissingMesh
|
||||
Blendshape: missing_mesh_shape
|
||||
LocalBlendshape: missing_mesh_shape
|
||||
- ReferenceMesh:
|
||||
referencePath:
|
||||
Blendshape: missing_mesh_shape_2
|
||||
LocalBlendshape: missing_mesh_shape_2
|
||||
|
Loading…
Reference in New Issue
Block a user