mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-24 13:29:01 +08:00
Animator merging & bone proxy support
This commit is contained in:
parent
625878e698
commit
9376fddc6e
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Animations;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
public class AnimatorCombiner
|
||||||
|
{
|
||||||
|
private readonly AnimatorController _combined;
|
||||||
|
|
||||||
|
private List<AnimatorControllerLayer> _layers = new List<AnimatorControllerLayer>();
|
||||||
|
|
||||||
|
private Dictionary<String, AnimatorControllerParameter> _parameters =
|
||||||
|
new Dictionary<string, AnimatorControllerParameter>();
|
||||||
|
|
||||||
|
private Dictionary<KeyValuePair<String, Motion>, Motion> _motions =
|
||||||
|
new Dictionary<KeyValuePair<string, Motion>, Motion>();
|
||||||
|
|
||||||
|
private Dictionary<KeyValuePair<String, AnimatorStateMachine>, AnimatorStateMachine> _stateMachines =
|
||||||
|
new Dictionary<KeyValuePair<string, AnimatorStateMachine>, AnimatorStateMachine>();
|
||||||
|
|
||||||
|
public AnimatorCombiner()
|
||||||
|
{
|
||||||
|
_combined = Util.CreateContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimatorController Finish()
|
||||||
|
{
|
||||||
|
_combined.parameters = _parameters.Values.ToArray();
|
||||||
|
_combined.layers = _layers.ToArray();
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
return _combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddController(String basePath, AnimatorController controller)
|
||||||
|
{
|
||||||
|
foreach (var param in controller.parameters)
|
||||||
|
{
|
||||||
|
if (_parameters.TryGetValue(param.name, out var acp))
|
||||||
|
{
|
||||||
|
if (acp.type != param.type)
|
||||||
|
{
|
||||||
|
throw new Exception($"Parameter {param.name} has different types in {basePath} and {controller.name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_parameters.Add(param.name, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
foreach (var layer in controller.layers)
|
||||||
|
{
|
||||||
|
insertLayer(basePath, layer, first);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertLayer(string basePath, AnimatorControllerLayer layer, bool first)
|
||||||
|
{
|
||||||
|
var newLayer = new AnimatorControllerLayer()
|
||||||
|
{
|
||||||
|
name = layer.name,
|
||||||
|
avatarMask = layer.avatarMask, // TODO map transforms
|
||||||
|
blendingMode = layer.blendingMode,
|
||||||
|
defaultWeight = first ? 1 : layer.defaultWeight,
|
||||||
|
syncedLayerIndex = layer.syncedLayerIndex, // TODO
|
||||||
|
syncedLayerAffectsTiming = layer.syncedLayerAffectsTiming, // TODO
|
||||||
|
iKPass = layer.iKPass,
|
||||||
|
stateMachine = mapStateMachine(basePath, layer.stateMachine),
|
||||||
|
};
|
||||||
|
|
||||||
|
_layers.Add(newLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnimatorStateMachine mapStateMachine(string basePath, AnimatorStateMachine layerStateMachine)
|
||||||
|
{
|
||||||
|
var cacheKey = new KeyValuePair<string, AnimatorStateMachine>(basePath, layerStateMachine);
|
||||||
|
|
||||||
|
if (_stateMachines.TryGetValue(cacheKey, out var asm))
|
||||||
|
{
|
||||||
|
return asm;
|
||||||
|
}
|
||||||
|
|
||||||
|
asm = deepClone(layerStateMachine, (obj) => customClone(obj, basePath));
|
||||||
|
|
||||||
|
_stateMachines[cacheKey] = asm;
|
||||||
|
return asm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object customClone(Object o, string basePath)
|
||||||
|
{
|
||||||
|
if (basePath == "") return null;
|
||||||
|
|
||||||
|
if (o is AnimationClip clip)
|
||||||
|
{
|
||||||
|
AnimationClip newClip = new AnimationClip();
|
||||||
|
newClip.name = "rebased " + clip.name;
|
||||||
|
AssetDatabase.AddObjectToAsset(newClip, _combined);
|
||||||
|
|
||||||
|
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
||||||
|
{
|
||||||
|
var newBinding = binding;
|
||||||
|
newBinding.path = PathMappings.MapPath(basePath + binding.path);
|
||||||
|
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
|
||||||
|
AnimationUtility.GetEditorCurve(clip, binding));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
|
||||||
|
{
|
||||||
|
var newBinding = objBinding;
|
||||||
|
newBinding.path = PathMappings.MapPath(basePath + objBinding.path);
|
||||||
|
AnimationUtility.SetObjectReferenceCurve(newClip, newBinding,
|
||||||
|
AnimationUtility.GetObjectReferenceCurve(clip, objBinding));
|
||||||
|
}
|
||||||
|
|
||||||
|
newClip.wrapMode = clip.wrapMode;
|
||||||
|
newClip.legacy = clip.legacy;
|
||||||
|
newClip.frameRate = clip.frameRate;
|
||||||
|
newClip.localBounds = clip.localBounds;
|
||||||
|
AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip));
|
||||||
|
|
||||||
|
return newClip;
|
||||||
|
} else if (o is Texture)
|
||||||
|
{
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private T deepClone<T>(T original,
|
||||||
|
Func<Object, Object> visitor,
|
||||||
|
Dictionary<Object, Object> cloneMap = null
|
||||||
|
) where T : Object
|
||||||
|
{
|
||||||
|
if (original == null) return null;
|
||||||
|
|
||||||
|
if (cloneMap == null) cloneMap = new Dictionary<Object, Object>();
|
||||||
|
|
||||||
|
if (cloneMap.ContainsKey(original))
|
||||||
|
{
|
||||||
|
return (T) cloneMap[original];
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = visitor(original);
|
||||||
|
if (obj != null)
|
||||||
|
{
|
||||||
|
cloneMap[original] = obj;
|
||||||
|
return (T) obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
||||||
|
if (ctor == null || obj is ScriptableObject)
|
||||||
|
{
|
||||||
|
obj = Object.Instantiate(original);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
obj = (T) ctor.Invoke(Array.Empty<object>());
|
||||||
|
EditorUtility.CopySerialized(original, obj);
|
||||||
|
}
|
||||||
|
cloneMap[original] = obj;
|
||||||
|
|
||||||
|
AssetDatabase.AddObjectToAsset(obj, _combined);
|
||||||
|
|
||||||
|
SerializedObject so = new SerializedObject(obj);
|
||||||
|
SerializedProperty prop = so.GetIterator();
|
||||||
|
|
||||||
|
bool enterChildren = true;
|
||||||
|
while (prop.Next(enterChildren))
|
||||||
|
{
|
||||||
|
enterChildren = true;
|
||||||
|
switch (prop.propertyType)
|
||||||
|
{
|
||||||
|
case SerializedPropertyType.ObjectReference:
|
||||||
|
prop.objectReferenceValue = deepClone(prop.objectReferenceValue, visitor, cloneMap);
|
||||||
|
break;
|
||||||
|
// Iterating strings can get super slow...
|
||||||
|
case SerializedPropertyType.String:
|
||||||
|
enterChildren = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
so.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
return (T) obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3c76e0db714645f7aa6580f208f98e49
|
||||||
|
timeCreated: 1661644852
|
@ -0,0 +1,32 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
public class BoneProxyHook : IVRCSDKPreprocessAvatarCallback
|
||||||
|
{
|
||||||
|
public int callbackOrder => HookSequence.SEQ_BONE_PROXY;
|
||||||
|
|
||||||
|
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||||
|
{
|
||||||
|
var boneProxies = avatarGameObject.GetComponentsInChildren<ModularAvatarBoneProxy>(true);
|
||||||
|
|
||||||
|
foreach (var proxy in boneProxies)
|
||||||
|
{
|
||||||
|
if (proxy.constraint != null) UnityEngine.Object.DestroyImmediate(proxy.constraint);
|
||||||
|
if (proxy.target != null)
|
||||||
|
{
|
||||||
|
var oldPath = RuntimeUtil.AvatarRootPath(proxy.gameObject);
|
||||||
|
Transform transform = proxy.transform;
|
||||||
|
transform.SetParent(proxy.target, false);
|
||||||
|
transform.localPosition = Vector3.zero;
|
||||||
|
transform.localRotation = Quaternion.identity;
|
||||||
|
PathMappings.Remap(oldPath, RuntimeUtil.AvatarRootPath(proxy.gameObject));
|
||||||
|
}
|
||||||
|
Object.DestroyImmediate(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21339639c2ce435e97773a969d21f43a
|
||||||
|
timeCreated: 1661649405
|
@ -3,7 +3,9 @@
|
|||||||
internal static class HookSequence
|
internal static class HookSequence
|
||||||
{
|
{
|
||||||
public const int SEQ_RESETTERS = -90000;
|
public const int SEQ_RESETTERS = -90000;
|
||||||
public const int SEQ_MERGE_ARMATURE = -80001;
|
public const int SEQ_MERGE_ARMATURE = SEQ_RESETTERS + 1;
|
||||||
public const int SEQ_RETARGET_MESH = -80000;
|
public const int SEQ_RETARGET_MESH = SEQ_MERGE_ARMATURE + 1;
|
||||||
|
public const int SEQ_BONE_PROXY = SEQ_RETARGET_MESH + 1;
|
||||||
|
public const int SEQ_MERGE_ANIMATORS = SEQ_BONE_PROXY + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Animations;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
public class MergeAnimatorHook : IVRCSDKPreprocessAvatarCallback
|
||||||
|
{
|
||||||
|
private const string SAMPLE_PATH_PACKAGE = "Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation";
|
||||||
|
private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers";
|
||||||
|
|
||||||
|
public int callbackOrder => HookSequence.SEQ_MERGE_ANIMATORS;
|
||||||
|
|
||||||
|
Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner> mergeSessions =
|
||||||
|
new Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner>();
|
||||||
|
|
||||||
|
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||||
|
{
|
||||||
|
var descriptor = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
InitSessions(descriptor.baseAnimationLayers);
|
||||||
|
InitSessions(descriptor.specialAnimationLayers);
|
||||||
|
|
||||||
|
var toMerge = avatarGameObject.transform.GetComponentsInChildren<MergeAnimator>(true);
|
||||||
|
|
||||||
|
foreach (var merge in toMerge)
|
||||||
|
{
|
||||||
|
if (merge.animator != null && mergeSessions.TryGetValue(merge.layerType, out var session))
|
||||||
|
{
|
||||||
|
var relativePath = RuntimeUtil.RelativePath(avatarGameObject, merge.gameObject);
|
||||||
|
mergeSessions[merge.layerType].AddController(
|
||||||
|
relativePath != "" ? relativePath + "/" : "",
|
||||||
|
(AnimatorController) merge.animator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (merge.deleteAttachedAnimator)
|
||||||
|
{
|
||||||
|
var animator = merge.GetComponent<Animator>();
|
||||||
|
if (animator != null) Object.DestroyImmediate(animator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers);
|
||||||
|
descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VRCAvatarDescriptor.CustomAnimLayer[] FinishSessions(
|
||||||
|
VRCAvatarDescriptor.CustomAnimLayer[] layers
|
||||||
|
)
|
||||||
|
{
|
||||||
|
layers = (VRCAvatarDescriptor.CustomAnimLayer[]) layers.Clone();
|
||||||
|
|
||||||
|
for (int i = 0; i < layers.Length; i++)
|
||||||
|
{
|
||||||
|
layers[i].isDefault = false;
|
||||||
|
layers[i].animatorController = mergeSessions[layers[i].type].Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitSessions(VRCAvatarDescriptor.CustomAnimLayer[] layers)
|
||||||
|
{
|
||||||
|
foreach (var layer in layers)
|
||||||
|
{
|
||||||
|
var controller = ResolveLayerController(layer);
|
||||||
|
if (controller == null) controller = new AnimatorController();
|
||||||
|
|
||||||
|
var session = new AnimatorCombiner();
|
||||||
|
session.AddController("", controller);
|
||||||
|
|
||||||
|
mergeSessions[layer.type] = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static AnimatorController ResolveLayerController(VRCAvatarDescriptor.CustomAnimLayer layer)
|
||||||
|
{
|
||||||
|
AnimatorController controller = null;
|
||||||
|
|
||||||
|
if (!layer.isDefault && layer.animatorController != null &&
|
||||||
|
layer.animatorController is AnimatorController c)
|
||||||
|
{
|
||||||
|
controller = c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
switch (layer.type)
|
||||||
|
{
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.Action:
|
||||||
|
name = "Action";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.Additive:
|
||||||
|
name = "Idle";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.Base:
|
||||||
|
name = "Locomotion";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.Gesture:
|
||||||
|
name = "Hands";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.Sitting:
|
||||||
|
name = "Sitting";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.FX:
|
||||||
|
name = "Face";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.TPose:
|
||||||
|
name = "UtilityTPose";
|
||||||
|
break;
|
||||||
|
case VRCAvatarDescriptor.AnimLayerType.IKPose:
|
||||||
|
name = "UtilityIKPose";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
name = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
name = "/vrc_AvatarV3" + name + "Layer";
|
||||||
|
|
||||||
|
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(SAMPLE_PATH_PACKAGE + name);
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(SAMPLE_PATH_LEGACY + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 32fddef4bb7c4a5fb1a06b13507bdee8
|
||||||
|
timeCreated: 1661644932
|
@ -38,7 +38,7 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
foreach (var renderer in avatarGameObject.transform.GetComponentsInChildren<SkinnedMeshRenderer>())
|
foreach (var renderer in avatarGameObject.transform.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||||
{
|
{
|
||||||
var bones = renderer.bones;
|
var bones = renderer.bones;
|
||||||
for (int i = 0; i < bones.Length; i++) bones[i] = MapBoneReference(bones[i]);
|
for (int i = 0; i < bones.Length; i++) bones[i] = MapBoneReference(bones[i], false);
|
||||||
renderer.bones = bones;
|
renderer.bones = bones;
|
||||||
renderer.rootBone = MapBoneReference(renderer.rootBone);
|
renderer.rootBone = MapBoneReference(renderer.rootBone);
|
||||||
renderer.probeAnchor = MapBoneReference(renderer.probeAnchor);
|
renderer.probeAnchor = MapBoneReference(renderer.probeAnchor);
|
||||||
@ -93,11 +93,11 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
so.ApplyModifiedPropertiesWithoutUndo();
|
so.ApplyModifiedPropertiesWithoutUndo();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Transform MapBoneReference(Transform bone)
|
private Transform MapBoneReference(Transform bone, bool markNonRetargetable = true)
|
||||||
{
|
{
|
||||||
if (bone != null && BoneRemappings.TryGetValue(bone, out var newBone))
|
if (bone != null && BoneRemappings.TryGetValue(bone, out var newBone))
|
||||||
{
|
{
|
||||||
BoneDatabase.MarkNonRetargetable(newBone);
|
if (markNonRetargetable) BoneDatabase.MarkNonRetargetable(newBone);
|
||||||
bone = newBone;
|
bone = newBone;
|
||||||
}
|
}
|
||||||
return bone;
|
return bone;
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
public static class PathMappings
|
||||||
|
{
|
||||||
|
private static SortedDictionary<string, string> Mappings = new SortedDictionary<string, string>();
|
||||||
|
private static List<string> CachedMappingKeys = null;
|
||||||
|
|
||||||
|
internal static void Clear()
|
||||||
|
{
|
||||||
|
Mappings.Clear();
|
||||||
|
CachedMappingKeys = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Remap(string from, string to)
|
||||||
|
{
|
||||||
|
Mappings[from] = to;
|
||||||
|
CachedMappingKeys = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string MapPath(string path)
|
||||||
|
{
|
||||||
|
if (CachedMappingKeys == null) CachedMappingKeys = new List<string>(Mappings.Keys);
|
||||||
|
var bsResult = CachedMappingKeys.BinarySearch(path);
|
||||||
|
if (bsResult >= 0) return Mappings[path];
|
||||||
|
|
||||||
|
int index = ~bsResult;
|
||||||
|
if (index == 0) return path;
|
||||||
|
|
||||||
|
var priorKey = CachedMappingKeys[index - 1];
|
||||||
|
if (path.StartsWith(priorKey + "/"))
|
||||||
|
{
|
||||||
|
return Mappings[priorKey] + path.Substring(priorKey.Length);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ClearPathMappings : IVRCSDKPreprocessAvatarCallback
|
||||||
|
{
|
||||||
|
public int callbackOrder => HookSequence.SEQ_RESETTERS;
|
||||||
|
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||||
|
{
|
||||||
|
PathMappings.Clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6a5a2ea7723848d1bfe793debcf298cc
|
||||||
|
timeCreated: 1661649007
|
@ -0,0 +1,12 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core
|
||||||
|
{
|
||||||
|
public class MergeAnimator : AvatarTagComponent
|
||||||
|
{
|
||||||
|
public RuntimeAnimatorController animator;
|
||||||
|
public VRCAvatarDescriptor.AnimLayerType layerType;
|
||||||
|
public bool deleteAttachedAnimator;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1bb122659f724ebf85fe095ac02dc339
|
||||||
|
timeCreated: 1661644807
|
@ -0,0 +1,154 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Animations;
|
||||||
|
using Object = System.Object;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core
|
||||||
|
{
|
||||||
|
public class ModularAvatarBoneProxy : AvatarTagComponent
|
||||||
|
{
|
||||||
|
public Transform target;
|
||||||
|
|
||||||
|
public HumanBodyBones boneReference = HumanBodyBones.LastBone;
|
||||||
|
public string subPath;
|
||||||
|
|
||||||
|
[SerializeField] [HideInInspector] public ParentConstraint constraint;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
EditorApplication.delayCall += CheckReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckReferences() {
|
||||||
|
if (this == null) return; // post-destroy
|
||||||
|
|
||||||
|
if (target == null && (boneReference != HumanBodyBones.LastBone || !string.IsNullOrWhiteSpace(subPath)))
|
||||||
|
{
|
||||||
|
UpdateDynamicMapping();
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
RuntimeUtil.MarkDirty(this);
|
||||||
|
}
|
||||||
|
} else if (target != null)
|
||||||
|
{
|
||||||
|
var origBoneReference = boneReference;
|
||||||
|
var origSubpath = subPath;
|
||||||
|
UpdateStaticMapping();
|
||||||
|
if (origSubpath != subPath || origBoneReference != boneReference)
|
||||||
|
{
|
||||||
|
RuntimeUtil.MarkDirty(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckConstraint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckConstraint()
|
||||||
|
{
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (constraint == null)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (constraint != null) DestroyImmediate(constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDynamicMapping()
|
||||||
|
{
|
||||||
|
var avatar = RuntimeUtil.FindAvatarInParents(transform);
|
||||||
|
if (avatar == null) return;
|
||||||
|
|
||||||
|
if (subPath == "$$AVATAR")
|
||||||
|
{
|
||||||
|
target = avatar.transform;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boneReference == HumanBodyBones.LastBone)
|
||||||
|
{
|
||||||
|
target = avatar.transform.Find(subPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animator = avatar.GetComponent<Animator>();
|
||||||
|
if (animator == null) return;
|
||||||
|
var bone = animator.GetBoneTransform(boneReference);
|
||||||
|
if (bone == null) return;
|
||||||
|
if (string.IsNullOrWhiteSpace(subPath)) target = bone;
|
||||||
|
else target = bone.Find(subPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStaticMapping()
|
||||||
|
{
|
||||||
|
var avatar = RuntimeUtil.FindAvatarInParents(transform);
|
||||||
|
var humanBones = new Dictionary<Transform, HumanBodyBones>();
|
||||||
|
var animator = avatar.GetComponent<Animator>();
|
||||||
|
if (animator == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var boneTypeObj in Enum.GetValues(typeof(HumanBodyBones)))
|
||||||
|
{
|
||||||
|
var boneType = (HumanBodyBones) boneTypeObj;
|
||||||
|
if (boneType == HumanBodyBones.LastBone) continue;
|
||||||
|
var bone = animator.GetBoneTransform(boneType);
|
||||||
|
if (bone != null) humanBones[bone] = boneType;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform iter = target;
|
||||||
|
Transform avatarTransform = avatar.transform;
|
||||||
|
|
||||||
|
if (target == avatarTransform)
|
||||||
|
{
|
||||||
|
boneReference = HumanBodyBones.LastBone;
|
||||||
|
subPath = "$$AVATAR";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (iter != avatarTransform && !humanBones.ContainsKey(iter))
|
||||||
|
{
|
||||||
|
iter = iter.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iter == avatarTransform)
|
||||||
|
{
|
||||||
|
boneReference = HumanBodyBones.LastBone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boneReference = humanBones[iter];
|
||||||
|
}
|
||||||
|
|
||||||
|
subPath = RuntimeUtil.RelativePath(iter.gameObject, target.gameObject);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 42581d8044b64899834d3d515ab3a144
|
||||||
|
timeCreated: 1661648057
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
|
||||||
@ -45,5 +46,14 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void MarkDirty(UnityEngine.Object obj)
|
||||||
|
{
|
||||||
|
if (PrefabUtility.IsPartOfPrefabInstance(obj))
|
||||||
|
{
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(obj);
|
||||||
|
}
|
||||||
|
EditorUtility.SetDirty(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user