mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-03-09 23:34:56 +08:00
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.
This commit is contained in:
parent
5b52ad45c1
commit
b154b01e45
@ -101,7 +101,7 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return PathMappings.MapPath(basePath + binding.path);
|
return PathMappings.MapPath(basePath + binding.path, binding.type == typeof(Transform));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,10 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
transform.SetParent(proxy.target, false);
|
transform.SetParent(proxy.target, false);
|
||||||
transform.localPosition = Vector3.zero;
|
transform.localPosition = Vector3.zero;
|
||||||
transform.localRotation = Quaternion.identity;
|
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);
|
Object.DestroyImmediate(proxy);
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,7 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
case Transform _: break;
|
case Transform _: break;
|
||||||
case ModularAvatarMergeArmature _: break;
|
case ModularAvatarMergeArmature _: break;
|
||||||
|
case MAInternalOffsetMarker _: break;
|
||||||
case VRCPhysBone _: case VRCPhysBoneCollider _: hasComponents = true;
|
case VRCPhysBone _: case VRCPhysBoneCollider _: hasComponents = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -146,7 +147,16 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
mergedSrcBone.transform.localRotation = src.transform.localRotation;
|
mergedSrcBone.transform.localRotation = src.transform.localRotation;
|
||||||
mergedSrcBone.transform.localScale = src.transform.localScale;
|
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);
|
mergedSrcBone.transform.SetParent(newParent.transform, true);
|
||||||
BoneRemappings[src.transform] = mergedSrcBone.transform;
|
BoneRemappings[src.transform] = mergedSrcBone.transform;
|
||||||
|
|
||||||
@ -192,6 +202,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
shouldZip = false;
|
shouldZip = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
|
var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
|
||||||
retain = retain || retainChild;
|
retain = retain || retainChild;
|
||||||
|
@ -7,26 +7,37 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
public static class PathMappings
|
public static class PathMappings
|
||||||
{
|
{
|
||||||
private static SortedDictionary<string, string> Mappings = new SortedDictionary<string, string>();
|
private static SortedDictionary<string, MappingEntry> Mappings = new SortedDictionary<string, MappingEntry>();
|
||||||
private static List<string> CachedMappingKeys = null;
|
private static List<string> CachedMappingKeys = null;
|
||||||
|
|
||||||
|
public struct MappingEntry
|
||||||
|
{
|
||||||
|
public string path;
|
||||||
|
public string transformPath;
|
||||||
|
|
||||||
|
public string Get(bool isTransformMapping)
|
||||||
|
{
|
||||||
|
return isTransformMapping ? transformPath : path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static void Clear()
|
internal static void Clear()
|
||||||
{
|
{
|
||||||
Mappings.Clear();
|
Mappings.Clear();
|
||||||
CachedMappingKeys = null;
|
CachedMappingKeys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Remap(string from, string to)
|
internal static void Remap(string from, MappingEntry to)
|
||||||
{
|
{
|
||||||
Mappings[from] = to;
|
Mappings[from] = to;
|
||||||
CachedMappingKeys = null;
|
CachedMappingKeys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string MapPath(string path)
|
internal static string MapPath(string path, bool isTransformMapping = false)
|
||||||
{
|
{
|
||||||
if (CachedMappingKeys == null) CachedMappingKeys = new List<string>(Mappings.Keys);
|
if (CachedMappingKeys == null) CachedMappingKeys = new List<string>(Mappings.Keys);
|
||||||
var bsResult = CachedMappingKeys.BinarySearch(path);
|
var bsResult = CachedMappingKeys.BinarySearch(path);
|
||||||
if (bsResult >= 0) return Mappings[path];
|
if (bsResult >= 0) return Mappings[path].Get(isTransformMapping);
|
||||||
|
|
||||||
int index = ~bsResult;
|
int index = ~bsResult;
|
||||||
if (index == 0) return path;
|
if (index == 0) return path;
|
||||||
@ -34,7 +45,7 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
var priorKey = CachedMappingKeys[index - 1];
|
var priorKey = CachedMappingKeys[index - 1];
|
||||||
if (path.StartsWith(priorKey + "/"))
|
if (path.StartsWith(priorKey + "/"))
|
||||||
{
|
{
|
||||||
return Mappings[priorKey] + path.Substring(priorKey.Length);
|
return Mappings[priorKey].Get(isTransformMapping) + path.Substring(priorKey.Length);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
namespace net.fushizen.modular_avatar.core
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c0b936b1b6ec46ea94aa30c4f5f480d3
|
||||||
|
timeCreated: 1661806899
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7b59aa936ba8466e85aede8aa3631b84
|
||||||
|
timeCreated: 1661807782
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Animations;
|
using UnityEngine.Animations;
|
||||||
using Object = System.Object;
|
using Object = System.Object;
|
||||||
@ -15,13 +14,13 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
public string subPath;
|
public string subPath;
|
||||||
|
|
||||||
[SerializeField] [HideInInspector] public ParentConstraint constraint;
|
[SerializeField] [HideInInspector] public ParentConstraint constraint;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
void OnValidate()
|
void OnValidate()
|
||||||
{
|
{
|
||||||
EditorApplication.delayCall += CheckReferences;
|
UnityEditor.EditorApplication.delayCall += CheckReferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckReferences() {
|
void CheckReferences() {
|
||||||
if (this == null) return; // post-destroy
|
if (this == null) return; // post-destroy
|
||||||
|
|
||||||
@ -48,29 +47,11 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
|
|
||||||
private void CheckConstraint()
|
private void CheckConstraint()
|
||||||
{
|
{
|
||||||
if (target != null)
|
if (constraint != null)
|
||||||
{
|
{
|
||||||
if (constraint == null)
|
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(constraint))
|
||||||
{
|
{
|
||||||
constraint = gameObject.AddComponent<ParentConstraint>();
|
UnityEngine.Object.DestroyImmediate(constraint);
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace net.fushizen.modular_avatar.core
|
namespace net.fushizen.modular_avatar.core
|
||||||
{
|
{
|
||||||
|
[ExecuteInEditMode]
|
||||||
public class ModularAvatarMergeArmature : AvatarTagComponent
|
public class ModularAvatarMergeArmature : AvatarTagComponent
|
||||||
{
|
{
|
||||||
public GameObject mergeTarget;
|
public GameObject mergeTarget;
|
||||||
public string mergeTargetPath;
|
public string mergeTargetPath;
|
||||||
public string prefix;
|
public string prefix;
|
||||||
public string suffix;
|
public string suffix;
|
||||||
|
public bool locked;
|
||||||
|
|
||||||
|
private bool wasLocked;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
void OnValidate()
|
void OnValidate()
|
||||||
{
|
{
|
||||||
@ -40,8 +47,55 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
suffix = gameObject.name.Substring(insetPos + mergeTarget.name.Length);
|
suffix = gameObject.name.Substring(insetPos + mergeTarget.name.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckLock();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void CheckLock()
|
||||||
|
{
|
||||||
|
if (RuntimeUtil.isPlaying) return;
|
||||||
|
|
||||||
|
if (locked != wasLocked)
|
||||||
|
{
|
||||||
|
if (!locked)
|
||||||
|
{
|
||||||
|
foreach (var comp in GetComponentsInChildren<MAInternalOffsetMarker>())
|
||||||
|
{
|
||||||
|
DestroyImmediate(comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasLocked = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mergeTarget == null) return;
|
||||||
|
foreach (var xform in GetComponentsInChildren<Transform>(true))
|
||||||
|
{
|
||||||
|
Transform baseObject = FindCorresponding(xform);
|
||||||
|
if (baseObject != null && xform.gameObject.GetComponent<MAInternalOffsetMarker>() == null)
|
||||||
|
{
|
||||||
|
var comp = xform.gameObject.AddComponent<MAInternalOffsetMarker>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
|
||||||
@ -49,13 +49,59 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
|
|
||||||
public static void MarkDirty(UnityEngine.Object obj)
|
public static void MarkDirty(UnityEngine.Object obj)
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (PrefabUtility.IsPartOfPrefabInstance(obj))
|
if (UnityEditor.PrefabUtility.IsPartOfPrefabInstance(obj))
|
||||||
{
|
{
|
||||||
PrefabUtility.RecordPrefabInstancePropertyModifications(obj);
|
UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(obj);
|
||||||
}
|
}
|
||||||
EditorUtility.SetDirty(obj);
|
UnityEditor.EditorUtility.SetDirty(obj);
|
||||||
#endif
|
#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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -619,7 +619,7 @@ PlayerSettings:
|
|||||||
webGLThreadsSupport: 0
|
webGLThreadsSupport: 0
|
||||||
webGLWasmStreaming: 0
|
webGLWasmStreaming: 0
|
||||||
scriptingDefineSymbols:
|
scriptingDefineSymbols:
|
||||||
1: VRC_SDK_VRCSDK3
|
1: VRC_SDK_VRCSDK3;MODULAR_AVATAR_DEBUG
|
||||||
platformArchitecture: {}
|
platformArchitecture: {}
|
||||||
scriptingBackend: {}
|
scriptingBackend: {}
|
||||||
il2cppCompilerConfiguration: {}
|
il2cppCompilerConfiguration: {}
|
||||||
|
Loading…
Reference in New Issue
Block a user