fix: blendshape sync not being processed (#466)

* Add integration test for blendshape sync

* fix: blendshape sync not being processed

This change refactors AnimationDatabase to be part of the same extension
context as the TrackObjectRenames functionality (which is renamed back to
PathMappings). This then allows us to sequence deactivation of this context
to come after blendshape processing completes.

Fixes: #461
This commit is contained in:
bd_ 2023-10-01 00:09:43 +09:00 committed by GitHub
parent 46c1f7ce49
commit c454bc1ed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1373 additions and 202 deletions

View File

@ -9,15 +9,15 @@ using VRC.SDK3.Avatars.Components;
public class ActiveAnimationRetargeterTests : TestBase public class ActiveAnimationRetargeterTests : TestBase
{ {
[Test] [Test]
public void SimpleRetarget() { public void SimpleRetarget()
{
var avatar = CreatePrefab("SimpleRetarget.prefab"); var avatar = CreatePrefab("SimpleRetarget.prefab");
var descriptor = avatar.GetComponent<VRCAvatarDescriptor>(); var descriptor = avatar.GetComponent<VRCAvatarDescriptor>();
// initialize context // initialize context
var buildContext = new BuildContext(descriptor); var buildContext = new BuildContext(descriptor);
var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>(); var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>()
new MergeAnimatorProcessor().OnPreprocessAvatar(avatar, buildContext); // we need this for AnimationDatabase .PathMappings;
buildContext.AnimationDatabase.Bootstrap(descriptor);
// get game objects // get game objects
var changedChild = avatar.transform.Find("Toggled/Child"); var changedChild = avatar.transform.Find("Toggled/Child");

View File

@ -12,8 +12,8 @@ namespace modular_avatar_tests
{ {
var prefab = CreatePrefab("HighQualityCurvesSettingPreserved.prefab"); var prefab = CreatePrefab("HighQualityCurvesSettingPreserved.prefab");
var context = new BuildContext(prefab, null); var context = new BuildContext(prefab, null);
context.ActivateExtensionContext<TrackObjectRenamesContext>(); context.ActivateExtensionContext<AnimationServicesContext>();
context.DeactivateExtensionContext<TrackObjectRenamesContext>(); context.DeactivateExtensionContext<AnimationServicesContext>();
var layer = findFxLayer(prefab, "Base Layer"); var layer = findFxLayer(prefab, "Base Layer");

View File

@ -18,7 +18,7 @@ namespace modular_avatar_tests
var av = CreateRoot("root"); var av = CreateRoot("root");
var bc = CreateContext(av); var bc = CreateContext(av);
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
toc.OnActivate(bc); toc.OnActivate(bc);
toc.OnDeactivate(bc); toc.OnDeactivate(bc);
@ -31,12 +31,12 @@ namespace modular_avatar_tests
var root = CreateRoot("root"); var root = CreateRoot("root");
var a = CreateChild(root, "a"); var a = CreateChild(root, "a");
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
toc.OnActivate(CreateContext(root)); toc.OnActivate(CreateContext(root));
Assert.AreEqual("a", toc.MapPath("a")); Assert.AreEqual("a", toc.PathMappings.MapPath("a"));
a.name = "b"; a.name = "b";
toc.ClearCache(); toc.PathMappings.ClearCache();
Assert.AreEqual("b", toc.MapPath("a")); Assert.AreEqual("b", toc.PathMappings.MapPath("a"));
} }
[Test] [Test]
@ -46,13 +46,13 @@ namespace modular_avatar_tests
var a = CreateChild(root, "a"); var a = CreateChild(root, "a");
var b = CreateChild(root, "b"); var b = CreateChild(root, "b");
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
toc.OnActivate(CreateContext(root)); toc.OnActivate(CreateContext(root));
Assert.AreEqual("a", toc.MapPath("a")); Assert.AreEqual("a", toc.PathMappings.MapPath("a"));
a.transform.parent = b.transform; a.transform.parent = b.transform;
toc.ClearCache(); toc.PathMappings.ClearCache();
Assert.AreEqual("b/a", toc.MapPath("a")); Assert.AreEqual("b/a", toc.PathMappings.MapPath("a"));
} }
[Test] [Test]
@ -63,14 +63,14 @@ namespace modular_avatar_tests
var b = CreateChild(a, "b"); var b = CreateChild(a, "b");
var c = CreateChild(b, "c"); var c = CreateChild(b, "c");
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
toc.OnActivate(CreateContext(root)); toc.OnActivate(CreateContext(root));
toc.MarkRemoved(b); toc.PathMappings.MarkRemoved(b);
c.transform.parent = a.transform; c.transform.parent = a.transform;
UnityObject.DestroyImmediate(b); UnityObject.DestroyImmediate(b);
Assert.AreEqual("a/c", toc.MapPath("a/b/c")); Assert.AreEqual("a/c", toc.PathMappings.MapPath("a/b/c"));
} }
[Test] [Test]
@ -82,14 +82,14 @@ namespace modular_avatar_tests
var c = CreateChild(b, "c"); var c = CreateChild(b, "c");
var d = CreateChild(c, "d"); var d = CreateChild(c, "d");
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
toc.OnActivate(CreateContext(root)); toc.OnActivate(CreateContext(root));
toc.MarkTransformLookthrough(b); toc.PathMappings.MarkTransformLookthrough(b);
toc.MarkTransformLookthrough(c); toc.PathMappings.MarkTransformLookthrough(c);
Assert.AreEqual("a/b/c", toc.MapPath("a/b/c")); Assert.AreEqual("a/b/c", toc.PathMappings.MapPath("a/b/c"));
Assert.AreEqual("a", toc.MapPath("a/b/c", true)); Assert.AreEqual("a", toc.PathMappings.MapPath("a/b/c", true));
Assert.AreEqual("a/b/c/d", toc.MapPath("a/b/c/d", true)); Assert.AreEqual("a/b/c/d", toc.PathMappings.MapPath("a/b/c/d", true));
} }
[Test] [Test]
@ -105,10 +105,10 @@ namespace modular_avatar_tests
var oldIk = descriptor.specialAnimationLayers.First(l => var oldIk = descriptor.specialAnimationLayers.First(l =>
l.type == VRCAvatarDescriptor.AnimLayerType.IKPose); l.type == VRCAvatarDescriptor.AnimLayerType.IKPose);
var toc = new TrackObjectRenamesContext(); var toc = new AnimationServicesContext();
var buildContext = CreateContext(root); var buildContext = CreateContext(root);
toc.OnActivate(buildContext); toc.OnActivate(buildContext);
toc.MarkTransformLookthrough(child); toc.PathMappings.MarkTransformLookthrough(child);
parent.name = "p2"; parent.name = "p2";

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 86c4fb6ff802457990f035f4dd663ffe
timeCreated: 1696062676

