diff options
Diffstat (limited to 'VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator')
32 files changed, 3165 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta new file mode 100644 index 00000000..735cec02 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60de7b24cb6cb524faa0fe1aa798ff88 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller new file mode 100644 index 00000000..29e105c4 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller @@ -0,0 +1,12 @@ +%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: EmptyController + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: [] diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta new file mode 100644 index 00000000..c9eeb575 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6017b05a9fb50634f99c9632977a33dd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask new file mode 100644 index 00000000..795a3c98 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask @@ -0,0 +1,11 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!319 &31900000 +AvatarMask: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: LyumaEmptyMask + m_Mask: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + m_Elements: [] diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta new file mode 100644 index 00000000..bc069894 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7681aa70297d4ba488f0182e6d7814de +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask new file mode 100644 index 00000000..2d1c3352 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask @@ -0,0 +1,13 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!319 &31900000 +AvatarMask: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: LyumaFullMask + m_Mask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + m_Elements: + - m_Path: + m_Weight: 1 diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta new file mode 100644 index 00000000..b4d889d2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2c751320a586b146b231e2753d6025c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask new file mode 100644 index 00000000..710aa1da --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask @@ -0,0 +1,129 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!319 &31900000 +AvatarMask: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: LyumaNoTransformMask + m_Mask: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + m_Elements: + - m_Path: + m_Weight: 1 + - m_Path: Armature + m_Weight: 0 + - m_Path: Armature/Hips + m_Weight: 0 + - m_Path: Armature/Hips/LeftUpLeg + m_Weight: 0 + - m_Path: Armature/Hips/LeftUpLeg/LeftLeg + m_Weight: 0 + - m_Path: Armature/Hips/LeftUpLeg/LeftLeg/LeftFoot + m_Weight: 0 + - m_Path: Armature/Hips/LeftUpLeg/LeftLeg/LeftFoot/LeftToe + m_Weight: 0 + - m_Path: Armature/Hips/RightUpLeg + m_Weight: 0 + - m_Path: Armature/Hips/RightUpLeg/RightLeg + m_Weight: 0 + - m_Path: Armature/Hips/RightUpLeg/RightLeg/RightFoot + m_Weight: 0 + - m_Path: Armature/Hips/RightUpLeg/RightLeg/RightFoot/RightToe + m_Weight: 0 + - m_Path: Armature/Hips/Spine + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandIndex1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandIndex1/LeftHandIndex2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandIndex1/LeftHandIndex2/LeftHandIndex3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandMiddle1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandMiddle1/LeftHandMiddle2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandMiddle1/LeftHandMiddle2/LeftHandMiddle3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandPinky1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandPinky1/LeftHandPinky2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandPinky1/LeftHandPinky2/LeftHandPinky3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandRing1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandRing1/LeftHandRing2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandRing1/LeftHandRing2/LeftHandRing3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandThumb1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandThumb1/LeftHandThumb2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/LeftHandThumb1/LeftHandThumb2/LeftHandThumb3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/LeftShoulder/LeftArm/LeftForeArm/LeftHand/lHandAttachmentPointL + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/Neck + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/Neck/Head + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/Neck/Head/HeadTop_End + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/Neck/Head/LeftEye + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/Neck/Head/RightEye + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/rHandAttachmentPointR + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandIndex1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandIndex1/RightHandIndex2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandIndex1/RightHandIndex2/RightHandIndex3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandMiddle1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandMiddle1/RightHandMiddle2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandMiddle1/RightHandMiddle2/RightHandMiddle3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandPinky1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandPinky1/RightHandPinky2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandPinky1/RightHandPinky2/RightHandPinky3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandRing1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandRing1/RightHandRing2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandRing1/RightHandRing2/RightHandRing3 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandThumb1 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandThumb1/RightHandThumb2 + m_Weight: 0 + - m_Path: Armature/Hips/Spine/Chest/RightShoulder/RightArm/RightForeArm/RightHand/RightHandThumb1/RightHandThumb2/RightHandThumb3 + m_Weight: 0 + - m_Path: Body + m_Weight: 0 diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta new file mode 100644 index 00000000..39c04767 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4df6ed1e3da0ef840b549a042ef7ae06 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor.meta new file mode 100644 index 00000000..befa95fd --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89ed23e5795598442903078ac52b2310 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs new file mode 100644 index 00000000..a866ca68 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs @@ -0,0 +1,144 @@ +/* Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEngine.Animations; +using UnityEditor.Animations; +using UnityEditor.Playables; +using UnityEngine.Playables; +using VRC.SDK3.Avatars.Components; + +[InitializeOnLoadAttribute] +public static class LyumaAv3EditorSupport +{ + static Dictionary<VRCAvatarDescriptor.AnimLayerType, string> animLayerToDefaultFile = new Dictionary<VRCAvatarDescriptor.AnimLayerType, string> { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_AvatarV3UtilityTPose"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_AvatarV3UtilityIKPose"}, + {VRCAvatarDescriptor.AnimLayerType.Base, "vrc_AvatarV3LocomotionLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Sitting, "vrc_AvatarV3SittingLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Additive, "vrc_AvatarV3IdleLayer"}, + {VRCAvatarDescriptor.AnimLayerType.FX, "vrc_AvatarV3FaceLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Action, "vrc_AvatarV3ActionLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Gesture, "vrc_AvatarV3HandsLayer"}, + }; + static Dictionary<VRCAvatarDescriptor.AnimLayerType, string> animLayerToDefaultAvaMaskFile = new Dictionary<VRCAvatarDescriptor.AnimLayerType, string> + { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.Base, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Sitting, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Additive, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.FX, "LyumaEmptyMask"}, // TODO + {VRCAvatarDescriptor.AnimLayerType.Action, null},//"vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.Gesture, "vrc_HandsOnly"}, + }; + + static void InitDefaults() { + foreach (var kv in animLayerToDefaultFile) { + if (kv.Value == null) { + LyumaAv3Runtime.animLayerToDefaultController[kv.Key] = null; + } else + { + AnimatorController ac = AssetDatabase.LoadAssetAtPath<AnimatorController>("Assets/VRCSDK/Examples3/Animation/Controllers/" + kv.Value + ".controller"); + if (ac == null) + { + Debug.LogWarning("Failed to resolve animator controller " + kv.Value + " for " + kv.Key); + ac = null; + } + LyumaAv3Runtime.animLayerToDefaultController[kv.Key] = ac; + } + } + foreach (var kv in animLayerToDefaultAvaMaskFile) { + if (kv.Value == null) { + LyumaAv3Runtime.animLayerToDefaultAvaMask[kv.Key] = null; + } else + { + AvatarMask mask = null; + foreach (var guid in AssetDatabase.FindAssets(kv.Value)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + mask = AssetDatabase.LoadAssetAtPath<AvatarMask>(path); + } + if (mask == null) + { + Debug.LogWarning("Failed to resolve avatar mask " + kv.Value + " for " + kv.Key); + mask = new AvatarMask(); + } + LyumaAv3Runtime.animLayerToDefaultAvaMask[kv.Key] = mask; + } + } + foreach (string guid in AssetDatabase.FindAssets("EmptyController")) { + LyumaAv3Emulator.EmptyController = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(AssetDatabase.GUIDToAssetPath(guid)); + } + + LyumaAv3Runtime.updateSelectionDelegate = (go) => { + if (go == null && LyumaAv3Emulator.emulatorInstance != null) { + Debug.Log("Resetting selected object: " + LyumaAv3Emulator.emulatorInstance); + go = LyumaAv3Emulator.emulatorInstance.gameObject; + } + Debug.Log("Setting selected object: " + go); + Selection.SetActiveObjectWithContext(go, go); + // Highlighter.Highlight("Inspector", "Animator To Debug"); + }; + + LyumaAv3Runtime.addRuntimeDelegate = (runtime) => { + GameObject go = runtime.gameObject; + try { + if (PrefabUtility.IsPartOfAnyPrefab(go)) { + PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } catch (System.Exception) {} + int moveUpCalls = go.GetComponents<Component>().Length - 2; + if (!PrefabUtility.IsPartOfAnyPrefab(go.GetComponents<Component>()[1])) { + for (int i = 0; i < moveUpCalls; i++) { + UnityEditorInternal.ComponentUtility.MoveComponentUp(runtime); + } + } + }; + LyumaAv3Menu.addRuntimeDelegate = (menu) => { + GameObject go = menu.gameObject; + try { + if (PrefabUtility.IsPartOfAnyPrefab(go)) { + PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } catch (System.Exception) {} + int moveUpCalls = go.GetComponents<Component>().Length - 2; + if (!PrefabUtility.IsPartOfAnyPrefab(go.GetComponents<Component>()[1])) { + for (int i = 0; i < moveUpCalls; i++) { + UnityEditorInternal.ComponentUtility.MoveComponentUp(menu); + } + } + }; + } + + // register an event handler when the class is initialized + static LyumaAv3EditorSupport() + { + InitDefaults(); + } + + [MenuItem("Tools/Enable Avatars 3.0 Emulator")] + public static void EnableAv3Testing() { + GameObject go = new GameObject("Avatars 3.0 Emulator Control"); + go.AddComponent<LyumaAv3Emulator>(); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta new file mode 100644 index 00000000..79bb5d5f --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77ddade7c6475a242a8e34b2b7554adc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs new file mode 100644 index 00000000..293f4ef1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs @@ -0,0 +1,354 @@ +/* Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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 UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; + +[CustomEditor(typeof(LyumaAv3Menu))] +public class LyumaAv3MenuEditor : Editor +{ + private readonly Dictionary<Texture2D, Texture2D> _resizedIcons = new Dictionary<Texture2D, Texture2D>(); + private VRCExpressionsMenu _currentMenu; + + public override void OnInspectorGUI() + { + var menu = (LyumaAv3Menu)target; + if (menu.Runtime == null) return; + if (menu.RootMenu == null) + { + menu.RootMenu = (VRCExpressionsMenu)EditorGUILayout.ObjectField(new GUIContent("Expressions Menu"), null, typeof(VRCExpressionsMenu), false); + return; + } + + var isInRootMenu = menu.MenuStack.Count == 0; + GUILayout.BeginHorizontal(); + if (GUILayout.Button(menu.IsMenuOpen ? "Close menu" : "Open menu")) + { + menu.ToggleMenu(); + } + + if (menu.gameObject.GetComponents<LyumaAv3Menu>().Length == 1) + { + if (GUILayout.Button("+", GUILayout.Width(20))) + { + OpenMenuForTwoHandedSupport(menu); + } + } + + GUILayout.EndHorizontal(); + + GUILayout.Label( + (isInRootMenu ? "Expressions" : LabelizeMenu()) + + (menu.IsMenuOpen ? "" : " [Menu is closed]"), + EditorStyles.boldLabel); + + if (!menu.IsMenuOpen) { + return; + } + + _currentMenu = menu.MenuStack.Count == 0 ? menu.RootMenu : menu.MenuStack.Last().ExpressionsMenu; + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField(_currentMenu, typeof(VRCExpressionsMenu), false); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(isInRootMenu || menu.HasActiveControl()); + if (GUILayout.Button("Back")) + { + menu.UserBack(); + } + EditorGUI.EndDisabledGroup(); + for (var controlIndex = 0; controlIndex < _currentMenu.controls.Count; controlIndex++) + { + var control = _currentMenu.controls[controlIndex]; + switch (control.type) + { + case VRCExpressionsMenu.Control.ControlType.Button: + FromToggle(control, "Button"); + break; + case VRCExpressionsMenu.Control.ControlType.Toggle: + FromToggle(control, "Toggle"); + break; + case VRCExpressionsMenu.Control.ControlType.SubMenu: + FromSubMenu(control); + break; + case VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet: + FromTwoAxis(control, controlIndex); + break; + case VRCExpressionsMenu.Control.ControlType.FourAxisPuppet: + FromFourAxis(control, controlIndex); + break; + case VRCExpressionsMenu.Control.ControlType.RadialPuppet: + FromRadial(control, controlIndex); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + if (_currentMenu.controls.Count == 0) + { + EditorGUILayout.LabelField("(This menu has no controls)"); + } + } + + private static void OpenMenuForTwoHandedSupport(LyumaAv3Menu menu) + { + var mainMenu = menu.Runtime.gameObject.AddComponent<LyumaAv3Menu>(); + mainMenu.Runtime = menu.Runtime; + mainMenu.RootMenu = menu.RootMenu; + } + + private string LabelizeMenu() + { + var menu = (LyumaAv3Menu)target; + + var lastMenu = menu.MenuStack.Last(); + if (lastMenu.MandatedParam == null) + { + return lastMenu.ExpressionsMenu.name; + } + + return lastMenu.ExpressionsMenu.name + " (" + lastMenu.MandatedParam.name + " = " + lastMenu.MandatedParam.value + ")"; + } + + private void FromToggle(VRCExpressionsMenu.Control control, string labelType) + { + var menu = (LyumaAv3Menu)target; + + var parameterName = control.parameter.name; + var controlValue = control.value; + + var isActive = menu.IsVisualActive(parameterName, controlValue); + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl()); + if (GreenBackground(isActive, () => ParameterizedButton(control, parameterName, controlValue))) + { + menu.UserToggle(parameterName, controlValue); + } + EditorGUI.EndDisabledGroup(); + LabelType(labelType); + EditorGUILayout.EndHorizontal(); + } + + private void FromSubMenu(VRCExpressionsMenu.Control control) + { + var menu = (LyumaAv3Menu)target; + + var parameterName = control.parameter.name; + var wantedValue = control.value; + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl()); + if (ParameterizedButton(control, parameterName, wantedValue)) + { + if (IsValidParameterName(parameterName)) + { + menu.UserSubMenu(control.subMenu, parameterName, wantedValue); + } + else + { + menu.UserSubMenu(control.subMenu); + } + } + EditorGUI.EndDisabledGroup(); + LabelType("SubMenu"); + EditorGUILayout.EndHorizontal(); + } + + private void FromRadial(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "Radial"); + + if (menu.IsActiveControl(controlIndex)) + { + if (control.subParameters.Length > 0) + { + SliderFloat(menu, control.subParameters[0], "Rotation", 0f, 1f); + } + } + } + + private void FromTwoAxis(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "TwoAxis"); + + if (menu.IsActiveControl(controlIndex)) + { + var sanitySubParamLength = control.subParameters.Length; + if (sanitySubParamLength > 0) SliderFloat(menu, control.subParameters[0], "Horizontal", -1f, 1f); + if (sanitySubParamLength > 1) SliderFloat(menu, control.subParameters[1], "Vertical", -1f, 1f); + + var oldColor = Color.HSVToRGB( + 0, + sanitySubParamLength > 0 ? menu.FindFloat(control.subParameters[0].name) * 0.5f + 0.5f : 0, + sanitySubParamLength > 1 ? menu.FindFloat(control.subParameters[1].name) * 0.5f + 0.5f : 0); + var newColor = EditorGUILayout.ColorField(oldColor); + if (oldColor.r != newColor.r || oldColor.g != newColor.g || oldColor.b != newColor.b) + { + Color.RGBToHSV(newColor, out _, out var s, out var v); + if (sanitySubParamLength > 0) menu.UserFloat(control.subParameters[0].name, s * 2 - 1); + if (sanitySubParamLength > 1) menu.UserFloat(control.subParameters[1].name, v * 2 - 1); + } + } + } + + private void FromFourAxis(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "FourAxis"); + + if (menu.IsActiveControl(controlIndex)) + { + var sanitySubParamLength = control.subParameters.Length; + if (sanitySubParamLength > 0) SliderFloat(menu, control.subParameters[0], "Up", 0f, 1f); + if (sanitySubParamLength > 1) SliderFloat(menu, control.subParameters[1], "Right", 0f, 1f); + if (sanitySubParamLength > 2) SliderFloat(menu, control.subParameters[2], "Down", 0f, 1f); + if (sanitySubParamLength > 3) SliderFloat(menu, control.subParameters[3], "Left", 0f, 1f); + + var oldColor = Color.HSVToRGB( + 0, + (sanitySubParamLength > 0 ? menu.FindFloat(control.subParameters[0].name) : 0) * 0.5f + 0.5f + -(sanitySubParamLength > 2 ? menu.FindFloat(control.subParameters[2].name) : 0) * 0.5f + 0.5f, + (sanitySubParamLength > 1 ? menu.FindFloat(control.subParameters[1].name) : 0) * 0.5f + 0.5f + -(sanitySubParamLength > 3 ? menu.FindFloat(control.subParameters[3].name) : 0) * 0.5f + 0.5f); + var newColor = EditorGUILayout.ColorField(oldColor); + if (oldColor.r != newColor.r || oldColor.g != newColor.g || oldColor.b != newColor.b) + { + Color.RGBToHSV(newColor, out _, out var s, out var v); + if (sanitySubParamLength > 0) menu.UserFloat(control.subParameters[0].name, Mathf.Clamp(v * 2 - 1, 0f, 1f)); + if (sanitySubParamLength > 1) menu.UserFloat(control.subParameters[1].name, Mathf.Clamp(s * 2 - 1, 0f, 1f)); + if (sanitySubParamLength > 2) menu.UserFloat(control.subParameters[2].name, -Mathf.Clamp(v * 2 - 1, -1f, 0f)); + if (sanitySubParamLength > 3) menu.UserFloat(control.subParameters[3].name, -Mathf.Clamp(s * 2 - 1, -1f, 0f)); + } + } + } + + private void SubControl(VRCExpressionsMenu.Control control, int controlIndex, LyumaAv3Menu menu, string labelType) + { + var parameterName = control.parameter.name; + var intValue = (int) control.value; + + var isActive = menu.IsVisualActive(parameterName, intValue); + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl() && !menu.IsActiveControl(controlIndex)); + if (GreenBackground(isActive || menu.IsActiveControl(controlIndex), () => ParameterizedButton(control, parameterName, intValue))) + { + if (!menu.IsActiveControl(controlIndex)) + { + if (IsValidParameterName(parameterName)) + { + menu.UserControlEnter(controlIndex, parameterName, intValue); + } + else + { + menu.UserControlEnter(controlIndex); + } + } + else + { + menu.UserControlExit(); + } + } + + EditorGUI.EndDisabledGroup(); + LabelType(labelType); + EditorGUILayout.EndHorizontal(); + } + + private static void SliderFloat(LyumaAv3Menu menu, VRCExpressionsMenu.Control.Parameter subParam, string intent, float left, float right) + { + if (subParam == null || subParam.name == "") + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Slider(intent, 0, left, right); + EditorGUI.EndDisabledGroup(); + return; + } + + menu.UserFloat(subParam.name, EditorGUILayout.Slider(intent + " (" + subParam.name + ")", menu.FindFloat(subParam.name), left, right)); + } + + private bool ParameterizedButton(VRCExpressionsMenu.Control control, string parameterName, float wantedValue) + { + var hasParameter = IsValidParameterName(parameterName); + return GUILayout.Button(new GUIContent(control.name + (hasParameter ? " (" + parameterName + " = " + wantedValue + ")" : ""), ResizedIcon(control.icon))); + } + + private Texture2D ResizedIcon(Texture2D originalIcon) + { + if (_resizedIcons.ContainsKey(originalIcon)) + { + return _resizedIcons[originalIcon]; + } + + var resizedIcon = GenerateResizedIcon(originalIcon, 32); + _resizedIcons[originalIcon] = resizedIcon; + return resizedIcon; + } + + private static Texture2D GenerateResizedIcon(Texture2D originalIcon, int width) + { + var render = new RenderTexture(width, width, 24); + RenderTexture.active = render; + Graphics.Blit(originalIcon, render); + + var resizedIcon = new Texture2D(width, width); + resizedIcon.ReadPixels(new Rect(0, 0, width, width), 0, 0); + resizedIcon.Apply(); + + return resizedIcon; + } + + private static T GreenBackground<T>(bool isActive, Func<T> inside) + { + var col = GUI.color; + try + { + if (isActive) GUI.color = Color.green; + return inside(); + } + finally + { + GUI.color = col; + } + } + + private static void LabelType(string toggle) + { + EditorGUILayout.LabelField(toggle, GUILayout.Width(70), GUILayout.ExpandHeight(true)); + } + + private static bool IsValidParameterName(string parameterName) + { + return !string.IsNullOrEmpty(parameterName); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta new file mode 100644 index 00000000..f613af7d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 187ea10b8ccd4441a6399698c23122e3 +timeCreated: 1604152513
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt new file mode 100644 index 00000000..f24741b1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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. diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta new file mode 100644 index 00000000..aa464bec --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cb94437f622beb945970e9c5362241bc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md new file mode 100644 index 00000000..93365fae --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md @@ -0,0 +1,120 @@ +# Avatar 3.0 Emulator + +## **[Download the latest version at: https://github.com/lyuma/Av3Emulator/releases](https://github.com/lyuma/Av3Emulator/releases)** + +### **New features in v 2.2.1:** +* Fix off-by-one errors with layer and playable weight changes +* Fix bugs with layer control behaviours +* Fixed saved parameters. They were broken in the last update. +* Added AvatarVersion variable, set to 3 in debug inspector. +* Allow testing IKPose and TPose calibration. +* Force exact path for default controllers from VRCSDK to avoid finding edited duplicates. +* Reduce logspam from parameter drivers. + +### **New features in v 2.1.1:** +* Supports new features in VRChat 2021.1.1 +* Expression menu support for Bool and Float toggles and submenus, in addition to existing support for Int. +* Removed support for Parameter Drivers from sub-animators, to match ingame. Use a checkbox on the "Avatar 3.0 Emulator" control object to re-enable the legacy behavior for nostalgia sake, I dunno. +* To test saving, there is a checkbox (on by default) which keeps saved parameters when the avatar is reset. +* Supports synced bools and triggers same as ingame. The rules for "Add" and "Set" operations are different for bools and triggers in expression parameters and those not. See below for the rules. +* Fixed issues with 8-bit float quantization. Should now match serialization in-game. Quantization of floats is now off by default except if you check the "Locally 8-bit quantized floats" box or make a non-local clone. +* *What is quantization?* Basically, 0.5 locally is not 0.5 for other users. You should not assume floats are sent precisely over the network. A float is serialized into a value between -127 and 127, and deserialized back to -1.0 to 1.0 range. Only -1.0, 0.0 and 1.0 are sent precisely over the network. + +Not implemented: saving and loading saved expression parameters. Parameters are lost every time you enter play mode. + +### **New features in v 2.0.0:** +* **Animator To Debug** dropdown has been fixed. View your animator in action in the Unity Animator window, and update parameters in real time. +* The **Lyuma Av3 Menu** component allows using your avatar's expression menu actions directly from the editor. Click + to open two radial menus at once to test combining puppets. (Thanks to @hai-vr for the contribution!) +* Support for testing Visemes. +* Support for the Is VR checkbox, tracking type and more. (Thanks to @hai-vr for the contribution!) +* Basic support for Generic avatars. +* After using **Tools** -> **Enable Avatars 3.0 Emulator**, set default VR tracking type and other settings by selecting the **Avatars 3.0 Emulator Control** object before entering Play Mode. + +### **About the Avatar 3.0 Emulator:** + +What is Avatars 3.0? Read the VRChat documentation here: https://docs.vrchat.com/v2020.3.2/docs/what-is-avatars-30 + +This is an emulator for Avatars 3.0 reimplemented in the unity editor on top the the unity [PlayableGraph](https://docs.unity3d.com/Manual/Playables-Graph.html) API, using the [AnimationControllerPlayable](https://docs.unity3d.com/2018.4/Documentation/ScriptReference/Animations.AnimatorControllerPlayable.html) and [AnimationLayerMixerPlayable](https://docs.unity3d.com/2018.4/Documentation/ScriptReference/Animations.AnimationLayerMixerPlayable.html) APIs. + +## Av3 Emulator Overview: + +[(Open the above full explanation image)](Screenshots/avatar3emu_tutorial.png) + +## Features: +* Should emulate most features of Avatar3. +* Test non-local syncing by duplicating or clicking the "Create Non Local Clone" checkbox. +* Supports live viewing and editing within unity's Animator window! Use the "Animator To Debug" dropdown to select which layer is visualized in the Animator window. +* Shows Tracking/Animation in the inspector. +* Gesture left/right weight to test analog Fist gesture strength. +* Custom Expression Menus +* Supports viewing and editing float and int paramters, view the Expression Menu, via Parameters tab of the Animator window, via the blend tree input, via Parameter Driver, or manually, by alt-clicking the ▶Floats and ▶Ints headers at the bottom of the Lyuma Av3 Runtime script. +* Visemes for both parameters and testing builtin blend shapes (note: visemes always set to 0% or 100%, not in between.) + +## Not implemented/todo: +* Custom inspector +* visualization of IK Tracking state when a limb is not in Animation mode. +* Eye Tracking / Blinking support is not implemented. +* Set View position not fully implemented. + +## How to use the Av3 Emulator: + +Go to the **Tools** menu, and select **Avatar 3.0 Emulator**. +This will add an object to your scene: you can always remove it if you don't want it to run. Use this object to set default VR mode, tracking type or Animator to Debug settings. Let me know if other settings would be useful here. + +To emulate walking and movement, click the avatar and scroll down the inspector to the bottom section with Lyuma Av3 Runtime component. Here you can change stuff. + +It also supports live interacting with the animator controller. To use this, first click your avatar (even if it was already selected), and then open up **Windows** -> **Animation** -> **Animator** ; and pick the controller using "**Animator To Debug**" dropdown. You can also change parameters from inside the controller, for example moving the red dot in the 2D Blend Tree for Standing. Crouch/Prone by changing the Upright slider; or test Sitting or AFK. + +If you wish to emulate walking, you can also do this by selecting Base layer, opening up the Locmotion controller with your avatar selected, and going to the Standing blendtree and dragging around the red dot. + +## NOTE: about viewing animator state from layers other than Base/locomotion: +The avatar should behave correctly when "Animator to Debug" is set to Base. When you pick another layer, for example FX, the *output* of the animator may differ slightly. For example, Direct BlendTrees with non-zero initial outputs may produce different results. Also, the whole playable weight may be forced to 1 on the debugged animator. + +Another useful tool is the "PlayableGraph Visualizer" which can be found in the unity Package Manager (Advanced -> Show preview packages). It is hard to use, but does a good job of visualizing clip, layer, and playable weights. + +## Inputing custom stage params: + +Use the expression menu under the **Lyuma Av3 Menu** header, or the Parameters tab of the Animator window, after selecting your layer as **Animator To Debug** in the inspector. + +For manual control, you can also alt-click the Floats and Ints sections at the bottom of the Lyuma Av3 Runtime script to expand them all, and change the values from there. + +## Notes about Set, Add ( blank ) and Random operations for boolean and trigger parameters. + +For **Bool and Trigger values not set in expression parameters**, the rules are straightforward: + +* Unsynced Bool Set: sets to true if Value is checked +* Unsynced Bool Random: sets to true if RAND() < Chance, false otherwise +* Unsynced Bool Add: sets to true if Value != 0.0 in debug inspector +* Unsynced trigger Set: sets unconditionally +* Unsynced trigger Random: sets if RAND() < Chance +* Unsynced trigger Add: sets unconditionally + +(Note that "Add" shows up as a blank dropdown. Unlike the inspector, Add uses the Value instead of the Chance field. You need to check the debug inspector. Also, there is no point in using "Add" in this case, so just fix it if you see a blank dropdown.) + +*HOWEVER*, For **Bool values set in expression paramters**, there is a notable difference in the case of the "Add" (blank) operation: + +* Synced Bool Set: sets to true if Value is checked +* Synced Bool Random: sets to true if RAND() < Chance, false otherwise +* Synced Bool Add: sets to true if (Value + (currentValue?1.0:0.0)) != 0.0; sets to false otherwise + +Using Add (blank dropdown) with a Value of -1.0 (in the debug inspector or using "Set" first), it is possible to make a toggle operation, but only for Bool values in your expression parameters. + +Finally, Triggers set in expression parameters act completely unintuitively. **AVOID USING PARAMETER DRIVERS ON TRIGGER PARAMETERS SET IN EXPRESSION PARAMETERS!!!** Still, if you are interested, here are the rules for synced Trigger parameters: + +* Synced trigger Set: Uses the Value in the Debug inspector. sets to true if Value != 0.0; sets to false if Value == 0.0; does not set trigger if set to 0.0 by next frame. +* Synced trigger Random: sets if RAND() < Chance, false otherwise +* Synced trigger Add: sets to true if (Value + (currentValue?1.0:0.0)) != 0.0; sets to false otherwise; does not set trigger if set to 0.0 by next frame. + +Synced Trigger parameters remember if they were set to true, and will only set again if explicitly set to false and then true again. + +## Other known issues: + +The `proxy_` animations included in the SDK are incomplete. Unless you override them, do not expect your avatar to have a full walking cycle, and it is normal for backflip (VRCEmote=6) to stop halfway. + +If you're having unexplained issues, they might happen in game too. The most common cause is due to Write Defaults being turned on in one or more states, in any layer, in any controller. You must have Write Defaults OFF **everywhere** to ensure proper operation 100% of the time. Please see the guide below. + +## Helpful guides + + +[(View full lock inspector explanation)](Screenshots/lock_inspector_tutorial.png) [(View full write defaults off checklist)](Screenshots/write_defaults_off.png) + diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md.meta new file mode 100644 index 00000000..54ba8815 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: be927fe9db2f22a4cb6214690375719f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta new file mode 100644 index 00000000..625b0169 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e280e7f1cd25c844996ce6a42648307f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png Binary files differnew file mode 100644 index 00000000..6ecfb309 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta new file mode 100644 index 00000000..cdb1108c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta @@ -0,0 +1,110 @@ +fileFormatVersion: 2 +guid: 1cd71a2b2dbacf243b80e505e47d4f73 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 9 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 0 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png Binary files differnew file mode 100644 index 00000000..74e50d2d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta new file mode 100644 index 00000000..0402fa18 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta @@ -0,0 +1,110 @@ +fileFormatVersion: 2 +guid: bf8b24d9bc98e3f4db47a81e56b15442 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 9 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 0 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png Binary files differnew file mode 100644 index 00000000..3d9a20cf --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta new file mode 100644 index 00000000..79faaaa4 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta @@ -0,0 +1,110 @@ +fileFormatVersion: 2 +guid: 03d061b2cee9a1049bc814b76b7953ae +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 9 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 0 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta new file mode 100644 index 00000000..e3453795 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ad5fb09acbb8ac45b5a829926d845a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs new file mode 100644 index 00000000..b0e5c1b5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs @@ -0,0 +1,104 @@ +/* Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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 UnityEngine; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; + +[RequireComponent(typeof(Animator))] +public class LyumaAv3Emulator : MonoBehaviour +{ + public bool DefaultToVR = false; + public bool DefaultTestInStation = false; + public LyumaAv3Runtime.TrackingTypeIndex DefaultTrackingType = LyumaAv3Runtime.TrackingTypeIndex.HeadHands; + public VRCAvatarDescriptor.AnimLayerType DefaultAnimatorToDebug = VRCAvatarDescriptor.AnimLayerType.Base; + public bool RestartEmulator; + private bool RestartingEmulator; + public bool CreateNonLocalClone; + [Tooltip("Simulate behavior with sub-animator parameter drivers prior to the 2021.1.1 patch (19 Jan 2021)")] + public bool legacySubAnimatorParameterDriverMode; + + static public LyumaAv3Emulator emulatorInstance; + static public RuntimeAnimatorController EmptyController; + + public List<LyumaAv3Runtime> runtimes = new List<LyumaAv3Runtime>(); + + private void Awake() + { + Animator animator = gameObject.GetOrAddComponent<Animator>(); + animator.enabled = false; + animator.runtimeAnimatorController = EmptyController; + emulatorInstance = this; + } + + private void Start() + { + VRCAvatarDescriptor[] avatars = FindObjectsOfType<VRCAvatarDescriptor>(); + Debug.Log("drv len "+avatars.Length); + foreach (var avadesc in avatars) + { + // Creates the playable director, and initializes animator. + var runtime = avadesc.gameObject.GetOrAddComponent<LyumaAv3Runtime>(); + runtime.emulator = this; + runtimes.Add(runtime); + + var mainMenu = avadesc.gameObject.AddComponent<LyumaAv3Menu>(); + mainMenu.Runtime = runtime; + mainMenu.RootMenu = avadesc.expressionsMenu; + } + } + private void OnDisable() { + foreach (var runtime in runtimes) { + runtime.enabled = false; + } + } + private void OnEnable() { + foreach (var runtime in runtimes) { + runtime.enabled = true; + } + } + private void OnDestroy() { + foreach (var runtime in runtimes) { + Destroy(runtime); + } + runtimes.Clear(); + } + + private void Update() { + if (RestartingEmulator) { + RestartingEmulator = false; + Start(); + } else if (RestartEmulator) { + RestartEmulator = false; + OnDestroy(); + RestartingEmulator = true; + } + if (CreateNonLocalClone) { + CreateNonLocalClone = false; + foreach (var runtime in runtimes) + { + if (runtime.AvatarSyncSource == runtime) + { + runtime.CreateNonLocalClone = true; + } + } + } + } + +} diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta new file mode 100644 index 00000000..9b3a898b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 226ca8e52c3922d4a85b20831b97caf3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs new file mode 100644 index 00000000..c64f51aa --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs @@ -0,0 +1,219 @@ +/* Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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 UnityEngine; +using System.Collections.Generic; +using VRC.SDK3.Avatars.ScriptableObjects; + +public class LyumaAv3Menu : MonoBehaviour +{ + [Serializable] + public class MenuConditional + { + public VRCExpressionsMenu ExpressionsMenu { get; } + public LyumaAv3Runtime.FloatParam MandatedParam { get; } + + public MenuConditional(VRCExpressionsMenu expressionsMenu) + { + ExpressionsMenu = expressionsMenu; + } + + public MenuConditional(VRCExpressionsMenu expressionsMenu, LyumaAv3Runtime.FloatParam mandatedParam) + { + ExpressionsMenu = expressionsMenu; + MandatedParam = mandatedParam; + } + + bool ShouldMenuRemainOpen(List<LyumaAv3Runtime.FloatParam> allConditions) + { + if (MandatedParam.name == null) return true; + + var actualParam = allConditions.Find(param => param.name == MandatedParam.name); + if (actualParam == null) return false; + return actualParam.value == MandatedParam.value; + } + } + + public LyumaAv3Runtime Runtime; + public VRCExpressionsMenu RootMenu; + public List<MenuConditional> MenuStack { get; } = new List<MenuConditional>(); + public bool IsMenuOpen { get; private set; } + private int? _activeControlIndex = null; + private string _activeControlParameterName; + + public delegate void AddRuntime(LyumaAv3Menu runtime); + public static AddRuntime addRuntimeDelegate; + + private void Awake() + { + IsMenuOpen = true; + + if (addRuntimeDelegate != null) { + addRuntimeDelegate(this); + } + } + + public void ToggleMenu() + { + if (IsMenuOpen && _activeControlIndex != null) + { + UserControlExit(); + } + + IsMenuOpen = !IsMenuOpen; + } + + public void UserToggle(string paramName, float wantedValue) { + var intx = Runtime.Ints.Find(param => param.name == paramName); + if (intx != null) { + var currentValue = intx.value; + var newValue = (int)wantedValue == currentValue ? 0 : wantedValue; + DoSetRuntimeX(paramName, newValue); + } + var floatx = Runtime.Floats.Find(param => param.name == paramName); + if (floatx != null) { + var currentValue = floatx.value; + var newValue = wantedValue == currentValue ? 0.0f : wantedValue; + DoSetRuntimeX(paramName, newValue); + } + var boolx = Runtime.Bools.Find(param => param.name == paramName); + if (boolx != null) { + var currentValue = boolx.value; + var newValue = !currentValue; + DoSetRuntimeX(paramName, newValue ? 1.0f : 0.0f); + } + } + + public void UserSubMenu(VRCExpressionsMenu subMenu) + { + MenuStack.Add(new MenuConditional(subMenu)); + } + + public void UserSubMenu(VRCExpressionsMenu subMenu, string paramName, float wantedValue) + { + MenuStack.Add(new MenuConditional(subMenu, new LyumaAv3Runtime.FloatParam {name = paramName, value = wantedValue})); + DoSetRuntimeX(paramName, wantedValue); + } + + public void UserBack() + { + if (MenuStack.Count == 0) return; + if (_activeControlIndex != null) return; + + var lastIndex = MenuStack.Count - 1; + + var last = MenuStack[lastIndex]; + if (last.MandatedParam != null) + { + DoSetRuntimeX(last.MandatedParam.name, 0.0f); + } + MenuStack.RemoveAt(lastIndex); + } + + public void UserControlEnter(int controlIndex) + { + if (_activeControlIndex != null) return; + + _activeControlIndex = controlIndex; + } + + public void UserControlEnter(int controlIndex, string paramName, float enterValue) + { + if (_activeControlIndex != null) return; + + _activeControlIndex = controlIndex; + _activeControlParameterName = paramName; + DoSetRuntimeX(paramName, enterValue); + } + + public void UserControlExit() + { + if (_activeControlIndex == null) return; + + if (_activeControlParameterName != null) + { + DoSetRuntimeX(_activeControlParameterName, 0.0f); + } + _activeControlIndex = null; + _activeControlParameterName = null; + } + + private void DoSetRuntimeX(string paramName, float newValue) + { + var intParam = Runtime.Ints.Find(param => param.name == paramName); + if (intParam != null) { + intParam.value = (int)newValue; + } + var floatParam = Runtime.Floats.Find(param => param.name == paramName); + if (floatParam != null) { + floatParam.value = newValue; + } + var boolParam = Runtime.Bools.Find(param => param.name == paramName); + if (boolParam != null) { + boolParam.value = newValue != 0.0; + } + } + + public bool IsVisualActive(string paramName, float value) + { + var intParam = Runtime.Ints.Find(param => param.name == paramName); + if (intParam != null) { + return intParam.value == (int)value; + } + + var floatParam = Runtime.Floats.Find(param => param.name == paramName); + if (floatParam != null) { + return floatParam.value == value; + } + + var boolParam = Runtime.Bools.Find(param => param.name == paramName); + if (boolParam != null) { + return boolParam.value == (value != 0.0); + } + return false; + } + + public float FindFloat(string paramName) + { + var floatParam = Runtime.Floats.Find(param => param.name == paramName); + if (floatParam == null) return 0; + + return floatParam.value; + } + + public void UserFloat(string paramName, float newValue) + { + var floatParam = Runtime.Floats.Find(param => param.name == paramName); + if (floatParam == null) return; + + floatParam.value = newValue; + } + + public bool HasActiveControl() + { + return _activeControlIndex != null; + } + + public bool IsActiveControl(int controlIndex) + { + return _activeControlIndex == controlIndex; + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta new file mode 100644 index 00000000..178833f5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3865e5f6001a4a9286e8c3f33314c306 +timeCreated: 1604151153
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs new file mode 100644 index 00000000..4b05b425 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs @@ -0,0 +1,1591 @@ +/* Copyright (c) 2020 Lyuma <xn.lyuma@gmail.com> + +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; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; + +// [RequireComponent(typeof(Animator))] +public class LyumaAv3Runtime : MonoBehaviour +{ + static public Dictionary<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController> animLayerToDefaultController = new Dictionary<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController>(); + static public Dictionary<VRCAvatarDescriptor.AnimLayerType, AvatarMask> animLayerToDefaultAvaMask = new Dictionary<VRCAvatarDescriptor.AnimLayerType, AvatarMask>(); + public delegate void UpdateSelectionFunc(GameObject obj); + public static UpdateSelectionFunc updateSelectionDelegate; + public delegate void AddRuntime(LyumaAv3Runtime runtime); + public static AddRuntime addRuntimeDelegate; + + [Tooltip("Resets avatar state machine instantly")] + public bool ResetAvatar; + [Tooltip("Resets avatar state machine and waits until you uncheck this to start")] + public bool ResetAndHold; + [Tooltip("Simulates saving and reloading the avatar")] + public bool KeepSavedParametersOnReset = true; + [Tooltip("In VRChat, 8-bit float quantization only happens remotely. Check this to test your robustness to quantization locally, too. (example: 0.5 -> 0.503")] + public bool locally8bitQuantizedFloats = false; + [Tooltip("Selects the playable layer to be visible in Unity's Animator window. Unless this is set to Base, creates duplicate playable layers with weight 0. It hopefully works the same.")] public VRCAvatarDescriptor.AnimLayerType AnimatorToDebug; + private char PrevAnimatorToDebug; + [HideInInspector] public string SourceObjectPath; + [Header("Assign to non-local duplicate")]public LyumaAv3Runtime AvatarSyncSource; + private int CloneCount; + public bool CreateNonLocalClone; + VRCAvatarDescriptor avadesc; + Avatar animatorAvatar; + Animator animator; + private RuntimeAnimatorController origAnimatorController; + + private List<AnimatorControllerPlayable> playables = new List<AnimatorControllerPlayable>(); + private List<Dictionary<string, int>> playableParamterIds = new List<Dictionary<string, int>>(); + private List<Dictionary<int, float>> playableParamterFloats = new List<Dictionary<int, float>>(); + private List<Dictionary<int, int>> playableParamterInts = new List<Dictionary<int, int>>(); + private List<Dictionary<int, bool>> playableParamterBools = new List<Dictionary<int, bool>>(); + AnimationLayerMixerPlayable playableMixer; + PlayableGraph playableGraph; + VRCExpressionsMenu expressionsMenu; + VRCExpressionParameters stageParameters; + int sittingIndex, tposeIndex, ikposeIndex; + int fxIndex, altFXIndex; + int actionIndex, altActionIndex; + int additiveIndex, altAdditiveIndex; + int gestureIndex, altGestureIndex; + + private int mouthOpenBlendShapeIdx; + private int[] visemeBlendShapeIdxs; + + public static float ClampFloatOnly(float val) { + if (val < -1.0f) { + val = -1.0f; + } + if (val > 1.0f) { + val = 1.0f; + } + return val; + } + public static float ClampAndQuantizeFloat(float val) { + val = ClampFloatOnly(val); + val *= 127.00f; + // if (val > 127.0f) { + // val = 127.0f; + // } + val = Mathf.Round(val); + val = (((sbyte)val) / 127.0f); + val = ClampFloatOnly(val); + return val; + } + public static int ClampByte(int val) { + if (val < 0) { + val = 0; + } + if (val > 255) { + val = 255; + } + return val; + } + + public enum VisemeIndex { + sil, PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, I, O, U + } + public enum GestureIndex { + Neutral, Fist, HandOpen, Fingerpoint, Victory, RockNRoll, HandGun, ThumbsUp + } + public enum TrackingTypeIndex { + Uninitialized, GenericRig, NoFingers, HeadHands, HeadHandsHip, HeadHandsHipFeet = 6 + } + static HashSet<string> BUILTIN_PARAMETERS = new HashSet<string> { + "Viseme", "GestureLeft", "GestureLeftWeight", "GestureRight", "GestureRightWeight", "VelocityX", "VelocityY", "VelocityZ", "LocomotionMode", "Upright", "AngularY", "GroundProximity", "Grounded", "Supine", "FootstepDisable", "Seated", "AFK", "TrackingType", "VRMode", "MuteSelf", "InStation" + }; + [Header("Built-in inputs / Viseme")] + public VisemeIndex Viseme; + [Range(0, 15)] public int VisemeIdx; + private int VisemeInt; + [Header("Built-in inputs / Hand Gestures")] + public GestureIndex GestureLeft; + [Range(0, 9)] public int GestureLeftIdx; + private char GestureLeftIdxInt; + [Range(0, 1)] public float GestureLeftWeight; + public GestureIndex GestureRight; + [Range(0, 9)] public int GestureRightIdx; + private char GestureRightIdxInt; + [Range(0, 1)] public float GestureRightWeight; + [Header("Built-in inputs / Locomotion")] + public Vector3 Velocity; + [Range(-1, 1)] public float AngularY; + [Range(0, 1)] public float Upright; + [Range(-1, 1)] public float GroundProximity; // Not implemented + private int LocomotionMode; // Does not exist. + public bool Grounded; + private bool PrevSeated, PrevTPoseCalibration, PrevIKPoseCalibration; + public bool Seated; + public bool AFK; + public bool TPoseCalibration; + public bool IKPoseCalibration; + //TODO: + bool Supine; // Not implemented + private bool FootstepDisable; // Does not exist. + [Header("Built-in inputs / Tracking Setup and Other")] + public TrackingTypeIndex TrackingType; + [Range(0, 6)] public int TrackingTypeIdx; + private char TrackingTypeIdxInt; + public bool VRMode; + public bool MuteSelf; + public bool InStation; + [HideInInspector] public int AvatarVersion = 3; + + [Header("Output State (Read-only)")] + public bool IsLocal; + public bool LocomotionIsDisabled; + private Vector3 HeadRelativeViewPosition; + public Vector3 ViewPosition; + float AvatarScaleFactor; + public VRCAnimatorTrackingControl.TrackingType trackingHead; + public VRCAnimatorTrackingControl.TrackingType trackingLeftHand; + public VRCAnimatorTrackingControl.TrackingType trackingRightHand; + public VRCAnimatorTrackingControl.TrackingType trackingHip; + public VRCAnimatorTrackingControl.TrackingType trackingLeftFoot; + public VRCAnimatorTrackingControl.TrackingType trackingRightFoot; + public VRCAnimatorTrackingControl.TrackingType trackingLeftFingers; + public VRCAnimatorTrackingControl.TrackingType trackingRightFingers; + public VRCAnimatorTrackingControl.TrackingType trackingEyesAndEyelids; + public VRCAnimatorTrackingControl.TrackingType trackingMouthAndJaw; + + [Serializable] + public class FloatParam + { + [HideInInspector] public string stageName; + public string name; + [HideInInspector] public bool synced; + [Range(-1, 1)] public float value; + [HideInInspector] public float lastValue; + } + [Header("User-generated inputs")] + public List<FloatParam> Floats = new List<FloatParam>(); + public Dictionary<string, int> FloatToIndex = new Dictionary<string, int>(); + + [Serializable] + public class IntParam + { + [HideInInspector] public string stageName; + public string name; + [HideInInspector] public bool synced; + public int value; + [HideInInspector] public int lastValue; + } + public List<IntParam> Ints = new List<IntParam>(); + public Dictionary<string, int> IntToIndex = new Dictionary<string, int>(); + + [Serializable] + public class BoolParam + { + [HideInInspector] public string stageName; + + public string name; + [HideInInspector] public bool synced; + public bool value; + [HideInInspector] public bool lastValue; + [HideInInspector] public bool[] hasTrigger; + [HideInInspector] public bool[] hasBool; + } + public List<BoolParam> Bools = new List<BoolParam>(); + public Dictionary<string, int> BoolToIndex = new Dictionary<string, int>(); + + public Dictionary<string, string> StageParamterToBuiltin = new Dictionary<string, string>(); + + public LyumaAv3Emulator emulator; + + static public Dictionary<Animator, LyumaAv3Runtime> animatorToTopLevelRuntime = new Dictionary<Animator, LyumaAv3Runtime>(); + private HashSet<Animator> attachedAnimators; + private HashSet<string> duplicateParameterAdds = new HashSet<string>(); + + const float BASE_HEIGHT = 1.4f; + + public IEnumerator DelayedEnterPoseSpace(bool setView, float time) { + yield return new WaitForSeconds(time); + if (setView) { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + if (head != null) { + ViewPosition = animator.transform.InverseTransformPoint(head.TransformPoint(HeadRelativeViewPosition)); + } + } else { + ViewPosition = avadesc.ViewPosition; + } + } + + class BlendingState { + float startWeight; + float goalWeight; + float blendStartTime; + float blendDuration; + public bool blending; + + public float UpdateBlending() { + if (blendDuration <= 0) { + blending = false; + return goalWeight; + } + float amt = (Time.time - blendStartTime) / blendDuration; + if (amt >= 1) { + blending = false; + return goalWeight; + } + return Mathf.Lerp(startWeight, goalWeight, amt); + } + public void StartBlend(float startWeight, float goalWeight, float duration) { + this.startWeight = startWeight; + this.blendDuration = duration; + this.blendStartTime = Time.time; + this.goalWeight = goalWeight; + this.blending = true; + } + } + class PlayableBlendingState : BlendingState { + public List<BlendingState> layerBlends = new List<BlendingState>(); + + } + List<PlayableBlendingState> playableBlendingStates = new List<PlayableBlendingState>(); + + static bool getTopLevelRuntime(string component, Animator innerAnimator, out LyumaAv3Runtime runtime) { + if (animatorToTopLevelRuntime.TryGetValue(innerAnimator, out runtime)) { + return true; + } + Transform transform = innerAnimator.transform; + while (transform != null && runtime == null) { + runtime = transform.GetComponent<LyumaAv3Runtime>(); + transform = transform.parent; + } + if (runtime != null) { + if (runtime.attachedAnimators != null) { + Debug.Log("[" + component + "]: " + innerAnimator + " found parent runtime after it was Awoken! Adding to cache. Did you move me?"); + animatorToTopLevelRuntime.Add(innerAnimator, runtime); + runtime.attachedAnimators.Add(innerAnimator); + } else { + Debug.Log("[" + component + "]: " + innerAnimator + " found parent runtime without being Awoken! Wakey Wakey..."); + runtime.Awake(); + } + return true; + } + Debug.LogError("[" + component + "]: outermost Animator is not known: " + innerAnimator + ". If you changed something, consider resetting avatar", innerAnimator); + + return false; + } + + static LyumaAv3Runtime() { + VRCAvatarParameterDriver.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAvatarParameterDriver", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCAvatarParameterDriver:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + if (animator != runtime.animator && (!runtime.emulator || !runtime.emulator.legacySubAnimatorParameterDriverMode)) { + return; + } + if (!runtime.IsLocal && behaviour.localOnly) { + return; + } + HashSet<string> newParameterAdds = new HashSet<string>(); + HashSet<string> deleteParameterAdds = new HashSet<string>(); + foreach (var parameter in behaviour.parameters) { + if (parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add || parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random) { + string dupeKey = parameter.value + ((parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add) ? "add " : "rand ") + parameter.name; + if (!runtime.duplicateParameterAdds.Contains(dupeKey)) { + newParameterAdds.Add(dupeKey); + continue; + } + deleteParameterAdds.Add(dupeKey); + } + string actualName = parameter.name; + int idx; + if (runtime.IntToIndex.TryGetValue(actualName, out idx)) { + switch (parameter.type) { + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: + runtime.Ints[idx].value = (int)parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: + runtime.Ints[idx].value += (int)parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + runtime.Ints[idx].value = UnityEngine.Random.Range((int)parameter.valueMin, (int)parameter.valueMax); + break; + } + } + if (runtime.FloatToIndex.TryGetValue(actualName, out idx)) { + switch (parameter.type) { + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: + runtime.Floats[idx].value = parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: + runtime.Floats[idx].value += parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + runtime.Floats[idx].value = UnityEngine.Random.Range(parameter.valueMin, parameter.valueMax); + break; + } + } + if (runtime.BoolToIndex.TryGetValue(actualName, out idx)) { + bool newValue; + BoolParam bp = runtime.Bools[idx]; + int whichController; + // bp.value = parameter.value != 0; + switch (parameter.type) { + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: + newValue = parameter.value != 0.0f; + if (!bp.synced) { + // Triggers ignore alue and Set unconditionally. + whichController = 0; + foreach (var p in runtime.playables) { + if (bp.hasBool[whichController]) { + p.SetBool(actualName, newValue); + } + whichController++; + } + whichController = 0; + foreach (var p in runtime.playables) { + if (bp.hasTrigger[whichController]) { + p.SetTrigger(actualName); + } + whichController++; + } + bp.lastValue = newValue; + } + bp.value = newValue; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: + /* editor script treats it as random, but it is its own operation */ + newValue = ((bp.value ? 1.0 : 0.0) + parameter.value) != 0.0f; // weird but ok... + Debug.Log("Add bool " + bp.name + " to " + newValue + ", " + (bp.value ? 1.0 : 0.0) + ", " + parameter.value); + if (!bp.synced) { + newValue = parameter.value != 0.0f; + whichController = 0; + // Triggers ignore value and Set unconditionally. + foreach (var p in runtime.playables) { + if (bp.hasBool[whichController]) { + p.SetBool(actualName, newValue); + } + whichController++; + } + whichController = 0; + foreach (var p in runtime.playables) { + if (bp.hasTrigger[whichController]) { + p.SetTrigger(actualName); + } + whichController++; + } + bp.lastValue = newValue; + } + bp.value = newValue; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + // random is *not* idempotent. + newValue = UnityEngine.Random.Range(0.0f, 1.0f) < parameter.chance; + if (!bp.synced) { + whichController = 0; + foreach (var p in runtime.playables) { + if (bp.hasBool[whichController]) { + p.SetBool(actualName, newValue); + } + whichController++; + } + if (newValue) { + whichController = 0; + foreach (var p in runtime.playables) { + if (bp.hasTrigger[whichController]) { + p.SetTrigger(actualName); + } + whichController++; + } + } + bp.lastValue = newValue; + } + bp.value = newValue; + break; + } + } + } + foreach (var key in deleteParameterAdds) { + runtime.duplicateParameterAdds.Remove(key); + } + foreach (var key in newParameterAdds) { + runtime.duplicateParameterAdds.Add(key); + } + }; + }; + VRCPlayableLayerControl.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCPlayableLayerControl", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCPlayableLayerControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + int idx = -1; + switch (behaviour.layer) + { + case VRCPlayableLayerControl.BlendableLayer.Action: + idx = runtime.actionIndex; + break; + case VRCPlayableLayerControl.BlendableLayer.Additive: + idx = runtime.additiveIndex; + break; + case VRCPlayableLayerControl.BlendableLayer.FX: + idx = runtime.fxIndex; + break; + case VRCPlayableLayerControl.BlendableLayer.Gesture: + idx = runtime.gestureIndex; + break; + } + if (idx >= 0 && idx < runtime.playableBlendingStates.Count) + { + runtime.playableBlendingStates[idx].StartBlend(runtime.playableMixer.GetInputWeight(idx + 1), behaviour.goalWeight, behaviour.blendDuration); + // Debug.Log("Start blend of whole playable " + idx + " from " + runtime.playableMixer.GetInputWeight(idx + 1) + " to " + behaviour.goalWeight); + } + }; + }; + VRCAnimatorLayerControl.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAnimatorLayerControl", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCAnimatorLayerControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + int idx = -1, altidx = -1; + switch (behaviour.playable) + { + case VRCAnimatorLayerControl.BlendableLayer.Action: + idx = runtime.actionIndex; + altidx = runtime.altActionIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.Additive: + idx = runtime.additiveIndex; + altidx = runtime.altAdditiveIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.FX: + idx = runtime.fxIndex; + altidx = runtime.altFXIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.Gesture: + idx = runtime.gestureIndex; + altidx = runtime.altGestureIndex; + break; + } + if (idx >= 0 && idx < runtime.playableBlendingStates.Count) + { + if (behaviour.layer >= 0 && behaviour.layer < runtime.playableBlendingStates[idx].layerBlends.Count) + { + runtime.playableBlendingStates[idx].layerBlends[behaviour.layer].StartBlend(runtime.playables[idx].GetLayerWeight(behaviour.layer), behaviour.goalWeight, behaviour.blendDuration); + // Debug.Log("Start blend of playable " + idx + " layer " + behaviour.layer + " from " + runtime.playables[idx].GetLayerWeight(behaviour.layer) + " to " + behaviour.goalWeight); + if (altidx >= 0) { + runtime.playableBlendingStates[altidx].layerBlends[behaviour.layer].StartBlend(runtime.playables[altidx].GetLayerWeight(behaviour.layer), behaviour.goalWeight, behaviour.blendDuration); + // Debug.Log("Start blend of alt playable " + altidx + " layer " + behaviour.layer + " from " + runtime.playables[altidx].GetLayerWeight(behaviour.layer) + " to " + behaviour.goalWeight); + } + } + } + }; + }; + VRCAnimatorLocomotionControl.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAnimatorLocomotionControl", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCAnimatorLocomotionControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + // I legit don't know + runtime.LocomotionIsDisabled = behaviour.disableLocomotion; + }; + }; + VRCAnimatorTemporaryPoseSpace.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAnimatorSetView", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCAnimatorSetView:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + // fixedDelay: Is the delay fixed or normalized... + // The layerIndex is not passed into the delegate, so we cannot reimplement fixedDelay. + runtime.StartCoroutine(runtime.DelayedEnterPoseSpace(behaviour.enterPoseSpace, behaviour.delayTime)); + }; + }; + VRCAnimatorTrackingControl.Initialize += (x) => { + x.ApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAnimatorTrackingControl", animator, out runtime)) { + return; + } + if (behaviour.debugString != null && behaviour.debugString.Length > 0) + { + Debug.Log("[VRCAnimatorTrackingControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); + } + if (!runtime) + { + return; + } + + if (behaviour.trackingMouth != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingMouthAndJaw = behaviour.trackingMouth; + } + if (behaviour.trackingHead != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingHead = behaviour.trackingHead; + } + if (behaviour.trackingRightFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingRightFingers = behaviour.trackingRightFingers; + } + if (behaviour.trackingEyes != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingEyesAndEyelids = behaviour.trackingEyes; + } + if (behaviour.trackingLeftFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingLeftFingers = behaviour.trackingLeftFingers; + } + if (behaviour.trackingLeftFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingLeftFoot = behaviour.trackingLeftFoot; + } + if (behaviour.trackingHip != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingHip = behaviour.trackingHip; + } + if (behaviour.trackingRightHand != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingRightHand = behaviour.trackingRightHand; + } + if (behaviour.trackingLeftHand != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingLeftHand = behaviour.trackingLeftHand; + } + if (behaviour.trackingRightFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingRightFoot = behaviour.trackingRightFoot; + } + }; + }; + } + + void OnDestroy () { + if (this.playableGraph.IsValid()) { + this.playableGraph.Destroy(); + } + foreach (var anim in attachedAnimators) { + LyumaAv3Runtime runtime; + if (animatorToTopLevelRuntime.TryGetValue(anim, out runtime) && runtime == this) + { + animatorToTopLevelRuntime.Remove(anim); + } + } + if (animator.playableGraph.IsValid()) + { + animator.playableGraph.Destroy(); + } + animator.runtimeAnimatorController = origAnimatorController; + } + + void Awake() + { + if (attachedAnimators != null) { + Debug.Log("Deduplicating Awake() call if we already got awoken by our children."); + return; + } + attachedAnimators = new HashSet<Animator>(); + if (AvatarSyncSource == null) { + Transform transform = this.transform; + SourceObjectPath = ""; + while (transform != null) { + SourceObjectPath = "/" + transform.name + SourceObjectPath; + transform = transform.parent; + } + AvatarSyncSource = this; + } else { + AvatarSyncSource = GameObject.Find(SourceObjectPath).GetComponent<LyumaAv3Runtime>(); + } + + AnimatorToDebug = VRCAvatarDescriptor.AnimLayerType.Base; + + if (LyumaAv3Emulator.emulatorInstance == null) { + Debug.LogError("LyumaAv3Runtime awoken without an LyumaAv3Emulator instance!", this); + } else { + this.VRMode = LyumaAv3Emulator.emulatorInstance.DefaultToVR; + this.TrackingType = LyumaAv3Emulator.emulatorInstance.DefaultTrackingType; + this.InStation = LyumaAv3Emulator.emulatorInstance.DefaultTestInStation; + this.AnimatorToDebug = LyumaAv3Emulator.emulatorInstance.DefaultAnimatorToDebug; + } + + animator = this.gameObject.GetOrAddComponent<Animator>(); + animatorAvatar = animator.avatar; + // Default values. + Grounded = true; + Upright = 1.0f; + if (!animator.isHuman) { + TrackingType = TrackingTypeIndex.GenericRig; + } else if (!VRMode) { + TrackingType = TrackingTypeIndex.HeadHands; + } + avadesc = this.gameObject.GetComponent<VRCAvatarDescriptor>(); + if (avadesc.VisemeSkinnedMesh == null) { + mouthOpenBlendShapeIdx = -1; + visemeBlendShapeIdxs = new int[0]; + } else { + mouthOpenBlendShapeIdx = avadesc.VisemeSkinnedMesh.sharedMesh.GetBlendShapeIndex(avadesc.MouthOpenBlendShapeName); + visemeBlendShapeIdxs = new int[avadesc.VisemeBlendShapes == null ? 0 : avadesc.VisemeBlendShapes.Length]; + if (avadesc.VisemeBlendShapes != null) { + for (int i = 0; i < avadesc.VisemeBlendShapes.Length; i++) { + visemeBlendShapeIdxs[i] = avadesc.VisemeSkinnedMesh.sharedMesh.GetBlendShapeIndex(avadesc.VisemeBlendShapes[i]); + } + } + } + InitializeAnimator(); + if (addRuntimeDelegate != null) { + addRuntimeDelegate(this); + } + } + + private void InitializeAnimator() + { + ResetAvatar = false; + PrevAnimatorToDebug = (char)(int)AnimatorToDebug; + + animator = this.gameObject.GetOrAddComponent<Animator>(); + animator.avatar = animatorAvatar; + animator.applyRootMotion = false; + animator.updateMode = AnimatorUpdateMode.Normal; + animator.cullingMode = AnimatorCullingMode.CullCompletely; + animator.runtimeAnimatorController = null; + + avadesc = this.gameObject.GetComponent<VRCAvatarDescriptor>(); + ViewPosition = avadesc.ViewPosition; + AvatarScaleFactor = ViewPosition.magnitude / BASE_HEIGHT; // mostly guessing... + HeadRelativeViewPosition = ViewPosition; + if (animator.avatar != null) + { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + if (head != null) { + HeadRelativeViewPosition = head.InverseTransformPoint(animator.transform.TransformPoint(ViewPosition)); + } + } + expressionsMenu = avadesc.expressionsMenu; + if (expressionsMenu != null) + { + stageParameters = avadesc.expressionParameters; + } + if (origAnimatorController != null) { + origAnimatorController = animator.runtimeAnimatorController; + } + + VRCAvatarDescriptor.CustomAnimLayer[] baselayers = avadesc.baseAnimationLayers; + VRCAvatarDescriptor.CustomAnimLayer[] speciallayers = avadesc.specialAnimationLayers; + List<VRCAvatarDescriptor.CustomAnimLayer> allLayers = new List<VRCAvatarDescriptor.CustomAnimLayer>(); + // foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + // if (AnimatorToDebug == cal.type) { + // allLayers.Add(cal); + // } + // } + // foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + // if (AnimatorToDebug == cal.type) { + // allLayers.Add(cal); + // } + // } + int i = 0; + if (AnimatorToDebug != VRCAvatarDescriptor.AnimLayerType.Base) { + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + if (AnimatorToDebug == cal.type) { + i++; + allLayers.Add(cal); + break; + } + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + if (AnimatorToDebug == cal.type) { + i++; + allLayers.Add(cal); + break; + } + } + // WE ADD ALL THE LAYERS A SECOND TIME BECAUSE! + // Add and Random Parameter drivers are not idepotent. + // To solve this, we ignore every other invocation. + // Therefore, we must add all layers twice, not just the one we are debugging...??? + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + if (AnimatorToDebug != cal.type) { + i++; + allLayers.Add(cal); + } + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + if (AnimatorToDebug != cal.type) { + i++; + allLayers.Add(cal); + } + } + } + int dupeOffset = i; + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + if (cal.type == VRCAvatarDescriptor.AnimLayerType.Base || cal.type == VRCAvatarDescriptor.AnimLayerType.Additive) { + i++; + allLayers.Add(cal); + } + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + i++; + allLayers.Add(cal); + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + if (!(cal.type == VRCAvatarDescriptor.AnimLayerType.Base || cal.type == VRCAvatarDescriptor.AnimLayerType.Additive)) { + i++; + allLayers.Add(cal); + } + } + + if (playableGraph.IsValid()) { + playableGraph.Destroy(); + } + playables.Clear(); + playableBlendingStates.Clear(); + + for (i = 0; i < allLayers.Count; i++) { + playables.Add(new AnimatorControllerPlayable()); + playableBlendingStates.Add(null); + } + + actionIndex = fxIndex = gestureIndex = additiveIndex = sittingIndex = ikposeIndex = tposeIndex = -1; + altActionIndex = altFXIndex = altGestureIndex = altAdditiveIndex = -1; + + foreach (var anim in attachedAnimators) { + LyumaAv3Runtime runtime; + if (animatorToTopLevelRuntime.TryGetValue(anim, out runtime) && runtime == this) + { + animatorToTopLevelRuntime.Remove(anim); + } + } + attachedAnimators.Clear(); + Animator[] animators = this.gameObject.GetComponentsInChildren<Animator>(true); + Debug.Log("anim len "+animators.Length); + foreach (Animator anim in animators) + { + attachedAnimators.Add(anim); + animatorToTopLevelRuntime.Add(anim, this); + } + + Dictionary<string, float> stageNameToValue = new Dictionary<string, float>(); + if (IsLocal) { + foreach (var val in Ints) { + stageNameToValue[val.stageName] = val.value; + } + foreach (var val in Floats) { + stageNameToValue[val.stageName] = val.value; + } + foreach (var val in Bools) { + stageNameToValue[val.stageName] = val.value ? 1.0f : 0.0f; + } + } + Ints.Clear(); + Bools.Clear(); + Floats.Clear(); + StageParamterToBuiltin.Clear(); + IntToIndex.Clear(); + FloatToIndex.Clear(); + BoolToIndex.Clear(); + playableParamterFloats.Clear(); + playableParamterIds.Clear(); + playableParamterInts.Clear(); + playableParamterBools.Clear(); + HashSet<string> usedparams = new HashSet<string>(BUILTIN_PARAMETERS); + i = 0; + if (stageParameters != null) + { + int stageId = 0; + foreach (var stageParam in stageParameters.parameters) + { + stageId++; // one-indexed + if (stageParam.name == null || stageParam.name.Length == 0) { + continue; + } + string stageName = stageParam.name + (stageParam.saved ? " (saved/SYNCED)" : " (SYNCED)"); //"Stage" + stageId; + float lastDefault = (stageParam.saved && KeepSavedParametersOnReset && stageNameToValue.ContainsKey(stageName) ? stageNameToValue[stageName] : stageParam.defaultValue); + StageParamterToBuiltin.Add(stageName, stageParam.name); + if ((int)stageParam.valueType == 0) + { + IntParam param = new IntParam(); + param.stageName = stageName; + param.synced = true; + param.name = stageParam.name; + param.value = (int)lastDefault; + param.lastValue = 0; + IntToIndex[param.name] = Ints.Count; + Ints.Add(param); + } + else if ((int)stageParam.valueType == 1) + { + FloatParam param = new FloatParam(); + param.stageName = stageName; + param.synced = true; + param.name = stageParam.name; + param.value = lastDefault; + param.lastValue = 0; + FloatToIndex[param.name] = Floats.Count; + Floats.Add(param); + } + else if ((int)stageParam.valueType == 2) + { + BoolParam param = new BoolParam(); + param.stageName = stageName; + param.synced = true; + param.name = stageParam.name; + param.value = lastDefault != 0.0; + param.lastValue = false; + param.hasBool = new bool[playables.Count]; + param.hasTrigger = new bool[playables.Count]; + BoolToIndex[param.name] = Bools.Count; + Bools.Add(param); + } + usedparams.Add(stageParam.name); + i++; + } + } + + if (animator.playableGraph.IsValid()) + { + animator.playableGraph.Destroy(); + } + // var director = avadesc.gameObject.GetComponent<PlayableDirector>(); + playableGraph = PlayableGraph.Create("LyumaAvatarRuntime - " + this.gameObject.name); + var externalOutput = AnimationPlayableOutput.Create(playableGraph, "ExternalAnimator", animator); + playableMixer = AnimationLayerMixerPlayable.Create(playableGraph, allLayers.Count + 1); + externalOutput.SetSourcePlayable(playableMixer); + animator.applyRootMotion = false; + + i = 0; + // playableMixer.ConnectInput(0, AnimatorControllerPlayable.Create(playableGraph, allLayers[layerToDebug - 1].animatorController), 0, 0); + foreach (VRCAvatarDescriptor.CustomAnimLayer vrcAnimLayer in allLayers) + { + i++; // Ignore zeroth layer. + bool additive = (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Additive); + RuntimeAnimatorController ac = null; + AvatarMask mask; + if (vrcAnimLayer.isDefault) { + ac = animLayerToDefaultController[vrcAnimLayer.type]; + mask = animLayerToDefaultAvaMask[vrcAnimLayer.type]; + } else + { + ac = vrcAnimLayer.animatorController; + mask = vrcAnimLayer.mask; + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.FX) { + mask = animLayerToDefaultAvaMask[vrcAnimLayer.type]; // Force mask to prevent muscle overrides. + } + } + if (ac == null) { + // i is not incremented. + continue; + } + AnimatorControllerPlayable humanAnimatorPlayable = AnimatorControllerPlayable.Create(playableGraph, ac); + PlayableBlendingState pbs = new PlayableBlendingState(); + for (int j = 0; j < humanAnimatorPlayable.GetLayerCount(); j++) + { + humanAnimatorPlayable.SetLayerWeight(j, 1f); + pbs.layerBlends.Add(new BlendingState()); + } + + // If we are debugging a particular layer, we must put that first. + // The Animator Controller window only shows the first layer. + int effectiveIdx = i; + + playableMixer.ConnectInput((int)effectiveIdx, humanAnimatorPlayable, 0, 1); + playables[effectiveIdx - 1] = humanAnimatorPlayable; + playableBlendingStates[effectiveIdx - 1] = pbs; + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Sitting) { + if (i >= dupeOffset) { + sittingIndex = effectiveIdx - 1; + } + playableMixer.SetInputWeight(effectiveIdx, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.IKPose) + { + if (i >= dupeOffset) { + ikposeIndex = effectiveIdx - 1; + } + playableMixer.SetInputWeight(effectiveIdx, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.TPose) + { + if (i >= dupeOffset) { + tposeIndex = effectiveIdx - 1; + } + playableMixer.SetInputWeight(effectiveIdx, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Action) + { + playableMixer.SetInputWeight(i, 0f); + if (i < dupeOffset) { + altActionIndex = effectiveIdx - 1; + } else { + actionIndex = effectiveIdx - 1; + } + + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture) { + if (i < dupeOffset) { + altGestureIndex = effectiveIdx - 1; + } else { + gestureIndex = effectiveIdx - 1; + } + + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Additive) + { + if (i < dupeOffset) { + altAdditiveIndex = effectiveIdx - 1; + } else { + additiveIndex = effectiveIdx - 1; + } + + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.FX) + { + if (i < dupeOffset) { + altFXIndex = effectiveIdx - 1; + } else { + fxIndex = effectiveIdx - 1; + } + } + // AnimationControllerLayer acLayer = new AnimationControllerLayer() + if (mask != null) + { + playableMixer.SetLayerMaskFromAvatarMask((uint)effectiveIdx, mask); + } + if (additive) + { + playableMixer.SetLayerAdditive((uint)effectiveIdx, true); + } + + if (i < dupeOffset) {//i == 0 && AnimatorToDebug != VRCAvatarDescriptor.AnimLayerType.Base) { + playableMixer.SetInputWeight(i, 0f); + } + } + + //playableParamterIds + int whichcontroller = 0; + playableParamterIds.Clear(); + foreach (AnimatorControllerPlayable playable in playables) { + Dictionary<string, int> parameterIndices = new Dictionary<string, int>(); + playableParamterInts.Add(new Dictionary<int, int>()); + playableParamterFloats.Add(new Dictionary<int, float>()); + playableParamterBools.Add(new Dictionary<int, bool>()); + // Debug.Log("SETUP index " + whichcontroller + " len " + playables.Count); + playableParamterIds.Add(parameterIndices); + int pcnt = playable.IsValid() ? playable.GetParameterCount() : 0; + for (i = 0; i < pcnt; i++) { + AnimatorControllerParameter aparam = playable.GetParameter(i); + string actualName; + if (!StageParamterToBuiltin.TryGetValue(aparam.name, out actualName)) { + actualName = aparam.name; + } + parameterIndices[actualName] = aparam.nameHash; + if (usedparams.Contains(actualName)) { + if (BoolToIndex.ContainsKey(aparam.name) && aparam.type == AnimatorControllerParameterType.Bool) { + Bools[BoolToIndex[aparam.name]].hasBool[whichcontroller] = true; + } + if (BoolToIndex.ContainsKey(aparam.name) && aparam.type == AnimatorControllerParameterType.Trigger) { + Bools[BoolToIndex[aparam.name]].hasTrigger[whichcontroller] = true; + } + continue; + } + if (aparam.type == AnimatorControllerParameterType.Int) { + IntParam param = new IntParam(); + param.stageName = aparam.name + " (local)"; + param.synced = false; + param.name = aparam.name; + param.value = aparam.defaultInt; + param.lastValue = param.value; + IntToIndex[param.name] = Ints.Count; + Ints.Add(param); + usedparams.Add(aparam.name); + } else if (aparam.type == AnimatorControllerParameterType.Float) { + FloatParam param = new FloatParam(); + param.stageName = aparam.name + " (local)"; + param.synced = false; + param.name = aparam.name; + param.value = aparam.defaultFloat; + param.lastValue = param.value; + FloatToIndex[param.name] = Floats.Count; + Floats.Add(param); + usedparams.Add(aparam.name); + } else if (aparam.type == AnimatorControllerParameterType.Trigger || aparam.type == AnimatorControllerParameterType.Bool) { + BoolParam param = new BoolParam(); + param.stageName = aparam.name + " (local)"; + param.synced = false; + param.name = aparam.name; + param.value = aparam.defaultBool; + param.lastValue = param.value; + param.hasBool = new bool[playables.Count]; + param.hasTrigger = new bool[playables.Count]; + param.hasBool[whichcontroller] = aparam.type == AnimatorControllerParameterType.Bool; + param.hasTrigger[whichcontroller] = aparam.type == AnimatorControllerParameterType.Trigger; + BoolToIndex[param.name] = Bools.Count; + Bools.Add(param); + usedparams.Add(aparam.name); + } + } + whichcontroller++; + } + + // Plays the Graph. + playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + Debug.Log(this.gameObject.name + " : Awoken and ready to Play."); + playableGraph.Play(); + Debug.Log(this.gameObject.name + " : Playing."); + } + + + private bool isResetting; + private bool isResettingHold; + private bool isResettingSel; + // Update is called once per frame + void Update() + { + if (isResettingSel) { + isResettingSel = false; + if (updateSelectionDelegate != null) { + updateSelectionDelegate(this.gameObject); + } + } + if (isResettingHold && (!ResetAvatar || !ResetAndHold)) { + ResetAndHold = ResetAvatar = false; + isResettingSel = true; + if (updateSelectionDelegate != null) { + updateSelectionDelegate(null); + } + } + if (ResetAvatar && ResetAndHold) { + return; + } + if (ResetAndHold && !ResetAvatar && !isResetting) { + ResetAvatar = true; + isResettingHold = true; + } + if (isResetting && !ResetAndHold) { + InitializeAnimator(); + isResetting = false; + isResettingHold = false; + } + if (PrevAnimatorToDebug != (char)(int)AnimatorToDebug || ResetAvatar) { + actionIndex = fxIndex = gestureIndex = additiveIndex = sittingIndex = ikposeIndex = tposeIndex = -1; + altActionIndex = altFXIndex = altGestureIndex = altAdditiveIndex = -1; + // animator.runtimeAnimatorController = null; + if (playableGraph.IsValid()) { + playableGraph.Destroy(); + } + if (animator.playableGraph.IsValid()) { + animator.playableGraph.Destroy(); + } + animator.Update(0); + animator.Rebind(); + animator.Update(0); + animator.StopPlayback(); + GameObject.DestroyImmediate(animator); + // animator.runtimeAnimatorController = EmptyController; + if (updateSelectionDelegate != null) { + updateSelectionDelegate(null); + } + isResetting = true; + isResettingSel = true; + return; + } + if (CreateNonLocalClone) { + CreateNonLocalClone = false; + GameObject go = GameObject.Instantiate(AvatarSyncSource.gameObject); + AvatarSyncSource.CloneCount++; + go.name = go.name.Substring(0, go.name.Length - 7) + " (Non-Local " + AvatarSyncSource.CloneCount + ")"; + go.transform.position = go.transform.position + AvatarSyncSource.CloneCount * new Vector3(0.4f, 0.0f, 0.4f); + } + if (AvatarSyncSource != this) { + for (int i = 0; i < Ints.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Ints[i].stageName)) { + Ints[i].value = ClampByte(AvatarSyncSource.Ints[i].value); + } + } + for (int i = 0; i < Floats.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { + Floats[i].value = ClampAndQuantizeFloat(AvatarSyncSource.Floats[i].value); + } + } + for (int i = 0; i < Bools.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Bools[i].stageName)) { + Bools[i].value = AvatarSyncSource.Bools[i].value; + } + } + VisemeInt = VisemeIdx = AvatarSyncSource.VisemeInt; + Viseme = (VisemeIndex)VisemeInt; + GestureLeft = AvatarSyncSource.GestureLeft; + GestureLeftIdx = AvatarSyncSource.GestureLeftIdx; + GestureLeftIdxInt = AvatarSyncSource.GestureLeftIdxInt; + GestureLeftWeight = AvatarSyncSource.GestureLeftWeight; + GestureRight = AvatarSyncSource.GestureRight; + GestureRightIdx = AvatarSyncSource.GestureRightIdx; + GestureRightIdxInt = AvatarSyncSource.GestureRightIdxInt; + GestureRightWeight = AvatarSyncSource.GestureRightWeight; + Velocity = AvatarSyncSource.Velocity; + AngularY = AvatarSyncSource.AngularY; + Upright = AvatarSyncSource.Upright; + LocomotionMode = AvatarSyncSource.LocomotionMode; + GroundProximity = AvatarSyncSource.GroundProximity; + Grounded = AvatarSyncSource.Grounded; + Seated = AvatarSyncSource.Seated; + AFK = AvatarSyncSource.AFK; + Supine = AvatarSyncSource.Supine; + TrackingType = AvatarSyncSource.TrackingType; + TrackingTypeIdx = AvatarSyncSource.TrackingTypeIdx; + TrackingTypeIdxInt = AvatarSyncSource.TrackingTypeIdxInt; + VRMode = AvatarSyncSource.VRMode; + MuteSelf = AvatarSyncSource.MuteSelf; + InStation = AvatarSyncSource.InStation; + FootstepDisable = AvatarSyncSource.FootstepDisable; + } + for (int i = 0; i < Floats.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { + if (locally8bitQuantizedFloats) { + Floats[i].value = ClampAndQuantizeFloat(Floats[i].value); + } else { + Floats[i].value = ClampFloatOnly(Floats[i].value); + } + } + } + for (int i = 0; i < Ints.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Ints[i].stageName)) { + Ints[i].value = ClampByte(Ints[i].value); + } + } + if (Seated != PrevSeated && sittingIndex >= 0) + { + playableBlendingStates[sittingIndex].StartBlend(playableMixer.GetInputWeight(sittingIndex + 1), Seated ? 1f : 0f, 0.25f); + PrevSeated = Seated; + } + if (TPoseCalibration != PrevTPoseCalibration) { + playableBlendingStates[tposeIndex].StartBlend(playableMixer.GetInputWeight(tposeIndex + 1), TPoseCalibration ? 1f : 0f, 0.0f); + PrevTPoseCalibration = TPoseCalibration; + } + if (IKPoseCalibration != PrevIKPoseCalibration) { + playableBlendingStates[ikposeIndex].StartBlend(playableMixer.GetInputWeight(ikposeIndex + 1), IKPoseCalibration ? 1f : 0f, 0.0f); + PrevIKPoseCalibration = IKPoseCalibration; + } + if (VisemeIdx != VisemeInt) { + VisemeInt = VisemeIdx; + Viseme = (VisemeIndex)VisemeInt; + } + if ((int)Viseme != VisemeInt) { + VisemeInt = (int)Viseme; + VisemeIdx = VisemeInt; + } + if (GestureLeftIdx != GestureLeftIdxInt) { + GestureLeft = (GestureIndex)GestureLeftIdx; + GestureLeftIdx = (int)GestureLeft; + GestureLeftIdxInt = (char)GestureLeftIdx; + } + if ((int)GestureLeft != (int)GestureLeftIdxInt) { + GestureLeftIdx = (int)GestureLeft; + GestureLeftIdxInt = (char)GestureLeftIdx; + } + if (GestureRightIdx != GestureRightIdxInt) { + GestureRight = (GestureIndex)GestureRightIdx; + GestureRightIdx = (int)GestureRight; + GestureRightIdxInt = (char)GestureRightIdx; + } + if ((int)GestureRight != (int)GestureRightIdxInt) { + GestureRightIdx = (int)GestureRight; + GestureRightIdxInt = (char)GestureRightIdx; + } + if (TrackingTypeIdx != TrackingTypeIdxInt) { + TrackingType = (TrackingTypeIndex)TrackingTypeIdx; + TrackingTypeIdx = (int)TrackingType; + TrackingTypeIdxInt = (char)TrackingTypeIdx; + } + if ((int)TrackingType != TrackingTypeIdxInt) { + TrackingTypeIdx = (int)TrackingType; + TrackingTypeIdxInt = (char)TrackingTypeIdx; + } + IsLocal = AvatarSyncSource == this; + + int whichcontroller; + whichcontroller = 0; + foreach (AnimatorControllerPlayable playable in playables) + { + if (!playable.IsValid()) { + continue; + } + // Debug.Log("Index " + whichcontroller + " len " + playables.Count); + Dictionary<string, int> parameterIndices = playableParamterIds[whichcontroller]; + int paramid; + foreach (FloatParam param in Floats) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (param.value != param.lastValue) { + playable.SetFloat(paramid, param.value); + } + } + } + foreach (IntParam param in Ints) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (param.value != param.lastValue) { + playable.SetInteger(paramid, param.value); + } + } + } + foreach (BoolParam param in Bools) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (param.value != param.lastValue) { + playable.SetBool(paramid, param.value); // also sets triggers. + // if (param.value) { + // playable.SetTrigger(paramid); + // } + } + } + } + whichcontroller++; + } + foreach (FloatParam param in Floats) { + param.lastValue = param.value; + } + foreach (IntParam param in Ints) { + param.lastValue = param.value; + } + foreach (BoolParam param in Bools) { + param.lastValue = param.value; + } + + whichcontroller = 0; + foreach (AnimatorControllerPlayable playable in playables) + { + if (!playable.IsValid()) { + continue; + } + // Debug.Log("Index " + whichcontroller + " len " + playables.Count); + Dictionary<string, int> parameterIndices = playableParamterIds[whichcontroller]; + Dictionary<int, int> paramterInts = playableParamterInts[whichcontroller]; + Dictionary<int, float> paramterFloats = playableParamterFloats[whichcontroller]; + Dictionary<int, bool> paramterBools = playableParamterBools[whichcontroller]; + int paramid; + float fparam; + int iparam; + bool bparam; + foreach (FloatParam param in Floats) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam)) { + if (fparam != playable.GetFloat(paramid)) { + param.value = param.lastValue = playable.GetFloat(paramid); + } + } + paramterFloats[paramid] = param.value; + } + } + foreach (IntParam param in Ints) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam)) { + if (iparam != playable.GetInteger(paramid)) { + param.value = param.lastValue = playable.GetInteger(paramid); + } + } + paramterInts[paramid] = param.value; + } + } + foreach (BoolParam param in Bools) + { + if (param.hasBool[whichcontroller] && parameterIndices.TryGetValue(param.name, out paramid)) + { + if (paramterBools.TryGetValue(paramid, out bparam)) { + if (bparam != (playable.GetBool(paramid))) { + param.value = param.lastValue = playable.GetBool(paramid); + } + } + paramterBools[paramid] = param.value; + } + } + if (parameterIndices.TryGetValue("Viseme", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + VisemeInt = VisemeIdx = playable.GetInteger(paramid); + Viseme = (VisemeIndex)VisemeInt; + } + playable.SetInteger(paramid, VisemeInt); + paramterInts[paramid] = VisemeInt; + } + if (parameterIndices.TryGetValue("GestureLeft", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + GestureLeftIdx = playable.GetInteger(paramid); + GestureLeftIdxInt = (char)GestureLeftIdx; + GestureLeft = (GestureIndex)GestureLeftIdx; + } + playable.SetInteger(paramid, (int)GestureLeft); + paramterInts[paramid] = (int)GestureLeft; + } + if (parameterIndices.TryGetValue("GestureLeftWeight", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + GestureLeftWeight = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, GestureLeftWeight); + paramterFloats[paramid] = GestureLeftWeight; + } + if (parameterIndices.TryGetValue("GestureRight", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + GestureRightIdx = playable.GetInteger(paramid); + GestureRightIdxInt = (char)GestureRightIdx; + GestureRight = (GestureIndex)GestureRightIdx; + } + playable.SetInteger(paramid, (int)GestureRight); + paramterInts[paramid] = (int)GestureRight; + } + if (parameterIndices.TryGetValue("GestureRightWeight", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + GestureRightWeight = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, GestureRightWeight); + paramterFloats[paramid] = GestureRightWeight; + } + if (parameterIndices.TryGetValue("VelocityX", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + Velocity.x = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, Velocity.x); + paramterFloats[paramid] = Velocity.x; + } + if (parameterIndices.TryGetValue("VelocityY", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + Velocity.y = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, Velocity.y); + paramterFloats[paramid] = Velocity.y; + } + if (parameterIndices.TryGetValue("VelocityZ", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + Velocity.z = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, Velocity.z); + paramterFloats[paramid] = Velocity.z; + } + if (parameterIndices.TryGetValue("AngularY", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + AngularY = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, AngularY); + paramterFloats[paramid] = AngularY; + } + if (parameterIndices.TryGetValue("Upright", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + Upright = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, Upright); + paramterFloats[paramid] = Upright; + } + if (parameterIndices.TryGetValue("GroundProximity", out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + GroundProximity = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, GroundProximity); + paramterFloats[paramid] = GroundProximity; + } + if (parameterIndices.TryGetValue("LocomotionMode", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + LocomotionMode = playable.GetInteger(paramid); + } + playable.SetInteger(paramid, LocomotionMode); + paramterInts[paramid] = LocomotionMode; + } + if (parameterIndices.TryGetValue("IsLocal", out paramid)) + { + playable.SetBool(paramid, IsLocal); + } + if (parameterIndices.TryGetValue("Grounded", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + Grounded = playable.GetBool(paramid); + } + playable.SetBool(paramid, Grounded); + paramterInts[paramid] = Grounded ? 1 : 0; + } + if (parameterIndices.TryGetValue("Seated", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + Seated = playable.GetBool(paramid); + } + playable.SetBool(paramid, Seated); + paramterInts[paramid] = Seated ? 1 : 0; + } + if (parameterIndices.TryGetValue("AFK", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + AFK = playable.GetBool(paramid); + } + playable.SetBool(paramid, AFK); + paramterInts[paramid] = AFK ? 1 : 0; + } + if (parameterIndices.TryGetValue("Supine", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + Supine = playable.GetBool(paramid); + } + playable.SetBool(paramid, Supine); + paramterInts[paramid] = Supine ? 1 : 0; + } + if (parameterIndices.TryGetValue("FootstepDisable", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + FootstepDisable = playable.GetBool(paramid); + } + playable.SetBool(paramid, FootstepDisable); + paramterInts[paramid] = FootstepDisable ? 1 : 0; + } + if (parameterIndices.TryGetValue("TrackingType", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + TrackingTypeIdx = playable.GetInteger(paramid); + TrackingTypeIdxInt = (char)TrackingTypeIdx; + TrackingType = (TrackingTypeIndex)TrackingTypeIdx; + } + playable.SetInteger(paramid, (int)TrackingType); + paramterInts[paramid] = (int)TrackingType; + } + if (parameterIndices.TryGetValue("VRMode", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + VRMode = playable.GetInteger(paramid) != 0; + } + playable.SetInteger(paramid, VRMode ? 1 : 0); + paramterInts[paramid] = VRMode ? 1 : 0; + } + if (parameterIndices.TryGetValue("MuteSelf", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { + MuteSelf = playable.GetBool(paramid); + } + playable.SetBool(paramid, MuteSelf); + paramterInts[paramid] = MuteSelf ? 1 : 0; + } + if (parameterIndices.TryGetValue("InStation", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) + { + InStation = playable.GetBool(paramid); + } + playable.SetBool(paramid, InStation); + paramterInts[paramid] = InStation ? 1 : 0; + } + if (parameterIndices.TryGetValue("AvatarVersion", out paramid)) { + playable.SetInteger(paramid, AvatarVersion); + } + whichcontroller++; + } + for (int i = 0; i < playableBlendingStates.Count; i++) { + var pbs = playableBlendingStates[i]; + if (pbs.blending) { + float newWeight = pbs.UpdateBlending(); + playableMixer.SetInputWeight(i + 1, newWeight); + // Debug.Log("Whole playable " + i + " is blending to " + newWeight); + } + for (int j = 0; j < pbs.layerBlends.Count; j++) { + if (pbs.layerBlends[j].blending) { + float newWeight = pbs.layerBlends[j].UpdateBlending(); + playables[i].SetLayerWeight(j, newWeight); + // Debug.Log("Playable " + i + " layer " + j + " is blending to " + newWeight); + } + } + } + if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone && avadesc.lipSyncJawBone != null) { + if (Viseme == VisemeIndex.sil) { + avadesc.lipSyncJawBone.transform.rotation = avadesc.lipSyncJawClosed; + } else { + avadesc.lipSyncJawBone.transform.rotation = avadesc.lipSyncJawOpen; + } + } else if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape && avadesc.VisemeSkinnedMesh != null && mouthOpenBlendShapeIdx != -1) { + if (Viseme == VisemeIndex.sil) { + avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(mouthOpenBlendShapeIdx, 0.0f); + } else { + avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(mouthOpenBlendShapeIdx, 100.0f); + } + } else if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && avadesc.VisemeSkinnedMesh != null) { + for (int i = 0; i < visemeBlendShapeIdxs.Length; i++) { + if (visemeBlendShapeIdxs[i] != -1) { + avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(visemeBlendShapeIdxs[i], (i == VisemeIdx ? 100.0f : 0.0f)); + } + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta new file mode 100644 index 00000000..fe086a2b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da29383b5c207b04585f808c1caad277 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |