mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 02:35: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(ClearEditorOnlyTags.Instance);
|
||||||
seq.Run(MeshSettingsPluginPass.Instance);
|
seq.Run(MeshSettingsPluginPass.Instance);
|
||||||
|
seq.Run(ScaleAdjusterPass.Instance);
|
||||||
#if MA_VRCSDK3_AVATARS
|
#if MA_VRCSDK3_AVATARS
|
||||||
seq.Run(RenameParametersPluginPass.Instance);
|
seq.Run(RenameParametersPluginPass.Instance);
|
||||||
seq.Run(MergeBlendTreePass.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")]
|
||||||
[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;
|
using UnityEditor;
|
||||||
#endif
|
#endif
|
||||||
using UnityEngine;
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
@ -11,9 +16,16 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
void OnValidate()
|
void OnValidate()
|
||||||
{
|
{
|
||||||
|
base.OnDestroy();
|
||||||
EditorApplication.delayCall += DeferredValidate;
|
EditorApplication.delayCall += DeferredValidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
ScaleAdjusterRenderer.InvalidateAll();
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
private void DeferredValidate()
|
private void DeferredValidate()
|
||||||
{
|
{
|
||||||
if (this == null) return;
|
if (this == null) return;
|
||||||
@ -23,8 +35,16 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
gameObject.AddComponent<ModularAvatarPBBlocker>();
|
gameObject.AddComponent<ModularAvatarPBBlocker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var avatar = ndmf.runtime.RuntimeUtil.FindAvatarInParents(transform);
|
||||||
|
ClearOverrides(avatar);
|
||||||
|
|
||||||
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||||
|
|
||||||
|
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||||
|
gameObject.hideFlags = HideFlags.None;
|
||||||
|
#endif
|
||||||
|
hideFlags = HideFlags.None;
|
||||||
|
|
||||||
var parentObject = transform.parent;
|
var parentObject = transform.parent;
|
||||||
var parentScaleAdjuster =
|
var parentScaleAdjuster =
|
||||||
parentObject != null ? parentObject.GetComponent<ModularAvatarScaleAdjuster>() : null;
|
parentObject != null ? parentObject.GetComponent<ModularAvatarScaleAdjuster>() : null;
|
||||||
@ -60,8 +80,19 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
while (root.parent != null) root = root.parent;
|
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))
|
foreach (var smr in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
{
|
{
|
||||||
|
if (smr.GetComponent<ScaleAdjusterRenderer>()) continue;
|
||||||
|
|
||||||
var bones = smr.bones;
|
var bones = smr.bones;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
@ -79,8 +110,6 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
smr.bones = bones;
|
smr.bones = bones;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DestroyImmediate(gameObject);
|
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.core.editor")]
|
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.core.editor")]
|
||||||
[assembly: InternalsVisibleTo("net.fushizen.xdress")]
|
[assembly: InternalsVisibleTo("net.fushizen.xdress")]
|
||||||
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
|
[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(AvatarActivator)) return;
|
||||||
if (type == typeof(TestComponent)) return;
|
if (type == typeof(TestComponent)) return;
|
||||||
if (type == typeof(ScaleProxy)) return;
|
if (type == typeof(ScaleProxy)) return;
|
||||||
|
if (type == typeof(ScaleAdjusterRenderer)) return;
|
||||||
|
|
||||||
// get icon
|
// get icon
|
||||||
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
||||||
@ -65,6 +66,7 @@ namespace modular_avatar_tests
|
|||||||
if (type == typeof(AvatarActivator)) return;
|
if (type == typeof(AvatarActivator)) return;
|
||||||
if (type == typeof(TestComponent)) return;
|
if (type == typeof(TestComponent)) return;
|
||||||
if (type == typeof(ScaleProxy)) return;
|
if (type == typeof(ScaleProxy)) return;
|
||||||
|
if (type == typeof(ScaleAdjusterRenderer)) return;
|
||||||
|
|
||||||
// get icon
|
// get icon
|
||||||
var helpUrl = type.GetCustomAttribute<HelpURLAttribute>();
|
var helpUrl = type.GetCustomAttribute<HelpURLAttribute>();
|
||||||
|
Loading…
Reference in New Issue
Block a user