mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 02:35:06 +08:00
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:
parent
46c1f7ce49
commit
c454bc1ed8
@ -9,15 +9,15 @@ using VRC.SDK3.Avatars.Components;
|
||||
public class ActiveAnimationRetargeterTests : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void SimpleRetarget() {
|
||||
public void SimpleRetarget()
|
||||
{
|
||||
var avatar = CreatePrefab("SimpleRetarget.prefab");
|
||||
var descriptor = avatar.GetComponent<VRCAvatarDescriptor>();
|
||||
|
||||
// initialize context
|
||||
var buildContext = new BuildContext(descriptor);
|
||||
var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||
new MergeAnimatorProcessor().OnPreprocessAvatar(avatar, buildContext); // we need this for AnimationDatabase
|
||||
buildContext.AnimationDatabase.Bootstrap(descriptor);
|
||||
var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>()
|
||||
.PathMappings;
|
||||
|
||||
// get game objects
|
||||
var changedChild = avatar.transform.Find("Toggled/Child");
|
||||
|
@ -12,8 +12,8 @@ namespace modular_avatar_tests
|
||||
{
|
||||
var prefab = CreatePrefab("HighQualityCurvesSettingPreserved.prefab");
|
||||
var context = new BuildContext(prefab, null);
|
||||
context.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||
context.DeactivateExtensionContext<TrackObjectRenamesContext>();
|
||||
context.ActivateExtensionContext<AnimationServicesContext>();
|
||||
context.DeactivateExtensionContext<AnimationServicesContext>();
|
||||
|
||||
var layer = findFxLayer(prefab, "Base Layer");
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace modular_avatar_tests
|
||||
var av = CreateRoot("root");
|
||||
|
||||
var bc = CreateContext(av);
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
|
||||
toc.OnActivate(bc);
|
||||
toc.OnDeactivate(bc);
|
||||
@ -31,12 +31,12 @@ namespace modular_avatar_tests
|
||||
var root = CreateRoot("root");
|
||||
var a = CreateChild(root, "a");
|
||||
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
toc.OnActivate(CreateContext(root));
|
||||
Assert.AreEqual("a", toc.MapPath("a"));
|
||||
Assert.AreEqual("a", toc.PathMappings.MapPath("a"));
|
||||
a.name = "b";
|
||||
toc.ClearCache();
|
||||
Assert.AreEqual("b", toc.MapPath("a"));
|
||||
toc.PathMappings.ClearCache();
|
||||
Assert.AreEqual("b", toc.PathMappings.MapPath("a"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -46,13 +46,13 @@ namespace modular_avatar_tests
|
||||
var a = CreateChild(root, "a");
|
||||
var b = CreateChild(root, "b");
|
||||
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
toc.OnActivate(CreateContext(root));
|
||||
|
||||
Assert.AreEqual("a", toc.MapPath("a"));
|
||||
Assert.AreEqual("a", toc.PathMappings.MapPath("a"));
|
||||
a.transform.parent = b.transform;
|
||||
toc.ClearCache();
|
||||
Assert.AreEqual("b/a", toc.MapPath("a"));
|
||||
toc.PathMappings.ClearCache();
|
||||
Assert.AreEqual("b/a", toc.PathMappings.MapPath("a"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -63,14 +63,14 @@ namespace modular_avatar_tests
|
||||
var b = CreateChild(a, "b");
|
||||
var c = CreateChild(b, "c");
|
||||
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
toc.OnActivate(CreateContext(root));
|
||||
|
||||
toc.MarkRemoved(b);
|
||||
toc.PathMappings.MarkRemoved(b);
|
||||
c.transform.parent = a.transform;
|
||||
UnityObject.DestroyImmediate(b);
|
||||
|
||||
Assert.AreEqual("a/c", toc.MapPath("a/b/c"));
|
||||
Assert.AreEqual("a/c", toc.PathMappings.MapPath("a/b/c"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -82,14 +82,14 @@ namespace modular_avatar_tests
|
||||
var c = CreateChild(b, "c");
|
||||
var d = CreateChild(c, "d");
|
||||
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
toc.OnActivate(CreateContext(root));
|
||||
|
||||
toc.MarkTransformLookthrough(b);
|
||||
toc.MarkTransformLookthrough(c);
|
||||
Assert.AreEqual("a/b/c", toc.MapPath("a/b/c"));
|
||||
Assert.AreEqual("a", toc.MapPath("a/b/c", true));
|
||||
Assert.AreEqual("a/b/c/d", toc.MapPath("a/b/c/d", true));
|
||||
toc.PathMappings.MarkTransformLookthrough(b);
|
||||
toc.PathMappings.MarkTransformLookthrough(c);
|
||||
Assert.AreEqual("a/b/c", toc.PathMappings.MapPath("a/b/c"));
|
||||
Assert.AreEqual("a", toc.PathMappings.MapPath("a/b/c", true));
|
||||
Assert.AreEqual("a/b/c/d", toc.PathMappings.MapPath("a/b/c/d", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -105,10 +105,10 @@ namespace modular_avatar_tests
|
||||
var oldIk = descriptor.specialAnimationLayers.First(l =>
|
||||
l.type == VRCAvatarDescriptor.AnimLayerType.IKPose);
|
||||
|
||||
var toc = new TrackObjectRenamesContext();
|
||||
var toc = new AnimationServicesContext();
|
||||
var buildContext = CreateContext(root);
|
||||
toc.OnActivate(buildContext);
|
||||
toc.MarkTransformLookthrough(child);
|
||||
toc.PathMappings.MarkTransformLookthrough(child);
|
||||
|
||||
parent.name = "p2";
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86c4fb6ff802457990f035f4dd663ffe
|
||||
timeCreated: 1696062676
|
@ -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: []
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc4f9e571dfd17546918cebf2097b2ed
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d7e117ac456499596ff775b10aa3876
|
||||
timeCreated: 1696062881
|
@ -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}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de3f4b692075ea940a363d048dd49d9a
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1be58bb091803cb488c7005def284caa
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
@ -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:
|
@ -55,7 +55,7 @@ namespace modular_avatar_tests.MergeArmatureTests
|
||||
nadena.dev.ndmf.BuildContext context =
|
||||
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||
context.ActivateExtensionContext<AnimationServicesContext>();
|
||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||
|
||||
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
|
||||
@ -83,7 +83,7 @@ namespace modular_avatar_tests.MergeArmatureTests
|
||||
nadena.dev.ndmf.BuildContext context =
|
||||
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||
context.ActivateExtensionContext<AnimationServicesContext>();
|
||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||
|
||||
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||
@ -107,7 +107,7 @@ namespace modular_avatar_tests.MergeArmatureTests
|
||||
nadena.dev.ndmf.BuildContext context =
|
||||
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||
context.ActivateExtensionContext<AnimationServicesContext>();
|
||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||
|
||||
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||
|
@ -11,13 +11,13 @@ namespace modular_avatar_tests.ReplaceObject
|
||||
{
|
||||
public class ReplaceObjectTests : TestBase
|
||||
{
|
||||
private TrackObjectRenamesContext pathMappings;
|
||||
private PathMappings pathMappings;
|
||||
|
||||
void Process(GameObject root)
|
||||
{
|
||||
var avDesc = root.GetComponent<VRCAvatarDescriptor>();
|
||||
var buildContext = new nadena.dev.ndmf.BuildContext(avDesc, null);
|
||||
pathMappings = buildContext.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||
pathMappings = buildContext.ActivateExtensionContext<AnimationServicesContext>().PathMappings;
|
||||
new ReplaceObjectPass(buildContext).Process();
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,13 @@ namespace modular_avatar_tests
|
||||
|
||||
var build_context =
|
||||
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||
var torc = new TrackObjectRenamesContext();
|
||||
var torc = new AnimationServicesContext();
|
||||
torc.OnActivate(build_context);
|
||||
|
||||
var bonedb = new BoneDatabase();
|
||||
bonedb.AddMergedBone(b.transform);
|
||||
|
||||
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc);
|
||||
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc.PathMappings);
|
||||
|
||||
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
|
||||
}
|
||||
@ -50,13 +50,13 @@ namespace modular_avatar_tests
|
||||
|
||||
var build_context =
|
||||
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||
var torc = new TrackObjectRenamesContext();
|
||||
var torc = new AnimationServicesContext();
|
||||
torc.OnActivate(build_context);
|
||||
|
||||
var bonedb = new BoneDatabase();
|
||||
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(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)),
|
||||
|
@ -14,10 +14,10 @@ public class WorldFixedObjectTest : TestBase
|
||||
var descriptor = avatar.GetComponent<VRCAvatarDescriptor>();
|
||||
|
||||
var fixedObject = avatar.transform.Find("FixedObject");
|
||||
|
||||
|
||||
// initialize context
|
||||
var buildContext = new BuildContext(descriptor);
|
||||
buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||
buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
|
||||
|
||||
new WorldFixedObjectProcessor(descriptor).Process(buildContext);
|
||||
|
||||
@ -32,7 +32,7 @@ public class WorldFixedObjectTest : TestBase
|
||||
Assert.That(movedFixedObject, Is.Not.Null);
|
||||
Assert.That(movedFixedObject, Is.EqualTo(fixedObject));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void NestedTest()
|
||||
{
|
||||
@ -41,10 +41,10 @@ public class WorldFixedObjectTest : TestBase
|
||||
|
||||
var fixedObject = avatar.transform.Find("FixedObject");
|
||||
var nestedFixed = avatar.transform.Find("FixedObject/NestedFixed");
|
||||
|
||||
|
||||
// initialize context
|
||||
var buildContext = new BuildContext(descriptor);
|
||||
buildContext.PluginBuildContext.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||
buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
|
||||
|
||||
new WorldFixedObjectProcessor(descriptor).Process(buildContext);
|
||||
|
||||
@ -64,4 +64,4 @@ public class WorldFixedObjectTest : TestBase
|
||||
Assert.That(nestedFixedObject, Is.Not.Null);
|
||||
Assert.That(nestedFixedObject, Is.EqualTo(nestedFixed));
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
private readonly BuildContext _context;
|
||||
private readonly BoneDatabase _boneDatabase;
|
||||
private readonly TrackObjectRenamesContext _pathMappings;
|
||||
private readonly PathMappings _pathMappings;
|
||||
private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>();
|
||||
|
||||
/// <summary>
|
||||
@ -55,7 +55,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
_context = context;
|
||||
_boneDatabase = boneDatabase;
|
||||
_pathMappings = context.PluginBuildContext.Extension<TrackObjectRenamesContext>();
|
||||
_pathMappings = context.PluginBuildContext.Extension<AnimationServicesContext>().PathMappings;
|
||||
|
||||
while (root != null && root.GetComponent<VRCAvatarDescriptor>() == null)
|
||||
{
|
||||
@ -74,7 +74,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Created = new List<GameObject>(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
root = root.parent;
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
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);
|
||||
}
|
||||
@ -152,7 +152,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private AnimationCurve GetActiveBinding(AnimationClip clip, string path)
|
||||
{
|
||||
return AnimationUtility.GetEditorCurve(clip,
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Data.Odbc;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
@ -8,28 +10,66 @@ using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
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 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 readonly bool IsProxyAnimation;
|
||||
|
||||
internal ClipHolder(Motion clip)
|
||||
internal ClipHolder(AnimationDatabase parentDatabase, Motion clip)
|
||||
{
|
||||
ParentDatabase = parentDatabase;
|
||||
CurrentClip = OriginalClip = 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<ClipHolder> _clips = new List<ClipHolder>();
|
||||
|
||||
private Dictionary<string, HashSet<ClipHolder>> _pathToClip =
|
||||
new Dictionary<string, HashSet<ClipHolder>>();
|
||||
private Dictionary<string, HashSet<ClipHolder>> _pathToClip = null;
|
||||
|
||||
internal AnimationDatabase()
|
||||
{
|
||||
Debug.Log("Creating animation database");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
BootstrapLayer(layer);
|
||||
@ -78,7 +124,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
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, () =>
|
||||
{
|
||||
@ -108,7 +155,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (state.motion == null) return;
|
||||
|
||||
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
|
||||
// animation, we'll restore the original in a later pass
|
||||
@ -139,6 +186,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
/// <returns></returns>
|
||||
internal ImmutableArray<ClipHolder> ClipsForPath(string path)
|
||||
{
|
||||
HydrateCaches();
|
||||
|
||||
if (_pathToClip.TryGetValue(path, out var clips))
|
||||
{
|
||||
return clips.ToImmutableArray();
|
||||
@ -158,7 +207,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
if (motion == null)
|
||||
{
|
||||
return new ClipHolder(null);
|
||||
return new ClipHolder(this, null);
|
||||
}
|
||||
|
||||
if (originalToHolder.TryGetValue(motion, out var holder))
|
||||
@ -166,13 +215,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return holder;
|
||||
}
|
||||
|
||||
InvalidateCaches();
|
||||
|
||||
switch (motion)
|
||||
{
|
||||
case AnimationClip clip:
|
||||
{
|
||||
holder = new ClipHolder(clip);
|
||||
holder = new ClipHolder(this, clip);
|
||||
processClip(holder);
|
||||
recordPaths(holder);
|
||||
_clips.Add(holder);
|
||||
break;
|
||||
}
|
||||
@ -187,9 +237,26 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
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)
|
||||
{
|
||||
var clip = holder.CurrentClip as AnimationClip;
|
||||
var clip = holder.GetCurrentClipUnsafe() as AnimationClip;
|
||||
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
||||
{
|
||||
@ -222,12 +289,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Dictionary<Motion, ClipHolder> originalToHolder
|
||||
)
|
||||
{
|
||||
if (!Util.IsTemporaryAsset(tree))
|
||||
if (!_context.IsTemporaryAsset(tree))
|
||||
{
|
||||
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 holders = new ClipHolder[children.Length];
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2c26040d44d4dacb838aceced3b3e52
|
||||
timeCreated: 1696063949
|
@ -27,6 +27,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
@ -67,7 +68,7 @@ namespace nadena.dev.modular_avatar.animation
|
||||
if (!EditorUtility.IsPersistent(assetContainer) ||
|
||||
string.IsNullOrEmpty(AssetDatabase.GetAssetPath(assetContainer)))
|
||||
{
|
||||
Debug.Log("Nonpersistent asset container: " + assetContainer.name);
|
||||
// Debug.Log("Nonpersistent asset container: " + assetContainer.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -12,6 +12,6 @@ namespace nadena.dev.modular_avatar.animation
|
||||
/// </summary>
|
||||
internal interface IOnCommitObjectRenames
|
||||
{
|
||||
void OnCommitObjectRenames(BuildContext buildContext, TrackObjectRenamesContext renameContext);
|
||||
void OnCommitObjectRenames(BuildContext buildContext, PathMappings renameContext);
|
||||
}
|
||||
}
|
@ -1,15 +1,12 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf;
|
||||
using nadena.dev.ndmf.util;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#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,
|
||||
/// use context.ObjectPath to obtain a suitable path for the target objects).
|
||||
/// </summary>
|
||||
internal sealed class TrackObjectRenamesContext : IExtensionContext
|
||||
internal sealed class PathMappings
|
||||
{
|
||||
private AnimationDatabase _animationDatabase;
|
||||
|
||||
private 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> _transformOriginalPathToMappedPath = null;
|
||||
|
||||
public void OnActivate(BuildContext context)
|
||||
internal void OnActivate(BuildContext context, AnimationDatabase animationDatabase)
|
||||
{
|
||||
_animationDatabase = animationDatabase;
|
||||
_objectToOriginalPaths.Clear();
|
||||
_transformLookthroughObjects.Clear();
|
||||
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)
|
||||
{
|
||||
if (binding.type == typeof(Animator) && binding.path == "")
|
||||
@ -276,13 +208,29 @@ namespace nadena.dev.modular_avatar.animation
|
||||
}
|
||||
|
||||
private AnimationClip ApplyMappingsToClip(AnimationClip originalClip,
|
||||
Dictionary<AnimationClip, AnimationClip> clipCache = null)
|
||||
Dictionary<AnimationClip, AnimationClip> clipCache)
|
||||
{
|
||||
if (originalClip == null) return null;
|
||||
if (clipCache != null && clipCache.TryGetValue(originalClip, out var cachedClip)) return cachedClip;
|
||||
|
||||
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();
|
||||
newClip.name = originalClip.name;
|
||||
|
||||
@ -296,7 +244,7 @@ namespace nadena.dev.modular_avatar.animation
|
||||
after.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
// 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;
|
||||
newBinding.path = MapPath(binding);
|
||||
@ -304,7 +252,7 @@ namespace nadena.dev.modular_avatar.animation
|
||||
AnimationUtility.GetEditorCurve(originalClip, binding));
|
||||
}
|
||||
|
||||
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(originalClip))
|
||||
foreach (var objBinding in objectBindings)
|
||||
{
|
||||
var newBinding = objBinding;
|
||||
newBinding.path = MapPath(objBinding);
|
||||
@ -326,36 +274,22 @@ namespace nadena.dev.modular_avatar.animation
|
||||
return newClip;
|
||||
}
|
||||
|
||||
public void OnDeactivate(BuildContext context)
|
||||
internal void OnDeactivate(BuildContext context)
|
||||
{
|
||||
context.AvatarDescriptor.baseAnimationLayers =
|
||||
MapLayers(context, context.AvatarDescriptor.baseAnimationLayers);
|
||||
context.AvatarDescriptor.specialAnimationLayers =
|
||||
MapLayers(context, context.AvatarDescriptor.specialAnimationLayers);
|
||||
Dictionary<AnimationClip, AnimationClip> clipCache = new Dictionary<AnimationClip, AnimationClip>();
|
||||
|
||||
_animationDatabase.ForeachClip(holder =>
|
||||
{
|
||||
if (holder.CurrentClip is AnimationClip clip)
|
||||
{
|
||||
holder.CurrentClip = ApplyMappingsToClip(clip, clipCache);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var listener in context.AvatarRootObject.GetComponentsInChildren<IOnCommitObjectRenames>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.modular_avatar.animation;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
@ -14,17 +15,23 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
internal readonly nadena.dev.ndmf.BuildContext PluginBuildContext;
|
||||
|
||||
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;
|
||||
|
||||
private bool SaveImmediate = false;
|
||||
|
||||
internal readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> ClonedMenus
|
||||
= new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
||||
|
||||
|
||||
public static implicit operator BuildContext(ndmf.BuildContext ctx) =>
|
||||
ctx.Extension<ModularAvatarContext>().BuildContext;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
|
@ -14,18 +14,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
private const string DEFAULT_EXP_MENU_GUID = "024fb8ef5b3988c46b446863c92f4522";
|
||||
private const string DEFAULT_EXP_PARAM_GUID = "03a6d797deb62f0429471c4e17ea99a7";
|
||||
|
||||
|
||||
internal static void FixupExpressionsMenu(BuildContext context)
|
||||
{
|
||||
context.AvatarDescriptor.customExpressions = true;
|
||||
|
||||
|
||||
var expressionsMenu = context.AvatarDescriptor.expressionsMenu;
|
||||
if (expressionsMenu == null)
|
||||
{
|
||||
var defaultExpMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(
|
||||
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_MENU_GUID)
|
||||
);
|
||||
|
||||
|
||||
expressionsMenu = Object.Instantiate(defaultExpMenu);
|
||||
context.AvatarDescriptor.expressionsMenu = expressionsMenu;
|
||||
}
|
||||
@ -35,20 +35,20 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var defaultExpParam = AssetDatabase.LoadAssetAtPath<VRCExpressionParameters>(
|
||||
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_PARAM_GUID)
|
||||
);
|
||||
|
||||
|
||||
context.AvatarDescriptor.expressionParameters = Object.Instantiate(defaultExpParam);
|
||||
}
|
||||
|
||||
|
||||
var parameters = context.AvatarDescriptor.expressionParameters.parameters
|
||||
?? 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);
|
||||
context.AvatarDescriptor.expressionsMenu = expressionsMenu;
|
||||
}
|
||||
|
||||
|
||||
// Walk menu recursively
|
||||
var visitedMenus = new HashSet<VRCExpressionsMenu>();
|
||||
var iconMapping = new Dictionary<Texture2D, Texture2D>();
|
||||
@ -68,7 +68,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
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 &&
|
||||
!string.IsNullOrEmpty(subParam.name) &&
|
||||
@ -93,7 +94,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
for (int i = 0; i < control.labels.Length; i++)
|
||||
{
|
||||
var label = control.labels[i];
|
||||
|
||||
|
||||
if (label.icon != null)
|
||||
{
|
||||
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;
|
||||
#else
|
||||
#else
|
||||
private const TextureFormat TargetFormat = TextureFormat.DXT5;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
private static Texture2D MaybeScaleIcon(BuildContext context, Texture2D original)
|
||||
{
|
||||
if (original.width <= 256 && original.height <= 256 && IsCompressedFormat(original.format))
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
|
||||
var newRatio = Math.Min(256f / original.width, 256f / original.height);
|
||||
var newWidth = Math.Min(256, Mathf.RoundToInt(original.width * newRatio));
|
||||
var newHeight = Math.Min(256, Mathf.RoundToInt(original.height * newRatio));
|
||||
|
||||
var newTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, true);
|
||||
context.SaveAsset(newTex);
|
||||
|
||||
|
||||
var tmpRenderTex = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.ARGB32);
|
||||
var originalActiveRenderTex = RenderTexture.active;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Graphics.Blit(original, tmpRenderTex);
|
||||
|
@ -42,7 +42,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private BuildContext context;
|
||||
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> thisPassAdded = new HashSet<Transform>();
|
||||
|
@ -84,10 +84,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
internal class RetargetMeshes
|
||||
{
|
||||
private BoneDatabase _boneDatabase;
|
||||
private TrackObjectRenamesContext _pathTracker;
|
||||
private PathMappings _pathTracker;
|
||||
|
||||
internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase,
|
||||
TrackObjectRenamesContext pathMappings)
|
||||
PathMappings pathMappings)
|
||||
{
|
||||
this._boneDatabase = boneDatabase;
|
||||
this._pathTracker = pathMappings;
|
||||
|
@ -36,7 +36,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
|
||||
seq.Run(RenameParametersPluginPass.Instance);
|
||||
seq.Run(MergeAnimatorPluginPass.Instance);
|
||||
seq.Run(MenuInstallPluginPass.Instance);
|
||||
seq.WithRequiredExtension(typeof(TrackObjectRenamesContext), _s2 =>
|
||||
seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
|
||||
{
|
||||
seq.Run(MergeArmaturePluginPass.Instance);
|
||||
seq.Run(BoneProxyPluginPass.Instance);
|
||||
@ -45,8 +45,8 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
|
||||
ctx => new WorldFixedObjectProcessor(ctx.AvatarDescriptor).Process(ctx)
|
||||
);
|
||||
seq.Run(ReplaceObjectPluginPass.Instance);
|
||||
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
|
||||
});
|
||||
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
|
||||
seq.Run(PhysbonesBlockerPluginPass.Instance);
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
MAContext(context).AnimationDatabase.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var queue = new Queue<AnimatorStateMachine>();
|
||||
|
||||
// Deep clone the animator
|
||||
if (!Util.IsTemporaryAsset(controller))
|
||||
if (!_context.PluginBuildContext.IsTemporaryAsset(controller))
|
||||
{
|
||||
controller = _context.DeepCloneAnimator(controller);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
_buildContext.Extension<TrackObjectRenamesContext>()
|
||||
_buildContext.Extension<AnimationServicesContext>().PathMappings
|
||||
.ReplaceObject(original, replacement);
|
||||
|
||||
// Destroy original
|
||||
|
@ -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)
|
||||
{
|
||||
Type avatarValidation = null;
|
||||
|
Loading…
Reference in New Issue
Block a user