View File

@ -0,0 +1,278 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: BSTAnimation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves:
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.1
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_0
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.2
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_1
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.3
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_0_local
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.4
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.missing_mesh_shape
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.5
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.missing_mesh_shape_2
path: BaseMesh
classID: 137
script: {fileID: 0}
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 2670104718
attribute: 1052778082
script: {fileID: 0}
typeID: 137
customType: 20
isPPtrCurve: 0
- serializedVersion: 2
path: 2670104718
attribute: 1237790452
script: {fileID: 0}
typeID: 137
customType: 20
isPPtrCurve: 0
- serializedVersion: 2
path: 2670104718
attribute: 3283797338
script: {fileID: 0}
typeID: 137
customType: 20
isPPtrCurve: 0
- serializedVersion: 2
path: 2670104718
attribute: 2937375684
script: {fileID: 0}
typeID: 137
customType: 20
isPPtrCurve: 0
- serializedVersion: 2
path: 2670104718
attribute: 386770050
script: {fileID: 0}
typeID: 137
customType: 20
isPPtrCurve: 0
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 0
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves:
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.1
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_0
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.2
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_1
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.3
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.shape_0_local
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.4
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.missing_mesh_shape
path: BaseMesh
classID: 137
script: {fileID: 0}
- curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0.5
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: blendShape.missing_mesh_shape_2
path: BaseMesh
classID: 137
script: {fileID: 0}
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cc4f9e571dfd17546918cebf2097b2ed
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System.Collections.Immutable;
using System.Linq;
using modular_avatar_tests;
using nadena.dev.modular_avatar.animation;
using nadena.dev.ndmf;
using NUnit.Framework;
using UnityEditor;
namespace modular_avatar_tests
{
public class BlendshapeSyncIntegrationTest : TestBase
{
[Test]
public void IntegrationTest_BlendshapeSync()
{
var root = CreatePrefab("BlendshapeSyncIntegrationTest.prefab");
AvatarProcessor.ProcessAvatar(root);
var clip = findFxClip(root, "Base Layer");
var bindings = AnimationUtility.GetCurveBindings(clip)
.Select(binding =>
{
var constantKey = AnimationUtility.GetEditorCurve(clip, binding).keys[0].value;
return (binding.path, binding.propertyName, constantKey);
}).ToImmutableHashSet();
Assert.True(bindings.Contains(("BaseMesh", "blendShape.shape_0", 0.1f)));
Assert.True(bindings.Contains(("BaseMesh", "blendShape.shape_0_local", 0.3f)));
Assert.True(bindings.Contains(("BaseMesh", "blendShape.shape_1", 0.2f)));
Assert.True(bindings.Contains(("BaseMesh", "blendShape.missing_mesh_shape", 0.4f)));
Assert.True(bindings.Contains(("BaseMesh", "blendShape.missing_mesh_shape_2", 0.5f)));
Assert.True(bindings.Contains(("SyncedMesh", "blendShape.shape_0_local", 0.1f)));
Assert.True(bindings.Contains(("SyncedMesh", "blendShape.shape_1", 0.2f)));
Assert.AreEqual(bindings.Count, 7);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d7e117ac456499596ff775b10aa3876
timeCreated: 1696062881

View File

@ -0,0 +1,584 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3825275463613500755
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3825275463613500751}
- component: {fileID: 3825275463613500750}
- component: {fileID: 3825275463613500753}
- component: {fileID: 3825275463613500752}
m_Layer: 0
m_Name: BlendshapeSyncIntegrationTest
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3825275463613500751
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3825275463613500755}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0.023681391, y: 1.0559628, z: -0.6872994}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 3646968714803193661}
- {fileID: 3646968713996568948}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!95 &3825275463613500750
Animator:
serializedVersion: 3
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3825275463613500755}
m_Enabled: 1
m_Avatar: {fileID: 0}
m_Controller: {fileID: 9100000, guid: 1be58bb091803cb488c7005def284caa, type: 2}
m_CullingMode: 0
m_UpdateMode: 0
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0
--- !u!114 &3825275463613500753
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3825275463613500755}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name:
m_EditorClassIdentifier:
Name:
ViewPosition: {x: 0, y: 1.6, z: 0.2}
Animations: 0
ScaleIPD: 1
lipSync: 0
lipSyncJawBone: {fileID: 0}
lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1}
lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1}
VisemeSkinnedMesh: {fileID: 0}
MouthOpenBlendShapeName: Facial_Blends.Jaw_Down
VisemeBlendShapes: []
unityVersion:
portraitCameraPositionOffset: {x: 0, y: 0, z: 0}
portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139}
networkIDs: []
customExpressions: 0
expressionsMenu: {fileID: 0}
expressionParameters: {fileID: 0}
enableEyeLook: 0
customEyeLookSettings:
eyeMovement:
confidence: 0.5
excitement: 0.5
leftEye: {fileID: 0}
rightEye: {fileID: 0}
eyesLookingStraight:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingUp:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingDown:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingLeft:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingRight:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidType: 0
upperLeftEyelid: {fileID: 0}
upperRightEyelid: {fileID: 0}
lowerLeftEyelid: {fileID: 0}
lowerRightEyelid: {fileID: 0}
eyelidsDefault:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsClosed:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsLookingUp:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsLookingDown:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsSkinnedMesh: {fileID: 0}
eyelidsBlendshapes:
customizeAnimationLayers: 1
baseAnimationLayers:
- isEnabled: 0
type: 0
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 4
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 5
animatorController: {fileID: 9100000, guid: 1be58bb091803cb488c7005def284caa,
type: 2}
mask: {fileID: 0}
isDefault: 0
specialAnimationLayers:
- isEnabled: 0
type: 6
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 7
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 8
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
AnimationPreset: {fileID: 0}
animationHashSet: []
autoFootsteps: 1
autoLocomotion: 1
collider_head:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_torso:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_footR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_footL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_handR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_handL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerIndexL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerMiddleL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerRingL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerLittleL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerIndexR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerMiddleR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerRingR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerLittleR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
--- !u!114 &3825275463613500752
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3825275463613500755}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3}
m_Name:
m_EditorClassIdentifier:
launchedFromSDKPipeline: 0
completedSDKPipeline: 0
blueprintId:
contentType: 0
assetBundleUnityVersion:
fallbackStatus: 0
--- !u!114 &3825275463971368602
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4167925416990528462}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6fd7cab7d93b403280f2f9da978d8a4f, type: 3}
m_Name:
m_EditorClassIdentifier:
Bindings:
- ReferenceMesh:
referencePath: BaseMesh
Blendshape: shape_0
LocalBlendshape: shape_0_local
- ReferenceMesh:
referencePath: BaseMesh
Blendshape: shape_1
LocalBlendshape: shape_1
- ReferenceMesh:
referencePath: MissingMesh
Blendshape: missing_mesh_shape
LocalBlendshape: missing_mesh_shape
- ReferenceMesh:
referencePath:
Blendshape: missing_mesh_shape_2
LocalBlendshape: missing_mesh_shape_2
--- !u!1001 &3825275463173128406
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 3825275463613500751}
m_Modifications:
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_RootOrder
value: 0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.x
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.y
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.z
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.x
value: -0.023681391
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.y
value: -1.0559628
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.z
value: 0.6872994
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071067
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.x
value: -0.7071068
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_DirtyAABB
value: 0
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.z
value: 1
objectReference: {fileID: 0}
- target: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_Name
value: BaseMesh
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 14ac2ad30c5d3444ca37f76cea5a7047, type: 3}
--- !u!4 &3646968714803193661 stripped
Transform:
m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
m_PrefabInstance: {fileID: 3825275463173128406}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &3825275463971368607
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 3825275463613500751}
m_Modifications:
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.x
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.y
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalScale.z
value: 0.1
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.x
value: -0.023681391
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.y
value: -1.0559628
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalPosition.z
value: 0.6872994
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071067
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.x
value: -0.7071068
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_DirtyAABB
value: 0
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_AABB.m_Extent.z
value: 1
objectReference: {fileID: 0}
- target: {fileID: -3887185075125053422, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_BlendShapeWeights.Array.size
value: 5
objectReference: {fileID: 0}
- target: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
propertyPath: m_Name
value: SyncedMesh
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 14ac2ad30c5d3444ca37f76cea5a7047, type: 3}
--- !u!1 &4167925416990528462 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
m_PrefabInstance: {fileID: 3825275463971368607}
m_PrefabAsset: {fileID: 0}
--- !u!4 &3646968713996568948 stripped
Transform:
m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 14ac2ad30c5d3444ca37f76cea5a7047,
type: 3}
m_PrefabInstance: {fileID: 3825275463971368607}
m_PrefabAsset: {fileID: 0}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: de3f4b692075ea940a363d048dd49d9a
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: BlendshapeSyncTestAnimator
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: 6361318802971715810}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &4455402730181834275
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: BSTAnimation
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: cc4f9e571dfd17546918cebf2097b2ed, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1107 &6361318802971715810
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 4455402730181834275}
m_Position: {x: 640.24945, y: 141.47957, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 4455402730181834275}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1be58bb091803cb488c7005def284caa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,97 @@
fileFormatVersion: 2
guid: 14ac2ad30c5d3444ca37f76cea5a7047
ModelImporter:
serializedVersion: 19301
internalIDToNameTable: []
externalObjects: {}
materials:
materialImportMode: 1
materialName: 0
materialSearch: 1
materialLocation: 1
animations:
legacyGenerateAnimations: 4
bakeSimulation: 0
resampleCurves: 1
optimizeGameObjects: 0
motionNodeName:
rigImportErrors:
rigImportWarnings:
animationImportErrors:
animationImportWarnings:
animationRetargetingWarnings:
animationDoRetargetingWarnings: 0
importAnimatedCustomProperties: 0
importConstraints: 0
animationCompression: 1
animationRotationError: 0.5
animationPositionError: 0.5
animationScaleError: 0.5
animationWrapMode: 0
extraExposedTransformPaths: []
extraUserProperties: []
clipAnimations: []
isReadable: 0
meshes:
lODScreenPercentages: []
globalScale: 1
meshCompression: 0
addColliders: 0
useSRGBMaterialColor: 1
sortHierarchyByName: 1
importVisibility: 1
importBlendShapes: 1
importCameras: 1
importLights: 1
fileIdsGeneration: 2
swapUVChannels: 0
generateSecondaryUV: 0
useFileUnits: 1
keepQuads: 0
weldVertices: 1
preserveHierarchy: 0
skinWeightsMode: 0
maxBonesPerVertex: 4
minBoneWeight: 0.001
meshOptimizationFlags: -1
indexFormat: 0
secondaryUVAngleDistortion: 8
secondaryUVAreaDistortion: 15.000001
secondaryUVHardAngle: 88
secondaryUVPackMargin: 4
useFileScale: 1
tangentSpace:
normalSmoothAngle: 60
normalImportMode: 0
tangentImportMode: 3
normalCalculationMode: 4
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
blendShapeNormalImportMode: 1
normalSmoothingSource: 0
referencedClips: []
importAnimation: 1
humanDescription:
serializedVersion: 3
human: []
skeleton: []
armTwist: 0.5
foreArmTwist: 0.5
upperLegTwist: 0.5
legTwist: 0.5
armStretch: 0.05
legStretch: 0.05
feetSpacing: 0
globalScale: 1
rootMotionBoneName:
hasTranslationDoF: 0
hasExtraRoot: 0
skeletonHasParents: 1
lastHumanDescriptionAvatarSource: {instanceID: 0}
autoGenerateAvatarMappingIfUnspecified: 1
animationType: 2
humanoidOversampling: 1
avatarSetup: 0
additionalBone: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -55,7 +55,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null); new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext)); context.ActivateExtensionContext<AnimationServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null); Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
@ -83,7 +83,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null); new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext)); context.ActivateExtensionContext<AnimationServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
@ -107,7 +107,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null); new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext)); context.ActivateExtensionContext<AnimationServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass Assert.IsTrue(m_bone == null); // destroyed by retargeting pass

View File

@ -11,13 +11,13 @@ namespace modular_avatar_tests.ReplaceObject
{ {
public class ReplaceObjectTests : TestBase public class ReplaceObjectTests : TestBase
{ {
private TrackObjectRenamesContext pathMappings; private PathMappings pathMappings;
void Process(GameObject root) void Process(GameObject root)
{ {
var avDesc = root.GetComponent<VRCAvatarDescriptor>(); var avDesc = root.GetComponent<VRCAvatarDescriptor>();
var buildContext = new nadena.dev.ndmf.BuildContext(avDesc, null); var buildContext = new nadena.dev.ndmf.BuildContext(avDesc, null);
pathMappings = buildContext.ActivateExtensionContext<TrackObjectRenamesContext>(); pathMappings = buildContext.ActivateExtensionContext<AnimationServicesContext>().PathMappings;
new ReplaceObjectPass(buildContext).Process(); new ReplaceObjectPass(buildContext).Process();
} }

View File

@ -23,13 +23,13 @@ namespace modular_avatar_tests
var build_context = var build_context =
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null); new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
var torc = new TrackObjectRenamesContext(); var torc = new AnimationServicesContext();
torc.OnActivate(build_context); torc.OnActivate(build_context);
var bonedb = new BoneDatabase(); var bonedb = new BoneDatabase();
bonedb.AddMergedBone(b.transform); bonedb.AddMergedBone(b.transform);
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc); new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc.PathMappings);
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone); Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
} }
@ -50,13 +50,13 @@ namespace modular_avatar_tests
var build_context = var build_context =
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null); new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
var torc = new TrackObjectRenamesContext(); var torc = new AnimationServicesContext();
torc.OnActivate(build_context); torc.OnActivate(build_context);
var bonedb = new BoneDatabase(); var bonedb = new BoneDatabase();
bonedb.AddMergedBone(b.transform); bonedb.AddMergedBone(b.transform);
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc); new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc.PathMappings);
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone); Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)), Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)),

