diff --git a/Assets/_ModularAvatar/EditModeTests/Animation.meta b/Assets/_ModularAvatar/EditModeTests/Animation.meta new file mode 100644 index 00000000..877aad4e --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b58e8719fe4803c42a95ea6a8f8e3108 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim b/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim new file mode 100644 index 00000000..b741669a --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim @@ -0,0 +1,178 @@ +%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: AnimationClip + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: {x: 0, y: 1, z: 0} + inSlope: {x: 0, y: 0, z: 0} + outSlope: {x: 0, y: 0, z: 0} + tangentMode: 0 + weightedMode: 0 + inWeight: {x: 0, y: 0.33333334, z: 0.33333334} + outWeight: {x: 0, y: 0.33333334, z: 0.33333334} + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + path: parent/child + m_ScaleCurves: [] + m_FloatCurves: + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_IsTrigger + path: parent/child + classID: 65 + 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: 3852520779 + attribute: 2693561734 + script: {fileID: 0} + typeID: 65 + customType: 0 + isPPtrCurve: 0 + - serializedVersion: 2 + path: 3852520779 + attribute: 1 + script: {fileID: 0} + typeID: 4 + customType: 0 + 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 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalPosition.x + path: parent/child + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalPosition.y + path: parent/child + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalPosition.z + path: parent/child + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_IsTrigger + path: parent/child + classID: 65 + script: {fileID: 0} + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim.meta b/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim.meta new file mode 100644 index 00000000..da186db9 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/AnimationClip.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5977900ad3e56e442bc83e80a14ff2c4 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab b/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab new file mode 100644 index 00000000..b62ce7ad --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab @@ -0,0 +1,399 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &271914551725279345 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3577530327576854121} + - component: {fileID: 6327882964168735651} + m_Layer: 0 + m_Name: child + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3577530327576854121 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 271914551725279345} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 745080012075344352} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &6327882964168735651 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 271914551725279345} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &3009138964498208574 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6202426740457961026} + - component: {fileID: 1135328136970846733} + - component: {fileID: 3344975340744285616} + - component: {fileID: 1383493159385676019} + m_Layer: 0 + m_Name: BasicObjectReferenceTest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6202426740457961026 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3009138964498208574} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.3337704, y: 0.13635278, z: 1.4326048} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 745080012075344352} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &1135328136970846733 +Animator: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3009138964498208574} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + 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 &3344975340744285616 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3009138964498208574} + 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: 54530ed85eedf574487742e93f56ba26, + 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: 9100000, guid: 54530ed85eedf574487742e93f56ba26, + type: 2} + mask: {fileID: 0} + isDefault: 0 + 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 &1383493159385676019 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3009138964498208574} + 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!1 &6481973405510051602 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 745080012075344352} + m_Layer: 0 + m_Name: parent + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &745080012075344352 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6481973405510051602} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 3577530327576854121} + m_Father: {fileID: 6202426740457961026} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab.meta b/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab.meta new file mode 100644 index 00000000..f339fca4 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/BasicObjectReferenceTest.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 672a59e472adcc944a5a04cd1400d49b +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim new file mode 100644 index 00000000..2d9ed074 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim @@ -0,0 +1,98 @@ +%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: HQ_OFF + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 0 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_Enabled + path: + classID: 114 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + 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: 0 + attribute: 3305885265 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + typeID: 114 + customType: 24 + 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: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_Enabled + path: + classID: 114 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim.meta b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim.meta new file mode 100644 index 00000000..4b9be87f --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_OFF.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d16d7af2be036d4e9867b4314772f65 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim new file mode 100644 index 00000000..930700f8 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim @@ -0,0 +1,98 @@ +%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: HQ_ON + 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: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_Enabled + path: + classID: 114 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + 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: 0 + attribute: 3305885265 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + typeID: 114 + customType: 24 + 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: 1 + inSlope: Infinity + outSlope: Infinity + tangentMode: 103 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_Enabled + path: + classID: 114 + script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim.meta b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim.meta new file mode 100644 index 00000000..53ca9a3b --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HQ_ON.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c2179264a332d7e4395b50fd2babf946 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller new file mode 100644 index 00000000..13c445f6 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller @@ -0,0 +1,101 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-1614219900459257297 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: HQ_ON + 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: c2179264a332d7e4395b50fd2babf946, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: HighQualityCurvesSettingPreserved + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: 5829098483273298939} + 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 &2527618587943162634 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: HQ_OFF + 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: 2d16d7af2be036d4e9867b4314772f65, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &5829098483273298939 +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: 2527618587943162634} + m_Position: {x: 314, y: 287, z: 0} + - serializedVersion: 1 + m_State: {fileID: -1614219900459257297} + m_Position: {x: 538, y: 388, 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: 2527618587943162634} diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller.meta b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller.meta new file mode 100644 index 00000000..e4a247ff --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3fabb6b36a3a1d84f9568e3fc9b9eb2a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab new file mode 100644 index 00000000..21d36007 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab @@ -0,0 +1,322 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1779618317021452234 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1779618317021451318} + - component: {fileID: 1779618317021451317} + - component: {fileID: 1779618317021451316} + - component: {fileID: 1779618317021452235} + m_Layer: 0 + m_Name: HighQualityCurvesSettingPreserved + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1779618317021451318 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1779618317021452234} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.11551872, y: 0.23841906, z: -2.698598} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1779618317021451317 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1779618317021452234} + 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: 3fabb6b36a3a1d84f9568e3fc9b9eb2a, + 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 &1779618317021451316 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1779618317021452234} + 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!95 &1779618317021452235 +Animator: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1779618317021452234} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorControllerStateOnDisable: 0 diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab.meta b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab.meta new file mode 100644 index 00000000..ad70e31e --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/HighQualityCurvesSettingPreserved.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3acbcc5c8c5020d49a58371ffdbe6b5d +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs b/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs new file mode 100644 index 00000000..7e4a70ea --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs @@ -0,0 +1,27 @@ +using nadena.dev.ndmf; +using nadena.dev.ndmf.animation; +using NUnit.Framework; +using UnityEditor; + +namespace modular_avatar_tests +{ + public class MiscAnimationTests : TestBase + { + [Test] + public void HighQualityCurvesSettingPreserved() + { + var prefab = CreatePrefab("HighQualityCurvesSettingPreserved.prefab"); + var context = new BuildContext(prefab, null); + context.ActivateExtensionContext(); + context.DeactivateExtensionContext(); + + var layer = findFxLayer(prefab, "Base Layer"); + + var hq_on = FindStateInLayer(layer, "HQ_ON"); + var hq_off = FindStateInLayer(layer, "HQ_OFF"); + + Assert.True(new SerializedObject(hq_on.motion).FindProperty("m_UseHighQualityCurve").boolValue); + Assert.False(new SerializedObject(hq_off.motion).FindProperty("m_UseHighQualityCurve").boolValue); + } + } +} \ No newline at end of file diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs.meta b/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs.meta new file mode 100644 index 00000000..85a54fe0 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/MiscAnimationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25fb1ff7b4e425c4ba9d13f0dcc6cfaa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller b/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller new file mode 100644 index 00000000..6f1d9c6b --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller @@ -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: ReferencesObject + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: 5545365013827754013} + 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!1107 &5545365013827754013 +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: 7237879935735861366} + m_Position: {x: 340, y: 110, 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: 7237879935735861366} +--- !u!1102 &7237879935735861366 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AnimationClip + 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: 5977900ad3e56e442bc83e80a14ff2c4, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller.meta b/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller.meta new file mode 100644 index 00000000..87bc0881 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/ReferencesObject.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 54530ed85eedf574487742e93f56ba26 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs b/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs new file mode 100644 index 00000000..88ba3fe4 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs @@ -0,0 +1,147 @@ +using System.Linq; +using nadena.dev.ndmf.animation; +using NUnit.Framework; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; + +namespace modular_avatar_tests +{ + using UnityObject = UnityEngine.Object; + + public class TrackObjectRenamesContextTests : TestBase + { + [Test] + public void testBasicContextInitialization() + { + var av = CreateRoot("root"); + + var bc = CreateContext(av); + var toc = new TrackObjectRenamesContext(); + + toc.OnActivate(bc); + toc.OnDeactivate(bc); + } + + + [Test] + public void TracksSimpleRenames() + { + var root = CreateRoot("root"); + var a = CreateChild(root, "a"); + + var toc = new TrackObjectRenamesContext(); + toc.OnActivate(CreateContext(root)); + Assert.AreEqual("a", toc.MapPath("a")); + a.name = "b"; + toc.ClearCache(); + Assert.AreEqual("b", toc.MapPath("a")); + } + + [Test] + public void TracksObjectMoves() + { + var root = CreateRoot("root"); + var a = CreateChild(root, "a"); + var b = CreateChild(root, "b"); + + var toc = new TrackObjectRenamesContext(); + toc.OnActivate(CreateContext(root)); + + Assert.AreEqual("a", toc.MapPath("a")); + a.transform.parent = b.transform; + toc.ClearCache(); + Assert.AreEqual("b/a", toc.MapPath("a")); + } + + [Test] + public void TracksCollapses() + { + var root = CreateRoot("root"); + var a = CreateChild(root, "a"); + var b = CreateChild(a, "b"); + var c = CreateChild(b, "c"); + + var toc = new TrackObjectRenamesContext(); + toc.OnActivate(CreateContext(root)); + + toc.MarkRemoved(b); + c.transform.parent = a.transform; + UnityObject.DestroyImmediate(b); + + Assert.AreEqual("a/c", toc.MapPath("a/b/c")); + } + + [Test] + public void TransformLookthrough() + { + var root = CreateRoot("root"); + var a = CreateChild(root, "a"); + var b = CreateChild(a, "b"); + var c = CreateChild(b, "c"); + var d = CreateChild(c, "d"); + + var toc = new TrackObjectRenamesContext(); + 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)); + } + + [Test] + public void TestAnimatorControllerUpdates() + { + var root = CreatePrefab("BasicObjectReferenceTest.prefab"); + var parent = root.transform.Find("parent").gameObject; + var child = parent.transform.Find("child").gameObject; + + var descriptor = root.GetComponent(); + var oldFx = descriptor.baseAnimationLayers.First(l => + l.type == VRCAvatarDescriptor.AnimLayerType.FX); + var oldIk = descriptor.specialAnimationLayers.First(l => + l.type == VRCAvatarDescriptor.AnimLayerType.IKPose); + + var toc = new TrackObjectRenamesContext(); + var buildContext = CreateContext(root); + toc.OnActivate(buildContext); + toc.MarkTransformLookthrough(child); + + parent.name = "p2"; + + toc.OnDeactivate(buildContext); + + var newFx = buildContext.AvatarDescriptor.baseAnimationLayers.First(l => + l.type == VRCAvatarDescriptor.AnimLayerType.FX); + var newIk = buildContext.AvatarDescriptor.specialAnimationLayers.First(l => + l.type == VRCAvatarDescriptor.AnimLayerType.IKPose); + + Assert.AreNotEqual(oldFx.animatorController, newFx.animatorController); + Assert.AreNotEqual(oldIk.animatorController, newIk.animatorController); + + CheckClips(newFx.animatorController as AnimatorController); + CheckClips(newIk.animatorController as AnimatorController); + + void CheckClips(AnimatorController controller) + { + var clip = controller.layers[0].stateMachine.states[0].state.motion + as AnimationClip; + + foreach (var binding in AnimationUtility.GetCurveBindings(clip)) + { + if (binding.type == typeof(Transform)) + { + Assert.AreEqual("p2", binding.path); + } + else + { + Assert.AreEqual("p2/child", binding.path); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs.meta b/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs.meta new file mode 100644 index 00000000..6d50ff12 --- /dev/null +++ b/Assets/_ModularAvatar/EditModeTests/Animation/TrackObjectRenamesContextTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e71c9edfe1c94c5c93603ce45a1e2310 +timeCreated: 1692516261 \ No newline at end of file diff --git a/Assets/_ModularAvatar/EditModeTests/TestBase.cs b/Assets/_ModularAvatar/EditModeTests/TestBase.cs index dc94a770..f49c9717 100644 --- a/Assets/_ModularAvatar/EditModeTests/TestBase.cs +++ b/Assets/_ModularAvatar/EditModeTests/TestBase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.editor.ErrorReporting; @@ -12,12 +13,29 @@ namespace modular_avatar_tests { public class TestBase { + private const string TEMP_ASSET_PATH = "Assets/ZZZ_Temp"; + private static Dictionary _scriptToDirectory = null; + private List objects; private const string MinimalAvatarGuid = "60d3416d1f6af4a47bf9056aefc38333"; [SetUp] public virtual void Setup() { + if (_scriptToDirectory == null) + { + _scriptToDirectory = new Dictionary(); + foreach (var guid in AssetDatabase.FindAssets("t:MonoScript", new string[] {"Assets/_ModularAvatar"})) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + var obj = AssetDatabase.LoadAssetAtPath(path); + if (obj != null && obj.GetClass() != null) + { + _scriptToDirectory.Add(obj.GetClass(), Path.GetDirectoryName(path)); + } + } + } + BuildReport.Clear(); objects = new List(); } @@ -33,6 +51,11 @@ namespace modular_avatar_tests Util.DeleteTemporaryAssets(); } + protected nadena.dev.ndmf.BuildContext CreateContext(GameObject root) + { + return new nadena.dev.ndmf.BuildContext(root, TEMP_ASSET_PATH); // TODO - cleanup + } + protected GameObject CreateRoot(string name) { var path = AssetDatabase.GUIDToAssetPath(MinimalAvatarGuid); @@ -59,9 +82,10 @@ namespace modular_avatar_tests return go; } + protected T LoadAsset(string relPath) where T : UnityEngine.Object { - var root = "Assets/_ModularAvatar/EditModeTests/" + GetType().Name + "/"; + var root = _scriptToDirectory[GetType()] + "/"; var path = root + relPath; var obj = AssetDatabase.LoadAssetAtPath(path); @@ -70,7 +94,6 @@ namespace modular_avatar_tests return obj; } - protected static AnimationClip findFxClip(GameObject prefab, string layerName) { var motion = findFxMotion(prefab, layerName) as AnimationClip; diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation.meta b/Packages/nadena.dev.modular-avatar/Editor/Animation.meta new file mode 100644 index 00000000..3c375294 --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0985d0cf1a434879b3ec7e80a90bc598 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs new file mode 100644 index 00000000..5468c6dd --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; + +namespace nadena.dev.ndmf.animation +{ + public static class AnimationUtil + { + private const string SAMPLE_PATH_PACKAGE = + "Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Controllers"; + + private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers"; + + private const string GUID_GESTURE_HANDSONLY_MASK = "b2b8bad9583e56a46a3e21795e96ad92"; + + + public static AnimatorController DeepCloneAnimator(BuildContext context, RuntimeAnimatorController controller) + { + if (controller == null) return null; + + var merger = new AnimatorCombiner(controller.name + " (clone)", context.AssetContainer); + switch (controller) + { + case AnimatorController ac: + merger.AddController("", ac, null); + break; + case AnimatorOverrideController oac: + merger.AddOverrideController("", oac, null); + break; + default: + throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); + } + + return merger.Finish(); + } + + internal static void CloneAllControllers(BuildContext context) + { + // Ensure all of the controllers on the avatar descriptor point to temporary assets. + // This helps reduce the risk that we'll accidentally modify the original assets. + + context.AvatarDescriptor.baseAnimationLayers = + CloneLayers(context, context.AvatarDescriptor.baseAnimationLayers); + context.AvatarDescriptor.specialAnimationLayers = + CloneLayers(context, context.AvatarDescriptor.specialAnimationLayers); + } + + private static VRCAvatarDescriptor.CustomAnimLayer[] CloneLayers( + BuildContext context, + VRCAvatarDescriptor.CustomAnimLayer[] layers + ) + { + if (layers == null) return null; + + for (int i = 0; i < layers.Length; i++) + { + var layer = layers[i]; + if (layer.animatorController != null && !context.IsTemporaryAsset(layer.animatorController)) + { + layer.animatorController = DeepCloneAnimator(context, layer.animatorController); + } + + layers[i] = layer; + } + + return layers; + } + + public static AnimatorController GetOrInitializeController( + this BuildContext context, + VRCAvatarDescriptor.AnimLayerType type) + { + return FindLayer(context.AvatarDescriptor.baseAnimationLayers) + ?? FindLayer(context.AvatarDescriptor.specialAnimationLayers); + + AnimatorController FindLayer(VRCAvatarDescriptor.CustomAnimLayer[] layers) + { + for (int i = 0; i < layers.Length; i++) + { + var layer = layers[i]; + if (layer.type == type) + { + if (layer.animatorController == null || layer.isDefault) + { + layer.animatorController = ResolveLayerController(layer); + if (type == VRCAvatarDescriptor.AnimLayerType.Gesture) + { + layer.mask = AssetDatabase.LoadAssetAtPath( + AssetDatabase.GUIDToAssetPath(GUID_GESTURE_HANDSONLY_MASK) + ); + } + + layers[i] = layer; + } + + return layer.animatorController as AnimatorController; + } + } + + return null; + } + } + + + private static AnimatorController ResolveLayerController(VRCAvatarDescriptor.CustomAnimLayer layer) + { + AnimatorController controller = null; + + if (!layer.isDefault && layer.animatorController != null && + layer.animatorController is AnimatorController c) + { + controller = c; + } + else + { + string name; + switch (layer.type) + { + case VRCAvatarDescriptor.AnimLayerType.Action: + name = "Action"; + break; + case VRCAvatarDescriptor.AnimLayerType.Additive: + name = "Idle"; + break; + case VRCAvatarDescriptor.AnimLayerType.Base: + name = "Locomotion"; + break; + case VRCAvatarDescriptor.AnimLayerType.Gesture: + name = "Hands"; + break; + case VRCAvatarDescriptor.AnimLayerType.Sitting: + name = "Sitting"; + break; + case VRCAvatarDescriptor.AnimLayerType.FX: + name = "Face"; + break; + case VRCAvatarDescriptor.AnimLayerType.TPose: + name = "UtilityTPose"; + break; + case VRCAvatarDescriptor.AnimLayerType.IKPose: + name = "UtilityIKPose"; + break; + default: + name = null; + break; + } + + if (name != null) + { + name = "/vrc_AvatarV3" + name + "Layer.controller"; + + controller = AssetDatabase.LoadAssetAtPath(SAMPLE_PATH_PACKAGE + name); + if (controller == null) + { + controller = AssetDatabase.LoadAssetAtPath(SAMPLE_PATH_LEGACY + name); + } + } + } + + return controller; + } + + public static bool IsProxyAnimation(this Motion m) + { + var path = AssetDatabase.GetAssetPath(m); + + // This is a fairly wide condition in order to deal with: + // 1. Future additions of proxy animations (so GUIDs are out) + // 2. Unitypackage based installations of the VRCSDK + // 3. VCC based installations of the VRCSDK + // 4. Very old VCC based installations of the VRCSDK where proxy animations were copied into Assets + return path.Contains("/AV3 Demo Assets/Animation/ProxyAnim/proxy") + || path.Contains("/VRCSDK/Examples3/Animation/ProxyAnim/proxy"); + } + + /// + /// Enumerates all states in an animator controller + /// + /// + /// + internal static IEnumerable States(AnimatorController ac) + { + HashSet visitedStateMachines = new HashSet(); + Queue pending = new Queue(); + + foreach (var layer in ac.layers) + { + if (layer.stateMachine != null) pending.Enqueue(layer.stateMachine); + } + + while (pending.Count > 0) + { + var next = pending.Dequeue(); + if (visitedStateMachines.Contains(next)) continue; + visitedStateMachines.Add(next); + + foreach (var child in next.stateMachines) + { + if (child.stateMachine != null) pending.Enqueue(child.stateMachine); + } + + foreach (var state in next.states) + { + yield return state.state; + } + } + } + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs.meta new file mode 100644 index 00000000..f571a983 --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimationUtil.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e0ef93cb5a6c4e46bb3318ae8116de77 +timeCreated: 1691238553 \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs new file mode 100644 index 00000000..6e6af04c --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs @@ -0,0 +1,451 @@ +/* + * MIT License + * + * Copyright (c) 2022-2023 bd_ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using nadena.dev.ndmf.util; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using Object = UnityEngine.Object; + +namespace nadena.dev.ndmf.animation +{ + public class AnimatorCombiner + { + private readonly AnimatorController _combined; + private bool isSaved; + + private AnimatorOverrideController _overrideController; + + private List _layers = new List(); + + private Dictionary _parameters = + new Dictionary(); + + private Dictionary, Motion> _motions = + new Dictionary, Motion>(); + + private Dictionary, AnimatorStateMachine> _stateMachines = + new Dictionary, AnimatorStateMachine>(); + + private Dictionary _cloneMap; + + private int controllerBaseLayer = 0; + + public AnimatorCombiner(String assetName, UnityEngine.Object assetContainer) + { + _combined = new AnimatorController(); + if (assetContainer != null) + { + if (!EditorUtility.IsPersistent(assetContainer) || + string.IsNullOrEmpty(AssetDatabase.GetAssetPath(assetContainer))) + { + Debug.Log("Nonpersistent asset container: " + assetContainer.name); + } + else + { + AssetDatabase.AddObjectToAsset(_combined, assetContainer); + } + } + + isSaved = assetContainer != null; + _combined.name = assetName; + } + + public AnimatorController Finish() + { + _combined.parameters = _parameters.Values.ToArray(); + _combined.layers = _layers.ToArray(); + return _combined; + } + + public void AddController(string basePath, AnimatorController controller, bool? writeDefaults) + { + controllerBaseLayer = _layers.Count; + _cloneMap = new Dictionary(); + + foreach (var param in controller.parameters) + { + if (_parameters.TryGetValue(param.name, out var acp)) + { + if (acp.type != param.type) + { + /* + BuildReport.LogFatal("error.merge_animator.param_type_mismatch", new[] + { + param.name, acp.type.ToString(), + param.type.ToString() + }); + */ + // TODO: Error reporting + throw new Exception("Parameter type mismatch"); + } + + continue; + } + + _parameters.Add(param.name, param); + } + + bool first = true; + var layers = controller.layers; + foreach (var layer in layers) + { + insertLayer(basePath, layer, first, writeDefaults, layers); + first = false; + } + } + + public void AddOverrideController(string basePath, AnimatorOverrideController overrideController, + bool? writeDefaults) + { + AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController; + if (controller == null) return; + _overrideController = overrideController; + try + { + this.AddController(basePath, controller, writeDefaults); + } + finally + { + _overrideController = null; + } + } + + private void insertLayer( + string basePath, + AnimatorControllerLayer layer, + bool first, + bool? writeDefaults, + AnimatorControllerLayer[] layers + ) + { + var newLayer = new AnimatorControllerLayer() + { + name = layer.name, + avatarMask = layer.avatarMask, // TODO map transforms + blendingMode = layer.blendingMode, + defaultWeight = first ? 1 : layer.defaultWeight, + syncedLayerIndex = layer.syncedLayerIndex, + syncedLayerAffectsTiming = layer.syncedLayerAffectsTiming, + iKPass = layer.iKPass, + stateMachine = mapStateMachine(basePath, layer.stateMachine), + }; + + UpdateWriteDefaults(newLayer.stateMachine, writeDefaults); + + if (newLayer.syncedLayerIndex != -1 && newLayer.syncedLayerIndex >= 0 && + newLayer.syncedLayerIndex < layers.Length) + { + // Transfer any motion overrides onto the new synced layer + var baseLayer = layers[newLayer.syncedLayerIndex]; + foreach (var state in WalkAllStates(baseLayer.stateMachine)) + { + var overrideMotion = layer.GetOverrideMotion(state); + if (overrideMotion != null) + { + newLayer.SetOverrideMotion((AnimatorState) _cloneMap[state], overrideMotion); + } + + var overrideBehaviors = (StateMachineBehaviour[]) layer.GetOverrideBehaviours(state)?.Clone(); + if (overrideBehaviors != null) + { + for (int i = 0; i < overrideBehaviors.Length; i++) + { + overrideBehaviors[i] = deepClone(overrideBehaviors[i], x => x, + new Dictionary()); + AdjustBehavior(overrideBehaviors[i]); + } + + newLayer.SetOverrideBehaviours((AnimatorState) _cloneMap[state], overrideBehaviors); + } + } + + newLayer.syncedLayerIndex += controllerBaseLayer; + } + + _layers.Add(newLayer); + } + + IEnumerable WalkAllStates(AnimatorStateMachine animatorStateMachine) + { + HashSet visited = new HashSet(); + + foreach (var state in VisitStateMachine(animatorStateMachine)) + { + yield return state; + } + + IEnumerable VisitStateMachine(AnimatorStateMachine layerStateMachine) + { + if (!visited.Add(layerStateMachine)) yield break; + + foreach (var state in layerStateMachine.states) + { + if (state.state == null) continue; + + yield return state.state; + } + + foreach (var child in layerStateMachine.stateMachines) + { + if (child.stateMachine == null) continue; + + if (visited.Contains(child.stateMachine)) continue; + visited.Add(child.stateMachine); + foreach (var state in VisitStateMachine(child.stateMachine)) + { + yield return state; + } + } + } + } + + private void UpdateWriteDefaults(AnimatorStateMachine stateMachine, bool? writeDefaults) + { + if (!writeDefaults.HasValue) return; + + var queue = new Queue(); + queue.Enqueue(stateMachine); + while (queue.Count > 0) + { + var sm = queue.Dequeue(); + foreach (var state in sm.states) + { + state.state.writeDefaultValues = writeDefaults.Value; + } + + foreach (var child in sm.stateMachines) + { + queue.Enqueue(child.stateMachine); + } + } + } + + private AnimatorStateMachine mapStateMachine(string basePath, AnimatorStateMachine layerStateMachine) + { + var cacheKey = new KeyValuePair(basePath, layerStateMachine); + + if (_stateMachines.TryGetValue(cacheKey, out var asm)) + { + return asm; + } + + asm = deepClone(layerStateMachine, (obj) => customClone(obj, basePath), _cloneMap); + + foreach (var state in WalkAllStates(asm)) + { + foreach (var behavior in state.behaviours) + { + AdjustBehavior(behavior); + } + } + + _stateMachines[cacheKey] = asm; + return asm; + } + + private void AdjustBehavior(StateMachineBehaviour behavior) + { + switch (behavior) + { + case VRCAnimatorLayerControl layerControl: + { + // TODO - need to figure out how to handle cross-layer references. For now this will handle + // intra-animator cases. + layerControl.layer += controllerBaseLayer; + break; + } + } + } + + private static string MapPath(EditorCurveBinding binding, string basePath) + { + if (binding.type == typeof(Animator) && binding.path == "") + { + return ""; + } + else + { + var newPath = binding.path == "" ? basePath : basePath + binding.path; + if (newPath.EndsWith("/")) + { + newPath = newPath.Substring(0, newPath.Length - 1); + } + + return newPath; + } + } + + private Object customClone(Object o, string basePath) + { + if (o is AnimationClip clip) + { + if (basePath == "" || clip.IsProxyAnimation()) return clip; + + AnimationClip newClip = new AnimationClip(); + newClip.name = clip.name; + if (isSaved) + { + AssetDatabase.AddObjectToAsset(newClip, _combined); + } + + foreach (var binding in AnimationUtility.GetCurveBindings(clip)) + { + var newBinding = binding; + newBinding.path = MapPath(binding, basePath); + newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName, + AnimationUtility.GetEditorCurve(clip, binding)); + } + + foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) + { + var newBinding = objBinding; + newBinding.path = MapPath(objBinding, basePath); + AnimationUtility.SetObjectReferenceCurve(newClip, newBinding, + AnimationUtility.GetObjectReferenceCurve(clip, objBinding)); + } + + newClip.wrapMode = clip.wrapMode; + newClip.legacy = clip.legacy; + newClip.frameRate = clip.frameRate; + newClip.localBounds = clip.localBounds; + AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip)); + + return newClip; + } + else if (o is Texture) + { + return o; + } + else + { + return null; + } + } + + private T deepClone(T original, + Func visitor, + Dictionary cloneMap + ) where T : Object + { + if (original == null) return null; + + // We want to avoid trying to copy assets not part of the animation system (eg - textures, meshes, + // MonoScripts...), so check for the types we care about here + switch (original) + { + // Any object referenced by an animator that we intend to mutate needs to be listed here. + case Motion _: + case AnimatorController _: + case AnimatorState _: + case AnimatorStateMachine _: + case AnimatorTransitionBase _: + case StateMachineBehaviour _: + break; // We want to clone these types + + // Leave textures, materials, and script definitions alone + case Texture2D _: + case MonoScript _: + case Material _: + return original; + + // Also avoid copying unknown scriptable objects. + // This ensures compatibility with e.g. avatar remote, which stores state information in a state + // behaviour referencing a custom ScriptableObject + case ScriptableObject _: + return original; + + default: + throw new Exception($"Unknown type referenced from animator: {original.GetType()}"); + } + + // When using AnimatorOverrideController, replace the original AnimationClip based on AnimatorOverrideController. + if (_overrideController != null && original is AnimationClip srcClip) + { + T overrideClip = _overrideController[srcClip] as T; + if (overrideClip != null) + { + original = overrideClip; + } + } + + if (cloneMap.ContainsKey(original)) + { + return (T) cloneMap[original]; + } + + var obj = visitor(original); + if (obj != null) + { + cloneMap[original] = obj; + return (T) obj; + } + + var ctor = original.GetType().GetConstructor(Type.EmptyTypes); + if (ctor == null || original is ScriptableObject) + { + obj = Object.Instantiate(original); + } + else + { + obj = (T) ctor.Invoke(Array.Empty()); + EditorUtility.CopySerialized(original, obj); + } + + cloneMap[original] = obj; + + if (isSaved && _combined != null && EditorUtility.IsPersistent(_combined)) + { + AssetDatabase.AddObjectToAsset(obj, _combined); + } + + SerializedObject so = new SerializedObject(obj); + SerializedProperty prop = so.GetIterator(); + + bool enterChildren = true; + while (prop.Next(enterChildren)) + { + enterChildren = true; + switch (prop.propertyType) + { + case SerializedPropertyType.ObjectReference: + prop.objectReferenceValue = deepClone(prop.objectReferenceValue, visitor, cloneMap); + break; + // Iterating strings can get super slow... + case SerializedPropertyType.String: + enterChildren = false; + break; + } + } + + so.ApplyModifiedPropertiesWithoutUndo(); + + return (T) obj; + } + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs.meta new file mode 100644 index 00000000..2896d8af --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/AnimatorCombiner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5a3369cd20964957920c3fe8b54df521 +timeCreated: 1691238359 \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs b/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs new file mode 100644 index 00000000..64fa78c4 --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs @@ -0,0 +1,11 @@ +namespace nadena.dev.ndmf.animation +{ + /// + /// This interface tags components which supply additional animation controllers for merging. They will be given + /// an opportunity to apply animation path updates when the TrackObjectRenamesContext is committed. + /// + public interface IOnCommitObjectRenames + { + void OnCommitObjectRenames(BuildContext buildContext, TrackObjectRenamesContext renameContext); + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs.meta new file mode 100644 index 00000000..b437d41b --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/IOnCommitObjectRenames.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 17002a48bb4b4ea79993e0549faf2d0e +timeCreated: 1692511752 \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs b/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs new file mode 100644 index 00000000..5e5a40ca --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using nadena.dev.ndmf.util; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.Core; +using VRC.SDK3.Avatars.Components; + +namespace nadena.dev.ndmf.animation +{ + using UnityObject = UnityEngine.Object; + + /// + /// This extension context tracks when objects are renamed, and updates animations accordingly. + /// 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). + /// + public sealed class TrackObjectRenamesContext : IExtensionContext + { + private Dictionary> + _objectToOriginalPaths = new Dictionary>(); + + private HashSet _transformLookthroughObjects = new HashSet(); + private ImmutableDictionary _originalPathToMappedPath = null; + private ImmutableDictionary _transformOriginalPathToMappedPath = null; + + public void OnActivate(BuildContext context) + { + _objectToOriginalPaths.Clear(); + _transformLookthroughObjects.Clear(); + ClearCache(); + + foreach (var xform in context.AvatarRootTransform.GetComponentsInChildren(true)) + { + _objectToOriginalPaths.Add(xform.gameObject, new List {xform.gameObject.AvatarRootPath()}); + } + } + + public void ClearCache() + { + _originalPathToMappedPath = null; + _transformOriginalPathToMappedPath = null; + } + + /// + /// Sets the "transform lookthrough" flag for an object. Any transform animations on this object will be + /// redirected to its parent. This is used in Modular Avatar as part of bone merging logic. + /// + /// + public void MarkTransformLookthrough(GameObject obj) + { + _transformLookthroughObjects.Add(obj); + } + + /// + /// Returns a path for use in dynamically generated animations for a given object. This can include objects not + /// present at the time of context activation; in this case, they will be assigned a randomly-generated internal + /// path and replaced during path remapping with the true path. + /// + /// + /// + public string GetObjectIdentifier(GameObject obj) + { + if (_objectToOriginalPaths.TryGetValue(obj, out var paths)) + { + return paths[0]; + } + else + { + var internalPath = "_NewlyCreatedObject/" + GUID.Generate() + "/" + obj.AvatarRootPath(); + _objectToOriginalPaths.Add(obj, new List {internalPath}); + return internalPath; + } + } + + /// + /// Marks an object as having been removed. Its paths will be remapped to its parent. + /// + /// + public void MarkRemoved(GameObject obj) + { + ClearCache(); + if (_objectToOriginalPaths.TryGetValue(obj, out var paths)) + { + var parent = obj.transform.parent.gameObject; + if (_objectToOriginalPaths.TryGetValue(parent, out var parentPaths)) + { + parentPaths.AddRange(paths); + } + + _objectToOriginalPaths.Remove(obj); + _transformLookthroughObjects.Remove(obj); + } + } + + + /// + /// Marks an object as having been replaced by another object. All references to the old object will be replaced + /// by the new object. References originally to the new object will continue to point to the new object. + /// + /// + /// + public void ReplaceObject(GameObject old, GameObject newObject) + { + ClearCache(); + + if (_objectToOriginalPaths.TryGetValue(old, out var paths)) + { + if (!_objectToOriginalPaths.TryGetValue(newObject, out var newObjectPaths)) + { + newObjectPaths = new List(); + _objectToOriginalPaths.Add(newObject, newObjectPaths); + } + + newObjectPaths.AddRange(paths); + + _objectToOriginalPaths.Remove(old); + } + + + if (_transformLookthroughObjects.Contains(old)) + { + _transformLookthroughObjects.Remove(old); + _transformLookthroughObjects.Add(newObject); + } + } + + + private ImmutableDictionary BuildMapping(ref ImmutableDictionary cache, + bool transformLookup) + { + if (cache != null) return cache; + + ImmutableDictionary dict = ImmutableDictionary.Empty; + + foreach (var kvp in _objectToOriginalPaths) + { + var obj = kvp.Key; + var paths = kvp.Value; + + if (transformLookup) + { + while (_transformLookthroughObjects.Contains(obj)) + { + obj = obj.transform.parent.gameObject; + } + } + + var newPath = obj.AvatarRootPath(); + foreach (var origPath in paths) + { + if (!dict.ContainsKey(origPath)) + { + dict = dict.Add(origPath, newPath); + } + } + } + + cache = dict; + return cache; + } + + public string MapPath(string path, bool isTransformMapping = false) + { + ImmutableDictionary mappings; + + if (isTransformMapping) + { + mappings = BuildMapping(ref _originalPathToMappedPath, true); + } + else + { + mappings = BuildMapping(ref _transformOriginalPathToMappedPath, false); + } + + if (mappings.TryGetValue(path, out var mappedPath)) + { + return mappedPath; + } + else + { + return path; + } + } + + public RuntimeAnimatorController ApplyMappingsToAnimator( + BuildContext context, + RuntimeAnimatorController controller, + Dictionary clipCache = null) + { + if (clipCache == null) + { + clipCache = new Dictionary(); + } + + 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> overrides = + new List>(); + + overrides = overrides.Select(kvp => + new KeyValuePair(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 == "") + { + return ""; + } + else + { + return MapPath(binding.path, binding.type == typeof(Transform)); + } + } + + private AnimationClip ApplyMappingsToClip(AnimationClip originalClip, + Dictionary clipCache = null) + { + if (originalClip == null) return null; + if (clipCache != null && clipCache.TryGetValue(originalClip, out var cachedClip)) return cachedClip; + + var newClip = new AnimationClip(); + newClip.name = originalClip.name; + + SerializedObject before = new SerializedObject(originalClip); + SerializedObject after = new SerializedObject(newClip); + + var before_hqCurve = before.FindProperty("m_UseHighQualityCurve"); + var after_hqCurve = after.FindProperty("m_UseHighQualityCurve"); + + after_hqCurve.boolValue = before_hqCurve.boolValue; + after.ApplyModifiedPropertiesWithoutUndo(); + + // TODO - should we use direct SerializedObject manipulation to avoid missing script issues? + foreach (var binding in AnimationUtility.GetCurveBindings(originalClip)) + { + var newBinding = binding; + newBinding.path = MapPath(binding); + newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName, + AnimationUtility.GetEditorCurve(originalClip, binding)); + } + + foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(originalClip)) + { + var newBinding = objBinding; + newBinding.path = MapPath(objBinding); + AnimationUtility.SetObjectReferenceCurve(newClip, newBinding, + AnimationUtility.GetObjectReferenceCurve(originalClip, objBinding)); + } + + newClip.wrapMode = newClip.wrapMode; + newClip.legacy = newClip.legacy; + newClip.frameRate = newClip.frameRate; + newClip.localBounds = newClip.localBounds; + AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(originalClip)); + + if (clipCache != null) + { + clipCache.Add(originalClip, newClip); + } + + return newClip; + } + + public void OnDeactivate(BuildContext context) + { + context.AvatarDescriptor.baseAnimationLayers = + MapLayers(context, context.AvatarDescriptor.baseAnimationLayers); + context.AvatarDescriptor.specialAnimationLayers = + MapLayers(context, context.AvatarDescriptor.specialAnimationLayers); + + foreach (var listener in context.AvatarRootObject.GetComponentsInChildren()) + { + 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; + } + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs.meta new file mode 100644 index 00000000..acdf8593 --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Animation/TrackObjectRenamesContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: deb8a666d3af40b0be0ac616cf98a05b +timeCreated: 1691237971 \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/PluginDefinition/PluginDefinition.cs b/Packages/nadena.dev.modular-avatar/Editor/PluginDefinition/PluginDefinition.cs index 13dc4f37..e5594c6a 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/PluginDefinition/PluginDefinition.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/PluginDefinition/PluginDefinition.cs @@ -16,13 +16,15 @@ namespace nadena.dev.modular_avatar.core.editor.plugin { public override string QualifiedName => "nadena.dev.modular-avatar"; public override string DisplayName => "Modular Avatar"; - + protected override void Configure() { - InPhase(BuildPhase.Resolving) - .Run(ResolveObjectReferences.Instance); + Sequence seq = InPhase(BuildPhase.Resolving); + seq.Run(ResolveObjectReferences.Instance); + // Protect against accidental destructive edits by cloning the input controllers ASAP + seq.Run("Clone animators", AnimationUtil.CloneAllControllers); - Sequence seq = InPhase(BuildPhase.Transforming); + seq = InPhase(BuildPhase.Transforming); seq.WithRequiredExtension(typeof(ModularAvatarContext), _s1 => { seq.Run(ClearEditorOnlyTags.Instance); @@ -38,9 +40,10 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.Run(ReplaceObjectPluginPass.Instance); }); seq.Run(BlendshapeSyncAnimationPluginPass.Instance); - seq.Run(PhysbonesBlockerPluginPass.Instance);; + seq.Run(PhysbonesBlockerPluginPass.Instance); + ; }); - + InPhase(BuildPhase.Optimizing) .WithRequiredExtension(typeof(ModularAvatarContext), s => s.Run(GCGameObjectsPluginPass.Instance)); diff --git a/Packages/nadena.dev.ndmf b/Packages/nadena.dev.ndmf index 408fce61..0fbda0b6 160000 --- a/Packages/nadena.dev.ndmf +++ b/Packages/nadena.dev.ndmf @@ -1 +1 @@ -Subproject commit 408fce61cf9810d2ad7f1395015960250e841778 +Subproject commit 0fbda0b606f2d5172aa6c21d659e94e168c89a8e