From 522a465b77e0b0750a594f6dfbb857178651c052 Mon Sep 17 00:00:00 2001 From: Lyuma Date: Mon, 13 Jul 2020 10:01:38 -0700 Subject: [PATCH] Initial release of Avatar 3.0 Emulator v0.2 --- AvatarMasks.meta | 8 + AvatarMasks/LyumaEmptyMask.mask | 11 + AvatarMasks/LyumaEmptyMask.mask.meta | 8 + AvatarMasks/LyumaFullMask.mask | 13 + AvatarMasks/LyumaFullMask.mask.meta | 8 + AvatarMasks/LyumaNoTransformMask.mask | 129 +++ AvatarMasks/LyumaNoTransformMask.mask.meta | 8 + Editor.meta | 8 + Editor/LyumaAv3EditorSupport.cs | 110 +++ Editor/LyumaAv3EditorSupport.cs.meta | 11 + README.md | 46 ++ README.md.meta | 7 + Scripts.meta | 8 + Scripts/LyumaAv3Emulator.cs | 79 ++ Scripts/LyumaAv3Emulator.cs.meta | 11 + Scripts/LyumaAv3Runtime.cs | 874 +++++++++++++++++++++ Scripts/LyumaAv3Runtime.cs.meta | 11 + 17 files changed, 1350 insertions(+) create mode 100644 AvatarMasks.meta create mode 100644 AvatarMasks/LyumaEmptyMask.mask create mode 100644 AvatarMasks/LyumaEmptyMask.mask.meta create mode 100644 AvatarMasks/LyumaFullMask.mask create mode 100644 AvatarMasks/LyumaFullMask.mask.meta create mode 100644 AvatarMasks/LyumaNoTransformMask.mask create mode 100644 AvatarMasks/LyumaNoTransformMask.mask.meta create mode 100644 Editor.meta create mode 100644 Editor/LyumaAv3EditorSupport.cs create mode 100644 Editor/LyumaAv3EditorSupport.cs.meta create mode 100644 README.md create mode 100644 README.md.meta create mode 100644 Scripts.meta create mode 100644 Scripts/LyumaAv3Emulator.cs create mode 100644 Scripts/LyumaAv3Emulator.cs.meta create mode 100644 Scripts/LyumaAv3Runtime.cs create mode 100644 Scripts/LyumaAv3Runtime.cs.meta diff --git a/AvatarMasks.meta b/AvatarMasks.meta new file mode 100644 index 0000000..735cec0 --- /dev/null +++ b/AvatarMasks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60de7b24cb6cb524faa0fe1aa798ff88 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AvatarMasks/LyumaEmptyMask.mask b/AvatarMasks/LyumaEmptyMask.mask new file mode 100644 index 0000000..795a3c9 --- /dev/null +++ b/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/AvatarMasks/LyumaEmptyMask.mask.meta b/AvatarMasks/LyumaEmptyMask.mask.meta new file mode 100644 index 0000000..bc06989 --- /dev/null +++ b/AvatarMasks/LyumaEmptyMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7681aa70297d4ba488f0182e6d7814de +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/AvatarMasks/LyumaFullMask.mask b/AvatarMasks/LyumaFullMask.mask new file mode 100644 index 0000000..2d1c335 --- /dev/null +++ b/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/AvatarMasks/LyumaFullMask.mask.meta b/AvatarMasks/LyumaFullMask.mask.meta new file mode 100644 index 0000000..b4d889d --- /dev/null +++ b/AvatarMasks/LyumaFullMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2c751320a586b146b231e2753d6025c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/AvatarMasks/LyumaNoTransformMask.mask b/AvatarMasks/LyumaNoTransformMask.mask new file mode 100644 index 0000000..710aa1d --- /dev/null +++ b/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/AvatarMasks/LyumaNoTransformMask.mask.meta b/AvatarMasks/LyumaNoTransformMask.mask.meta new file mode 100644 index 0000000..39c0476 --- /dev/null +++ b/AvatarMasks/LyumaNoTransformMask.mask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4df6ed1e3da0ef840b549a042ef7ae06 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 31900000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..befa95f --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89ed23e5795598442903078ac52b2310 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LyumaAv3EditorSupport.cs b/Editor/LyumaAv3EditorSupport.cs new file mode 100644 index 0000000..3207dc7 --- /dev/null +++ b/Editor/LyumaAv3EditorSupport.cs @@ -0,0 +1,110 @@ +/* Copyright (c) 2020 Lyuma + +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.Components; + +[InitializeOnLoadAttribute] +public static class LyumaAv3EditorSupport +{ + static Dictionary animLayerToDefaultFile = new Dictionary { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_AvatarV3UtilityTPose"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_AvatarV3UtilityIKPose"}, + {VRCAvatarDescriptor.AnimLayerType.SpecialIK, "vrc_AvatarV3UtilityTPose"}, + {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 animLayerToDefaultAvaMaskFile = new Dictionary + { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.SpecialIK, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.Base, "LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Sitting, "LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Additive, "LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.FX, "LyumaEmptyMask"}, + {VRCAvatarDescriptor.AnimLayerType.Action,"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 = null; + foreach (var guid in AssetDatabase.FindAssets(kv.Value)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + ac = AssetDatabase.LoadAssetAtPath(path); + } + 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(path); + } + if (mask == null) + { + Debug.LogWarning("Failed to resolve avatar mask " + kv.Value + " for " + kv.Key); + mask = new AvatarMask(); + } + LyumaAv3Runtime.animLayerToDefaultAvaMask[kv.Key] = mask; + } + } + } + + // 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(); + } +} \ No newline at end of file diff --git a/Editor/LyumaAv3EditorSupport.cs.meta b/Editor/LyumaAv3EditorSupport.cs.meta new file mode 100644 index 0000000..79bb5d5 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..178bf76 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Avatar 3.0 Emulator +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. + +## Features: +* Should emulate most features of Avatar3. +* Test non-local syncing by duplicating or clicking the "Create Non Local Clone" checkbox. +* Supports viewing and editing float and int paramters. Alt-click the ▶Floats and ▶Ints headers at the bottom. +* Supports live viewing of animator controller state. To use, click the avatar in the scene, then in project view, click the correct animator controller. This will be your own if overriding; or it will be one of the defaults in VRCSDK/Examples3/Animation/Controllers. If you did this right, the Animator window should "Auto Live Link" the controller state and allow you to observe what is happening. +* Shows Tracking/Animation in the inspector. + +## Not implemented/todo: +* Custom inspector +* Custom Expression Menus +* visualization of IK Tracking state when a limb is not in Animation mode. +* Eye Tracking / Blinking support +* Set View position is wrong. +* Gesture left/right weight seems wrong + +## To use: +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. + +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 find the locomotion or base controller from project. If you customize it, pick your customized version... otherwise, go to VRCSDK3/Examples3/Animation/Controllers and click `vrc_AvatarV3LocomotionLayer` (or whichever controller you want to debug). 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 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: +Only the Base layer will show parameters and layer weights in the unity Animator window. Other layers will show 0's for everything and every layer will have weight 0. + +You can still edit parameter values, just they show 0 when you finish editing. Additionally, while it is ok to open Blend Trees in the *inspector*, opening a BlendTree in the animation editor (such as double-clicking on it) will force the input values to 0. I believe this to be a Unity bug. + +A workaround is don't double-click blendtrees while playing, or if you want to test the state machine, you can put your FX layer into the base slot temporarily to test it, and tick reset Avatar in the emulator component. Another 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: + +For testing your own controls, 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. Unfortunately the expressions menu is not emulated yet: you must change the values directly. + +## Other known issues: + +As mentioned above, a Unity bug prevents you from double-clicking blendtrees in the Animator window, or from *observing* parameter values or layer weights in the Animator window. + +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. + +Avoid changing parameter values too quickly (for example click-dragging on the inspector row): it can cause the avatar to glitch out. diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..54ba881 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: be927fe9db2f22a4cb6214690375719f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts.meta b/Scripts.meta new file mode 100644 index 0000000..e345379 --- /dev/null +++ b/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ad5fb09acbb8ac45b5a829926d845a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/LyumaAv3Emulator.cs b/Scripts/LyumaAv3Emulator.cs new file mode 100644 index 0000000..0ad83cc --- /dev/null +++ b/Scripts/LyumaAv3Emulator.cs @@ -0,0 +1,79 @@ +/* Copyright (c) 2020 Lyuma + +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; + +public class LyumaAv3Emulator : MonoBehaviour +{ + public bool RestartEmulator; + private bool RestartingEmulator; + public bool CreateNonLocalClone; + + public List runtimes = new List(); + + private void Start() + { + VRC.SDK3.Components.VRCAvatarDescriptor[] avatars = FindObjectsOfType(); + Debug.Log("drv len "+avatars.Length); + foreach (var avadesc in avatars) + { + // Creates the playable director, and initializes animator. + runtimes.Add(avadesc.gameObject.GetOrAddComponent()); + } + } + 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/Scripts/LyumaAv3Emulator.cs.meta b/Scripts/LyumaAv3Emulator.cs.meta new file mode 100644 index 0000000..9b3a898 --- /dev/null +++ b/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/Scripts/LyumaAv3Runtime.cs b/Scripts/LyumaAv3Runtime.cs new file mode 100644 index 0000000..9f4cb2a --- /dev/null +++ b/Scripts/LyumaAv3Runtime.cs @@ -0,0 +1,874 @@ +/* Copyright (c) 2020 Lyuma + +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.Components; +using VRC.SDK3.ScriptableObjects; + +[RequireComponent(typeof(Animator))] +public class LyumaAv3Runtime : MonoBehaviour +{ + static public Dictionary animLayerToDefaultController = new Dictionary(); + static public Dictionary animLayerToDefaultAvaMask = new Dictionary(); + + [HideInInspector] public string SourceObjectPath; + [Header("Assign to non-local duplicate")]public LyumaAv3Runtime AvatarSyncSource; + private int CloneCount; + public bool CreateNonLocalClone; + VRCAvatarDescriptor avadesc; + Animator animator; + private RuntimeAnimatorController origAnimatorController; + + private List playables = new List(); + private List> playableParamterIds = new List>(); + private List> playableParamterFloats = new List>(); + private List> playableParamterInts = new List>(); + AnimationLayerMixerPlayable playableMixer; + PlayableGraph playableGraph; + VRCExpressionsMenu expressionsMenu; + VRCStageParameters stageParameters; + int sittingIndex; + int fxIndex; + int actionIndex; + int additiveIndex; + int gestureIndex; + + public static float ClampFloat(float val) { + if (val < -1.0f) { + val = -1.0f; + } + if (val > 1.0f) { + val = 1.0f; + } + if (val > 0.0f) { + val *= 128f / 127; // apply bias. + } + val = (((sbyte)((val) * -127.0f)) / -127.0f); + if (val > 1.0f) + { + val = 1.0f; + } + 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 + } + static HashSet BUILTIN_PARAMETERS = new HashSet { + "Viseme", "GestureLeft", "GestureLeftWeight", "GestureRight", "GestureRightWeight", "VelocityX", "VelocityY", "VelocityZ", "LocomotionMode", "Upright", "AngularY", "GroundProximity" + }; + [Header("Built-in locomotion inputs")] + public int VisemeI; + public VisemeIndex VisemeDD; + private int Viseme; + public GestureIndex GestureLeft; + [Range(0, 1)] public float GestureLeftWeight; + public GestureIndex GestureRight; + [Range(0, 1)] public float GestureRightWeight; + public Vector3 Velocity; + [Range(-1, 1)] public float AngularY; // Not documented + [Range(-1, 1)] public float Upright; // Not documented + [Range(-1, 1)] public float GroundProximity; // Not documented + public int LocomotionMode; // Not documented + public bool Grounded; + private bool PrevSeated; + public bool Seated; + public bool AFK; + //TODO: + public bool Supine; // Not documented + public bool FootstepDisable; // Not documented + + [Header("Output State (Read-only)")] + public bool IsLocal; + public bool LocomotionIsDisabled; + private Vector3 HeadRelativeViewPosition; + public Vector3 ViewPosition; + public VRCAnimatorTrackingControl.TrackingType trackingRightFingers; + public VRCAnimatorTrackingControl.TrackingType trackingLeftFingers; + public VRCAnimatorTrackingControl.TrackingType trackingEyes; + public VRCAnimatorTrackingControl.TrackingType trackingLeftFoot; + public VRCAnimatorTrackingControl.TrackingType trackingHip; + public VRCAnimatorTrackingControl.TrackingType trackingRightHand; + public VRCAnimatorTrackingControl.TrackingType trackingLeftHand; + public VRCAnimatorTrackingControl.TrackingType trackingRightFoot; + + [Serializable] + public class FloatParam + { + public string name; + public bool synced; + [Range(-1, 1)] public float value; + } + [Header("User-generated inputs")] + public List Floats = new List(); + public Dictionary FloatToIndex = new Dictionary(); + + [Serializable] + public class IntParam + { + public string name; + public bool synced; + public int value; + } + public List Ints = new List(); + public Dictionary IntToIndex = new Dictionary(); + + static public Dictionary animatorToTopLevelRuntime = new Dictionary(); + private List attachedAnimators = new List(); + + public IEnumerator DelayedSetView(bool setView, float time) { + yield return new WaitForSeconds(time); + if (setView) { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + 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 layerBlends = new List(); + + } + List playableBlendingStates = new List(); + + static LyumaAv3Runtime () { + VRCAvatarParameterDriver.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_AvatarParameterDriver behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCAvatarParameterDriver: animator is not known: " + animator, animator); + 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; + } + foreach (var parameter in behaviour.parameters) + { + int idx; + if (runtime.IntToIndex.TryGetValue(parameter.name, out idx)) { + runtime.Ints[idx].value = (int)parameter.value; + } + if (runtime.FloatToIndex.TryGetValue(parameter.name, out idx)) { + runtime.Floats[idx].value = parameter.value; + } + } + }; + }; + VRCPlayableLayerControl.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_PlayableLayerControl behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCPlayableLayerControl: animator is not known: " + animator, animator); + 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), behaviour.goalWeight, behaviour.blendDuration); + } + }; + }; + VRCAnimatorLayerControl.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_AnimatorLayerControl behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCAnimatorLayerControl: animator is not known: " + animator, animator); + 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; + switch (behaviour.playable) + { + case VRCAnimatorLayerControl.BlendableLayer.Action: + idx = runtime.actionIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.Additive: + idx = runtime.additiveIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.FX: + idx = runtime.fxIndex; + break; + case VRCAnimatorLayerControl.BlendableLayer.Gesture: + idx = runtime.gestureIndex; + 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); + } + } + }; + }; + VRCAnimatorLocomotionControl.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_AnimatorLocomotionControl behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCAnimatorLocomotionControl: animator is not known: " + animator, animator); + 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; + }; + }; + VRCAnimatorSetView.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_AnimatorSetView behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCAnimatorSetView: animator is not known: " + animator, animator); + 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; + } + runtime.StartCoroutine(runtime.DelayedSetView(behaviour.setView, behaviour.delayTime)); + }; + }; + VRCAnimatorTrackingControl.Initialize += (x) => { + x.ApplySettings += (VRC.SDKBase.VRC_AnimatorTrackingControl behaviour, Animator animator) => + { + LyumaAv3Runtime runtime; + if (!animatorToTopLevelRuntime.TryGetValue(animator, out runtime)) { + Debug.LogError("[VRCAnimatorTrackingControl: animator is not known: " + animator, animator); + 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.trackingRightFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingRightFingers = behaviour.trackingRightFingers; + } + if (behaviour.trackingEyes != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.trackingEyes = 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 () { + 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 (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(); + } + + animator = this.gameObject.GetComponent(); + avadesc = this.gameObject.GetComponent(); + ViewPosition = avadesc.ViewPosition; + HeadRelativeViewPosition = ViewPosition; + if (animator.avatar != null) + { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head);; + HeadRelativeViewPosition = head.InverseTransformPoint(animator.transform.TransformPoint(ViewPosition)); + } + expressionsMenu = avadesc.expressionsMenu; + if (expressionsMenu != null) + { + stageParameters = expressionsMenu.stageParameters; + } + origAnimatorController = animator.runtimeAnimatorController; + animator.runtimeAnimatorController = null; + if (animator.playableGraph.IsValid()) + { + animator.playableGraph.Destroy(); + } + animator.applyRootMotion = false; + + VRCAvatarDescriptor.CustomAnimLayer[] baselayers = avadesc.baseAnimationLayers; + VRCAvatarDescriptor.CustomAnimLayer[] speciallayers = avadesc.specialAnimationLayers; + List allLayers = new List(); + allLayers.AddRange(baselayers); + allLayers.AddRange(speciallayers); + + // var director = avadesc.gameObject.GetComponent(); + playableGraph = PlayableGraph.Create("LyumaAvatarRuntime - " + this.gameObject.name); + var externalOutput = AnimationPlayableOutput.Create(playableGraph, "ExternalAnimator", animator); + playableMixer = AnimationLayerMixerPlayable.Create(playableGraph, allLayers.Count + 1); + externalOutput.SetSourcePlayable(playableMixer); + int i; + playables.Clear(); + playableBlendingStates.Clear(); + actionIndex = fxIndex = gestureIndex = additiveIndex = sittingIndex = -1; + + Animator[] animators = this.gameObject.GetComponentsInChildren(); + Debug.Log("anim len "+animators.Length); + foreach (Animator anim in animators) + { + attachedAnimators.Add(anim); + animatorToTopLevelRuntime.Add(anim, this); + } + + // Default values. + Grounded = true; + Upright = 1.0f; + + Ints.Clear(); + Floats.Clear(); + HashSet usedparams = new HashSet(BUILTIN_PARAMETERS); + i = 0; + if (stageParameters != null) + { + foreach (var stageParam in stageParameters.stageParameters) + { + if (stageParam.name == null || stageParam.name.Length == 0) { + continue; + } + if ((int)stageParam.valueType == 0) + { + IntParam param = new IntParam(); + param.synced = true; + param.name = stageParam.name; + param.value = 0; + IntToIndex[param.name] = Ints.Count; + Ints.Add(param); + } + else + { + FloatParam param = new FloatParam(); + param.synced = true; + param.name = stageParam.name; + param.value = 0; + FloatToIndex[param.name] = Floats.Count; + Floats.Add(param); + } + usedparams.Add(stageParam.name); + i++; + } + } + + i = 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 (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()); + } + playableMixer.ConnectInput((int)i, humanAnimatorPlayable, 0, 1); + playables.Add(humanAnimatorPlayable); + playableBlendingStates.Add(pbs); + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Sitting) { + sittingIndex = i; + playableMixer.SetInputWeight(i, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.IKPose) + { + playableMixer.SetInputWeight(i, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.TPose) + { + playableMixer.SetInputWeight(i, 0f); + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Action) + { + playableMixer.SetInputWeight(i, 0f); + actionIndex = i; + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture) + { + gestureIndex = i; + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Additive) + { + additiveIndex = i; + } + if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.FX) + { + fxIndex = i; + } + // AnimationControllerLayer acLayer = new AnimationControllerLayer() + if (mask != null) + { + playableMixer.SetLayerMaskFromAvatarMask((uint)i, mask); + } + if (additive) + { + playableMixer.SetLayerAdditive((uint)i, true); + } + } + + //playableParamterIds + int whichcontroller = 0; + playableParamterIds.Clear(); + foreach (AnimatorControllerPlayable playable in playables) { + int pcnt = playable.GetParameterCount(); + Dictionary parameterIndices = new Dictionary(); + playableParamterInts.Add(new Dictionary()); + playableParamterFloats.Add(new Dictionary()); + // Debug.Log("SETUP index " + whichcontroller + " len " + playables.Count); + playableParamterIds.Add(parameterIndices); + for (i = 0; i < pcnt; i++) { + AnimatorControllerParameter aparam = playable.GetParameter(i); + parameterIndices[aparam.name] = aparam.nameHash; + if (usedparams.Contains(aparam.name)) { + continue; + } + if (aparam.type == AnimatorControllerParameterType.Int) { + IntParam param = new IntParam(); + param.synced = false; + param.name = aparam.name; + param.value = aparam.defaultInt; + IntToIndex[param.name] = Ints.Count; + Ints.Add(param); + usedparams.Add(aparam.name); + } else if (aparam.type == AnimatorControllerParameterType.Float) { + FloatParam param = new FloatParam(); + param.synced = false; + param.name = aparam.name; + param.value = aparam.defaultFloat; + FloatToIndex[param.name] = Floats.Count; + Floats.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."); + } + + // Update is called once per frame + void Update() + { + 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 (Ints[i].synced) { + Ints[i].value = ClampByte(AvatarSyncSource.Ints[i].value); + } + } + for (int i = 0; i < Floats.Count; i++) { + if (Floats[i].synced) { + Floats[i].value = ClampFloat(AvatarSyncSource.Floats[i].value); + } + } + Viseme = VisemeI = AvatarSyncSource.Viseme; + VisemeDD = (VisemeIndex)Viseme; + GestureLeft = AvatarSyncSource.GestureLeft; + GestureLeftWeight = AvatarSyncSource.GestureLeftWeight; + GestureRight = AvatarSyncSource.GestureRight; + 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; + FootstepDisable = AvatarSyncSource.FootstepDisable; + } + foreach (FloatParam param in Floats) + { + param.value = ClampFloat(param.value); + } + foreach (IntParam param in Ints) + { + param.value = ClampByte(param.value); + } + if (Seated != PrevSeated && sittingIndex >= 0) + { + playableBlendingStates[sittingIndex].StartBlend(playableMixer.GetInputWeight(sittingIndex), Seated ? 1f : 0f, 0.5f); + PrevSeated = Seated; + } + if (VisemeI != Viseme) { + Viseme = VisemeI; + VisemeDD = (VisemeIndex)Viseme; + } + if ((int)VisemeDD != Viseme) { + Viseme = (int)VisemeDD; + VisemeI = Viseme; + } + IsLocal = AvatarSyncSource == this; + int whichcontroller = 0; + foreach (AnimatorControllerPlayable playable in playables) + { + // Debug.Log("Index " + whichcontroller + " len " + playables.Count); + Dictionary parameterIndices = playableParamterIds[whichcontroller]; + Dictionary paramterInts = playableParamterInts[whichcontroller]; + Dictionary paramterFloats = playableParamterFloats[whichcontroller]; + int paramid; + float fparam; + int iparam; + foreach (FloatParam param in Floats) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { + // Debug.Log("Reflecting change in float parameter " + param.name + " from " + param.value + " to " + playable.GetFloat(paramid)); + param.value = playable.GetFloat(paramid); + } + playable.SetFloat(paramid, param.value); + paramterFloats[paramid] = param.value; + } + } + foreach (IntParam param in Ints) + { + if (parameterIndices.TryGetValue(param.name, out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + // Debug.Log("Reflecting change in int parameter " + param.name + " from " + param.value + " to " + playable.GetFloat(paramid)); + param.value = playable.GetInteger(paramid); + } + playable.SetInteger(param.name, param.value); + paramterInts[paramid] = param.value; + } + } + if (parameterIndices.TryGetValue("Viseme", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + Viseme = VisemeI = playable.GetInteger(paramid); + VisemeDD = (VisemeIndex)Viseme; + } + playable.SetInteger(paramid, Viseme); + paramterInts[paramid] = Viseme; + } + if (parameterIndices.TryGetValue("GestureLeft", out paramid)) + { + if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { + GestureLeft = (GestureIndex)playable.GetInteger(paramid); + } + 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)) { + GestureRight = (GestureIndex)playable.GetInteger(paramid); + } + 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; + } + whichcontroller++; + } + for (int i = 0; i < playableBlendingStates.Count; i++) { + var pbs = playableBlendingStates[i]; + if (pbs.blending) { + float newWeight = pbs.UpdateBlending(); + playableMixer.SetInputWeight(i, 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); + } + } + } + } +} diff --git a/Scripts/LyumaAv3Runtime.cs.meta b/Scripts/LyumaAv3Runtime.cs.meta new file mode 100644 index 0000000..fe086a2 --- /dev/null +++ b/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: