From b154b01e45b3084b88605bb8082fd9f04bb88d3f Mon Sep 17 00:00:00 2001 From: bd_ Date: Mon, 29 Aug 2022 15:13:26 -0700 Subject: [PATCH] Support editing original bone animations in modular components This adds support for animation mapping for MergeArmature components, as well as supporting locking the position of MergeArmature-controlled transforms to their original counterparts, to ease animation editing. With this change it might be best to eliminate the bone proxy component, as it's a bit redundant now. --- .../Editor/AnimatorMerger.cs | 2 +- .../Editor/BoneProxyHook.cs | 5 +- .../Editor/MergeArmatureHook.cs | 14 ++++- .../Editor/PathMappings.cs | 21 +++++-- .../Runtime/AnimationEditHandler.cs | 4 ++ .../Runtime/AnimationEditHandler.cs.meta | 3 + .../Runtime/MAInternalOffsetMarker.cs | 58 +++++++++++++++++++ .../Runtime/MAInternalOffsetMarker.cs.meta | 3 + .../Runtime/ModularAvatarBoneProxy.cs | 33 +++-------- .../Runtime/ModularAvatarMergeArmature.cs | 56 +++++++++++++++++- .../Runtime/RuntimeUtil.cs | 56 ++++++++++++++++-- ProjectSettings/ProjectSettings.asset | 2 +- 12 files changed, 216 insertions(+), 41 deletions(-) create mode 100644 Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs create mode 100644 Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs.meta create mode 100644 Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs create mode 100644 Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs.meta diff --git a/Packages/net.fushizen.modular-avatar/Editor/AnimatorMerger.cs b/Packages/net.fushizen.modular-avatar/Editor/AnimatorMerger.cs index 984454dc..b0a9ace0 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/AnimatorMerger.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/AnimatorMerger.cs @@ -101,7 +101,7 @@ namespace net.fushizen.modular_avatar.core.editor } else { - return PathMappings.MapPath(basePath + binding.path); + return PathMappings.MapPath(basePath + binding.path, binding.type == typeof(Transform)); } } diff --git a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs index 48169306..c2954878 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs @@ -21,7 +21,10 @@ namespace net.fushizen.modular_avatar.core.editor transform.SetParent(proxy.target, false); transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; - PathMappings.Remap(oldPath, RuntimeUtil.AvatarRootPath(proxy.gameObject)); + PathMappings.Remap(oldPath, new PathMappings.MappingEntry() { + path = RuntimeUtil.AvatarRootPath(proxy.gameObject), + transformPath = RuntimeUtil.AvatarRootPath(proxy.gameObject) + }); } Object.DestroyImmediate(proxy); } diff --git a/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs b/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs index 847525d6..b5d75911 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs @@ -114,6 +114,7 @@ namespace net.fushizen.modular_avatar.core.editor { case Transform _: break; case ModularAvatarMergeArmature _: break; + case MAInternalOffsetMarker _: break; case VRCPhysBone _: case VRCPhysBoneCollider _: hasComponents = true; break; default: @@ -146,7 +147,16 @@ namespace net.fushizen.modular_avatar.core.editor mergedSrcBone.transform.localRotation = src.transform.localRotation; mergedSrcBone.transform.localScale = src.transform.localScale; - if (zipMerge) BoneDatabase.AddMergedBone(mergedSrcBone.transform); + if (zipMerge) + { + BoneDatabase.AddMergedBone(mergedSrcBone.transform); + var srcPath = RuntimeUtil.AvatarRootPath(src); + PathMappings.Remap(srcPath, new PathMappings.MappingEntry() + { + transformPath = zipMerge ? RuntimeUtil.AvatarRootPath(newParent) : srcPath, + path = srcPath + }); + } mergedSrcBone.transform.SetParent(newParent.transform, true); BoneRemappings[src.transform] = mergedSrcBone.transform; @@ -192,6 +202,8 @@ namespace net.fushizen.modular_avatar.core.editor shouldZip = false; } } + + var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip); retain = retain || retainChild; diff --git a/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs b/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs index 23e6874c..041f001e 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs @@ -7,26 +7,37 @@ namespace net.fushizen.modular_avatar.core.editor { public static class PathMappings { - private static SortedDictionary Mappings = new SortedDictionary(); + private static SortedDictionary Mappings = new SortedDictionary(); private static List CachedMappingKeys = null; + public struct MappingEntry + { + public string path; + public string transformPath; + + public string Get(bool isTransformMapping) + { + return isTransformMapping ? transformPath : path; + } + } + internal static void Clear() { Mappings.Clear(); CachedMappingKeys = null; } - internal static void Remap(string from, string to) + internal static void Remap(string from, MappingEntry to) { Mappings[from] = to; CachedMappingKeys = null; } - internal static string MapPath(string path) + internal static string MapPath(string path, bool isTransformMapping = false) { if (CachedMappingKeys == null) CachedMappingKeys = new List(Mappings.Keys); var bsResult = CachedMappingKeys.BinarySearch(path); - if (bsResult >= 0) return Mappings[path]; + if (bsResult >= 0) return Mappings[path].Get(isTransformMapping); int index = ~bsResult; if (index == 0) return path; @@ -34,7 +45,7 @@ namespace net.fushizen.modular_avatar.core.editor var priorKey = CachedMappingKeys[index - 1]; if (path.StartsWith(priorKey + "/")) { - return Mappings[priorKey] + path.Substring(priorKey.Length); + return Mappings[priorKey].Get(isTransformMapping) + path.Substring(priorKey.Length); } return path; } diff --git a/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs b/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs new file mode 100644 index 00000000..0c44ed41 --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs @@ -0,0 +1,4 @@ +namespace net.fushizen.modular_avatar.core +{ + +} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs.meta b/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs.meta new file mode 100644 index 00000000..d4b45e0e --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Runtime/AnimationEditHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c0b936b1b6ec46ea94aa30c4f5f480d3 +timeCreated: 1661806899 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs b/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs new file mode 100644 index 00000000..62260797 --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs @@ -0,0 +1,58 @@ +using Codice.CM.WorkspaceServer.Tree; +using UnityEngine; + +namespace net.fushizen.modular_avatar.core +{ + [ExecuteInEditMode] + public class MAInternalOffsetMarker : AvatarTagComponent + { + void OnValidate() + { +#if MODULAR_AVATAR_DEBUG + hideFlags = HideFlags.None; +#else + hideFlags = HideFlags.HideInInspector; +#endif + } + + private const float POS_EPSILON = 0.01f; + private const float ROT_EPSILON = 0.01f; + + private Vector3 lastLocalPos; + private Vector3 lastLocalScale; + private Quaternion lastLocalRot; + + public Transform correspondingObject; + + public bool lockBasePosition; + + public void Update() + { + if (correspondingObject == null) return; + + // ReSharper disable once LocalVariableHidesMember + var transform = this.transform; + if ((transform.localPosition - lastLocalPos).sqrMagnitude > POS_EPSILON + || (transform.localScale - lastLocalScale).sqrMagnitude > POS_EPSILON + || Quaternion.Angle(lastLocalRot, transform.localRotation) > ROT_EPSILON) + { + if (lockBasePosition) transform.position = correspondingObject.position; + else correspondingObject.localPosition = transform.localPosition; + + correspondingObject.localScale = transform.localScale; + correspondingObject.localRotation = transform.localRotation; + } + else + { + if (lockBasePosition) transform.position = correspondingObject.position; + else transform.localPosition = correspondingObject.localPosition; + transform.localScale = correspondingObject.localScale; + transform.localRotation = correspondingObject.localRotation; + } + + lastLocalPos = transform.localPosition; + lastLocalScale = transform.localScale; + lastLocalRot = transform.localRotation; + } + } +} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs.meta b/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs.meta new file mode 100644 index 00000000..de60abbc --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Runtime/MAInternalOffsetMarker.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7b59aa936ba8466e85aede8aa3631b84 +timeCreated: 1661807782 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs index dd626520..0545c8d1 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using UnityEditor; using UnityEngine; using UnityEngine.Animations; using Object = System.Object; @@ -15,13 +14,13 @@ namespace net.fushizen.modular_avatar.core public string subPath; [SerializeField] [HideInInspector] public ParentConstraint constraint; - - #if UNITY_EDITOR + +#if UNITY_EDITOR void OnValidate() { - EditorApplication.delayCall += CheckReferences; + UnityEditor.EditorApplication.delayCall += CheckReferences; } - + void CheckReferences() { if (this == null) return; // post-destroy @@ -48,29 +47,11 @@ namespace net.fushizen.modular_avatar.core private void CheckConstraint() { - if (target != null) + if (constraint != null) { - if (constraint == null) + if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(constraint)) { - constraint = gameObject.AddComponent(); - constraint.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; - constraint.AddSource(new ConstraintSource() - { - weight = 1, - sourceTransform = target - }); - constraint.translationOffsets = new Vector3[] {Vector3.zero}; - constraint.rotationOffsets = new Vector3[] {Vector3.zero}; - constraint.locked = true; - constraint.constraintActive = true; - } - else - { - constraint.SetSource(0, new ConstraintSource() - { - weight = 1, - sourceTransform = target - }); + UnityEngine.Object.DestroyImmediate(constraint); } } } diff --git a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs index 04d15130..3e46517d 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs @@ -1,18 +1,25 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; +#if UNITY_EDITOR using UnityEditor; +#endif using UnityEngine; +using Object = UnityEngine.Object; namespace net.fushizen.modular_avatar.core { + [ExecuteInEditMode] public class ModularAvatarMergeArmature : AvatarTagComponent { public GameObject mergeTarget; public string mergeTargetPath; public string prefix; public string suffix; - + public bool locked; + + private bool wasLocked; #if UNITY_EDITOR void OnValidate() { @@ -40,8 +47,55 @@ namespace net.fushizen.modular_avatar.core suffix = gameObject.name.Substring(insetPos + mergeTarget.name.Length); } } + + CheckLock(); }; } #endif + + void CheckLock() + { + if (RuntimeUtil.isPlaying) return; + + if (locked != wasLocked) + { + if (!locked) + { + foreach (var comp in GetComponentsInChildren()) + { + DestroyImmediate(comp); + } + + wasLocked = false; + } + else + { + if (mergeTarget == null) return; + foreach (var xform in GetComponentsInChildren(true)) + { + Transform baseObject = FindCorresponding(xform); + if (baseObject != null && xform.gameObject.GetComponent() == null) + { + var comp = xform.gameObject.AddComponent(); + comp.correspondingObject = baseObject; + comp.lockBasePosition = baseObject.gameObject == mergeTarget; + } + } + + wasLocked = true; + } + } + } + + private Transform FindCorresponding(Transform xform) + { + if (xform == null) return null; + if (xform == transform) return mergeTarget.transform; + + var correspondingParent = FindCorresponding(xform.parent); + if (correspondingParent == null) return null; + + return correspondingParent.Find(prefix + xform.name + suffix); + } } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs index c4bde401..f65503ab 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using JetBrains.Annotations; -using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -49,13 +49,59 @@ namespace net.fushizen.modular_avatar.core public static void MarkDirty(UnityEngine.Object obj) { - #if UNITY_EDITOR - if (PrefabUtility.IsPartOfPrefabInstance(obj)) +#if UNITY_EDITOR + if (UnityEditor.PrefabUtility.IsPartOfPrefabInstance(obj)) { - PrefabUtility.RecordPrefabInstancePropertyModifications(obj); + UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(obj); } - EditorUtility.SetDirty(obj); + UnityEditor.EditorUtility.SetDirty(obj); #endif } + +#if UNITY_EDITOR + private static UnityEngine.Object cachedAnimationWindowState; + private static readonly Type animationWindowStateType + = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditorInternal.AnimationWindowState"); + private static readonly PropertyInfo recordingProp = animationWindowStateType.GetProperty( + "recording", + BindingFlags.Instance | BindingFlags.Public + ); + private static readonly PropertyInfo previewingProp = animationWindowStateType.GetProperty( + "previewing", + BindingFlags.Instance | BindingFlags.Public + ); + private static readonly PropertyInfo playingProp = animationWindowStateType.GetProperty( + "playing", + BindingFlags.Instance | BindingFlags.Public + ); +#endif + + public static bool IsAnimationEditMode() + { +#if !UNITY_EDITOR + return false; +#else + + if (cachedAnimationWindowState == null) + { + foreach (var obj in Resources.FindObjectsOfTypeAll(animationWindowStateType)) + { + cachedAnimationWindowState = obj; + } + } + + if (cachedAnimationWindowState == null) return false; + + return (bool) recordingProp.GetValue(cachedAnimationWindowState, null) + || (bool) previewingProp.GetValue(cachedAnimationWindowState, null) + || (bool) playingProp.GetValue(cachedAnimationWindowState, null); +#endif + } + +#if UNITY_EDITOR + public static bool isPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode; +#else + public static bool isPlaying => true; +#endif } } \ No newline at end of file diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 0c17f186..42897a34 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -619,7 +619,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLWasmStreaming: 0 scriptingDefineSymbols: - 1: VRC_SDK_VRCSDK3 + 1: VRC_SDK_VRCSDK3;MODULAR_AVATAR_DEBUG platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {}