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
|
||||
{
|
||||
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.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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -193,6 +203,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
|
||||
retain = retain || retainChild;
|
||||
}
|
||||
|
@ -7,26 +7,37 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
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;
|
||||
|
||||
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<string>(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;
|
||||
}
|
||||
|
@ -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.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using Object = System.Object;
|
||||
@ -19,7 +18,7 @@ namespace net.fushizen.modular_avatar.core
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
EditorApplication.delayCall += CheckReferences;
|
||||
UnityEditor.EditorApplication.delayCall += CheckReferences;
|
||||
}
|
||||
|
||||
void CheckReferences() {
|
||||
@ -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<ParentConstraint>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
|
||||
@ -50,12 +50,58 @@ namespace net.fushizen.modular_avatar.core
|
||||
public static void MarkDirty(UnityEngine.Object obj)
|
||||
{
|
||||
#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
|
||||
}
|
||||
|
||||
#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
|
||||
webGLWasmStreaming: 0
|
||||
scriptingDefineSymbols:
|
||||
1: VRC_SDK_VRCSDK3
|
||||
1: VRC_SDK_VRCSDK3;MODULAR_AVATAR_DEBUG
|
||||
platformArchitecture: {}
|
||||
scriptingBackend: {}
|
||||
il2cppCompilerConfiguration: {}
|
||||
|
Loading…
Reference in New Issue
Block a user