View File

@ -14,10 +14,10 @@ public class WorldFixedObjectTest : TestBase
var descriptor = avatar.GetComponent<VRCAvatarDescriptor>(); var descriptor = avatar.GetComponent<VRCAvatarDescriptor>();
var fixedObject = avatar.transform.Find("FixedObject"); var fixedObject = avatar.transform.Find("FixedObject");
// initialize context // initialize context
var buildContext = new BuildContext(descriptor); var buildContext = new BuildContext(descriptor);
buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>(); buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
new WorldFixedObjectProcessor(descriptor).Process(buildContext); new WorldFixedObjectProcessor(descriptor).Process(buildContext);
@ -32,7 +32,7 @@ public class WorldFixedObjectTest : TestBase
Assert.That(movedFixedObject, Is.Not.Null); Assert.That(movedFixedObject, Is.Not.Null);
Assert.That(movedFixedObject, Is.EqualTo(fixedObject)); Assert.That(movedFixedObject, Is.EqualTo(fixedObject));
} }
[Test] [Test]
public void NestedTest() public void NestedTest()
{ {
@ -41,10 +41,10 @@ public class WorldFixedObjectTest : TestBase
var fixedObject = avatar.transform.Find("FixedObject"); var fixedObject = avatar.transform.Find("FixedObject");
var nestedFixed = avatar.transform.Find("FixedObject/NestedFixed"); var nestedFixed = avatar.transform.Find("FixedObject/NestedFixed");
// initialize context // initialize context
var buildContext = new BuildContext(descriptor); var buildContext = new BuildContext(descriptor);
buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>(); buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
new WorldFixedObjectProcessor(descriptor).Process(buildContext); new WorldFixedObjectProcessor(descriptor).Process(buildContext);
@ -64,4 +64,4 @@ public class WorldFixedObjectTest : TestBase
Assert.That(nestedFixedObject, Is.Not.Null); Assert.That(nestedFixedObject, Is.Not.Null);
Assert.That(nestedFixedObject, Is.EqualTo(nestedFixed)); Assert.That(nestedFixedObject, Is.EqualTo(nestedFixed));
} }
} }

View File

