feat: MA Convert Constraints (#1010)

This commit is contained in:
bd_ 2024-08-16 18:52:04 -07:00 committed by GitHub
parent d83c3351d7
commit 7384715059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 216 additions and 7 deletions

View File

@ -1,7 +1,7 @@
{
"dependencies": {
"com.vrchat.avatars": {
"version": "3.5.0"
"version": "3.7.0"
},
"nadena.dev.ndmf": {
"version": "1.4.0"
@ -9,13 +9,13 @@
},
"locked": {
"com.vrchat.avatars": {
"version": "3.6.1",
"version": "3.7.0",
"dependencies": {
"com.vrchat.base": "3.6.1"
"com.vrchat.base": "3.7.0"
}
},
"com.vrchat.base": {
"version": "3.6.1",
"version": "3.7.0",
"dependencies": {}
},
"nadena.dev.ndmf": {

View File

@ -0,0 +1,14 @@
using UnityEditor;
namespace nadena.dev.modular_avatar.core.editor
{
[CustomEditor(typeof(ModularAvatarConvertConstraints))]
[CanEditMultipleObjects]
internal class MAConvertConstraintsEditor : MAEditorBase
{
protected override void OnInnerInspectorGUI()
{
// no UI
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 484ea04548b945ce9cf5fd6d49b50244
timeCreated: 1723778102

View File

@ -0,0 +1,125 @@
using System.Collections.Generic;
using nadena.dev.ndmf;
using UnityEditor;
#if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER
using UnityEngine;
using UnityEngine.Animations;
using VRC.SDK3.Avatars;
using System.Linq;
using nadena.dev.modular_avatar.animation;
using VRC.Dynamics;
#endif
namespace nadena.dev.modular_avatar.core.editor
{
internal class ConstraintConverterPass : Pass<ConstraintConverterPass>
{
#if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER
[InitializeOnLoadMethod]
private static void Init()
{
AvatarDynamicsSetup.IsUnityConstraintAutoConverted += constraint =>
{
var component = constraint as Component;
if (component == null) return false;
var converted = component.GetComponentInParent<ModularAvatarConvertConstraints>();
return converted != null && RuntimeUtil.FindAvatarInParents(converted.transform) ==
RuntimeUtil.FindAvatarInParents(component.transform);
};
AvatarDynamicsSetup.OnConvertUnityConstraintsAcrossGameObjects += (constraints, isAutoFix) =>
{
if (!isAutoFix) return false;
var avatars = constraints.Select(c => RuntimeUtil.FindAvatarInParents(c.transform)).Distinct();
foreach (var avatar in avatars) Undo.AddComponent<ModularAvatarConvertConstraints>(avatar.gameObject);
return true;
};
}
protected override void Execute(ndmf.BuildContext context)
{
var converters = context.AvatarRootObject.GetComponentsInChildren<ModularAvatarConvertConstraints>(true)
.Select(c => c.gameObject)
.ToHashSet(new ObjectIdentityComparer<GameObject>());
if (converters.Count == 0) return;
var constraintGameObjects = context.AvatarRootObject.GetComponentsInChildren<IConstraint>(true)
.Select(c => (c as Component)?.gameObject)
.Distinct()
.Where(go => go.GetComponentsInParent<ModularAvatarConvertConstraints>(true)
.Select(c => c.gameObject)
.Any(converters.Contains)
).ToArray();
var targetConstraintComponents =
constraintGameObjects.SelectMany(go => go.GetComponents<IConstraint>()).ToArray();
AvatarDynamicsSetup.DoConvertUnityConstraints(targetConstraintComponents, null, false);
var asc = context.Extension<AnimationServicesContext>();
// Also look for preexisting VRCConstraints so we can go fix up any broken animation clips from people who
// clicked auto fix :(
var existingVRCConstraints = converters.SelectMany(c => c.GetComponentsInChildren<VRCConstraintBase>(true))
.Select(c => c.gameObject)
.Distinct();
var targetPaths = constraintGameObjects
.Union(existingVRCConstraints)
.Select(c => asc.PathMappings.GetObjectIdentifier(c))
.ToHashSet();
// Update animation clips
var clips = targetPaths.SelectMany(tp => asc.AnimationDatabase.ClipsForPath(tp))
.ToHashSet();
foreach (var clip in clips) RemapSingleClip(clip, targetPaths);
}
private void RemapSingleClip(AnimationDatabase.ClipHolder clip, HashSet<string> targetPaths)
{
var motion = clip.CurrentClip as AnimationClip;
if (motion == null) return;
var bindings = AnimationUtility.GetCurveBindings(motion);
var toUpdateBindings = new List<EditorCurveBinding>();
var toUpdateCurves = new List<AnimationCurve>();
foreach (var ecb in bindings)
{
if (!targetPaths.Contains(ecb.path)) continue;
if (typeof(IConstraint).IsAssignableFrom(ecb.type))
if (AvatarDynamicsSetup.TryGetSubstituteAnimationBinding(ecb.type, ecb.propertyName,
out var newType, out var newProp, out var isArray))
{
var newBinding = new EditorCurveBinding
{
path = ecb.path,
type = newType,
propertyName = newProp
};
var curve = AnimationUtility.GetEditorCurve(motion, ecb);
if (curve != null)
{
toUpdateBindings.Add(newBinding);
toUpdateCurves.Add(curve);
toUpdateBindings.Add(ecb);
toUpdateCurves.Add(null);
}
}
}
if (toUpdateBindings.Count == 0) return;
AnimationUtility.SetEditorCurves(motion, toUpdateBindings.ToArray(), toUpdateCurves.ToArray());
}
#else
protected override void Execute(ndmf.BuildContext context) {}
#endif
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5c172d4eac3d4902826a96656cf1ce34
timeCreated: 1723776385

View File

@ -70,6 +70,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
#endif
seq.Run(GameObjectDelayDisablePass.Instance);
seq.Run(ConstraintConverterPass.Instance);
});
#if MA_VRCSDK3_AVATARS
seq.Run(MenuInstallPluginPass.Instance);

View File

@ -8,7 +8,8 @@
"nadena.dev.ndmf",
"nadena.dev.ndmf.vrchat",
"nadena.dev.ndmf.reactive-query.core",
"nadena.dev.ndmf.runtime"
"nadena.dev.ndmf.runtime",
"VRC.SDK3A.Editor"
],
"includePlatforms": [
"Editor"
@ -19,7 +20,6 @@
"precompiledReferences": [
"Newtonsoft.Json.dll",
"System.Collections.Immutable.dll",
"System.Memory.dll",
"VRCSDKBase.dll",
"VRCSDKBase-Editor.dll",
"VRCSDK3A.dll",
@ -48,6 +48,11 @@
"name": "com.vrchat.avatars",
"expression": "3.5.2",
"define": "MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER"
},
{
"name": "com.vrchat.avatars",
"expression": "3.7.0-beta.2",
"define": "MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER"
}
],
"noEngineReferences": false

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
[DisallowMultipleComponent]
#if MA_VRCSDK3_AVATARS
[AddComponentMenu("Modular Avatar/MA Convert Constraints")]
#else
[AddComponentMenu("")]
#endif
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/convert-constraints?lang=auto")]
public class ModularAvatarConvertConstraints : AvatarTagComponent
{
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e362b3df8a3d478c82bf5ffe18f622e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
# Convert Constraints
The Convert Constraints component directs Modular Avatar to nondestructively convert Unity constraints to VRChat
constraints on build. It will convert any constraints (and animations referencing them) on the same object it is
attached to, and any children of that object. It will also attempt to fix animations broken by using VRCSDK's Auto Fix
button with older versions of Modular Avatar.
## When should I use this?
It's probably a good idea to put this on your avatar root in most cases, as preconverting constraints improves
performance significantly. When MA is installed, the VRChat Auto Fix button will automatically add this component to
your avatar root if it's not already there.
## When should I not use this?
This component is primarily provided to allow users to disable this functionality (by removing this component) if it is
suspected to be causing problems.

View File

@ -0,0 +1,15 @@
# Convert Constraints
Convert Constraintsコンポーネントは、ビルド時にUnityのConstraintsをVRChatのConstraintに非破壊的に変換するようにModular
Avatarに指示します。
アタッチされているオブジェクトとその子オブジェクトにあるConstraint、およびそれらを参照しているアニメーションを変換します。
また、VRCSDKのAuto Fixを古いバージョンのModular Avatarで使用して壊れたアニメーションも修正しようとします。
## いつ使うもの?
あらかじめ変換するとパフォーマンスが大幅に向上するため、ほとんどの場合はアバタールートにこれを配置するのが良いでしょう。MAがインストールされている場合、
VRChatのAuto Fixボタンは、アバタールートにこのコンポーネントがまだ存在しない場合、自動的にこのコンポーネントを追加します。
## 非推奨の場合
このコンポーネントは、問題の原因となる可能性がある場合に、このコンポーネントを削除してこの機能を無効にするために提供されています。

View File

@ -15,7 +15,7 @@
"com.unity.nuget.newtonsoft-json": "2.0.0"
},
"vpmDependencies": {
"com.vrchat.avatars": ">=3.6.1",
"com.vrchat.avatars": ">=3.7.0",
"nadena.dev.ndmf": ">=1.5.0-beta.3 <2.0.0-a"
}
}