mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
fix/reimplement: scale adjuster results in infinite loops sometimes (#677)
Closes: #676
This commit is contained in:
parent
e01b707916
commit
197d847514
3
Editor/HarmonyPatches.meta
Normal file
3
Editor/HarmonyPatches.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf056e4454ad43ababbfff1dd06ec1d0
|
||||
timeCreated: 1708235917
|
@ -0,0 +1,59 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to prevent various internal objects from showing up in the Prefab Overrides window...
|
||||
/// </summary>
|
||||
internal class HideScaleAdjusterFromPrefabOverrideView
|
||||
{
|
||||
internal static Type t_PrefabOverrides;
|
||||
internal static PropertyInfo p_AddedGameObjects, p_ObjectOverrides;
|
||||
|
||||
internal static void Patch(Harmony harmony)
|
||||
{
|
||||
var t_PrefabOverridesTreeView = AccessTools.TypeByName("UnityEditor.PrefabOverridesTreeView");
|
||||
var m_GetPrefabOverrides = AccessTools.Method(t_PrefabOverridesTreeView, "GetPrefabOverrides");
|
||||
|
||||
var m_postfix = AccessTools.Method(typeof(HideScaleAdjusterFromPrefabOverrideView), "Postfix");
|
||||
|
||||
t_PrefabOverrides = AccessTools.TypeByName("UnityEditor.PrefabOverridesTreeView+PrefabOverrides");
|
||||
p_AddedGameObjects = AccessTools.Property(t_PrefabOverrides, "addedGameObjects");
|
||||
p_ObjectOverrides = AccessTools.Property(t_PrefabOverrides, "objectOverrides");
|
||||
|
||||
harmony.Patch(original: m_GetPrefabOverrides, postfix: new HarmonyMethod(m_postfix));
|
||||
}
|
||||
|
||||
[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));
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0893522c012a46358e5ecf1df6628b2e
|
||||
timeCreated: 1708237029
|
21
Editor/HarmonyPatches/PatchLoader.cs
Normal file
21
Editor/HarmonyPatches/PatchLoader.cs
Normal file
@ -0,0 +1,21 @@
|
||||
#region
|
||||
|
||||
using HarmonyLib;
|
||||
using UnityEditor;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
internal class PatchLoader
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void ApplyPatches()
|
||||
{
|
||||
var harmony = new Harmony("nadena.dev.modular_avatar");
|
||||
|
||||
SnoopHeaderRendering.Patch(harmony);
|
||||
HideScaleAdjusterFromPrefabOverrideView.Patch(harmony);
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/HarmonyPatches/PatchLoader.cs.meta
Normal file
3
Editor/HarmonyPatches/PatchLoader.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e92aedf0fa324b8fb1b425a7fc2b1771
|
||||
timeCreated: 1708235934
|
40
Editor/HarmonyPatches/SnoopHeaderRendering.cs
Normal file
40
Editor/HarmonyPatches/SnoopHeaderRendering.cs
Normal file
@ -0,0 +1,40 @@
|
||||
#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 Patch(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));
|
||||
|
||||
var t_GUIUtility = typeof(GUIUtility);
|
||||
var m_ProcessEvent = AccessTools.Method(t_GUIUtility, "ProcessEvent");
|
||||
|
||||
harmony.Patch(original: m_ProcessEvent, prefix: new HarmonyMethod(m_prefix));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Prefix()
|
||||
{
|
||||
ScaleAdjusterRenderer.ClearAllOverrides();
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/HarmonyPatches/SnoopHeaderRendering.cs.meta
Normal file
3
Editor/HarmonyPatches/SnoopHeaderRendering.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cafb5b7e681644cbbeafbeb12d833f6e
|
||||
timeCreated: 1708235926
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "nadena.dev.modular-avatar.harmony-patches",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"nadena.dev.modular-avatar.core",
|
||||
"nadena.dev.modular-avatar.core.editor",
|
||||
"VRC.SDKBase.Editor"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"VRCSDK_HAS_HARMONY"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.vrchat.base",
|
||||
"expression": "(3.4.999,)",
|
||||
"define": "VRCSDK_HAS_HARMONY"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f9cfa335019051479cc80ce30c95a68
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -36,6 +36,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
|
||||
{
|
||||
seq.Run(ClearEditorOnlyTags.Instance);
|
||||
seq.Run(MeshSettingsPluginPass.Instance);
|
||||
seq.Run(ScaleAdjusterPass.Instance);
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
seq.Run(RenameParametersPluginPass.Instance);
|
||||
seq.Run(MergeBlendTreePass.Instance);
|
||||
|
51
Editor/ScaleAdjusterPass.cs
Normal file
51
Editor/ScaleAdjusterPass.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ScaleAdjusterPass : Pass<ScaleAdjusterPass>
|
||||
{
|
||||
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>())
|
||||
{
|
||||
var proxyTransform = component.transform;
|
||||
var parentAdjuster = component.transform.parent?.GetComponent<ModularAvatarScaleAdjuster>();
|
||||
if (parentAdjuster != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(component);
|
||||
|
||||
proxyTransform.localScale = parentAdjuster.Scale;
|
||||
UnityEngine.Object.DestroyImmediate(parentAdjuster);
|
||||
|
||||
boneMappings.Add(proxyTransform.parent, proxyTransform);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sar in context.AvatarRootObject.GetComponentsInChildren<ScaleAdjusterRenderer>())
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(sar.gameObject);
|
||||
}
|
||||
|
||||
foreach (var smr in context.AvatarRootObject.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
var bones = smr.bones;
|
||||
for (int i = 0; i < bones.Length; i++)
|
||||
{
|
||||
if (boneMappings.TryGetValue(bones[i], out var newBone))
|
||||
{
|
||||
bones[i] = newBone;
|
||||
}
|
||||
}
|
||||
smr.bones = bones;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/ScaleAdjusterPass.cs.meta
Normal file
3
Editor/ScaleAdjusterPass.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62b78456069047e0949dd1d8c4b25edc
|
||||
timeCreated: 1708253304
|
@ -1,4 +1,9 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
#region
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#endregion
|
||||
|
||||
[assembly: InternalsVisibleTo("net.fushizen.xdress")]
|
||||
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
|
||||
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
|
||||
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")]
|
@ -1,268 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[Serializable]
|
||||
internal struct ScalePatch
|
||||
{
|
||||
public SkinnedMeshRenderer smr;
|
||||
public int boneIndex;
|
||||
|
||||
public ScalePatch(SkinnedMeshRenderer smr, int boneIndex)
|
||||
{
|
||||
this.smr = smr;
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public bool Equals(ScalePatch other)
|
||||
{
|
||||
return smr.Equals(other.smr) && boneIndex == other.boneIndex;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ScalePatch other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (smr.GetHashCode() * 397) ^ boneIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Modular Avatar/MA Scale Adjuster")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/scale-adjuster?lang=auto")]
|
||||
public sealed class ModularAvatarScaleAdjuster : AvatarTagComponent
|
||||
{
|
||||
[SerializeField] private Vector3 m_Scale = Vector3.one;
|
||||
|
||||
public Vector3 Scale
|
||||
{
|
||||
get => m_Scale;
|
||||
set
|
||||
{
|
||||
m_Scale = value;
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] internal Transform scaleProxy;
|
||||
|
||||
[SerializeField] private List<ScalePatch> patches = new List<ScalePatch>();
|
||||
|
||||
private bool initialized = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
PatchRenderers();
|
||||
|
||||
scaleProxy.localScale = m_Scale;
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
initialized = false;
|
||||
EditorApplication.delayCall += Update;
|
||||
}
|
||||
|
||||
private void PatchRenderers()
|
||||
{
|
||||
if (initialized || this == null) return;
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(this))
|
||||
{
|
||||
// Ensure we're using the same ScaleProxy as the corresponding prefab asset.
|
||||
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(this);
|
||||
if (this.scaleProxy == null || prefab.scaleProxy == null || prefab.scaleProxy !=
|
||||
PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy))
|
||||
{
|
||||
if (prefab.scaleProxy == null && scaleProxy != null)
|
||||
{
|
||||
// Push our ScaleProxy down into the prefab (this happens after applying the ScaleAdjuster
|
||||
// component to a prefab)
|
||||
var assetPath = AssetDatabase.GetAssetPath(prefab);
|
||||
PrefabUtility.ApplyAddedGameObject(scaleProxy.gameObject, assetPath,
|
||||
InteractionMode.AutomatedAction);
|
||||
prefab.scaleProxy = PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear any duplicate scaleProxy we have
|
||||
|
||||
if (scaleProxy != null) DestroyImmediate(scaleProxy.gameObject);
|
||||
}
|
||||
|
||||
var so = new SerializedObject(this);
|
||||
var sp = so.FindProperty(nameof(scaleProxy));
|
||||
PrefabUtility.RevertPropertyOverride(sp, InteractionMode.AutomatedAction);
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
// Find the corresponding child
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
if (PrefabUtility.GetCorrespondingObjectFromSource(t) == prefab.scaleProxy)
|
||||
{
|
||||
scaleProxy = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scaleProxy == null && !PrefabUtility.IsPartOfPrefabAsset(this))
|
||||
{
|
||||
scaleProxy = new GameObject(gameObject.name + " (Scale Proxy)").transform;
|
||||
scaleProxy.SetParent(transform, false);
|
||||
scaleProxy.localPosition = Vector3.zero;
|
||||
scaleProxy.localRotation = Quaternion.identity;
|
||||
scaleProxy.localScale = m_Scale;
|
||||
scaleProxy.gameObject.AddComponent<ScaleProxy>();
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
}
|
||||
|
||||
if (scaleProxy != null)
|
||||
{
|
||||
scaleProxy.hideFlags = HideFlags.HideInHierarchy;
|
||||
|
||||
RewriteBoneReferences(transform, scaleProxy);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void RewriteBoneReferences(Transform oldBone, Transform newBone, Transform selfTransform = null)
|
||||
{
|
||||
if (selfTransform == null) selfTransform = transform;
|
||||
|
||||
var prefabNewBone = PrefabUtility.GetCorrespondingObjectFromSource(newBone);
|
||||
|
||||
var oldPatches = new HashSet<ScalePatch>(this.patches);
|
||||
var newPatches = new HashSet<ScalePatch>();
|
||||
var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(selfTransform);
|
||||
|
||||
if (avatarRoot != null)
|
||||
{
|
||||
foreach (var smr in avatarRoot.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
{
|
||||
var serializedObject = new SerializedObject(smr);
|
||||
var bonesArray = serializedObject.FindProperty("m_Bones");
|
||||
int boneCount = bonesArray.arraySize;
|
||||
|
||||
var parentSmr = PrefabUtility.GetCorrespondingObjectFromSource(smr);
|
||||
var parentBones = parentSmr != null ? parentSmr.bones : null;
|
||||
var propMods = PrefabUtility.GetPropertyModifications(smr);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
{
|
||||
var boneProp = bonesArray.GetArrayElementAtIndex(i);
|
||||
var bone = boneProp.objectReferenceValue as Transform;
|
||||
if (bone == oldBone || bone == newBone ||
|
||||
(bone == null && oldPatches.Contains(new ScalePatch(smr, i))))
|
||||
{
|
||||
if (parentBones != null && parentBones[i] == prefabNewBone)
|
||||
{
|
||||
// Remove any prefab overrides for this bone entry
|
||||
changed = boneProp.objectReferenceValue != newBone;
|
||||
boneProp.objectReferenceValue = newBone;
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
PrefabUtility.RevertPropertyOverride(boneProp, InteractionMode.AutomatedAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
boneProp.objectReferenceValue = newBone;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
newPatches.Add(new ScalePatch(smr, i));
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
ConfigurePrefab();
|
||||
}
|
||||
}
|
||||
|
||||
if (this != null && newPatches != oldPatches)
|
||||
{
|
||||
this.patches = newPatches.ToList();
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigurePrefab()
|
||||
{
|
||||
if (this == null || !PrefabUtility.IsPartOfPrefabInstance(this)) return;
|
||||
var source = PrefabUtility.GetCorrespondingObjectFromSource(this);
|
||||
var path = AssetDatabase.GetAssetPath(source);
|
||||
var root = PrefabUtility.LoadPrefabContents(path);
|
||||
|
||||
foreach (var obj in root.GetComponentsInChildren<ModularAvatarScaleAdjuster>())
|
||||
{
|
||||
obj.PatchRenderers();
|
||||
}
|
||||
|
||||
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
UnpatchRenderers();
|
||||
}
|
||||
|
||||
private void UnpatchRenderers()
|
||||
{
|
||||
var scaleProxy2 = this.scaleProxy;
|
||||
var transform2 = this.transform;
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (scaleProxy2 == null) return;
|
||||
|
||||
if (transform2 != null)
|
||||
{
|
||||
RewriteBoneReferences(scaleProxy2, transform2, transform2);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DestroyImmediate(scaleProxy2.gameObject);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
// not supported in Unity 2019...
|
||||
}
|
||||
};
|
||||
}
|
||||
#else
|
||||
private void Update()
|
||||
{
|
||||
// placeholder to make builds work
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
3
Runtime/ScaleAdjuster.meta
Normal file
3
Runtime/ScaleAdjuster.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96f10f3195df42bba58b9efb61948b9c
|
||||
timeCreated: 1708232523
|
128
Runtime/ScaleAdjuster/ModularAvatarScaleAdjuster.cs
Normal file
128
Runtime/ScaleAdjuster/ModularAvatarScaleAdjuster.cs
Normal file
@ -0,0 +1,128 @@
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Modular Avatar/MA Scale Adjuster")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/scale-adjuster?lang=auto")]
|
||||
public sealed class ModularAvatarScaleAdjuster : AvatarTagComponent
|
||||
{
|
||||
private const string ADJUSTER_OBJECT = "MA Scale Adjuster Proxy Renderer";
|
||||
[SerializeField] private Vector3 m_Scale = Vector3.one;
|
||||
|
||||
public Vector3 Scale
|
||||
{
|
||||
get => m_Scale;
|
||||
set
|
||||
{
|
||||
m_Scale = value;
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] [FormerlySerializedAs("scaleProxy")]
|
||||
internal Transform legacyScaleProxy;
|
||||
|
||||
internal Transform scaleProxy;
|
||||
|
||||
private bool initialized = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (scaleProxy == null || initialized == false)
|
||||
{
|
||||
InitializeProxy();
|
||||
}
|
||||
|
||||
if (legacyScaleProxy != null && !PrefabUtility.IsPartOfPrefabAsset(legacyScaleProxy))
|
||||
{
|
||||
DestroyImmediate(legacyScaleProxy.gameObject);
|
||||
legacyScaleProxy = null;
|
||||
}
|
||||
|
||||
scaleProxy.localScale = m_Scale;
|
||||
}
|
||||
|
||||
private void InitializeProxy()
|
||||
{
|
||||
if (scaleProxy == null)
|
||||
{
|
||||
scaleProxy = new GameObject(gameObject.name + " (Scale Proxy)").transform;
|
||||
scaleProxy.SetParent(transform, false);
|
||||
scaleProxy.localPosition = Vector3.zero;
|
||||
scaleProxy.localRotation = Quaternion.identity;
|
||||
scaleProxy.localScale = m_Scale;
|
||||
scaleProxy.gameObject.AddComponent<ScaleProxy>();
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
}
|
||||
|
||||
ConfigureRenderers();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (scaleProxy != null)
|
||||
{
|
||||
DestroyImmediate(scaleProxy.gameObject);
|
||||
}
|
||||
|
||||
ScaleAdjusterRenderer.InvalidateAll();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
|
||||
private void ConfigureRenderers()
|
||||
{
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(transform);
|
||||
if (avatar == null) return;
|
||||
foreach (var smr in avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
{
|
||||
if (smr.GetComponent<ScaleAdjusterRenderer>() != null) continue;
|
||||
|
||||
var child = smr.transform.Find(ADJUSTER_OBJECT)?.GetComponent<ScaleAdjusterRenderer>();
|
||||
if (child == null)
|
||||
{
|
||||
var childObj = new GameObject(ADJUSTER_OBJECT);
|
||||
|
||||
var childSmr = childObj.AddComponent<SkinnedMeshRenderer>();
|
||||
EditorUtility.CopySerialized(smr, childSmr);
|
||||
|
||||
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;
|
||||
child.ClearBoneCache();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
// placeholder to make builds work
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
178
Runtime/ScaleAdjuster/ScaleAdjusterRenderer.cs
Normal file
178
Runtime/ScaleAdjuster/ScaleAdjusterRenderer.cs
Normal file
@ -0,0 +1,178 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
//[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(SkinnedMeshRenderer))]
|
||||
internal class ScaleAdjusterRenderer : MonoBehaviour, IEditorOnly
|
||||
{
|
||||
private static event Action OnPreInspector;
|
||||
private static int RecreateHierarchyIndexCount = 0;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
static void Setup()
|
||||
{
|
||||
UnityEditor.EditorApplication.hierarchyChanged += InvalidateAll;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void InvalidateAll()
|
||||
{
|
||||
RecreateHierarchyIndexCount++;
|
||||
}
|
||||
|
||||
private SkinnedMeshRenderer myRenderer;
|
||||
private SkinnedMeshRenderer parentRenderer;
|
||||
|
||||
private bool wasActive = false;
|
||||
private bool redoBoneMappings = true;
|
||||
private int lastRecreateHierarchyIndex = -1;
|
||||
|
||||
internal Dictionary<Transform, Transform> BoneMappings = new Dictionary<Transform, Transform>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
redoBoneMappings = true;
|
||||
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
gameObject.hideFlags = HideFlags.None;
|
||||
#else
|
||||
gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSaveInBuild;
|
||||
#endif
|
||||
if (BoneMappings == null)
|
||||
{
|
||||
BoneMappings = new Dictionary<Transform, Transform>();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
private Transform MapBone(Transform bone)
|
||||
{
|
||||
if (bone == null) return null;
|
||||
if (BoneMappings.TryGetValue(bone, out var newBone) && newBone != null) return newBone;
|
||||
return bone;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearOverrides();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (myRenderer == null)
|
||||
{
|
||||
myRenderer = GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
if (parentRenderer == null)
|
||||
{
|
||||
parentRenderer = transform.parent.GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
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); });
|
||||
|
||||
if (BoneMappings.Count == 0)
|
||||
{
|
||||
DestroyImmediate(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
myRenderer.rootBone = MapBone(parentRenderer.rootBone);
|
||||
myRenderer.bones = parentRenderer.bones.Select(MapBone).ToArray();
|
||||
redoBoneMappings = false;
|
||||
lastRecreateHierarchyIndex = RecreateHierarchyIndexCount;
|
||||
}
|
||||
|
||||
myRenderer.quality = parentRenderer.quality;
|
||||
myRenderer.shadowCastingMode = parentRenderer.shadowCastingMode;
|
||||
myRenderer.receiveShadows = parentRenderer.receiveShadows;
|
||||
myRenderer.lightProbeUsage = parentRenderer.lightProbeUsage;
|
||||
myRenderer.reflectionProbeUsage = parentRenderer.reflectionProbeUsage;
|
||||
myRenderer.probeAnchor = parentRenderer.probeAnchor;
|
||||
myRenderer.motionVectorGenerationMode = parentRenderer.motionVectorGenerationMode;
|
||||
myRenderer.allowOcclusionWhenDynamic = parentRenderer.allowOcclusionWhenDynamic;
|
||||
|
||||
var blendShapeCount = myRenderer.sharedMesh.blendShapeCount;
|
||||
|
||||
for (int i = 0; i < blendShapeCount; i++)
|
||||
{
|
||||
myRenderer.SetBlendShapeWeight(i, parentRenderer.GetBlendShapeWeight(i));
|
||||
}
|
||||
|
||||
ClearOverrides();
|
||||
|
||||
myRenderer.enabled = parentRenderer.enabled;
|
||||
}
|
||||
|
||||
public void OnWillRenderObject()
|
||||
{
|
||||
if (myRenderer == null || parentRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearOverrides();
|
||||
|
||||
if (!parentRenderer.enabled || !parentRenderer.gameObject.activeInHierarchy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parentRenderer.enabled = false;
|
||||
wasActive = true;
|
||||
OnPreInspector += ClearOverrides;
|
||||
}
|
||||
|
||||
private void OnPostRender()
|
||||
{
|
||||
ClearOverrides();
|
||||
}
|
||||
|
||||
private void ClearOverrides()
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
if (wasActive && parentRenderer != null)
|
||||
{
|
||||
parentRenderer.enabled = true;
|
||||
wasActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearBoneCache()
|
||||
{
|
||||
redoBoneMappings = true;
|
||||
}
|
||||
|
||||
internal static void ClearAllOverrides()
|
||||
{
|
||||
OnPreInspector?.Invoke();
|
||||
OnPreInspector = null;
|
||||
}
|
||||
}
|
||||
}
|
3
Runtime/ScaleAdjuster/ScaleAdjusterRenderer.cs.meta
Normal file
3
Runtime/ScaleAdjuster/ScaleAdjusterRenderer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8bc16baa6c345eea5edf47232ee4069
|
||||
timeCreated: 1708232586
|
@ -1,7 +1,12 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
@ -11,9 +16,16 @@ namespace nadena.dev.modular_avatar.core
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
base.OnDestroy();
|
||||
EditorApplication.delayCall += DeferredValidate;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
ScaleAdjusterRenderer.InvalidateAll();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private void DeferredValidate()
|
||||
{
|
||||
if (this == null) return;
|
||||
@ -23,8 +35,16 @@ namespace nadena.dev.modular_avatar.core
|
||||
gameObject.AddComponent<ModularAvatarPBBlocker>();
|
||||
}
|
||||
|
||||
var avatar = ndmf.runtime.RuntimeUtil.FindAvatarInParents(transform);
|
||||
ClearOverrides(avatar);
|
||||
|
||||
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
gameObject.hideFlags = HideFlags.None;
|
||||
#endif
|
||||
hideFlags = HideFlags.None;
|
||||
|
||||
var parentObject = transform.parent;
|
||||
var parentScaleAdjuster =
|
||||
parentObject != null ? parentObject.GetComponent<ModularAvatarScaleAdjuster>() : null;
|
||||
@ -60,8 +80,19 @@ namespace nadena.dev.modular_avatar.core
|
||||
while (root.parent != null) root = root.parent;
|
||||
}
|
||||
|
||||
ClearOverrides(root);
|
||||
|
||||
DestroyImmediate(gameObject);
|
||||
}
|
||||
|
||||
private void ClearOverrides(Transform root)
|
||||
{
|
||||
// This clears bone overrides that date back to the 1.9.0-rc.2 implementation, to ease rc.2 -> rc.3
|
||||
// migrations. It'll be removed in 1.10.
|
||||
foreach (var smr in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
{
|
||||
if (smr.GetComponent<ScaleAdjusterRenderer>()) continue;
|
||||
|
||||
var bones = smr.bones;
|
||||
bool changed = false;
|
||||
|
||||
@ -79,8 +110,6 @@ namespace nadena.dev.modular_avatar.core
|
||||
smr.bones = bones;
|
||||
}
|
||||
}
|
||||
|
||||
DestroyImmediate(gameObject);
|
||||
}
|
||||
#endif
|
||||
}
|
3
Runtime/TestBehavior.cs.meta
Normal file
3
Runtime/TestBehavior.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2c964d1229a4ce698db85bec9043608
|
||||
timeCreated: 1708232304
|
@ -1,6 +1,11 @@
|
||||
#region
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#endregion
|
||||
|
||||
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.core.editor")]
|
||||
[assembly: InternalsVisibleTo("net.fushizen.xdress")]
|
||||
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
|
||||
[assembly: InternalsVisibleTo("Tests")]
|
||||
[assembly: InternalsVisibleTo("Tests")]
|
||||
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")]
|
@ -40,6 +40,7 @@ namespace modular_avatar_tests
|
||||
if (type == typeof(AvatarActivator)) return;
|
||||
if (type == typeof(TestComponent)) return;
|
||||
if (type == typeof(ScaleProxy)) return;
|
||||
if (type == typeof(ScaleAdjusterRenderer)) return;
|
||||
|
||||
// get icon
|
||||
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
||||
@ -65,6 +66,7 @@ namespace modular_avatar_tests
|
||||
if (type == typeof(AvatarActivator)) return;
|
||||
if (type == typeof(TestComponent)) return;
|
||||
if (type == typeof(ScaleProxy)) return;
|
||||
if (type == typeof(ScaleAdjusterRenderer)) return;
|
||||
|
||||
// get icon
|
||||
var helpUrl = type.GetCustomAttribute<HelpURLAttribute>();
|
||||
|
Loading…
Reference in New Issue
Block a user