@ -16,7 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
private readonly BuildContext _context; private readonly BuildContext _context;
private readonly BoneDatabase _boneDatabase; private readonly BoneDatabase _boneDatabase;
private readonly TrackObjectRenamesContext _pathMappings; private readonly PathMappings _pathMappings;
private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>(); private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>();
/// <summary> /// <summary>
@ -55,7 +55,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
_context = context; _context = context;
_boneDatabase = boneDatabase; _boneDatabase = boneDatabase;
_pathMappings = context.PluginBuildContext.Extension<TrackObjectRenamesContext>(); _pathMappings = context.PluginBuildContext.Extension<AnimationServicesContext>().PathMappings;
while (root != null && root.GetComponent<VRCAvatarDescriptor>() == null) while (root != null && root.GetComponent<VRCAvatarDescriptor>() == null)
{ {
@ -74,7 +74,7 @@ namespace nadena.dev.modular_avatar.core.editor
Created = new List<GameObject>(), Created = new List<GameObject>(),
}); });
} }
root = root.parent; root = root.parent;
} }
@ -132,7 +132,7 @@ namespace nadena.dev.modular_avatar.core.editor
foreach (var holder in _context.AnimationDatabase.ClipsForPath(path)) foreach (var holder in _context.AnimationDatabase.ClipsForPath(path))
{ {
if (!Util.IsTemporaryAsset(holder.CurrentClip)) if (!_context.PluginBuildContext.IsTemporaryAsset(holder.CurrentClip))
{ {
holder.CurrentClip = Object.Instantiate(holder.CurrentClip); holder.CurrentClip = Object.Instantiate(holder.CurrentClip);
} }
@ -152,7 +152,7 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
private AnimationCurve GetActiveBinding(AnimationClip clip, string path) private AnimationCurve GetActiveBinding(AnimationClip clip, string path)
{ {
return AnimationUtility.GetEditorCurve(clip, return AnimationUtility.GetEditorCurve(clip,

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Data.Odbc;
using nadena.dev.modular_avatar.core.editor;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
@ -8,28 +10,66 @@ using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.animation
{ {
/// <summary>
/// The animation database records the set of all clips which are used in the avatar, and which paths they
/// manipulate.
/// </summary>
internal class AnimationDatabase internal class AnimationDatabase
{ {
internal class ClipHolder internal class ClipHolder
{ {
internal Motion CurrentClip; private readonly AnimationDatabase ParentDatabase;
private Motion _currentClip;
internal Motion CurrentClip
{
get
{
ParentDatabase.InvalidateCaches();
return _currentClip;
}
set
{
ParentDatabase.InvalidateCaches();
_currentClip = value;
}
}
internal Motion OriginalClip { get; } internal Motion OriginalClip { get; }
internal readonly bool IsProxyAnimation; internal readonly bool IsProxyAnimation;
internal ClipHolder(Motion clip) internal ClipHolder(AnimationDatabase parentDatabase, Motion clip)
{ {
ParentDatabase = parentDatabase;
CurrentClip = OriginalClip = clip; CurrentClip = OriginalClip = clip;
IsProxyAnimation = clip != null && Util.IsProxyAnimation(clip); IsProxyAnimation = clip != null && Util.IsProxyAnimation(clip);
} }
/// <summary>
/// Returns the current clip without invalidating caches. Do not modify this clip without taking extra
/// steps to invalidate caches on the AnimationDatabase.
/// </summary>
/// <returns></returns>
internal Motion GetCurrentClipUnsafe()
{
return _currentClip;
}
} }
private ndmf.BuildContext _context;
private List<Action> _clipCommitActions = new List<Action>(); private List<Action> _clipCommitActions = new List<Action>();
private List<ClipHolder> _clips = new List<ClipHolder>(); private List<ClipHolder> _clips = new List<ClipHolder>();
private Dictionary<string, HashSet<ClipHolder>> _pathToClip = private Dictionary<string, HashSet<ClipHolder>> _pathToClip = null;
new Dictionary<string, HashSet<ClipHolder>>();
internal AnimationDatabase()
{
Debug.Log("Creating animation database");
}
internal void Commit() internal void Commit()
{ {
@ -64,8 +104,14 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
internal void Bootstrap(VRCAvatarDescriptor avatarDescriptor) internal void OnActivate(ndmf.BuildContext context)
{ {
_context = context;
AnimationUtil.CloneAllControllers(context);
var avatarDescriptor = context.AvatarDescriptor;
foreach (var layer in avatarDescriptor.baseAnimationLayers) foreach (var layer in avatarDescriptor.baseAnimationLayers)
{ {
BootstrapLayer(layer); BootstrapLayer(layer);
@ -78,7 +124,8 @@ namespace nadena.dev.modular_avatar.core.editor
void BootstrapLayer(VRCAvatarDescriptor.CustomAnimLayer layer) void BootstrapLayer(VRCAvatarDescriptor.CustomAnimLayer layer)
{ {
if (!layer.isDefault && layer.animatorController is AnimatorController ac && Util.IsTemporaryAsset(ac)) if (!layer.isDefault && layer.animatorController is AnimatorController ac &&
context.IsTemporaryAsset(ac))
{ {
BuildReport.ReportingObject(ac, () => BuildReport.ReportingObject(ac, () =>
{ {
@ -108,7 +155,7 @@ namespace nadena.dev.modular_avatar.core.editor
if (state.motion == null) return; if (state.motion == null) return;
var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder); var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder);
if (!Util.IsTemporaryAsset(state.motion)) if (!_context.IsTemporaryAsset(state.motion))
{ {
// Protect the original animations from mutations by creating temporary clones; in the case of a proxy // Protect the original animations from mutations by creating temporary clones; in the case of a proxy
// animation, we'll restore the original in a later pass // animation, we'll restore the original in a later pass
@ -139,6 +186,8 @@ namespace nadena.dev.modular_avatar.core.editor
/// <returns></returns> /// <returns></returns>
internal ImmutableArray<ClipHolder> ClipsForPath(string path) internal ImmutableArray<ClipHolder> ClipsForPath(string path)
{ {
HydrateCaches();
if (_pathToClip.TryGetValue(path, out var clips)) if (_pathToClip.TryGetValue(path, out var clips))
{ {
return clips.ToImmutableArray(); return clips.ToImmutableArray();
@ -158,7 +207,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
if (motion == null) if (motion == null)
{ {
return new ClipHolder(null); return new ClipHolder(this, null);
} }
if (originalToHolder.TryGetValue(motion, out var holder)) if (originalToHolder.TryGetValue(motion, out var holder))
@ -166,13 +215,14 @@ namespace nadena.dev.modular_avatar.core.editor
return holder; return holder;
} }
InvalidateCaches();
switch (motion) switch (motion)
{ {
case AnimationClip clip: case AnimationClip clip:
{ {
holder = new ClipHolder(clip); holder = new ClipHolder(this, clip);
processClip(holder); processClip(holder);
recordPaths(holder);
_clips.Add(holder); _clips.Add(holder);
break; break;
} }
@ -187,9 +237,26 @@ namespace nadena.dev.modular_avatar.core.editor
return holder; return holder;
} }
private void InvalidateCaches()
{
_pathToClip = null;
}
private void HydrateCaches()
{
if (_pathToClip == null)
{
_pathToClip = new Dictionary<string, HashSet<ClipHolder>>();
foreach (var clip in _clips)
{
recordPaths(clip);
}
}
}
private void recordPaths(ClipHolder holder) private void recordPaths(ClipHolder holder)
{ {
var clip = holder.CurrentClip as AnimationClip; var clip = holder.GetCurrentClipUnsafe() as AnimationClip;
foreach (var binding in AnimationUtility.GetCurveBindings(clip)) foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{ {
@ -222,12 +289,12 @@ namespace nadena.dev.modular_avatar.core.editor
Dictionary<Motion, ClipHolder> originalToHolder Dictionary<Motion, ClipHolder> originalToHolder
) )
{ {
if (!Util.IsTemporaryAsset(tree)) if (!_context.IsTemporaryAsset(tree))
{ {
throw new Exception("Blendtree must be a temporary asset"); throw new Exception("Blendtree must be a temporary asset");
} }
var treeHolder = new ClipHolder(tree); var treeHolder = new ClipHolder(this, tree);
var children = tree.children; var children = tree.children;
var holders = new ClipHolder[children.Length]; var holders = new ClipHolder[children.Length];

View File

@ -0,0 +1,69 @@
using System;
using nadena.dev.ndmf;
namespace nadena.dev.modular_avatar.animation
{
/// <summary>
/// This extension context amortizes a number of animation-related processing steps - notably,
/// collecting the set of all animation clips from the animators, and committing changes to them
/// in a deferred manner.
///
/// Restrictions: While this context is active, any changes to clips must be done by editing them via
/// ClipHolders in the AnimationDatabase. Any newly added clips must be registered in the AnimationDatabase,
/// and any new references to clips require setting appropriate ClipCommitActions.
///
/// New references to objects created in clips must use paths obtained from the
/// ObjectRenameTracker.GetObjectIdentifier method.
/// </summary>
internal sealed class AnimationServicesContext : IExtensionContext
{
private AnimationDatabase _animationDatabase;
private PathMappings _pathMappings;
public void OnActivate(BuildContext context)
{
_animationDatabase = new AnimationDatabase();
_animationDatabase.OnActivate(context);
_pathMappings = new PathMappings();
_pathMappings.OnActivate(context, _animationDatabase);
}
public void OnDeactivate(BuildContext context)
{
_pathMappings.OnDeactivate(context);
_animationDatabase.Commit();
_pathMappings = null;
_animationDatabase = null;
}
public AnimationDatabase AnimationDatabase
{
get
{
if (_animationDatabase == null)
{
throw new InvalidOperationException(
"AnimationDatabase is not available outside of the AnimationServicesContext");
}
return _animationDatabase;
}
}
public PathMappings PathMappings
{
get
{
if (_pathMappings == null)
{
throw new InvalidOperationException(
"ObjectRenameTracker is not available outside of the AnimationServicesContext");
}
return _pathMappings;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2c26040d44d4dacb838aceced3b3e52
timeCreated: 1696063949

View File

@ -27,6 +27,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.core.editor;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -67,7 +68,7 @@ namespace nadena.dev.modular_avatar.animation
if (!EditorUtility.IsPersistent(assetContainer) || if (!EditorUtility.IsPersistent(assetContainer) ||
string.IsNullOrEmpty(AssetDatabase.GetAssetPath(assetContainer))) string.IsNullOrEmpty(AssetDatabase.GetAssetPath(assetContainer)))
{ {
Debug.Log("Nonpersistent asset container: " + assetContainer.name); // Debug.Log("Nonpersistent asset container: " + assetContainer.name);
} }
else else
{ {

View File

@ -12,6 +12,6 @@ namespace nadena.dev.modular_avatar.animation
/// </summary> /// </summary>
internal interface IOnCommitObjectRenames internal interface IOnCommitObjectRenames
{ {
void OnCommitObjectRenames(BuildContext buildContext, TrackObjectRenamesContext renameContext); void OnCommitObjectRenames(BuildContext buildContext, PathMappings renameContext);
} }
} }

View File

@ -1,15 +1,12 @@
#region #region
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.util; using nadena.dev.ndmf.util;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
#endregion #endregion
@ -27,8 +24,10 @@ namespace nadena.dev.modular_avatar.animation
/// Users of this context need to be aware that, when creating new curves (or otherwise introducing new motions, /// Users of this context need to be aware that, when creating new curves (or otherwise introducing new motions,
/// use context.ObjectPath to obtain a suitable path for the target objects). /// use context.ObjectPath to obtain a suitable path for the target objects).
/// </summary> /// </summary>
internal sealed class TrackObjectRenamesContext : IExtensionContext internal sealed class PathMappings
{ {
private AnimationDatabase _animationDatabase;
private Dictionary<GameObject, List<string>> private Dictionary<GameObject, List<string>>
_objectToOriginalPaths = new Dictionary<GameObject, List<string>>(); _objectToOriginalPaths = new Dictionary<GameObject, List<string>>();
@ -36,8 +35,9 @@ namespace nadena.dev.modular_avatar.animation
private ImmutableDictionary<string, string> _originalPathToMappedPath = null; private ImmutableDictionary<string, string> _originalPathToMappedPath = null;
private ImmutableDictionary<string, string> _transformOriginalPathToMappedPath = null; private ImmutableDictionary<string, string> _transformOriginalPathToMappedPath = null;
public void OnActivate(BuildContext context) internal void OnActivate(BuildContext context, AnimationDatabase animationDatabase)
{ {
_animationDatabase = animationDatabase;
_objectToOriginalPaths.Clear(); _objectToOriginalPaths.Clear();
_transformLookthroughObjects.Clear(); _transformLookthroughObjects.Clear();
ClearCache(); ClearCache();
@ -195,74 +195,6 @@ namespace nadena.dev.modular_avatar.animation
} }
} }
public RuntimeAnimatorController ApplyMappingsToAnimator(
BuildContext context,
RuntimeAnimatorController controller,
Dictionary<AnimationClip, AnimationClip> clipCache = null)
{
if (clipCache == null)
{
clipCache = new Dictionary<AnimationClip, AnimationClip>();
}
if (controller == null) return null;
switch (controller)
{
case AnimatorController ac:
if (!context.IsTemporaryAsset(ac))
{
ac = AnimationUtil.DeepCloneAnimator(context, ac);
}
foreach (var asset in ac.ReferencedAssets())
{
if (asset is AnimatorState state)
{
if (state.motion is AnimationClip clip)
{
state.motion = ApplyMappingsToClip(clip, clipCache);
}
}
else if (asset is BlendTree tree)
{
var children = tree.children;
for (int i = 0; i < children.Length; i++)
{
var child = children[i];
if (child.motion is AnimationClip clip)
{
child.motion = ApplyMappingsToClip(clip, clipCache);
}
}
tree.children = children;
}
}
return ac;
case AnimatorOverrideController aoc:
{
AnimatorOverrideController newController = new AnimatorOverrideController();
newController.runtimeAnimatorController =
ApplyMappingsToAnimator(context, aoc.runtimeAnimatorController);
List<KeyValuePair<AnimationClip, AnimationClip>> overrides =
new List<KeyValuePair<AnimationClip, AnimationClip>>();
overrides = overrides.Select(kvp =>
new KeyValuePair<AnimationClip, AnimationClip>(kvp.Key,
ApplyMappingsToClip(kvp.Value, clipCache)))
.ToList();
newController.ApplyOverrides(overrides);
return newController;
}
default:
throw new Exception("Unknown animator controller type: " + controller.GetType().Name);
}
}
private string MapPath(EditorCurveBinding binding) private string MapPath(EditorCurveBinding binding)
{ {
if (binding.type == typeof(Animator) && binding.path == "") if (binding.type == typeof(Animator) && binding.path == "")
@ -276,13 +208,29 @@ namespace nadena.dev.modular_avatar.animation
} }
private AnimationClip ApplyMappingsToClip(AnimationClip originalClip, private AnimationClip ApplyMappingsToClip(AnimationClip originalClip,
Dictionary<AnimationClip, AnimationClip> clipCache = null) Dictionary<AnimationClip, AnimationClip> clipCache)
{ {
if (originalClip == null) return null; if (originalClip == null) return null;
if (clipCache != null && clipCache.TryGetValue(originalClip, out var cachedClip)) return cachedClip; if (clipCache != null && clipCache.TryGetValue(originalClip, out var cachedClip)) return cachedClip;
if (originalClip.IsProxyAnimation()) return originalClip; if (originalClip.IsProxyAnimation()) return originalClip;
var curveBindings = AnimationUtility.GetCurveBindings(originalClip);
var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(originalClip);
bool hasMapping = false;
foreach (var binding in curveBindings.Concat(objectBindings))
{
if (MapPath(binding) != binding.path)
{
hasMapping = true;
break;
}
}
if (!hasMapping) return originalClip;
var newClip = new AnimationClip(); var newClip = new AnimationClip();
newClip.name = originalClip.name; newClip.name = originalClip.name;
@ -296,7 +244,7 @@ namespace nadena.dev.modular_avatar.animation
after.ApplyModifiedPropertiesWithoutUndo(); after.ApplyModifiedPropertiesWithoutUndo();
// TODO - should we use direct SerializedObject manipulation to avoid missing script issues? // TODO - should we use direct SerializedObject manipulation to avoid missing script issues?
foreach (var binding in AnimationUtility.GetCurveBindings(originalClip)) foreach (var binding in curveBindings)
{ {
var newBinding = binding; var newBinding = binding;
newBinding.path = MapPath(binding); newBinding.path = MapPath(binding);
@ -304,7 +252,7 @@ namespace nadena.dev.modular_avatar.animation
AnimationUtility.GetEditorCurve(originalClip, binding)); AnimationUtility.GetEditorCurve(originalClip, binding));
} }
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(originalClip)) foreach (var objBinding in objectBindings)
{ {
var newBinding = objBinding; var newBinding = objBinding;
newBinding.path = MapPath(objBinding); newBinding.path = MapPath(objBinding);
@ -326,36 +274,22 @@ namespace nadena.dev.modular_avatar.animation
return newClip; return newClip;
} }
public void OnDeactivate(BuildContext context) internal void OnDeactivate(BuildContext context)
{ {
context.AvatarDescriptor.baseAnimationLayers = Dictionary<AnimationClip, AnimationClip> clipCache = new Dictionary<AnimationClip, AnimationClip>();
MapLayers(context, context.AvatarDescriptor.baseAnimationLayers);
context.AvatarDescriptor.specialAnimationLayers = _animationDatabase.ForeachClip(holder =>
MapLayers(context, context.AvatarDescriptor.specialAnimationLayers); {
if (holder.CurrentClip is AnimationClip clip)
{
holder.CurrentClip = ApplyMappingsToClip(clip, clipCache);
}
});
foreach (var listener in context.AvatarRootObject.GetComponentsInChildren<IOnCommitObjectRenames>()) foreach (var listener in context.AvatarRootObject.GetComponentsInChildren<IOnCommitObjectRenames>())
{ {
listener.OnCommitObjectRenames(context, this); listener.OnCommitObjectRenames(context, this);
} }
} }
// TODO: port test AnimatesAddedBones from MA
private VRCAvatarDescriptor.CustomAnimLayer[] MapLayers(
BuildContext buildContext,
VRCAvatarDescriptor.CustomAnimLayer[] layers
)
{
if (layers == null) return null;
for (int i = 0; i < layers.Length; i++)
{
var layer = layers[i];
layer.animatorController = ApplyMappingsToAnimator(buildContext, layer.animatorController);
layers[i] = layer;
}
return layers;
}
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using nadena.dev.modular_avatar.animation;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -14,17 +15,23 @@ namespace nadena.dev.modular_avatar.core.editor
internal readonly nadena.dev.ndmf.BuildContext PluginBuildContext; internal readonly nadena.dev.ndmf.BuildContext PluginBuildContext;
internal VRCAvatarDescriptor AvatarDescriptor => PluginBuildContext.AvatarDescriptor; internal VRCAvatarDescriptor AvatarDescriptor => PluginBuildContext.AvatarDescriptor;
internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase();
internal AnimationDatabase AnimationDatabase =>
PluginBuildContext.Extension<AnimationServicesContext>().AnimationDatabase;
internal PathMappings PathMappings =>
PluginBuildContext.Extension<AnimationServicesContext>().PathMappings;
internal UnityEngine.Object AssetContainer => PluginBuildContext.AssetContainer; internal UnityEngine.Object AssetContainer => PluginBuildContext.AssetContainer;
private bool SaveImmediate = false; private bool SaveImmediate = false;
internal readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> ClonedMenus internal readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> ClonedMenus
= new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>(); = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
public static implicit operator BuildContext(ndmf.BuildContext ctx) => public static implicit operator BuildContext(ndmf.BuildContext ctx) =>
ctx.Extension<ModularAvatarContext>().BuildContext; ctx.Extension<ModularAvatarContext>().BuildContext;
/// <summary> /// <summary>
/// This dictionary overrides the _original contents_ of ModularAvatarMenuInstallers. Notably, this does not /// This dictionary overrides the _original contents_ of ModularAvatarMenuInstallers. Notably, this does not
/// replace the source menu for the purposes of identifying any other MAMIs that might install to the same /// replace the source menu for the purposes of identifying any other MAMIs that might install to the same

View File

@ -14,18 +14,18 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
private const string DEFAULT_EXP_MENU_GUID = "024fb8ef5b3988c46b446863c92f4522"; private const string DEFAULT_EXP_MENU_GUID = "024fb8ef5b3988c46b446863c92f4522";
private const string DEFAULT_EXP_PARAM_GUID = "03a6d797deb62f0429471c4e17ea99a7"; private const string DEFAULT_EXP_PARAM_GUID = "03a6d797deb62f0429471c4e17ea99a7";
internal static void FixupExpressionsMenu(BuildContext context) internal static void FixupExpressionsMenu(BuildContext context)
{ {
context.AvatarDescriptor.customExpressions = true; context.AvatarDescriptor.customExpressions = true;
var expressionsMenu = context.AvatarDescriptor.expressionsMenu; var expressionsMenu = context.AvatarDescriptor.expressionsMenu;
if (expressionsMenu == null) if (expressionsMenu == null)
{ {
var defaultExpMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>( var defaultExpMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_MENU_GUID) AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_MENU_GUID)
); );
expressionsMenu = Object.Instantiate(defaultExpMenu); expressionsMenu = Object.Instantiate(defaultExpMenu);
context.AvatarDescriptor.expressionsMenu = expressionsMenu; context.AvatarDescriptor.expressionsMenu = expressionsMenu;
} }
@ -35,20 +35,20 @@ namespace nadena.dev.modular_avatar.core.editor
var defaultExpParam = AssetDatabase.LoadAssetAtPath<VRCExpressionParameters>( var defaultExpParam = AssetDatabase.LoadAssetAtPath<VRCExpressionParameters>(
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_PARAM_GUID) AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_PARAM_GUID)
); );
context.AvatarDescriptor.expressionParameters = Object.Instantiate(defaultExpParam); context.AvatarDescriptor.expressionParameters = Object.Instantiate(defaultExpParam);
} }
var parameters = context.AvatarDescriptor.expressionParameters.parameters var parameters = context.AvatarDescriptor.expressionParameters.parameters
?? new VRCExpressionParameters.Parameter[0]; ?? new VRCExpressionParameters.Parameter[0];
var parameterNames = parameters.Select(p=> p.name).ToImmutableHashSet(); var parameterNames = parameters.Select(p => p.name).ToImmutableHashSet();
if (!Util.IsTemporaryAsset(expressionsMenu)) if (!context.PluginBuildContext.IsTemporaryAsset(expressionsMenu))
{ {
expressionsMenu = context.CloneMenu(expressionsMenu); expressionsMenu = context.CloneMenu(expressionsMenu);
context.AvatarDescriptor.expressionsMenu = expressionsMenu; context.AvatarDescriptor.expressionsMenu = expressionsMenu;
} }
// Walk menu recursively // Walk menu recursively
var visitedMenus = new HashSet<VRCExpressionsMenu>(); var visitedMenus = new HashSet<VRCExpressionsMenu>();
var iconMapping = new Dictionary<Texture2D, Texture2D>(); var iconMapping = new Dictionary<Texture2D, Texture2D>();
@ -68,7 +68,8 @@ namespace nadena.dev.modular_avatar.core.editor
control.parameter.name = ""; control.parameter.name = "";
} }
foreach (var subParam in control.subParameters ?? Array.Empty<VRCExpressionsMenu.Control.Parameter>()) foreach (var subParam in control.subParameters ??
Array.Empty<VRCExpressionsMenu.Control.Parameter>())
{ {
if (subParam != null && if (subParam != null &&
!string.IsNullOrEmpty(subParam.name) && !string.IsNullOrEmpty(subParam.name) &&
@ -93,7 +94,7 @@ namespace nadena.dev.modular_avatar.core.editor
for (int i = 0; i < control.labels.Length; i++) for (int i = 0; i < control.labels.Length; i++)
{ {
var label = control.labels[i]; var label = control.labels[i];
if (label.icon != null) if (label.icon != null)
{ {
if (!iconMapping.TryGetValue(label.icon, out var newIcon)) if (!iconMapping.TryGetValue(label.icon, out var newIcon))
@ -110,29 +111,29 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
#if UNITY_ANDROID #if UNITY_ANDROID
private const TextureFormat TargetFormat = TextureFormat.ASTC_4x4; private const TextureFormat TargetFormat = TextureFormat.ASTC_4x4;
#else #else
private const TextureFormat TargetFormat = TextureFormat.DXT5; private const TextureFormat TargetFormat = TextureFormat.DXT5;
#endif #endif
private static Texture2D MaybeScaleIcon(BuildContext context, Texture2D original) private static Texture2D MaybeScaleIcon(BuildContext context, Texture2D original)
{ {
if (original.width <= 256 && original.height <= 256 && IsCompressedFormat(original.format)) if (original.width <= 256 && original.height <= 256 && IsCompressedFormat(original.format))
{ {
return original; return original;
} }
var newRatio = Math.Min(256f / original.width, 256f / original.height); var newRatio = Math.Min(256f / original.width, 256f / original.height);
var newWidth = Math.Min(256, Mathf.RoundToInt(original.width * newRatio)); var newWidth = Math.Min(256, Mathf.RoundToInt(original.width * newRatio));
var newHeight = Math.Min(256, Mathf.RoundToInt(original.height * newRatio)); var newHeight = Math.Min(256, Mathf.RoundToInt(original.height * newRatio));
var newTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, true); var newTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, true);
context.SaveAsset(newTex); context.SaveAsset(newTex);
var tmpRenderTex = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.ARGB32); var tmpRenderTex = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.ARGB32);
var originalActiveRenderTex = RenderTexture.active; var originalActiveRenderTex = RenderTexture.active;
try try
{ {
Graphics.Blit(original, tmpRenderTex); Graphics.Blit(original, tmpRenderTex);

View File

@ -42,7 +42,8 @@ namespace nadena.dev.modular_avatar.core.editor
private BuildContext context; private BuildContext context;
private BoneDatabase BoneDatabase = new BoneDatabase(); private BoneDatabase BoneDatabase = new BoneDatabase();
private TrackObjectRenamesContext PathMappings => frameworkContext.Extension<TrackObjectRenamesContext>(); private PathMappings PathMappings => frameworkContext.Extension<AnimationServicesContext>()
.PathMappings;
private HashSet<Transform> mergedObjects = new HashSet<Transform>(); private HashSet<Transform> mergedObjects = new HashSet<Transform>();
private HashSet<Transform> thisPassAdded = new HashSet<Transform>(); private HashSet<Transform> thisPassAdded = new HashSet<Transform>();

View File

@ -84,10 +84,10 @@ namespace nadena.dev.modular_avatar.core.editor
internal class RetargetMeshes internal class RetargetMeshes
{ {
private BoneDatabase _boneDatabase; private BoneDatabase _boneDatabase;
private TrackObjectRenamesContext _pathTracker; private PathMappings _pathTracker;
internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase, internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase,
TrackObjectRenamesContext pathMappings) PathMappings pathMappings)
{ {
this._boneDatabase = boneDatabase; this._boneDatabase = boneDatabase;
this._pathTracker = pathMappings; this._pathTracker = pathMappings;

View File

@ -36,7 +36,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
seq.Run(RenameParametersPluginPass.Instance); seq.Run(RenameParametersPluginPass.Instance);
seq.Run(MergeAnimatorPluginPass.Instance); seq.Run(MergeAnimatorPluginPass.Instance);
seq.Run(MenuInstallPluginPass.Instance); seq.Run(MenuInstallPluginPass.Instance);
seq.WithRequiredExtension(typeof(TrackObjectRenamesContext), _s2 => seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
{ {
seq.Run(MergeArmaturePluginPass.Instance); seq.Run(MergeArmaturePluginPass.Instance);
seq.Run(BoneProxyPluginPass.Instance); seq.Run(BoneProxyPluginPass.Instance);
@ -45,8 +45,8 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
ctx => new WorldFixedObjectProcessor(ctx.AvatarDescriptor).Process(ctx) ctx => new WorldFixedObjectProcessor(ctx.AvatarDescriptor).Process(ctx)
); );
seq.Run(ReplaceObjectPluginPass.Instance); seq.Run(ReplaceObjectPluginPass.Instance);
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
}); });
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
seq.Run(PhysbonesBlockerPluginPass.Instance); seq.Run(PhysbonesBlockerPluginPass.Instance);
seq.Run("Fixup Expressions Menu", ctx => seq.Run("Fixup Expressions Menu", ctx =>
{ {
@ -170,11 +170,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
{ {
protected override void Execute(ndmf.BuildContext context) protected override void Execute(ndmf.BuildContext context)
{ {
// The animation database is currently only used by the merge armature hook; it should probably become
// an extension context instead.
MAContext(context).AnimationDatabase.Bootstrap(context.AvatarDescriptor);
new MergeArmatureHook().OnPreprocessAvatar(context, context.AvatarRootObject); new MergeArmatureHook().OnPreprocessAvatar(context, context.AvatarRootObject);
MAContext(context).AnimationDatabase.Commit();
} }
} }

View File

@ -232,7 +232,7 @@ namespace nadena.dev.modular_avatar.core.editor
var queue = new Queue<AnimatorStateMachine>(); var queue = new Queue<AnimatorStateMachine>();
// Deep clone the animator // Deep clone the animator
if (!Util.IsTemporaryAsset(controller)) if (!_context.PluginBuildContext.IsTemporaryAsset(controller))
{ {
controller = _context.DeepCloneAnimator(controller); controller = _context.DeepCloneAnimator(controller);
} }

View File

@ -123,7 +123,7 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
_buildContext.Extension<TrackObjectRenamesContext>() _buildContext.Extension<AnimationServicesContext>().PathMappings
.ReplaceObject(original, replacement); .ReplaceObject(original, replacement);
// Destroy original // Destroy original

View File

@ -190,15 +190,6 @@ namespace nadena.dev.modular_avatar.core.editor
}; };
} }
public static bool IsTemporaryAsset(Object obj)
{
var path = AssetDatabase.GetAssetPath(obj);
var generatedAssetsFolder = OverridePath ?? generatedAssetsPath;
return !EditorUtility.IsPersistent(obj) || string.IsNullOrEmpty(path) ||
path.StartsWith(generatedAssetsFolder + "/");
}
public static Type FindType(string typeName) public static Type FindType(string typeName)
{ {
Type avatarValidation = null; Type avatarValidation = null;