From 799e6680d40119dc9c2a9e0b320054a40324bebe Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Fri, 27 Dec 2024 00:56:58 -0500 Subject: VRCSDK3Avatars found! --- .../Assets/Resources/Lyuma/Av3Emulator.meta | 8 + .../Resources/Lyuma/Av3Emulator/AvatarMasks.meta | 8 + .../AvatarMasks/EmptyController.controller | 12 + .../AvatarMasks/EmptyController.controller.meta | 8 + .../Av3Emulator/AvatarMasks/LyumaEmptyMask.mask | 11 + .../AvatarMasks/LyumaEmptyMask.mask.meta | 8 + .../Av3Emulator/AvatarMasks/LyumaFullMask.mask | 13 + .../AvatarMasks/LyumaFullMask.mask.meta | 8 + .../AvatarMasks/LyumaNoTransformMask.mask | 129 + .../AvatarMasks/LyumaNoTransformMask.mask.meta | 8 + .../Assets/Resources/Lyuma/Av3Emulator/Editor.meta | 8 + .../Av3Emulator/Editor/LyumaAv3EditorSupport.cs | 268 +++ .../Editor/LyumaAv3EditorSupport.cs.meta | 11 + .../Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs | 343 +++ .../Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta | 3 + .../Lyuma/Av3Emulator/GestureManagerBridge.meta | 8 + .../Av3Emulator/GestureManagerBridge/Editor.meta | 8 + .../Editor/GestureManagerAv3MenuEditor.cs | 357 +++ .../Editor/GestureManagerAv3MenuEditor.cs.meta | 11 + .../Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt | 23 + .../Resources/Lyuma/Av3Emulator/LICENSE.txt.meta | 7 + .../Assets/Resources/Lyuma/Av3Emulator/README.md | 232 ++ .../Resources/Lyuma/Av3Emulator/README.md.meta | 7 + .../Resources/Lyuma/Av3Emulator/Screenshots.meta | 8 + .../Lyuma/Av3Emulator/Screenshots/av3_example.png | Bin 0 -> 376724 bytes .../Av3Emulator/Screenshots/av3_example.png.meta | 116 + .../Av3Emulator/Screenshots/av3_radial_menu.png | Bin 0 -> 70146 bytes .../Screenshots/av3_radial_menu.png.meta | 116 + .../Screenshots/avatar3emu_tutorial.png | Bin 0 -> 384133 bytes .../Screenshots/avatar3emu_tutorial.png.meta | 110 + .../Screenshots/lock_inspector_tutorial.png | Bin 0 -> 37199 bytes .../Screenshots/lock_inspector_tutorial.png.meta | 110 + .../Av3Emulator/Screenshots/write_defaults_off.png | Bin 0 -> 132071 bytes .../Screenshots/write_defaults_off.png.meta | 110 + .../Resources/Lyuma/Av3Emulator/Scripts.meta | 8 + .../Av3Emulator/Scripts/A3EOSCConfiguration.cs | 219 ++ .../Scripts/A3EOSCConfiguration.cs.meta | 11 + .../Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs | 682 ++++++ .../Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta | 11 + .../Av3Emulator/Scripts/GestureManagerAv3Menu.cs | 51 + .../Scripts/GestureManagerAv3Menu.cs.meta | 11 + .../Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs | 155 ++ .../Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta | 11 + .../Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs | 235 ++ .../Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta | 3 + .../Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs | 375 +++ .../Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta | 11 + .../Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs | 2529 ++++++++++++++++++++ .../Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta | 11 + .../Assets/Resources/Lyuma/Av3Emulator/foo.patch | 84 + .../Resources/Lyuma/Av3Emulator/foo.patch.meta | 7 + 51 files changed, 6483 insertions(+) create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch create mode 100644 VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch.meta (limited to 'VRCSDK3Avatars/Assets/Resources/Lyuma') diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator.meta new file mode 100644 index 00000000..61a97805 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e42c5d0b3e2b3f64e8a88c225b3cef62 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta new file mode 100644 index 00000000..735cec02 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60de7b24cb6cb524faa0fe1aa798ff88 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller new file mode 100644 index 00000000..29e105c4 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/EmptyController.controller.meta new file mode 100644 index 00000000..c9eeb575 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask new file mode 100644 index 00000000..795a3c98 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaEmptyMask.mask.meta new file mode 100644 index 00000000..bc069894 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask new file mode 100644 index 00000000..2d1c3352 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaFullMask.mask.meta new file mode 100644 index 00000000..b4d889d2 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask new file mode 100644 index 00000000..710aa1da --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/AvatarMasks/LyumaNoTransformMask.mask.meta new file mode 100644 index 00000000..39c04767 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor.meta new file mode 100644 index 00000000..befa95fd --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89ed23e5795598442903078ac52b2310 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs new file mode 100644 index 00000000..66c73166 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs @@ -0,0 +1,268 @@ +/* Copyright (c) 2020-2022 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.Compilation; +using UnityEditor.Playables; +using UnityEngine.Playables; +using VRC.SDK3.Avatars.Components; + +[InitializeOnLoadAttribute] +public static class LyumaAv3EditorSupport +{ + static Dictionary animLayerToDefaultFile = new Dictionary { + {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 animLayerToDefaultAvaMaskFile = new Dictionary + { + {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("Assets/VRCSDK/Examples3/Animation/Controllers/" + kv.Value + ".controller"); + if (ac == null) + { + Debug.LogWarning("Failed to resolve animator controller " + kv.Value + " for " + kv.Key); + foreach (var guid in AssetDatabase.FindAssets(kv.Value)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + if (path.EndsWith("/" + kv.Value + ".controller")) { + ac = AssetDatabase.LoadAssetAtPath(path); + break; + } + } + } + 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; + } + } + foreach (string guid in AssetDatabase.FindAssets("EmptyController")) { + LyumaAv3Emulator.EmptyController = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); + } + + LyumaAv3Runtime.updateSelectionDelegate = (obj) => { + if (obj == null && LyumaAv3Emulator.emulatorInstance != null) { + // Debug.Log("Resetting selected object: " + LyumaAv3Emulator.emulatorInstance); + obj = LyumaAv3Emulator.emulatorInstance.gameObject; + } + // Debug.Log("Setting selected object: " + go); + Selection.SetActiveObjectWithContext(obj, obj); + // Highlighter.Highlight("Inspector", "Animator To Debug"); + }; + + LyumaAv3Runtime.updateSceneLayersDelegate = (layers) => { + if (Tools.visibleLayers == layers) { + return; + } + // Debug.Log("Setting selected layers: " + layers); + Tools.visibleLayers = layers; + Camera[] cameras = new Camera[255]; + Camera.GetAllCameras(cameras); + foreach (Camera c in cameras) { + if (c != null && c.targetTexture == null && c.GetComponentInParent() == null && c.gameObject.activeInHierarchy && c.isActiveAndEnabled) { + c.cullingMask = layers; + } + } + // Highlighter.Highlight("Inspector", "Animator To Debug"); + }; + + LyumaAv3Runtime.addRuntimeDelegate = (runtime) => { + MoveComponentToTop(runtime); + }; + + // Currently PhysBone and ContactManager cause exceptions if scripts reload during Play mode. + // This applies a workaround: disable the objects before compile; call RuntimeInit to recreate them after. + LyumaAv3Runtime.ApplyOnEnableWorkaroundDelegate = () => { + CompilationPipeline.assemblyCompilationStarted -= WorkaroundDestroyManagersBeforeCompile; + CompilationPipeline.assemblyCompilationStarted += WorkaroundDestroyManagersBeforeCompile; + GameObject gotmp = GameObject.Find("/TempReloadDontDestroy"); + if (gotmp != null) { + GameObject.DestroyImmediate(gotmp); + var avatarDynamicsSetup = typeof(VRCExpressionsMenuEditor).Assembly.GetType("VRC.SDK3.Avatars.AvatarDynamicsSetup"); + if (avatarDynamicsSetup != null) { + var RuntimeInit = avatarDynamicsSetup.GetMethod("RuntimeInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + if (RuntimeInit != null) { + Debug.Log("Caling avatarDynamicsSetup.RuntimeInit(): " + RuntimeInit); + RuntimeInit.Invoke(null, new object[0]); + } + } + Debug.Log("DONE workaround"); + } + }; + + LyumaAv3Osc.GetEditorViewportDelegate = () => { + try { + Rect ret = UnityEditor.SceneView.currentDrawingSceneView.position; + // Gizmos are relative to the active window in terms of x and y. + ret.x = 1.0f; + ret.y = 1.0f; + ret.height -= 7.0f; + return ret; + } catch { + Vector2 gvsize = Handles.GetMainGameViewSize(); + return new Rect(0, -18, gvsize.x, gvsize.y); + } + }; + LyumaAv3Osc.DrawDebugRectDelegate = (Rect pos, Color col, Color outlineCol) => { + // Debug.Log("Debug raw rect " + pos); + Color origColor = GUI.color; + GUI.color = col; + UnityEditor.Handles.BeginGUI(); + UnityEditor.Handles.DrawSolidRectangleWithOutline(pos, col, outlineCol); + UnityEditor.Handles.EndGUI(); + GUI.color = origColor; + }; + LyumaAv3Osc.DrawDebugTextDelegate = (Rect pos, Color backgroundCol, Color outlineCol, Color textCol, string str, TextAnchor alignment) => { + // Debug.Log("Debug raw text " + str + " at " + pos); + Color origColor = GUI.color; + GUI.color = backgroundCol; + var view = UnityEditor.SceneView.currentDrawingSceneView; + // Vector2 size = GUI.skin.label.CalcSize(new GUIContent(str)); + // Rect pos = new Rect(location.x, location.y, size.x, size.y); + UnityEditor.Handles.BeginGUI(); + UnityEditor.Handles.DrawSolidRectangleWithOutline(pos, backgroundCol, outlineCol); + GUI.color = textCol.r + textCol.b + textCol.g > 0.5f ? new Color(0,0,0,textCol.a * 0.5f) : new Color(1,1,1,textCol.a * 0.5f);//new Color(1.0f, 1.0f, 1.0f, textCol.a * 0.25f); + var style = new GUIStyle(); + style.fontStyle = FontStyle.Bold; + style.alignment = alignment; + style.normal.textColor = GUI.color; + pos.y += 1; + GUI.Label(pos, str, style); + pos.x += 1; + GUI.Label(pos, str, style); + pos.y -= 1; + GUI.Label(pos, str, style); + pos.x -= 1; + GUI.Label(pos, str, style); + pos.x += 0.5f; + pos.y += 0.5f; + GUI.color = textCol; + style.normal.textColor = GUI.color; + GUI.Label(pos, str, style); + UnityEditor.Handles.EndGUI(); + GUI.color = origColor; + }; + } + + public static void OnPlayModeStateChange(UnityEditor.PlayModeStateChange pmsc) { + // We don't want any of our callbacks causing trouble outside of play mode. + if (pmsc != UnityEditor.PlayModeStateChange.EnteredPlayMode) { + CompilationPipeline.assemblyCompilationStarted -= WorkaroundDestroyManagersBeforeCompile; + } + } + + private static void WorkaroundDestroyManagersBeforeCompile(string obj) { + Debug.Log("Compile Started"); + GameObject gotmp = new GameObject("TempReloadDontDestroy"); + Object.DontDestroyOnLoad(gotmp); + GameObject go; + go = GameObject.Find("/TriggerManager"); + if (go != null) { + Object.DestroyImmediate(go); + } + go = GameObject.Find("/PhysBoneManager"); + if (go != null) { + Object.DestroyImmediate(go); + } + } + + static void MoveComponentToTop(Component c) { + GameObject go = c.gameObject; + Component[] components = go.GetComponents(); + for (int i = 0; i < components.Length; i++) { + if (components[i].GetType().Name.Contains("PipelineSaver")) { + return; + } + } + try { + if (PrefabUtility.IsPartOfAnyPrefab(go)) { + PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } catch (System.Exception) {} + int moveUpCalls = components.Length - 2; + if (!PrefabUtility.IsPartOfAnyPrefab(go.GetComponents()[1])) { + for (int i = 0; i < moveUpCalls; i++) { + UnityEditorInternal.ComponentUtility.MoveComponentUp(c); + } + } + } + + // register an event handler when the class is initialized + static LyumaAv3EditorSupport() + { + InitDefaults(); + EditorApplication.playModeStateChanged += OnPlayModeStateChange; + } + + [MenuItem("Tools/Enable Avatars 3.0 Emulator")] + public static void EnableAv3Testing() { + GameObject go = GameObject.Find("/Avatars 3.0 Emulator Control"); + if (go != null) { + go.SetActive(true); + } else { + go = new GameObject("Avatars 3.0 Emulator Control"); + } + Selection.SetActiveObjectWithContext(go, go); + go.GetOrAddComponent(); + go.GetOrAddComponent(); + EditorGUIUtility.PingObject(go); + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta new file mode 100644 index 00000000..79bb5d5f --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs new file mode 100644 index 00000000..4da25e95 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs @@ -0,0 +1,343 @@ +/* Copyright (c) 2020-2022 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.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; + +[CustomEditor(typeof(LyumaAv3Menu), true)] +public class LyumaAv3MenuEditor : Editor +{ + private VRCExpressionsMenu _currentMenu; + + public override void OnInspectorGUI() + { + var menu = (LyumaAv3Menu)target; + GUILayout.BeginHorizontal(); + if (GUILayout.Button(menu.IsMenuOpen ? "Close menu" : "Open menu")) + { + menu.ToggleMenu(); + } + + if (menu.gameObject.GetComponents().Length == 1) + { + if (GUILayout.Button("+", GUILayout.Width(20))) + { + OpenMenuForTwoHandedSupport(menu); + } + } + + GUILayout.EndHorizontal(); + + RenderButtonMenu(); + } + + protected void RenderButtonMenu() { + + 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.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(); + if (_currentMenu == null) { + EditorGUILayout.LabelField("(This submenu is null)"); + return; + } + 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(); + mainMenu.useLegacyMenu = menu.useLegacyMenu; + mainMenu.Runtime = menu.Runtime; + mainMenu.RootMenu = menu.RootMenu; + } + + private string LabelizeMenu() + { + var menu = (LyumaAv3Menu)target; + + var lastMenu = menu.MenuStack.Last(); + if (lastMenu.MandatedParam == null) + { + if (lastMenu.ExpressionsMenu == null) { + return "SubMenu linked to null menu!"; + } + 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 + ")" : ""), control.icon), GUILayout.Height(36),GUILayout.MinWidth(40)); + } + + private static T GreenBackground(bool isActive, Func 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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta new file mode 100644 index 00000000..f613af7d --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge.meta new file mode 100644 index 00000000..5580206a --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 200c7996a9251cf4ea212a7f2b29e486 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor.meta new file mode 100644 index 00000000..ab77bcf0 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6060046a0d401124d832ae1a59c38880 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs new file mode 100644 index 00000000..57edaaf7 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs @@ -0,0 +1,357 @@ +/* Copyright (c) 2020-2022 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. */ + +// #define DISABLE_GESTURE_MANAGER +# if !DISABLE_GESTURE_MANAGER +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; +using GestureManager.Scripts.Editor.Modules.Vrc3; + +[CustomEditor(typeof(GestureManagerAv3Menu))] +public class GestureManagerAv3MenuEditor : LyumaAv3MenuEditor +{ + private readonly Dictionary _resizedIcons = new Dictionary(); + + class StubAv3Module : GestureManager.Scripts.Editor.Modules.Vrc3.ModuleVrc3 { + class VelocityParam : GestureManager.Scripts.Editor.Modules.Vrc3.Params.Vrc3Param { + public LyumaAv3Runtime runtime; + public int axis; + public VelocityParam(string name, LyumaAv3Runtime runtime, int axis) : base(name, AnimatorControllerParameterType.Float) { + this.axis = axis; + this.runtime = runtime; + } + public override float Get() { + Vector3 vel = runtime.Velocity; + return vel[axis]; + } + protected internal override void InternalSet(float value) { + Vector3 vel = runtime.Velocity; + vel[axis] = value; + runtime.Velocity = vel; + } + } + class ReflectedVrcParam : GestureManager.Scripts.Editor.Modules.Vrc3.Params.Vrc3Param { + public System.Reflection.FieldInfo property; + public LyumaAv3Runtime runtime; + public ReflectedVrcParam(string name, LyumaAv3Runtime runtime, System.Reflection.FieldInfo property) : base(name, + property.FieldType == typeof(bool) ? AnimatorControllerParameterType.Bool : (property.FieldType == typeof(float) ? AnimatorControllerParameterType.Float : AnimatorControllerParameterType.Int)) { + this.property = property; + this.runtime = runtime; + } + public override float Get() { + if (property.FieldType == typeof(bool)) { + return ((bool)property.GetValue(runtime)) ? 1.0f : 0.0f; + } else if (property.FieldType == typeof(float)) { + return (float)property.GetValue(runtime); + } else if (property.FieldType == typeof(int)) { + return (int)property.GetValue(runtime); + } else { + return (float)(int)Convert.ChangeType(property.GetValue(runtime), typeof(int)); + } + } + protected internal override void InternalSet(float value) { + // UnityEngine.Debug.Log("Internal set bool " + param.name + " to " + value + " was " + param.value + "(" + param.lastValue + ")"); + if (property.FieldType == typeof(bool)) { + property.SetValue(runtime, value > 0.5f); + } else if (property.FieldType == typeof(float)) { + property.SetValue(runtime, value); + } else if (property.FieldType == typeof(int)) { + property.SetValue(runtime, (int)value); + } else { + property.SetValue(runtime, Convert.ChangeType((int)value, Enum.GetUnderlyingType(property.FieldType))); + } + } + } + class StubVrcBoolParam : GestureManager.Scripts.Editor.Modules.Vrc3.Params.Vrc3Param { + public LyumaAv3Runtime.BoolParam param; + public StubVrcBoolParam(string name, LyumaAv3Runtime.BoolParam param) : base(name, AnimatorControllerParameterType.Bool) { this.param = param; } + public override float Get() { + return param.value ? 1.0f : 0.0f; + } + protected internal override void InternalSet(float value) { + UnityEngine.Debug.Log("Internal set bool " + param.name + " to " + value + " was " + param.value + "(" + param.lastValue + ")"); + param.value = value > 0.5f ? true: false; + } + } + class StubVrcIntParam : GestureManager.Scripts.Editor.Modules.Vrc3.Params.Vrc3Param { + public LyumaAv3Runtime.IntParam param; + public StubVrcIntParam(string name, LyumaAv3Runtime.IntParam param) : base(name, AnimatorControllerParameterType.Int) { this.param = param; } + public override float Get() { + return param.value; + } + protected internal override void InternalSet(float value) { + UnityEngine.Debug.Log("Internal set int " + param.name + " to " + value + " was " + param.value + "(" + param.lastValue + ")"); + param.value = (int)value; + } + } + class StubVrcFloatParam : GestureManager.Scripts.Editor.Modules.Vrc3.Params.Vrc3Param { + public LyumaAv3Runtime.FloatParam param; + public StubVrcFloatParam(string name, LyumaAv3Runtime.FloatParam param) : base(name, AnimatorControllerParameterType.Float) { this.param = param; } + public override float Get() { + return param.value; + } + protected internal override void InternalSet(float value) { + param.value = value; + param.exportedValue = value; + } + } + private LyumaAv3Runtime _runtime; + public StubAv3Module(LyumaAv3Runtime runtime, VRCAvatarDescriptor avatarDescriptor) : base(null, avatarDescriptor) + { + _runtime = runtime; + } + + public override void Update() + { + // UnityEngine.Debug.Log("Updating " + Params.Keys); + // if (_dummyMode != DummyMode.None && (!DummyAvatar || Avatar.activeSelf)) DisableDummy(); + // foreach (var weightController in _weightControllers) weightController.Update(); + foreach (var param in Params.Values) { + if (param is StubVrcBoolParam) { + StubVrcBoolParam bparam = (StubVrcBoolParam) param; + if (bparam.param.lastValue != bparam.param.value) { + bparam.Set(this, bparam.param.value ? 1.0f : 0.0f); + } + } + if (param is StubVrcIntParam) { + StubVrcIntParam iparam = (StubVrcIntParam) param; + if (iparam.param.lastValue != iparam.param.value) { + iparam.Set(this, iparam.param.value); + } + } + if (param is StubVrcFloatParam) { + StubVrcFloatParam fparam = (StubVrcFloatParam) param; + if (fparam.param.lastValue != fparam.param.value) { + fparam.Set(this, fparam.param.value); + } + } + } + } + + public void addParam(string builtinprop, string gestureProp) { + var property = _runtime.GetType().GetField(builtinprop, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Default); + if (property == null) { + UnityEngine.Debug.Log("Failed to find property " + builtinprop + " for " + _runtime); + } else { + Params.Add(gestureProp, new ReflectedVrcParam(gestureProp, _runtime, property)); + } + } + public override void InitForAvatar() + { + if (Params.Count > 0) { + _runtime.ResetAvatar = true; + return; + } + Params.Clear(); + foreach (var builtinprop in LyumaAv3Runtime.BUILTIN_PARAMETERS) { + if (builtinprop == "VelocityX") { + Params.Add(builtinprop, new VelocityParam(builtinprop, _runtime, 0)); + } else if (builtinprop == "VelocityY") { + Params.Add(builtinprop, new VelocityParam(builtinprop, _runtime, 1)); + } else if (builtinprop == "VelocityZ") { + Params.Add(builtinprop, new VelocityParam(builtinprop, _runtime, 2)); + } else { + addParam(builtinprop, builtinprop); + } + } + addParam("IKPoseCalibration", "PoseIK"); + addParam("TPoseCalibration", "PoseT"); + addParam("Jump", "Av3 Emu Jump"); + foreach (var xbool in _runtime.Bools) { + if (Params.ContainsKey(xbool.name)) { + UnityEngine.Debug.LogWarning("Duplicate parameter " + xbool.name); + } else { + Params.Add(xbool.name, new StubVrcBoolParam(xbool.name, xbool)); + } + } + foreach (var xint in _runtime.Ints) { + if (Params.ContainsKey(xint.name)) { + UnityEngine.Debug.LogWarning("Duplicate parameter " + xint.name + " maybe with different type."); + } else { + Params.Add(xint.name, new StubVrcIntParam(xint.name, xint)); + } + } + foreach (var xfloat in _runtime.Floats) { + if (Params.ContainsKey(xfloat.name)) { + UnityEngine.Debug.LogWarning("Duplicate parameter " + xfloat.name + " maybe with different type."); + } else { + Params.Add(xfloat.name, new StubVrcFloatParam(xfloat.name, xfloat)); + } + } + // UnityEngine.Debug.Log("Initing params " + Params.Keys); + } + + // public override AnimationClip GetFinalGestureByIndex(GestureHand hand, int gestureIndex) + // { + // later + // return ModuleVrc3Styles.Data.GestureClips[gestureIndex]; + // } + + // public void NoExpressionRefresh() + // { + // if (Dummy.State) return; + // if (Menu) ResetAvatar(); + // } + + // public void ResetAvatar() + // { + // _runtime.ResetAvatar = true; + // // InitForAvatar(); + // } + + } + public override bool RequiresConstantRepaint() => false; //Manager.Module?.RequiresConstantRepaint ?? false; + + private VisualElement _root; + + private const int AntiAliasing = 4; + + + StubAv3Module av3Module; + RadialMenu cachedMenu; + public RadialMenu GetOrCreateRadial(UnityEditor.Editor editor) + { + if (av3Module.Params.Count == 0) { + av3Module.InitForAvatar(); + } + cachedMenu = (RadialMenu)(typeof(GestureManager.Scripts.Editor.Modules.Vrc3.ModuleVrc3).GetMethod("GetOrCreateRadial", + System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance).Invoke( + av3Module, new object[]{editor})); + return cachedMenu; + } + public override VisualElement CreateInspectorGUI() + { + + // UnityEngine.Debug.Log("Create Inspector GUI"); + VRCAvatarDescriptor avadesc = ((Component)target).GetComponent(); + av3Module = new StubAv3Module(((Component)target).GetComponent(), avadesc); + _root = new VisualElement(); + _root.Add(new IMGUIContainer(ManagerGui)); + foreach (var inspectorWindow in Resources.FindObjectsOfTypeAll().Where(window => window.titleContent.text == "Inspector")) { + if (!inspectorWindow || inspectorWindow.GetAntiAliasing() == AntiAliasing) continue; + + inspectorWindow.SetAntiAliasing(AntiAliasing); + // Dumb workaround method to trigger the internal MakeParentsSettingsMatchMe() method on the EditorWindow. + inspectorWindow.minSize = inspectorWindow.minSize; + } + return _root; + } + + private static GUIStyle _guiHandTitle = null; + internal static GUIStyle GuiHandTitle => _guiHandTitle ?? (_guiHandTitle = new GUIStyle(GUI.skin.label) + { + fontSize = 12, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.UpperCenter, + padding = new RectOffset(10, 10, 10, 10) + }); + + private void ManagerGui () { + var gmenu = (GestureManagerAv3Menu)target; + GUILayout.BeginHorizontal(); + if (GUILayout.Button(gmenu.IsMenuOpen ? "Close menu" : "Open menu")) + { + gmenu.compact = true; + gmenu.ToggleMenu(); + } + + if (gmenu.gameObject.GetComponents().Length == 1) + { + if (GUILayout.Button("+", GUILayout.Width(20))) + { + OpenMenuForTwoHandedSupport(gmenu); + } + } + var rect = EditorGUILayout.GetControlRect(false, 1, GUILayout.Width(120)); + GUILayout.EndHorizontal(); + if (gmenu.IsMenuOpen) { + rect.height = 48; + gmenu.useLegacyMenu = !GUI.Toggle(rect, !gmenu.useLegacyMenu, "GestureManager\nRadial Menu", "Button"); + GUILayout.BeginVertical(GUILayout.Height(gmenu.compact && gmenu.useLegacyMenu ? 10 : RadialMenu.Size + 100)); + } + if (gmenu.useLegacyMenu) { + gmenu.compact = EditorGUILayout.Toggle("Compact Options", gmenu.compact); + EditorGUILayout.Space(4); + rect = EditorGUILayout.GetControlRect(false, 0); + rect.height = 1; + EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1)); + if (cachedMenu != null) { + cachedMenu.style.display = DisplayStyle.None; + } + RenderButtonMenu(); + + if (gmenu.IsMenuOpen) { + // ensure these are matched with above (Same condition). + // GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + } + return; + } + EditorGUILayout.Space(10); + gmenu.compact = true; + + var menu = GetOrCreateRadial(this as UnityEditor.Editor); + if (!gmenu.IsMenuOpen) { + GUILayout.Label( + "Expressions" + + (gmenu.IsMenuOpen ? "" : " [Menu is closed]"), + EditorStyles.boldLabel); + menu.style.display = DisplayStyle.None; + // No EndVertical() because we didn't do BeginVertical() if not IsMenuOpen. + return; + } + + GUILayout.Space(14); + rect = EditorGUILayout.GetControlRect(false, 0); + rect.height = 1; + EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1)); + GUILayout.Label("Radial Menu", GuiHandTitle); + + GUILayout.Label("", GUILayout.ExpandWidth(true), GUILayout.Height(RadialMenu.Size)); + if (gmenu.IsMenuOpen) { + if (!(Event.current.type == EventType.Layout || Event.current.type == EventType.Used)) { + menu.Rect = GUILayoutUtility.GetLastRect(); + } + menu.Render(_root, menu.Rect); + // float extraSize = RadialMenu.Size; + menu.style.display = DisplayStyle.Flex; + // if (extraSize > 0) GUILayout.Label("", GUILayout.ExpandWidth(true), GUILayout.Height(extraSize)); + } + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + } + + private static void OpenMenuForTwoHandedSupport(GestureManagerAv3Menu menu) + { + var mainMenu = menu.Runtime.gameObject.AddComponent(); + mainMenu.useLegacyMenu = menu.useLegacyMenu; + mainMenu.Runtime = menu.Runtime; + mainMenu.RootMenu = menu.RootMenu; + } + +} +#endif diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs.meta new file mode 100644 index 00000000..4be246b5 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/GestureManagerBridge/Editor/GestureManagerAv3MenuEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 422cccb017ef62b42addb5fe7616770c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt new file mode 100644 index 00000000..38502217 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2020-2022 Lyuma and contributors +LyumaAv3Menu (c) 2020-2021 hai-vr +GestureManager (c) 2021 Leonardo Lorenzoni + +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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta new file mode 100644 index 00000000..aa464bec --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/LICENSE.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cb94437f622beb945970e9c5362241bc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md new file mode 100644 index 00000000..435a3f4e --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md @@ -0,0 +1,232 @@ +# Avatar 3.0 Emulator + +## **[Download the latest version at: https://github.com/lyuma/Av3Emulator/releases](https://github.com/lyuma/Av3Emulator/releases)** + +## Planned features for 3.0: + +* Unity Package support + +### **New features in v 2.9.10 (3.0 rc7):** + +2.9.10: + +* Fixed legacy menu being completely broken. +* Fixed ParameterDriver Copy when setting float parameters. +* Made ParameterDriver Copy set boolean to true if negative, to match docs. +* Allow dragging Expression Value in the Floats section of the inspector. +* In the Floats section, "Value" will show the animated Animator parameter (if driven by curve) which may be different from the expression value which is used for sync and parameter driver Copy. + +### **New features in v 2.9.9 (3.0 rc6):** + +2.9.9: + +* **Requires SDK VRCSDK3-AVATAR-2022.06.03 or newer** Please update the SDK if it is before June 2022. +* Added support for the new Copy parameters feature. +* Bifurcate exported and internal float values to avoid pollution from AAPs. This should match game better, as you cannot sync an AAP value over the network. +You will not see `exportedValue` and `value` sliders for floats. Value will represent what the Animator sees, while Exported represents the value which is synced (and used for parameter Copy). +* Please let me know if there are bugs related to the bifurcated float parameters. +* Make buttons compact by default to avoid issue with huge buttons. +* Remove use of Reflection, as we are breaking compatibility with older VRCSDK versions. + +### **New features in v 2.9.8 (3.0 rc5):** + +2.9.8: + +* Upgrade GestureManager to version 3.4 in the .unitypackage build. Some constructor arguments changed, so be sure to upgrade both. +* Allow all parameter types for contacts, Strech, IsGrabbed and Angle. (Thanks, bd_) +* Fix for non-local clones and reduced logspam (Thanks, bd_) +* Fix animated animator parameters (AAP) support for Debug Duplicate Animator. +* Ensure animator window is refreshed when switching Debug Duplicate Animator. +* Fix possible bug when using Default Animator to Debug in emulator control. + +Using reflection, the emulator continues to be compatible with older pre-physbone SDK versions. This may be the last version of Av3Emulator supporting older SDKs. + +**Avatar Dynamics integration (read NOTE)** + +Latest VRCSDK provides Avatar Dynamics support built-in. + +**NOTE**: To test Avatar Dynamics, you must have a **Camera** in your scene **tagged as "Main Camera"** (top of the inspector next to the layer dropdown). + +To test, you must be in **Game View**. Make use of _GameObject menu -> Align With View (Ctrl+Shift+F)_ for easily copying the Scene View to your camera transform. + +This update hooks into the driven parameters on both `PhysBone` and `ContactReceiver`. Enabled by default. Can be disabled by ticking a box on the Emulator Control before entering play mode. Recommended to set Radius greater than 0. + +For non-open beta, now supports hot reloading scripts without restarting play mode. This can be useful for rapid testing. + +**OSC Now works in the emulator (opt-in)**: Supports sending and receiving OSC messages for your avatar. Off by default. Simply tick "**Enable Avatar OSC**" to turn it on. + +Supports generating, loading and saving JSON config files for OSC for use in VRChat. + +**Compatible with VRCFaceTracking**. Avatar switch messages are now implemented! Messages are sent without bundles for compatibility. + +Other features: + +* Checks for PipelineSaver component: No longer should block upload. +* GestureManager menu is now off by default, and easy to switch on and off as needed. +* No longer crashes when some animators are set to null or VRCSDK is installed in the wrong place. +* Try to reduce inspector bouncing while navigating menu. +* Added new Jump box to simulate a jump (grounded, up velocity, down velocity). Also supported from OSC. +* Improved a bunch of bugs with Mirror and Shadow clones thanks to 3. They can be disabled by clicking the boxes in the main Emulator Control. +* Merged in contribution by 3 to support head scaling. +* Support for IK Sync to local clones. +* Many more bugfixes. + +### **New features in v 2.9.7 (3.0 rc4):** + +2.9.7: Fix VRCPhysBone and ContactReceiver components disabled by default. + +2.9.6: Mostly a stability update to fix common issues with running the emulator. + +Grabbing and posing should work more reliably now. Make sure not to leave your bone Radius at 0. + +* Made AvatarDynamics parameters more strict to match VRC implementation, so float params must be float; bool params must be bool, and so on. +* Fixed issues with grabbing and posing bones. +* Fixed more build errors. +* Fixed grabbing bones not working when the OSC folder was missing. +* Added some try/catch for exceptions related to the OSC folder in LocalLow. + +2.9.5: Fixed Build and Publish AGAIN. + +### **New features in v 2.9.0 (3.0 beta):** + +**Release note**: Lyuma's Av3 Emulator now comes in two versions: lite/classic version with a basic menu; and the other which includes VRC-Gesture-Manager and the radial menu. + +
+ +* New! Integration with the Avatar 3.0 Menu when [VRC Gesture Manager by BlackStartx](https://github.com/BlackStartx/VRC-Gesture-Manager) is installed. +* Support for MirrorReflection duplicate with only FX playable (can choose which version to show/hide). +* Attempt to emulate the uninitialized state for remote players. +* Add a "update interval" to simulate network delay when sending parameters. + +* FIXED: Default layer weights were being ignored. Unity defaults layers to weight 0, and this should make such mistakes easier to catch. +* FIXED: Add ParameterDriver was failing half the time. +* FIXED: AngularY float range to match smooth turn in VR (by NotAKidOnSteam) +* FIXED: GestureWeight is always 0 for neutral; always 1 for most gestures, and only varies from 0 to 1 for Fist. +* FIXED: Local player bounds forced to update when offscreen; and animator culling turned off. + +Known issues: Edit Mode in radial menu does not work + +As always, watch out for mistakes with Write Defaults. Also, while animating your own avatar might work for you, it may break the avatar for remote players in game: this cannot be perfectly replicated in editor. + +### **New features in v 2.2.2:**
+* Fix max value for random int, for example used in ragdoll system (Thanks, ksivl) +* Fix crash when emulator is enabled and exiting play mode (Thanks, ksivl) +* Made a further attempt to mitigate interfering with the upload process if a PipelineSaver component is present. + +### **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: +![Avatar 3.0 overview](Screenshots/a3_example.png) +![Avatar 3.0 explanation](Screenshots/avatar3emu_tutorial.png) +[(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 + +![Lock your inspector to allow investigating other objects](Screenshots/lock_inspector_tutorial.png)![Checklist for turning off Write Defaults.](Screenshots/write_defaults_off.png) +[(View full lock inspector explanation)](Screenshots/lock_inspector_tutorial.png) [(View full write defaults off checklist)](Screenshots/write_defaults_off.png) + diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md.meta new file mode 100644 index 00000000..54ba8815 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: be927fe9db2f22a4cb6214690375719f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta new file mode 100644 index 00000000..625b0169 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e280e7f1cd25c844996ce6a42648307f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png new file mode 100644 index 00000000..20d54292 Binary files /dev/null and b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png differ diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png.meta new file mode 100644 index 00000000..7a20b706 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_example.png.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: 5ed1b1dad0b84d04999404e4bd3003dc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + 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: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + 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: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png new file mode 100644 index 00000000..d8a7e9f3 Binary files /dev/null and b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png differ diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png.meta new file mode 100644 index 00000000..e3f55e36 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/av3_radial_menu.png.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: c5847b8fc9b4ff94199303aee18be714 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + 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: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + 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: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png new file mode 100644 index 00000000..6ecfb309 Binary files /dev/null and b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png differ diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/avatar3emu_tutorial.png.meta new file mode 100644 index 00000000..cdb1108c --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png new file mode 100644 index 00000000..74e50d2d Binary files /dev/null and b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png differ diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/lock_inspector_tutorial.png.meta new file mode 100644 index 00000000..0402fa18 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png new file mode 100644 index 00000000..3d9a20cf Binary files /dev/null and b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png differ diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Screenshots/write_defaults_off.png.meta new file mode 100644 index 00000000..79faaaa4 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta new file mode 100644 index 00000000..e3453795 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ad5fb09acbb8ac45b5a829926d845a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs new file mode 100644 index 00000000..e278b06c --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs @@ -0,0 +1,219 @@ +using System; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; + +[Serializable] +public struct A3EOSCConfiguration { + public bool UseRealPipelineIdJSONFile; + public bool SendRecvAllParamsNotInJSON; + public bool GenerateOSCConfig; + public bool LoadOSCConfig; + public bool SaveOSCConfig; + public string OSCAvatarID; + public string OSCFilePath; + public OuterJson OSCJsonConfig; + + static bool whichtest; + [Serializable] + public struct InputOutputPath { + public string address; + public string type; + } + [Serializable] + public struct InnerJson { + public string name; + public InputOutputPath input; + public InputOutputPath output; + } + [Serializable] + public class OuterJson { + public string id; + public string name; + public InnerJson[] parameters; + } + readonly static string [][] OSC_BUILTIN_PARAMETERS = { + new string[]{"VelocityZ","Float"}, + new string[]{"VelocityY","Float"}, + new string[]{"VelocityX","Float"}, + new string[]{"InStation","Bool"}, + new string[]{"Seated","Bool"}, + new string[]{"AFK","Bool"}, + new string[]{"Upright","Float"}, + new string[]{"AngularY","Float"}, + new string[]{"Grounded","Bool"}, + new string[]{"MuteSelf","Bool"}, + new string[]{"VRMode","Int"}, + new string[]{"TrackingType","Int"}, + new string[]{"GestureRightWeight","Float"}, + new string[]{"GestureRight","Int"}, + new string[]{"GestureLeftWeight","Float"}, + new string[]{"GestureLeft","Int"}, + new string[]{"Voice","Float"}, + new string[]{"Viseme","Int"} + }; + public static OuterJson GenerateOuterJSON(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters expparams, string id, string name) { + OuterJson oj = new OuterJson(); + oj.id = id; + oj.name = name; + const string ADDRESS_PREFIX = "/avatar/parameters/"; + int nonempty; + if (expparams == null) { + expparams = ScriptableObject.CreateInstance(); + expparams.parameters = new VRCExpressionParameters.Parameter[] { + new VRCExpressionParameters.Parameter { + defaultValue = 0, + saved = false, + name = "VRCEmote", + valueType = VRCExpressionParameters.ValueType.Int + }, + new VRCExpressionParameters.Parameter { + defaultValue = 0, + saved = false, + name = "VRCFaceBlendH", + valueType = VRCExpressionParameters.ValueType.Float + }, + new VRCExpressionParameters.Parameter { + defaultValue = 0, + saved = false, + name = "VRCFaceBlendV", + valueType = VRCExpressionParameters.ValueType.Float + } + }; + } + nonempty = expparams.parameters.Length; + foreach (var p in expparams.parameters) { + if (p.name.Length == 0) { + nonempty--; + } + } + oj.parameters = new InnerJson[OSC_BUILTIN_PARAMETERS.Length + nonempty]; + int idx = 0; + // VRC writes these in reverse order. No idea why. + foreach (var p in expparams.parameters) { + if (p.name.Length != 0) { + oj.parameters[nonempty - idx - 1] = new InnerJson { + name = p.name, + input = new InputOutputPath { + address = ADDRESS_PREFIX + p.name, + type = (p.valueType == VRCExpressionParameters.ValueType.Int ? "Int" : + (p.valueType == VRCExpressionParameters.ValueType.Float ? "Float" : "Bool")) + }, + output = new InputOutputPath { + address = ADDRESS_PREFIX + p.name, + type = (p.valueType == VRCExpressionParameters.ValueType.Int ? "Int" : + (p.valueType == VRCExpressionParameters.ValueType.Float ? "Float" : "Bool")) + } + }; + idx++; + } + } + for (int i = 0; i < OSC_BUILTIN_PARAMETERS.Length; i++) { + var bname = OSC_BUILTIN_PARAMETERS[i][0]; + var btype = OSC_BUILTIN_PARAMETERS[i][1]; + oj.parameters[idx] = new InnerJson { + name = bname, + output = new InputOutputPath { + address = ADDRESS_PREFIX + bname, + type = btype + } + }; + idx++; + } + return oj; + } + public static OuterJson ReadJSON(string full_file_path) { + return JsonUtility.FromJson(System.IO.File.ReadAllText(full_file_path)); + } + public static void WriteJSON(string filename, OuterJson oj) { + // pretty print, remove empty {"input":{"address":"","type":""}} junk that unity dumps in, and match VRChat's whitespace. + System.IO.File.WriteAllLines(filename, System.Text.RegularExpressions.Regex.Replace("\ufeff" + + System.Text.RegularExpressions.Regex.Replace( + JsonUtility.ToJson(oj, true), ",\\s*\"input\"\\s*:\\s*{\\s*\"address\"\\s*:\\s*\"\"\\s*,\\s*\"type\"\\s*:\\s*\"\"\\s*}\\s*",""), + "\n(\\s*)\\1(\\S)", "\n$1$2").Split('\n')); + } + + public const string AVTR_EMULATOR_PREFIX = "avtr_LyumaAv3Emulator_"; + public void EnsureOSCJSONConfig(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters expparams, string avatarid, string name) { + try { + string localLowPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if (localLowPath.EndsWith("Local")) { + localLowPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(localLowPath), "LocalLow"); + } + string userid = null; + string vrcOSCPath = System.IO.Path.Combine(localLowPath, "VRChat", "vrchat", "OSC"); + + System.Type apiusertype = System.Type.GetType("VRC.Core.APIUser, VRCCore-Editor"); + if (apiusertype != null) { + var idprop = apiusertype.GetProperty("id", System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public); + var prop = apiusertype.GetProperty("CurrentUser", System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.Public); + // Debug.Log("idprop " + idprop); + if (idprop != null && prop != null) { + var apiuserinst = prop.GetValue(null); + if (apiuserinst != null) { + // Debug.Log("apiuser " + apiuserinst); + userid = (string)idprop.GetValue(apiuserinst); + } + } + } + try { + System.IO.Directory.CreateDirectory(vrcOSCPath); + } catch (System.IO.IOException) { + } + if (userid == null || userid.Length == 0) { + // do not have a known user account. + // find the most recent user folder. + DateTime dt = DateTime.MinValue; + // Debug.Log("lets-a look at " + vrcOSCPath); + foreach (string file in System.IO.Directory.GetDirectories(vrcOSCPath, "*", System.IO.SearchOption.TopDirectoryOnly)) { + // Debug.Log("enumerate a file " + file); + DateTime thisdt = System.IO.File.GetLastWriteTime(file); + if (thisdt > dt) { + userid = System.IO.Path.GetFileName(file); + dt = thisdt; + } + } + } + if (userid == null || userid.Length == 0) { + OSCAvatarID = "not_logged_in"; + OSCFilePath = "No User folder was found. Please play VRC or login."; + OSCJsonConfig = GenerateOuterJSON(expparams, "not_logged_in", name); + return; + } + string avatarDirectory = System.IO.Path.Combine(vrcOSCPath, userid, "Avatars"); + try { + System.IO.Directory.CreateDirectory(avatarDirectory); + } catch (System.IO.IOException) { + } + if (avatarid != null && UseRealPipelineIdJSONFile) { + OSCAvatarID = avatarid; // json file already exists: let's use it. + OSCFilePath = System.IO.Path.Combine(avatarDirectory, avatarid + ".json"); + } else { + avatarid = AVTR_EMULATOR_PREFIX + (whichtest ? "A" : "B"); + OSCFilePath = System.IO.Path.Combine(avatarDirectory, avatarid + ".json"); + whichtest = !whichtest; + OSCAvatarID = avatarid; + WriteJSON(OSCFilePath, GenerateOuterJSON(expparams, avatarid, name)); + } + if (System.IO.File.Exists(OSCFilePath)) { + try { + OSCJsonConfig = ReadJSON(OSCFilePath); + } catch (Exception e) { + Debug.LogException(e); + Debug.Log("File failed to load. Generating new JSON for " + OSCAvatarID); + OSCJsonConfig = GenerateOuterJSON(expparams, OSCAvatarID, name); + } + } else { + Debug.Log("File does not exist. Generating new JSON for " + OSCAvatarID); + OSCJsonConfig = GenerateOuterJSON(expparams, OSCAvatarID, name); + } + } catch (Exception e) { + Debug.LogException(e); + OSCAvatarID = "exception_generating_json"; + OSCFilePath = e.Message; + Debug.Log("Unable to determine Avatar ID or JSON file path. Generating config."); + OSCJsonConfig = GenerateOuterJSON(expparams, "exception_generating_json", name); + } + } + +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta new file mode 100644 index 00000000..2e6245cb --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a616815e5eee504280da16619284024 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs new file mode 100644 index 00000000..6b6d8d54 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs @@ -0,0 +1,682 @@ +/* A3ESimpleOSC for C#, version 0.1 +Copyright (c) 2022 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. */ + +#if UNITY_5_3_OR_NEWER +#define UNITY +#endif + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +public class A3ESimpleOSC +{ + public enum Impulse {IMPULSE} + public struct TimeTag { + public int secs; + public int nsecs; + public override string ToString() { + return "" + secs + ":" + nsecs; + } + #if UNITY + public static implicit operator UnityEngine.Vector2Int(TimeTag tt) { + return new UnityEngine.Vector2Int { x = tt.secs, y = tt.nsecs }; + } + public static implicit operator TimeTag(UnityEngine.Vector2Int v2) { + return new TimeTag { secs = v2.x, nsecs = v2.y }; + } + #endif + } + public struct OSCColor { + public byte r; + public byte g; + public byte b; + public byte a; + public override string ToString() { + return "OSCColor<" + r + "," + g + "," + b + "," + a + ">"; + } + #if UNITY + public static implicit operator UnityEngine.Color(OSCColor c) { + return (UnityEngine.Color)new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a }; + } + public static implicit operator OSCColor(UnityEngine.Color c) { + UnityEngine.Color32 c32 = (UnityEngine.Color32)c; + return new OSCColor { r = c32.r, g = c32.g, b = c32.b, a = c32.a }; + } + public static implicit operator OSCColor(UnityEngine.Color32 c) { + return new OSCColor { r = c.r, g = c.g, b = c.b, a = c.a }; + } + public static implicit operator UnityEngine.Color32(OSCColor c) { + return new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a }; + } + #endif + } + public struct OSCMessage { + public IPEndPoint sender; + public uint bundleId; // 0 if not in a bundle; positive integer if part of a bundle. + public string path; + public TimeTag time; + public string typeTag; + public object[] arguments; + public override string ToString() { + System.Text.StringBuilder ret = new System.Text.StringBuilder(); + ret.Append(""); + return ret.ToString(); + } + public void DebugInto(System.Text.StringBuilder ret, bool dispIPTime=true) { + ret.Append(path); + if (dispIPTime && sender != null) { + ret.Append("; from "); + ret.Append(sender.ToString()); + } + if (dispIPTime && (time.secs != 0 || time.nsecs != 0)) { + ret.Append(" @"); + ret.Append(time); + } + if (dispIPTime) { + ret.Append("; type "); + ret.Append(typeTag); + } + ret.Append(":\n"); + DebugObjectArrayInto(ret, " ", arguments); + } + } + + public static bool DebugLoggingEnabled = true; // Set to false to handle bad data without logspam. + static void CryWolf(string logMsg) { + if (DebugLoggingEnabled) { + #if UNITY + UnityEngine.Debug.LogWarning(logMsg); + #else + System.Console.WriteLine(logMsg); + #endif + } + } + + public static void DebugObjectArrayInto(System.Text.StringBuilder sb, string indent, object[] args) { + int idx = 0; + foreach (var arg in args) { + sb.Append(indent); + sb.Append("[Arg"); + sb.Append(idx); + sb.Append("] = "); + switch (arg) { + case null: + sb.Append("null"); + break; + case object[] subArgs: + sb.Append("[\n"); + DebugObjectArrayInto(sb, indent + " ", subArgs); + sb.Append(indent + "]"); + break; + case byte[] subBytes: + sb.Append("new byte[] {"); + bool first = true; + foreach (byte b in subBytes) { + if (!first) { + sb.Append(","); + } + first = false; + sb.Append((int)b); + } + sb.Append("}"); + break; + case float f: + sb.Append(f.ToString("F4")); + break; + case int i: + sb.Append(i.ToString()); + break; + case bool b: + sb.Append(b.ToString()); + break; + default: + sb.Append("("); + sb.Append(arg.GetType().Name); + sb.Append(")"); + sb.Append(arg); + break; + } + sb.Append("\n"); + idx++; + } + } + + + /// Parsing / decoding functions: + static object ParseType(char typeTag, byte[] data, ref int offset) { + switch(typeTag) { + case 'T': + return true; + case 'F': + return false; + case 'N': + return null; + case 'I': + return Impulse.IMPULSE; + case 'i': + int iret = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset)); + offset += 4; + return iret; + case 'f': + byte[] tmp = new byte[4]; + Array.Copy(data, offset, tmp, 0, 4); + if (BitConverter.IsLittleEndian) { + Array.Reverse(tmp); + } + float fret = BitConverter.ToSingle(tmp, 0); + offset += 4; + return fret; + case 't': + int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset)); + int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 4)); + offset += 8; + return new TimeTag { secs = secs, nsecs = nanosecs }; + case 's': + int strend = offset; + while (data[strend] != 0) { + strend++; + } + tmp = new byte[strend - offset]; + Array.Copy(data, offset, tmp, 0, strend - offset); + offset = strend; + offset = (offset + 4) & ~3; + return System.Text.Encoding.UTF8.GetString(tmp); + case 'b': + int len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset)); + offset += 4; + tmp = new byte[len]; + Array.Copy(data, offset, tmp, 0, len); + offset += len; + offset = (offset + 3) & ~3; + return tmp; + // Non-standard types: + case 'r': + byte r = data[offset++]; + byte g = data[offset++]; + byte b = data[offset++]; + byte a = data[offset++]; + return new OSCColor { r = r, g = g, b = b, a = a }; + case 'h': + long lret = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, offset)); + offset += 8; + return lret; + case 'd': + byte[] dtmp = new byte[8]; + Array.Copy(data, offset, dtmp, 0, 8); + if (BitConverter.IsLittleEndian) { + Array.Reverse(dtmp); + } + double dret = BitConverter.ToDouble(dtmp, 0); + offset += 8; + return dret; + case 'c': + uint cret = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset)); + offset += 4; + return cret; + default: + CryWolf("Unknown type tag " + typeTag + " offset " + offset); + break; + } + return null; + } + + static void SerializeTypeInto(byte[] data, ref int offset, object value, char typeTag) { + // Debug.Log("Serialize " + value.GetType() + " " + (value) + " as " + typeTag); + byte[]tmp; + switch (typeTag) { + case 'T': + case 'F': + case 'N': + case 'I': + break; + case 'i': + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)value)), 0, data, offset, 4); + offset += 4; + break; + case 'f': + tmp = BitConverter.GetBytes((float)value); + if (BitConverter.IsLittleEndian) { + Array.Reverse(tmp); + } + Array.Copy(tmp, 0, data, offset, 4); + offset += 4; + break; + case 't': + TimeTag v2; + switch (value) { + #if UNITY + case UnityEngine.Vector2Int i2: + v2 = (TimeTag)i2; + break; + #endif + default: + v2 = (TimeTag)value; + break; + } + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.secs)), 0, data, offset, 4); + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.nsecs)), 0, data, offset + 4, 4); + offset += 8; + break; + case 's': + tmp = System.Text.Encoding.UTF8.GetBytes((string)value); + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + break; + case 'b': + tmp = (byte[])value; + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tmp.Length)), 0, data, offset, 4); + offset += 4; + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 3) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + break; + // Non-standard types: + case 'r': + OSCColor col; + switch (value) { + #if UNITY + case UnityEngine.Color32 unic32: + col = (OSCColor)unic32; + break; + case UnityEngine.Color unic: + col = (OSCColor)unic; + break; + #endif + default: + col = (OSCColor)value; + break; + } + data[offset++] = col.r; + data[offset++] = col.g; + data[offset++] = col.b; + data[offset++] = col.a; + break; + case 'h': + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)value)), 0, data, offset, 8); + offset += 8; + break; + case 'd': + tmp = BitConverter.GetBytes((double)value); + if (BitConverter.IsLittleEndian) { + Array.Reverse(tmp); + } + Array.Copy(tmp, 0, data, offset, 8); + offset += 8; + break; + case 'c': + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(uint)value)), 0, data, offset, 4); + offset += 4; + break; + default: + CryWolf("Unexpected type tag to serialize " + typeTag + " offset " + offset); + break; + } + } + + public static void DecodeOSCInto(ConcurrentQueue outQueue, byte[] data, int offset, int length, IPEndPoint senderIp=null, uint bundleId=0, TimeTag bundleTimetag=new TimeTag(), uint bundleIdNested=0) { + if (offset == 0 && length > 20 && data[offset] == '#' && data[offset + 1] == 'b' && data[offset + 2] == 'u' && data[offset + 3] == 'n' && + data[offset + 4] == 'd' && data[offset + 5] == 'l' && data[offset + 6] == 'e' && data[offset + 7] == 0) { + int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 8)); + int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 12)); + bundleTimetag = new TimeTag { secs = secs, nsecs = nanosecs }; + offset += 16; + while (offset < length) { + int msglen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset)); + offset += 4; + DecodeOSCInto(outQueue, data, offset, msglen, senderIp, bundleId, bundleTimetag, bundleId); + offset += msglen; + } + return; + } + OSCMessage msg = new OSCMessage(); + msg.time = bundleTimetag; + msg.sender = senderIp; + msg.bundleId = bundleIdNested; + int strlen = 0; + while (data[offset + strlen] != 0) { + strlen++; + } + msg.path = System.Text.Encoding.UTF8.GetString(data, offset, strlen); + offset += strlen; + offset = (offset + 4) & ~3; + while (data[offset] != ',') { + offset++; + } + int typetags = offset; + while (data[offset] != 0) { + offset++; + } + msg.typeTag = System.Text.Encoding.ASCII.GetString(data, typetags, offset - typetags); + offset = (offset + 4) & ~3; + //msg.arguments = new object[msg.typeTag.Length]; + List topLevelArguments = new List(); + List> nested = new List>(); + nested.Add(topLevelArguments); + for (int i = 1; i < msg.typeTag.Length; i++) { + // Debug.Log("doing type tag " + msg.typeTag[i] + " offset: " + offset); + object obj; + switch (msg.typeTag[i]) { + case '[': + nested.Add(new List()); + break; + case ']': + if (nested.Count > 1) { + obj = nested[nested.Count - 1].ToArray(); + nested.RemoveAt(nested.Count - 1); + nested[nested.Count - 1].Add(obj); + } + break; + default: + obj = ParseType(msg.typeTag[i], data, ref offset); + nested[nested.Count - 1].Add(obj); + break; + } + } + if (nested.Count != 1) { + CryWolf("Invalid nested count (mismatched start and end array in OSC message): " + msg.typeTag); + } + msg.arguments = topLevelArguments.ToArray(); + outQueue.Enqueue(msg); + } + + static void GenerateOSCTypeTagInto(System.Text.StringBuilder typeTag, object[] packet) { + if (typeTag.Length == 0) { + typeTag.Append(','); + } + foreach (object po in packet) { + switch(po) { + case object[] subArray: + typeTag.Append('['); + GenerateOSCTypeTagInto(typeTag, subArray); + typeTag.Append(']'); + break; + case float f: + typeTag.Append('f'); + break; + case int i: + typeTag.Append('i'); + break; + #if UNITY + case UnityEngine.Color32 ui32: + case UnityEngine.Color ui: + #endif + case OSCColor i: + typeTag.Append('r'); + break; + case true: + typeTag.Append('T'); + break; + case false: + typeTag.Append('F'); + break; + case null: + typeTag.Append('N'); + break; + case Impulse.IMPULSE: + typeTag.Append('I'); + break; + case string s: + typeTag.Append('s'); + break; + case byte[] b: + typeTag.Append('b'); + break; + #if UNITY + case UnityEngine.Vector2Int i2: + #endif + case TimeTag tt: + typeTag.Append('t'); + break; + case double d: + typeTag.Append('d'); + break; + case long l: + typeTag.Append('h'); + break; + case uint c: // represent OSC char as uint. confusing??? + typeTag.Append('c'); + break; + default: + CryWolf("Invalid type " + po.GetType() + " at " + typeTag); + break; + } + } + } + + public static void EncodeOSCInto(byte[] data, ref int offset, OSCMessage msg, string type_tag_override="") { + if (msg.typeTag == null || msg.typeTag.Length == 0) { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + GenerateOSCTypeTagInto(sb, msg.arguments); + msg.typeTag = sb.ToString(); + } + byte[] tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.path); + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.typeTag); + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + List nested = new List(); + nested.Add(msg.arguments); + List nestedIdx = new List(); + nestedIdx.Add(0); + foreach (char ch in msg.typeTag) { + switch (ch) { + case ',': + continue; + case '[': + object[] newArr = (object[])nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]]; + nested.Add(newArr); + nestedIdx[nestedIdx.Count-1] += 1; + nestedIdx.Add(0); + break; + case ']': + nested.RemoveAt(nested.Count-1); + nestedIdx.RemoveAt(nestedIdx.Count-1); + break; + default: + SerializeTypeInto(data, ref offset, nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]], ch); + nestedIdx[nestedIdx.Count-1] += 1; + break; + } + } + } + + public static void EncodeOSCBundleInto(byte[] data, ref int offset, List packets, TimeTag tt) { + Array.Copy(new byte[]{(byte)'#',(byte)'b',(byte)'u',(byte)'n',(byte)'d',(byte)'l',(byte)'e',0}, 0, data, offset, 8); + offset += 8; + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.secs)), 0, data, offset, 4); + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.nsecs)), 0, data, offset + 4, 4); + offset += 8; + foreach (var msg in packets) { + int startOffset = offset; + offset += 4; + EncodeOSCInto(data, ref offset, msg); + int endOffset = offset; + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(endOffset - startOffset - 4))), 0, data, startOffset, 4); + } + } + + public class UDPThread { + UdpClient udpServer; + public bool shutdown; + public int udp_port; + public uint bundleCounter; // just so callers know if it came from a bundle. + public ConcurrentQueue receivedMessageQueue = new ConcurrentQueue(); + + public IPEndPoint Open(int udp_port) { + udpServer = new UdpClient(udp_port); + return (IPEndPoint)udpServer.Client.LocalEndPoint; + } + public IPEndPoint Open(IPEndPoint local_udp_endpoint) { + udpServer = new UdpClient(local_udp_endpoint); + return (IPEndPoint)udpServer.Client.LocalEndPoint; + } + public void Connect(IPEndPoint endPoint) { + udpServer.Connect(endPoint); + } + + public void mythread(){ + while (!shutdown) { + var incomingIP = new IPEndPoint(IPAddress.Any, 0); + try { + var data = udpServer.Receive(ref incomingIP); + try { + if (bundleCounter == 0) { + bundleCounter += 1; + } + DecodeOSCInto(receivedMessageQueue, data, 0, data.Length, incomingIP, bundleCounter); + bundleCounter += 1; + } catch (Exception e) { + CryWolf(e.ToString()); + } + } catch (SocketException) { + if (!shutdown) { + continue; //throw; + } + } + } + } + public void Close() { + shutdown = true; + if (udpServer != null) { + udpServer.Close(); + } + } + public void SendBytes(byte[] buffer, int length, IPEndPoint endPoint) { + if (endPoint == null) { + udpServer.Send(buffer, length); + } else { + udpServer.Send(buffer, length, endPoint); + } + } + } + + Thread runningThread; + UDPThread udpThreadState; + byte[]scratchSpace = new byte[8192]; + IPEndPoint unconnectedEndpoint = null; + + // Two methods for establishing send relationship: + // I. A connected UDP socket cannot receive messages from other hosts. + public void Connect(IPEndPoint endPoint) { + udpThreadState.Connect(endPoint); + } + // II. This can be called freely for every datagram and only affects data sent. + public void SetUnconnectedEndpoint(IPEndPoint endPoint) { + unconnectedEndpoint = endPoint; + } + + // Two methods for creating a socket (with or without bound local ip/port) + // I. Open a socket only. Avoids creating a thread. + public IPEndPoint OpenSendOnlyClient(int udp_port=0) { + return OpenSendOnlyClient(new IPEndPoint(IPAddress.Any, udp_port)); + } + public IPEndPoint OpenSendOnlyClient(IPEndPoint local_udp_endpoint) { + runningThread = null; + udpThreadState = new UDPThread(); + return udpThreadState.Open(local_udp_endpoint); + } + + // II. Open a socket, and creates a thread for receiving. + public IPEndPoint OpenClient(int udp_port=0) { + return OpenClient(new IPEndPoint(IPAddress.Any, udp_port)); + } + public IPEndPoint OpenClient(IPEndPoint local_udp_endpoint) { + if (udpThreadState != null) { + StopClient(); + udpThreadState = null; + } + udpThreadState = new UDPThread(); + IPEndPoint localEndPoint = udpThreadState.Open(local_udp_endpoint); + runningThread = new Thread(new ThreadStart(udpThreadState.mythread)); + runningThread.Start(); + return localEndPoint; + } + + // Call this to close the socket, and join the thread if any. + public void StopClient() { + if (udpThreadState != null && !udpThreadState.shutdown) { + udpThreadState.Close(); + if (runningThread != null) { + runningThread.Join(5000); + } + } + } + + // Read data waiting in the buffer. + public void GetIncomingOSC(List incomingMessages) { + OSCMessage msg; + if (udpThreadState != null && runningThread != null) { + while (udpThreadState.receivedMessageQueue.TryDequeue(out msg)) { + incomingMessages.Add(msg); + } + } + } + + // Send a single OSCMessage, not bundled. + public void SendOSCPacket(OSCMessage msg, byte[] buffer=null) { + if (buffer == null) { + buffer = scratchSpace; + } + int encodedLength = 0; + EncodeOSCInto(buffer, ref encodedLength, msg); + udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint); + } + + // Send a single OSCMessage as a bundle. + public void SendOSCBundle(List messages, TimeTag ts, byte[] buffer=null) { + if (messages.Count == 0) { + CryWolf("Attempt to send bundle with no messages!"); + } + if (buffer == null) { + buffer = scratchSpace; + } + int encodedLength = 0; + EncodeOSCBundleInto(buffer, ref encodedLength, messages, ts); + udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint); + } + public void SendRaw(byte[] buffer, int encodedLength) { + udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint); + } + + // udpServer.Send(new byte[] { 1 }, 1); // if data is received reply letting the client know that we got his data +} \ No newline at end of file diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta new file mode 100644 index 00000000..05af50c7 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 569586a013b244842a857866dc572531 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs new file mode 100644 index 00000000..b91de425 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs @@ -0,0 +1,51 @@ +/* Copyright (c) 2020-2022 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 UnityEngine; +using System.Collections.Generic; +using VRC.SDK3.Avatars.ScriptableObjects; + +public class GestureManagerAv3Menu : LyumaAv3Menu +{ + + // public LyumaAv3Runtime Runtime; + // public VRCExpressionsMenu RootMenu; + // public bool IsMenuOpen { get; private set; } + // private int? _activeControlIndex = null; + // private string _activeControlParameterName; + public bool compact = true; + + private void Awake() + { + IsMenuOpen = true; + + if (LyumaAv3Runtime.addRuntimeDelegate != null) { + LyumaAv3Runtime.addRuntimeDelegate(this); + } + } + + // public void ToggleMenu() + // { + + // IsMenuOpen = !IsMenuOpen; + // } + +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta new file mode 100644 index 00000000..2e1b707f --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4cc9dba3b86035b43b8baaca33369f11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs new file mode 100644 index 00000000..bb7e9dc3 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs @@ -0,0 +1,155 @@ +/* Copyright (c) 2020-2022 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; +using VRC.SDK3.Avatars.Components; + +[RequireComponent(typeof(Animator))] +public class LyumaAv3Emulator : MonoBehaviour +{ + static readonly ulong EMULATOR_VERSION = 0x2_09_08_00; + + 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; + public int CreateNonLocalCloneCount; + [Tooltip("Simulate behavior with sub-animator parameter drivers prior to the 2021.1.1 patch (19 Jan 2021)")] + public bool legacySubAnimatorParameterDriverMode; + public bool legacyMenuGUI = true; + private bool lastLegacyMenuGUI = true; + public bool DisableAvatarDynamicsIntegration; + public bool WorkaroundPlayModeScriptCompile = true; + public bool DisableMirrorClone; + public bool DisableShadowClone; + private bool lastHead; + public bool EnableHeadScaling; + public bool ViewMirrorReflection; + public bool ViewBothRealAndMirror; + + static public LyumaAv3Emulator emulatorInstance; + static public RuntimeAnimatorController EmptyController; + + public List runtimes = new List(); + + private void Awake() + { + Animator animator = gameObject.GetOrAddComponent(); + animator.enabled = false; + animator.runtimeAnimatorController = EmptyController; + emulatorInstance = this; + VRCAvatarDescriptor[] avatars = FindObjectsOfType(); + Debug.Log(this.name + ": Setting up Av3Emulator on "+avatars.Length + " avatars.", this); + foreach (var avadesc in avatars) + { + if (avadesc.GetComponent() != null) { + Debug.Log("Found PipelineSaver on " + avadesc.name + ". Disabling clones and mirror copy.", avadesc); + DisableMirrorClone = true; + DisableShadowClone = true; + CreateNonLocalClone = false; + EnableHeadScaling = false; + } + try { + // Creates the playable director, and initializes animator. + var oml = avadesc.gameObject.GetOrAddComponent(); + oml.startTransform = this.transform; + bool alreadyHadComponent = avadesc.gameObject.GetComponent() != null; + var runtime = avadesc.gameObject.GetOrAddComponent(); + if (oml != null) { + GameObject.DestroyImmediate(oml); + } + runtime.emulator = this; + runtime.VRMode = DefaultToVR; + runtime.TrackingType = DefaultTrackingType; + runtime.InStation = DefaultTestInStation; + runtime.DebugDuplicateAnimator = DefaultAnimatorToDebug; + runtime.EnableHeadScaling = EnableHeadScaling; + runtimes.Add(runtime); + if (!alreadyHadComponent && !DisableShadowClone) { + runtime.CreateShadowClone(); + } + if (!alreadyHadComponent && !DisableMirrorClone) { + runtime.CreateMirrorClone(); + } + runtime.DisableMirrorAndShadowClones = DisableShadowClone && DisableMirrorClone; + } catch (System.Exception e) { + Debug.LogException(e); + } + } + if (WorkaroundPlayModeScriptCompile) { + LyumaAv3Runtime.ApplyOnEnableWorkaroundDelegate(); + } + } + + private void OnDestroy() { + foreach (var runtime in runtimes) { + Destroy(runtime); + } + runtimes.Clear(); + LyumaAv3Runtime.updateSceneLayersDelegate(~0); + } + + private void Update() { + if (RestartingEmulator) { + RestartingEmulator = false; + Awake(); + } else if (RestartEmulator) { + RestartEmulator = false; + OnDestroy(); + RestartingEmulator = true; + } + if (ViewBothRealAndMirror) { + LyumaAv3Runtime.updateSceneLayersDelegate(~0); + } else if (ViewMirrorReflection && !ViewBothRealAndMirror) { + LyumaAv3Runtime.updateSceneLayersDelegate(~(1<<10)); + } else if (!ViewMirrorReflection && !ViewBothRealAndMirror) { + LyumaAv3Runtime.updateSceneLayersDelegate(~(1<<18)); + } + if (EnableHeadScaling != lastHead) { + lastHead = EnableHeadScaling; + foreach (var runtime in runtimes) { + runtime.EnableHeadScaling = EnableHeadScaling; + } + } + if (lastLegacyMenuGUI != legacyMenuGUI) { + lastLegacyMenuGUI = legacyMenuGUI; + foreach (var runtime in runtimes) { + runtime.legacyMenuGUI = legacyMenuGUI; + } + } + if (CreateNonLocalClone) { + CreateNonLocalCloneCount -= 1; + if (CreateNonLocalCloneCount <= 0) { + CreateNonLocalClone = false; + } + foreach (var runtime in runtimes) + { + if (runtime.AvatarSyncSource == runtime) + { + runtime.CreateNonLocalClone = true; + } + } + } + } + +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta new file mode 100644 index 00000000..9b3a898b --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs new file mode 100644 index 00000000..1fa5c89f --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs @@ -0,0 +1,235 @@ +/* Copyright (c) 2020-2022 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 UnityEngine; +using System.Collections.Generic; +using VRC.SDK3.Avatars.ScriptableObjects; + +public class LyumaAv3Menu : MonoBehaviour +{ + public bool useLegacyMenu = true; + [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 allConditions) + { + if (MandatedParam.name == null) return true; + + var actualParam = allConditions.Find(param => param.name == MandatedParam.name); + if (actualParam == null) return false; + return actualParam.exportedValue == MandatedParam.exportedValue; + } + } + + public LyumaAv3Runtime Runtime; + public VRCExpressionsMenu RootMenu; + public List MenuStack { get; } = new List(); + public bool IsMenuOpen { get; protected set; } + private int? _activeControlIndex = null; + private string _activeControlParameterName; + + private void Awake() + { + IsMenuOpen = true; + + if (LyumaAv3Runtime.addRuntimeDelegate != null) { + LyumaAv3Runtime.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.exportedValue; + 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, exportedValue = 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; + floatParam.exportedValue = 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.exportedValue == 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.exportedValue; + } + + public void UserFloat(string paramName, float newValue) + { + var floatParam = Runtime.Floats.Find(param => param.name == paramName); + if (floatParam == null) return; + + floatParam.value = newValue; + floatParam.exportedValue = newValue; + } + + public bool HasActiveControl() + { + return _activeControlIndex != null; + } + + public bool IsActiveControl(int controlIndex) + { + return _activeControlIndex == controlIndex; + } + public bool IsControlIKSynced(string ParamName) { + if (_activeControlIndex == null || !_activeControlIndex.HasValue) { + return false; + } + int idx = _activeControlIndex.Value; + var lastStack = MenuStack.Count == 0 ? RootMenu : MenuStack[MenuStack.Count - 1].ExpressionsMenu; + if (lastStack != null && lastStack.controls != null && idx < lastStack.controls.Count) { + var control = lastStack.controls[idx]; + foreach (var subp in control.subParameters) { + if (subp.name == ParamName) { + return true; + } + } + } + return false; + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta new file mode 100644 index 00000000..178833f5 --- /dev/null +++ b/VRCSDK3Avatars/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/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs new file mode 100644 index 00000000..b2d679ae --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs @@ -0,0 +1,375 @@ +/* Copyright (c) 2020-2022 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; +using System.Linq; + +public class LyumaAv3Osc : MonoBehaviour { + public delegate Rect GetEditorViewport(); + public static GetEditorViewport GetEditorViewportDelegate; + + public delegate void DrawDebugRect(Rect pos, Color col, Color outlineCol); + public static DrawDebugRect DrawDebugRectDelegate; + public delegate void DrawDebugText(Rect contentRect, Color backgroundCol, Color outlineCol, Color textCol, string str, TextAnchor alignment); + public static DrawDebugText DrawDebugTextDelegate; + + private LyumaAv3Emulator emulator; + A3ESimpleOSC receiver; + [Header("OSC Connection")] + public bool openSocket = false; + public bool disableOSC = false; + public bool resendAllParameters = false; + byte[] oscBuffer = new byte[65535]; + public int udpPort = 9000; + public string outgoingUdpIp = "127.0.0.1"; + public int outgoingUdpPort = 9001; + [SerializeField] private string commandLine = ""; + private int oldPort = 9000; + [Header("OSC Status")] + [SerializeField] private int localPort; + [SerializeField] private string localIp; + [SerializeField] private int numberOfOSCMessages; + [Header("Target Avatar")] + public VRC.SDK3.Avatars.Components.VRCAvatarDescriptor avatarDescriptor; + public bool forwardToAllAvatarsInScene; + + [Header("Gizmo settings")] + public bool alwaysShowOSCGizmos = true; + public bool clearGizmos = false; + public Color GizmoFilledColor = new Color(1.0f,0.0f,1.0f,0.1f); + public Color GizmoBackgroundColor = new Color(0.75f,0.0f,0.6f,0.05f); + public Color GizmoOutlineColor = new Color(0.9f,0.7f,0.8f,0.5f); + public Color GizmoTextColor = new Color(1.0f,0.8f,1.0f,0.9f); //new Color(0.2f,1.0f,0.5f,1.0f); + protected Color GizmoBoundsColor = new Color(0.0f,0.0f,0.0f,0.6f); //new Color(0.2f,1.0f,0.5f,1.0f); + public bool GizmoShowSenderIP; + [Header("Debug options")] + public bool sendLoopbackOSCReplies; + public bool debugPrintReceivedMessages; + + + + public Dictionary knownPaths = new Dictionary(); + Dictionary minMaxByPath = new Dictionary(); + private List messages = new List(); + Dictionary lastSent = new Dictionary(); + + public void Start() { + LyumaAv3Emulator[] emulators = FindObjectsOfType(); + if (emulators == null || emulators.Length == 0) { + return; + } + emulator = emulators[0]; + if (emulator != null && emulator.runtimes != null) { + if (emulator.runtimes.Count > 0) { + avatarDescriptor = emulator.runtimes[0].GetComponent(); + } + } + } + public void Update() { + LyumaAv3Runtime runtime = avatarDescriptor != null ?avatarDescriptor.GetComponent() : null; + commandLine = "--osc=" + udpPort + ":" + outgoingUdpIp + ":" + outgoingUdpPort; + if (clearGizmos) { + clearGizmos = false; + knownPaths.Clear(); + minMaxByPath.Clear(); + } + if (openSocket && receiver != null && oldPort != udpPort) { + receiver.StopClient(); + receiver = null; + } + if ((!disableOSC && openSocket) && receiver == null) { + localIp = ""; + localPort = -1; + oldPort = udpPort; + receiver = new A3ESimpleOSC(); + bool success = false; + try { + var localEndpoint = receiver.OpenClient(udpPort); + localIp = localEndpoint.Address.ToString(); + localPort = localEndpoint.Port; + success = localEndpoint.Port == udpPort || (udpPort == 0 && localEndpoint.Port > 0); + } catch (System.Exception e) { + localIp = e.Message; + Debug.LogException(e); + } + if (!success) { + Debug.LogError("Failed to bind socket to OSC"); + openSocket = false; + } else { + resendAllParameters = true; + } + } + if ((disableOSC||!openSocket) && receiver != null) { + receiver.StopClient(); + receiver = null; + } + if (resendAllParameters) { + resendAllParameters = false; + lastSent.Clear(); + } + messages.Clear(); + if (receiver != null) { + receiver.GetIncomingOSC(messages); + if (sendLoopbackOSCReplies && messages.Count > 0) { + receiver.SetUnconnectedEndpoint(messages[0].sender); + var tt = new A3ESimpleOSC.TimeTag(); + for (int i = 0; i < messages.Count; i++) { + if (messages[i].bundleId != 0) { + tt = messages[i].time; + break; + } + } + receiver.SendOSCBundle(messages, tt); + } + foreach (var msg in messages) { + numberOfOSCMessages += 1; + if (debugPrintReceivedMessages) { + Debug.Log("Got OSC message: " + msg.ToString()); + } + knownPaths[msg.path] = msg; + if (!minMaxByPath.ContainsKey(msg.path)) { + minMaxByPath[msg.path] = new Vector2(0,0); + } + } + if (forwardToAllAvatarsInScene) { + if (emulator != null && emulator.runtimes != null) { + foreach (var instRuntime in emulator.runtimes) { + instRuntime.HandleOSCMessages(messages); + } + } + } else if (runtime != null) { + runtime.HandleOSCMessages(messages); + } + } + if (runtime != null && receiver != null) { + messages.Clear(); + runtime.GetOSCDataInto(messages); + if (messages.Count > 0) { + receiver.SetUnconnectedEndpoint(new System.Net.IPEndPoint(System.Net.IPAddress.Parse(outgoingUdpIp), outgoingUdpPort)); + // receiver.SendOSCBundle(messages, new A3ESimpleOSC.TimeTag { secs=-1, nsecs=-1 }, oscBuffer); + foreach (var message in messages) { + if (lastSent.ContainsKey(message.path) && Enumerable.SequenceEqual(message.arguments, lastSent[message.path].arguments)) { + continue; + } + lastSent[message.path] = message; + if (debugPrintReceivedMessages) { + Debug.Log("Sending " + message + " to " + outgoingUdpIp + ":" + outgoingUdpPort); + } + receiver.SendOSCPacket(message, oscBuffer); + } + } + } + if (!enabled && receiver != null) { + receiver = null; + } + } + public void OnDestroy() { + if (receiver != null) { + receiver.StopClient(); + } + } + + Vector3 ScreenToWorld(float x, float y) { + Camera camera = Camera.current; + Vector3 s = camera.WorldToScreenPoint(transform.position); + return camera.ScreenToWorldPoint(new Vector3(x, camera.pixelHeight - y, s.z)); + } + + Rect ScreenRect(int x, int y, int w, int h) { + Vector3 tl = ScreenToWorld(x, y); + Vector3 br = ScreenToWorld(x + w, y + h); + return new Rect(tl.x, tl.y, br.x - tl.x, br.y - tl.y); + } + void OnDrawGizmos() { + if (alwaysShowOSCGizmos) { + ActuallyDrawGizmos(); + } + } + void RenderBoxType(Rect r, Vector2 v2, Vector2 minMaxHoriz, Vector2 minMaxVert) { + float xrange = Mathf.Max(0.001f, minMaxHoriz.y - minMaxHoriz.x); + float yrange = Mathf.Max(0.001f, minMaxVert.y - minMaxVert.x); + Rect r2 = (Rect)r; + Rect r3 = (Rect)r; + float f = v2.x; + float g = v2.y; + float widOffset = r.width * Mathf.Clamp01((v2.x - minMaxHoriz.x) / xrange); + float heiOffset = r.height * Mathf.Clamp01(1.0f - (v2.y - minMaxVert.x) / yrange); + r.y += heiOffset; + r.height -= heiOffset; + DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor); + r2.width = widOffset; + DrawDebugRectDelegate(r2, GizmoFilledColor, GizmoFilledColor); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n\n\n\n"+minMaxHoriz.x.ToString("F2"), TextAnchor.MiddleLeft); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMaxHoriz.y.ToString("F2")+"\n\n\n\n", TextAnchor.MiddleRight); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMaxVert.x.ToString("F2")+"\n", TextAnchor.LowerCenter); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n"+minMaxVert.y.ToString("F2"), TextAnchor.UpperCenter); + } + void RenderBoxType(Rect r, float f2, Vector2 minMax) { + float xrange = Mathf.Max(0.001f, minMax.y - minMax.x); + float widOffset = r.width * Mathf.Clamp01(1.0f - (f2 - minMax.x) / xrange); + Rect r3 = (Rect)r; + r.width -= widOffset; + DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n\n\n\n"+minMax.x.ToString("F2"), TextAnchor.MiddleLeft); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMax.y.ToString("F2")+"\n\n\n\n", TextAnchor.MiddleRight); + } + void RenderBoxType(Rect r, int i2, Vector2Int minMax) { + float heiOffset = r.height * Mathf.Clamp01(1.0f - (i2 - minMax.x) / Mathf.Max(1.0f, minMax.y - minMax.x)); + Rect r3 = (Rect)r; + r.y += heiOffset; + r.height -= heiOffset; + DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMax.x.ToString()+"\n", TextAnchor.LowerCenter); + DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n"+minMax.y.ToString(), TextAnchor.UpperCenter); + } + void RenderBoxType(Rect r, bool b2) { + if (b2) { + DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor); + } + } + + + HashSet usedPartners = new HashSet(); + Dictionary replacePairs = new Dictionary { + {"Vertical", "Horizontal"}, + {"Z", "X"}, + {"Y", "X"}, + {"z", "x"}, + {"y", "x"}, + }; + void OnDrawGizmosSelected() { + if (!alwaysShowOSCGizmos) { + ActuallyDrawGizmos(); + } + } + void ActuallyDrawGizmos() { + Camera camera = Camera.current; + Matrix4x4 origMatrix = Gizmos.matrix; + Gizmos.matrix = camera.projectionMatrix * camera.transform.localToWorldMatrix; + Rect viewportSize = GetEditorViewportDelegate(); + Rect pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,190); + usedPartners.Clear(); + // float maxy = 0; + int numBoxes = 0; + foreach (var pathPair in knownPaths) { + if (usedPartners.Contains(pathPair.Key)) { + // Already got output + continue; + } + A3ESimpleOSC.OSCMessage msg; + foreach (var replacePair in replacePairs) { + if (pathPair.Key.EndsWith(replacePair.Key)) { + if (pathPair.Value.arguments.Length >= 1 && pathPair.Value.arguments[0].GetType() == typeof(float)) { + string key = pathPair.Key.Substring(0, pathPair.Key.Length - replacePair.Key.Length) + replacePair.Value; + if (knownPaths.TryGetValue(key, out msg) && msg.arguments.Length >= 1 && msg.arguments[0].GetType() == typeof(float)) { + usedPartners.Add(key); + break; + } + } + } + } + numBoxes++; + pos.x += pos.width + 10; + if (pos.x + pos.width > viewportSize.width) { + pos.x = 5 + viewportSize.x; + pos.y += pos.height + 10; + } + } + if (pos.y + pos.height > viewportSize.height) { + pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,pos.height * viewportSize.height / (pos.y + pos.height + 100)); + } else { + pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,190); + } + foreach (var pathPair in knownPaths) { + bool isVec2 = false; + Vector2 vecVal = new Vector2(); + if (usedPartners.Contains(pathPair.Key)) { + // Already got output + continue; + } + Vector2 minmaxVert = minMaxByPath[pathPair.Key]; + Vector2 minmaxHoriz = new Vector2(); + System.Text.StringBuilder str = new System.Text.StringBuilder(); + foreach (var replacePair in replacePairs) { + if (pathPair.Key.EndsWith(replacePair.Key)) { + if (pathPair.Value.arguments.Length >= 1 && pathPair.Value.arguments[0].GetType() == typeof(float)) { + string key = pathPair.Key.Substring(0, pathPair.Key.Length - replacePair.Key.Length) + replacePair.Value; + A3ESimpleOSC.OSCMessage msg; + if (knownPaths.TryGetValue(key, out msg) && msg.arguments.Length >= 1 && msg.arguments[0].GetType() == typeof(float)) { + msg.DebugInto(str, GizmoShowSenderIP); + vecVal.y = (float)pathPair.Value.arguments[0]; + minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, vecVal.y), Mathf.Max(minmaxVert.y, vecVal.y)); + minMaxByPath[pathPair.Key] = minmaxVert; + + vecVal.x = (float)msg.arguments[0]; + minmaxHoriz = minMaxByPath[key]; + minmaxHoriz = new Vector2(Mathf.Min(minmaxHoriz.x, vecVal.x), Mathf.Max(minmaxHoriz.y, vecVal.x)); + minMaxByPath[key] = minmaxHoriz; + isVec2 = true; + break; + } + } + } + } + pathPair.Value.DebugInto(str, GizmoShowSenderIP); + Rect subPos = new Rect(pos); + + Color bgc = (Color)(GizmoBackgroundColor); + if (isVec2) { + RenderBoxType(subPos, vecVal, minmaxHoriz, minmaxVert); + } else if (pathPair.Value.arguments != null && pathPair.Value.arguments.Length >= 1) { + A3ESimpleOSC.OSCMessage msg = pathPair.Value; + switch (msg.arguments[0]) { + case float f: + minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, f), Mathf.Max(minmaxVert.y, f)); + minMaxByPath[pathPair.Key] = minmaxVert; + RenderBoxType(subPos, f, minmaxVert); + break; + case int i: + minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, (float)i), Mathf.Max(minmaxVert.y, (float)i)); + minMaxByPath[pathPair.Key] = minmaxVert; + if (i != 0) { + DrawDebugRectDelegate(subPos, GizmoFilledColor, GizmoFilledColor); + } + RenderBoxType(subPos, i, new Vector2Int(0,255)); // Hardcode bounds. new Vector2Int((int)minmaxVert.x, (int)minmaxVert.y)); + break; + case bool b: + if (b) { + DrawDebugRectDelegate(subPos, GizmoFilledColor, GizmoFilledColor); + } + RenderBoxType(subPos, b ? 1 : 0, new Vector2Int(0,1)); + // RenderBoxType(subPos, b); + break; + } + } + // Color c0 = new Color(OUTLINE_COLOR); + DrawDebugRectDelegate(pos, bgc, GizmoOutlineColor); + DrawDebugTextDelegate(pos, new Color(0.0f,0.0f,1.0f,0.02f), GizmoOutlineColor, GizmoTextColor, str.ToString().Replace(";", "\n").Replace(" @", "\n@" ) + .Replace("(Single)", "(Float)").Replace("/avatar/parameters/", ""), TextAnchor.MiddleCenter); + pos.x += pos.width + 10; + if (pos.x + pos.width > viewportSize.width) { + pos.x = 5 + viewportSize.x; + pos.y += pos.height + 10; + } + // pos = new Vector3(pos.x + 105, pos.y, pos.z); + } + Gizmos.matrix = origMatrix; + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta new file mode 100644 index 00000000..b3b5946e --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 784aa748a7308b94a8f15dcd76531956 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs new file mode 100644 index 00000000..04d73cc2 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs @@ -0,0 +1,2529 @@ +/* Copyright (c) 2020-2022 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.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; +using VRC.SDK3.Dynamics.Contact.Components; +using VRC.SDK3.Dynamics.PhysBone.Components; + +// [RequireComponent(typeof(Animator))] +public class LyumaAv3Runtime : MonoBehaviour +{ + static public Dictionary animLayerToDefaultController = new Dictionary(); + static public Dictionary animLayerToDefaultAvaMask = new Dictionary(); + public delegate void UpdateSelectionFunc(UnityEngine.Object obj); + public static UpdateSelectionFunc updateSelectionDelegate; + public delegate void AddRuntime(Component runtime); + public static AddRuntime addRuntimeDelegate; + public delegate void UpdateSceneLayersFunc(int layers); + public static UpdateSceneLayersFunc updateSceneLayersDelegate; + public delegate void ApplyOnEnableWorkaroundDelegateType(); + public static ApplyOnEnableWorkaroundDelegateType ApplyOnEnableWorkaroundDelegate; + + public LyumaAv3Runtime OriginalSourceClone = null; + + [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("Click if you modified your menu or parameter list")] + public bool RefreshExpressionParams; + [Tooltip("Simulates saving and reloading the avatar")] + public bool KeepSavedParametersOnReset = true; + [HideInInspector] public bool legacyMenuGUI = true; + private bool lastLegacyMenuGUI = true; + [Header("Animator to Debug. Unity is glitchy when not 'Base'.")] + [Tooltip("Selects the playable layer to be visible with parameters in the Animator. If you view any other playable in the Animator window, parameters will say 0 and will not update.")] + public VRCAvatarDescriptor.AnimLayerType DebugDuplicateAnimator; + private char PrevAnimatorToDebug; + [Tooltip("Selects the playable layer to be visible in Unity's Animator window. Does not reset avatar. Unless this is set to Base, will cause 'Invalid Layer Index' logspam; layers will show wrong weight and parameters will all be 0.")] + public VRCAvatarDescriptor.AnimLayerType ViewAnimatorOnlyNoParams; + private char PrevAnimatorToViewLiteParamsShow0; + [HideInInspector] public string SourceObjectPath; + [HideInInspector] public LyumaAv3Runtime AvatarSyncSource; + private float nextUpdateTime = 0.0f; + [Header("OSC (double click OSC Controller for debug and port settings)")] + public bool EnableAvatarOSC = false; + public LyumaAv3Osc OSCController = null; + public A3EOSCConfiguration OSCConfigurationFile = new A3EOSCConfiguration(); + + [Header("Network Clones and Sync")] + public bool CreateNonLocalClone; + [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; + private int CloneCount; + [Range(0.0f, 2.0f)] public float NonLocalSyncInterval = 0.2f; + [Tooltip("Parameters visible in the radial menu will IK sync")] public bool IKSyncRadialMenu = true; + [Header("PlayerLocal and MirrorReflection")] + public bool EnableHeadScaling; + public bool DisableMirrorAndShadowClones; + [HideInInspector] public LyumaAv3Runtime MirrorClone; + [HideInInspector] public LyumaAv3Runtime ShadowClone; + [Tooltip("To view both copies at once")] public bool DebugOffsetMirrorClone = false; + public bool ViewMirrorReflection; + private bool LastViewMirrorReflection; + public bool ViewBothRealAndMirror; + private bool LastViewBothRealAndMirror; + [HideInInspector] public VRCAvatarDescriptor avadesc; + Avatar animatorAvatar; + Animator animator; + private RuntimeAnimatorController origAnimatorController; + public Dictionary allControllers = new Dictionary(); + + private Transform[] allTransforms; + private Transform[] allMirrorTransforms; + private Transform[] allShadowTransforms; + private List playables = new List(); + private List> playableParamterIds = new List>(); + private List> playableParamterFloats = new List>(); + private List> playableParamterInts = new List>(); + private List> playableParamterBools = new List>(); + 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; + + [NonSerialized] public VRCPhysBone[] AvDynamicsPhysBones = new VRCPhysBone[]{}; + [NonSerialized] public VRCContactReceiver[] AvDynamicsContactReceivers = new VRCContactReceiver[]{}; + + public class Av3EmuParameterAccess : VRC.SDKBase.IAnimParameterAccess { + public LyumaAv3Runtime runtime; + public string paramName; + public bool boolVal { + get { + // Debug.Log(paramName + " GETb"); + int idx; + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return runtime.Ints[idx].value != 0; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx))return runtime.Floats[idx].exportedValue != 0.0f; + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value; + return false; + } + set { + // Debug.Log(paramName + " SETb " + value); + int idx; + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = value ? 1 : 0; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { + runtime.Floats[idx].value = value ? 1.0f : 0.0f; + runtime.Floats[idx].exportedValue = runtime.Floats[idx].value; + } + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value; + } + } + public int intVal { + get { + int idx; + // Debug.Log(paramName + " GETi"); + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return runtime.Ints[idx].value; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) return (int)runtime.Floats[idx].exportedValue; + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value ? 1 : 0; + return 0; + } + set { + // Debug.Log(paramName + " SETi " + value); + int idx; + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = value; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { + runtime.Floats[idx].value = (float)value; + runtime.Floats[idx].exportedValue = runtime.Floats[idx].value; + } + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value != 0; + } + } + public float floatVal { + get { + // Debug.Log(paramName + " GETf"); + int idx; + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return (float)runtime.Ints[idx].value; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) return runtime.Floats[idx].exportedValue; + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value ? 1.0f : 0.0f; + return 0.0f; + } + set { + // Debug.Log(paramName + " SETf " + value); + int idx; + if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = (int)value; + if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { + runtime.Floats[idx].value = value; + runtime.Floats[idx].exportedValue = value; + } + if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value != 0.0f; + } + } + } + + public void assignContactParameters(VRCContactReceiver[] behaviours) { + AvDynamicsContactReceivers = behaviours; + foreach (var mb in AvDynamicsContactReceivers) { + var old_value = mb.paramAccess; + if (old_value == null || old_value.GetType() != typeof(Av3EmuParameterAccess)) { + string parameter = mb.parameter; + Av3EmuParameterAccess accessInst = new Av3EmuParameterAccess(); + accessInst.runtime = this; + accessInst.paramName = parameter; + mb.paramAccess = accessInst; + accessInst.floatVal = mb.paramValue; + // Debug.Log("Assigned access " + contactReceiverState.paramAccess.GetValue(mb) + " to param " + parameter + ": was " + old_value); + } + } + } + public void assignPhysBoneParameters(VRCPhysBone[] behaviours) { + AvDynamicsPhysBones = behaviours; + foreach (var mb in AvDynamicsPhysBones) { + var old_value = mb.param_Stretch; + if (old_value == null || old_value.GetType() != typeof(Av3EmuParameterAccess)) { + string parameter = mb.parameter; + Av3EmuParameterAccess accessInst = new Av3EmuParameterAccess(); + accessInst.runtime = this; + accessInst.paramName = parameter + VRCPhysBone.PARAM_ANGLE; + mb.param_Angle = accessInst; + accessInst.floatVal = mb.param_AngleValue; + accessInst = new Av3EmuParameterAccess(); + accessInst.runtime = this; + accessInst.paramName = parameter + VRCPhysBone.PARAM_ISGRABBED; + mb.param_IsGrabbed = accessInst; + accessInst.boolVal = mb.param_IsGrabbedValue; + accessInst = new Av3EmuParameterAccess(); + accessInst.runtime = this; + accessInst.paramName = parameter + VRCPhysBone.PARAM_STRETCH; + mb.param_Stretch = accessInst; + accessInst.floatVal = mb.param_StretchValue; + // Debug.Log("Assigned strech access " + physBoneState.param_Stretch.GetValue(mb) + " to param " + parameter + ": was " + old_value); + } + } + } + + 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 + } + public static HashSet BUILTIN_PARAMETERS = new HashSet { + "Viseme", "GestureLeft", "GestureLeftWeight", "GestureRight", "GestureRightWeight", "VelocityX", "VelocityY", "VelocityZ", "Upright", "AngularY", "Grounded", "Seated", "AFK", "TrackingType", "VRMode", "MuteSelf", "InStation" + }; + public static readonly HashSet MirrorCloneComponentBlacklist = new HashSet { + typeof(Camera), typeof(FlareLayer), typeof(AudioSource), typeof(Rigidbody), typeof(Joint) + }; + public static readonly HashSet ShadowCloneComponentBlacklist = new HashSet { + typeof(Camera), typeof(FlareLayer), typeof(AudioSource), typeof(Light), typeof(ParticleSystemRenderer), typeof(Rigidbody), typeof(Joint) + }; + [Header("Built-in inputs / Viseme")] + public VisemeIndex Viseme; + [Range(0, 15)] public int VisemeIdx; + private int VisemeInt; + [Tooltip("Voice amount from 0.0f to 1.0f for the current viseme")] + [Range(0,1)] public float Voice; + [Header("Built-in inputs / Hand Gestures")] + public GestureIndex GestureLeft; + [Range(0, 9)] public int GestureLeftIdx; + private char GestureLeftIdxInt; + [Range(0, 1)] public float GestureLeftWeight; + private float OldGestureLeftWeight; + public GestureIndex GestureRight; + [Range(0, 9)] public int GestureRightIdx; + private char GestureRightIdxInt; + [Range(0, 1)] public float GestureRightWeight; + private float OldGestureRightWeight; + [Header("Built-in inputs / Locomotion")] + public Vector3 Velocity; + [Range(-400, 400)] public float AngularY; + [Range(0, 1)] public float Upright; + public bool Grounded; + public bool Jump; + public float JumpPower = 5; + public float RunSpeed = 0.0f; + private bool WasJump; + private Vector3 JumpingHeight; + private Vector3 JumpingVelocity; + private bool PrevSeated, PrevTPoseCalibration, PrevIKPoseCalibration; + public bool Seated; + public bool AFK; + public bool TPoseCalibration; + public bool IKPoseCalibration; + [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; + private bool MuteTogglerOn; + public bool InStation; + [HideInInspector] public int AvatarVersion = 3; + + [Header("Output State (Read-only)")] + public bool IsLocal; + [HideInInspector] public bool IsMirrorClone; + [HideInInspector] public bool IsShadowClone; + public bool LocomotionIsDisabled; + + [Serializable] + public struct IKTrackingOutput { + public Vector3 HeadRelativeViewPosition; + public Vector3 ViewPosition; + public float AvatarScaleFactorGuess; + 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; + } + public IKTrackingOutput IKTrackingOutputData; + + [Serializable] + public class FloatParam + { + [HideInInspector] public string stageName; + public string name; + [HideInInspector] public bool synced; + [Range(-1, 1)] public float expressionValue; + [HideInInspector] public float lastExpressionValue_; + [Range(-1, 1)] public float value; + [HideInInspector] private float exportedValue_; + public float exportedValue { + get { + return exportedValue_; + } set { + this.exportedValue_ = value; + this.value = value; + this.lastExpressionValue_ = value; + this.expressionValue = value; + } + } + [HideInInspector] public float lastValue; + } + [Header("User-generated inputs")] + public List Floats = new List(); + public Dictionary FloatToIndex = new Dictionary(); + + [Serializable] + public class IntParam + { + [HideInInspector] public string stageName; + public string name; + [HideInInspector] public bool synced; + public int value; + [HideInInspector] public int lastValue; + } + public List Ints = new List(); + public Dictionary IntToIndex = new Dictionary(); + + [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 Bools = new List(); + public Dictionary BoolToIndex = new Dictionary(); + + public Dictionary StageParamterToBuiltin = new Dictionary(); + + [HideInInspector] public LyumaAv3Emulator emulator; + + static public Dictionary animatorToTopLevelRuntime = new Dictionary(); + private HashSet attachedAnimators; + private HashSet duplicateParameterAdds = new HashSet(); + + 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) { + IKTrackingOutputData.ViewPosition = animator.transform.InverseTransformPoint(head.TransformPoint(IKTrackingOutputData.HeadRelativeViewPosition)); + } + } else { + IKTrackingOutputData.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 HashSet issuedWarningAnimators = new HashSet(); + 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(); + 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); + runtime.Awake(); + } + return true; + } + + if (!issuedWarningAnimators.Contains(innerAnimator)) + { + issuedWarningAnimators.Add(innerAnimator); + Debug.LogWarning("[" + component + "]: outermost Animator is not known: " + innerAnimator + ". If you changed something, consider resetting avatar", innerAnimator); + } + + return false; + } + + float getAdjustedParameterAsFloat(string paramName, bool convertRange=false, float srcMin=0.0f, float srcMax=0.0f, float dstMin=0.0f, float dstMax=0.0f) { + float newValue = 0; + int idx; + if (FloatToIndex.TryGetValue(paramName, out idx)) { + newValue = Floats[idx].exportedValue; + } else if (IntToIndex.TryGetValue(paramName, out idx)) { + newValue = (float)Ints[idx].value; + } else if (BoolToIndex.TryGetValue(paramName, out idx)) { + newValue = Bools[idx].value ? 1.0f : 0.0f; + } + if (convertRange) { + if (dstMax != dstMin) { + newValue = Mathf.Lerp(dstMin, dstMax, Mathf.Clamp01(Mathf.InverseLerp(srcMin, srcMax, newValue))); + } else { + newValue = dstMin; + } + } + return newValue; + } + + static LyumaAv3Runtime() { + VRCAvatarParameterDriver.OnApplySettings += (behaviour, animator) => + { + LyumaAv3Runtime runtime; + if (!getTopLevelRuntime("VRCAvatarParameterDriver", animator, out runtime)) { + return; + } + if (runtime.IsMirrorClone || runtime.IsShadowClone) { + 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 newParameterAdds = new HashSet(); + HashSet deleteParameterAdds = new HashSet(); + foreach (var parameter in behaviour.parameters) { + if (runtime.DebugDuplicateAnimator != VRCAvatarDescriptor.AnimLayerType.Base && !runtime.IsMirrorClone && !runtime.IsShadowClone && (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 + 1); + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: + runtime.Ints[idx].value = (int)runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); + break; + } + } + if (runtime.FloatToIndex.TryGetValue(actualName, out idx)) { + switch (parameter.type) { + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: + runtime.Floats[idx].exportedValue = parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: + runtime.Floats[idx].exportedValue += parameter.value; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + runtime.Floats[idx].exportedValue = UnityEngine.Random.Range(parameter.valueMin, parameter.valueMax); + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: + runtime.Floats[idx].exportedValue = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); + break; + } + runtime.Floats[idx].value = runtime.Floats[idx].exportedValue; + } + 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; + 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); + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + // random is *not* idempotent. + newValue = UnityEngine.Random.Range(0.0f, 1.0f) < parameter.chance; + break; + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: + newValue = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax) != 0.0f; + break; + default: + continue; + } + 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; + } + } + 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 (runtime.IsMirrorClone && runtime.IsShadowClone) { + 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 (runtime.IsMirrorClone) { + 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 (runtime.IsMirrorClone && runtime.IsShadowClone) { + 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 (runtime.IsMirrorClone && runtime.IsShadowClone) { + 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 (runtime.IsMirrorClone && runtime.IsShadowClone) { + 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.IKTrackingOutputData.trackingMouthAndJaw = behaviour.trackingMouth; + } + if (behaviour.trackingHead != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingHead = behaviour.trackingHead; + } + if (behaviour.trackingRightFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingRightFingers = behaviour.trackingRightFingers; + } + if (behaviour.trackingEyes != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingEyesAndEyelids = behaviour.trackingEyes; + } + if (behaviour.trackingLeftFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingLeftFingers = behaviour.trackingLeftFingers; + } + if (behaviour.trackingLeftFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingLeftFoot = behaviour.trackingLeftFoot; + } + if (behaviour.trackingHip != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingHip = behaviour.trackingHip; + } + if (behaviour.trackingRightHand != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingRightHand = behaviour.trackingRightHand; + } + if (behaviour.trackingLeftHand != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingLeftHand = behaviour.trackingLeftHand; + } + if (behaviour.trackingRightFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) + { + runtime.IKTrackingOutputData.trackingRightFoot = behaviour.trackingRightFoot; + } + }; + }; + } + + void OnDestroy () { + if (this.playableGraph.IsValid()) { + this.playableGraph.Destroy(); + } + if (attachedAnimators != null) { + foreach (var anim in attachedAnimators) { + LyumaAv3Runtime runtime; + if (animatorToTopLevelRuntime.TryGetValue(anim, out runtime) && runtime == this) + { + animatorToTopLevelRuntime.Remove(anim); + } + } + } + if (animator != null) { + if (animator.playableGraph.IsValid()) + { + animator.playableGraph.Destroy(); + } + animator.runtimeAnimatorController = origAnimatorController; + } + } + + void Awake() + { + if (AvatarSyncSource != null && OriginalSourceClone == null) { + Debug.Log("Awake returning early for " + gameObject.name, this); + return; + } + if (attachedAnimators != null) { + Debug.Log("Deduplicating Awake() call if we already got awoken by our children.", this); + return; + } + // Debug.Log("AWOKEN " + gameObject.name, this); + attachedAnimators = new HashSet(); + if (AvatarSyncSource == null) { + var oml = GetComponent(); + if (oml != null && oml.startTransform != null) { + this.emulator = oml.startTransform.GetComponent(); + GameObject.DestroyImmediate(oml); + } + Transform transform = this.transform; + SourceObjectPath = ""; + while (transform != null) { + SourceObjectPath = "/" + transform.name + SourceObjectPath; + transform = transform.parent; + } + AvatarSyncSource = this; + } else { + AvatarSyncSource = GameObject.Find(SourceObjectPath).GetComponent(); + } + + if (this.emulator != null) { + DebugDuplicateAnimator = this.emulator.DefaultAnimatorToDebug; + ViewAnimatorOnlyNoParams = this.emulator.DefaultAnimatorToDebug; + } + + animator = this.gameObject.GetOrAddComponent(); + if (animatorAvatar != null && animator.avatar == null) { + animator.avatar = animatorAvatar; + } else { + 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(); + 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]); + } + } + } + bool shouldClone = false; + if (OriginalSourceClone == null) { + OriginalSourceClone = this; + shouldClone = true; + } + if (shouldClone && GetComponent() == null) { + GameObject cloned = GameObject.Instantiate(gameObject); + cloned.hideFlags = HideFlags.HideAndDontSave; + cloned.SetActive(false); + OriginalSourceClone = cloned.GetComponent(); + Debug.Log("Spawned a hidden source clone " + OriginalSourceClone, OriginalSourceClone); + OriginalSourceClone.OriginalSourceClone = OriginalSourceClone; + } + foreach (var smr in gameObject.GetComponentsInChildren(true)) { + smr.updateWhenOffscreen = (AvatarSyncSource == this || IsMirrorClone || IsShadowClone); + } + int desiredLayer = 9; + if (AvatarSyncSource == this) { + desiredLayer = 10; + } + if (IsMirrorClone) { + desiredLayer = 18; + } + if (IsShadowClone) { + desiredLayer = 9; // the Shadowclone is always on playerLocal and never on UI Menu + } + if (gameObject.layer != 12 || desiredLayer == 18) { + gameObject.layer = desiredLayer; + } + allTransforms = gameObject.GetComponentsInChildren(true); + foreach (Transform t in allTransforms) { + if (t.gameObject.layer != 12 || desiredLayer == 18) { + t.gameObject.layer = desiredLayer; + } + } + + InitializeAnimator(); + if (addRuntimeDelegate != null) { + addRuntimeDelegate(this); + } + if (AvatarSyncSource == this) { + CreateAv3MenuComponent(); + } + if (this.AvatarSyncSource != this || IsMirrorClone || IsShadowClone) { + PrevAnimatorToViewLiteParamsShow0 = (char)(int)ViewAnimatorOnlyNoParams; + } + if (!IsMirrorClone && !IsShadowClone && AvatarSyncSource == this) { + var pipelineManager = avadesc.GetComponent(); + string avatarid = pipelineManager != null ? pipelineManager.blueprintId : null; + OSCConfigurationFile.EnsureOSCJSONConfig(avadesc.expressionParameters, avatarid, this.gameObject.name); + } + } + + public void CreateMirrorClone() { + if (AvatarSyncSource == this && GetComponent() == null) { + OriginalSourceClone.IsMirrorClone = true; + MirrorClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent(); + MirrorClone.GetComponent().avatar = null; + OriginalSourceClone.IsMirrorClone = false; + GameObject o = MirrorClone.gameObject; + o.name = gameObject.name + " (MirrorReflection)"; + o.SetActive(true); + allMirrorTransforms = MirrorClone.gameObject.GetComponentsInChildren(true); + foreach (Component component in MirrorClone.gameObject.GetComponentsInChildren(true)) { + if (MirrorCloneComponentBlacklist.Contains(component.GetType()) || component.GetType().ToString().Contains("DynamicBone") + || component.GetType().ToString().Contains("VRCContact") || component.GetType().ToString().Contains("VRCPhysBone")) { + UnityEngine.Object.Destroy(component); + } + } + } + } + + public void CreateShadowClone() { + if (AvatarSyncSource == this && GetComponent() == null) { + OriginalSourceClone.IsShadowClone = true; + ShadowClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent(); + ShadowClone.GetComponent().avatar = null; + OriginalSourceClone.IsShadowClone = false; + GameObject o = ShadowClone.gameObject; + o.name = gameObject.name + " (ShadowClone)"; + o.SetActive(true); + allShadowTransforms = ShadowClone.gameObject.GetComponentsInChildren(true); + foreach (Component component in ShadowClone.gameObject.GetComponentsInChildren(true)) { + if (ShadowCloneComponentBlacklist.Contains(component.GetType()) || component.GetType().ToString().Contains("DynamicBone") + || component.GetType().ToString().Contains("VRCContact") || component.GetType().ToString().Contains("VRCPhysBone")) { + UnityEngine.Object.Destroy(component); + continue; + } + if (component.GetType() == typeof(SkinnedMeshRenderer) || component.GetType() == typeof(MeshRenderer)) { + Renderer renderer = component as Renderer; + renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly; // ShadowCastingMode.TwoSided isn't accounted for and does not work locally + } + } + foreach (Renderer renderer in gameObject.GetComponentsInChildren(true)) { + renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; // ShadowCastingMode.TwoSided isn't accounted for and does not work locally + } + } + } + + private void InitializeAnimator() + { + ResetAvatar = false; + PrevAnimatorToDebug = (char)(int)DebugDuplicateAnimator; + ViewAnimatorOnlyNoParams = DebugDuplicateAnimator; + + animator = this.gameObject.GetOrAddComponent(); + animator.avatar = animatorAvatar; + animator.applyRootMotion = false; + animator.updateMode = AnimatorUpdateMode.Normal; + animator.cullingMode = (this == AvatarSyncSource || IsMirrorClone || IsShadowClone) ? AnimatorCullingMode.AlwaysAnimate : AnimatorCullingMode.CullCompletely; + animator.runtimeAnimatorController = null; + + avadesc = this.gameObject.GetComponent(); + IKTrackingOutputData.ViewPosition = avadesc.ViewPosition; + IKTrackingOutputData.AvatarScaleFactorGuess = IKTrackingOutputData.ViewPosition.magnitude / BASE_HEIGHT; // mostly guessing... + IKTrackingOutputData.HeadRelativeViewPosition = IKTrackingOutputData.ViewPosition; + if (animator.avatar != null) + { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + if (head != null) { + IKTrackingOutputData.HeadRelativeViewPosition = head.InverseTransformPoint(animator.transform.TransformPoint(IKTrackingOutputData.ViewPosition)); + } + } + expressionsMenu = avadesc.expressionsMenu; + stageParameters = avadesc.expressionParameters; + if (origAnimatorController != null) { + origAnimatorController = animator.runtimeAnimatorController; + } + + VRCAvatarDescriptor.CustomAnimLayer[] baselayers = avadesc.baseAnimationLayers; + VRCAvatarDescriptor.CustomAnimLayer[] speciallayers = avadesc.specialAnimationLayers; + List allLayers = new List(); + // 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 (DebugDuplicateAnimator != VRCAvatarDescriptor.AnimLayerType.Base && !IsMirrorClone && !IsShadowClone) { + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { + if (DebugDuplicateAnimator == cal.type) { + i++; + allLayers.Add(cal); + break; + } + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + if (DebugDuplicateAnimator == 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 (DebugDuplicateAnimator != cal.type) { + i++; + allLayers.Add(cal); + } + } + foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { + if (DebugDuplicateAnimator != cal.type) { + i++; + allLayers.Add(cal); + } + } + } + int dupeOffset = i; + if (!IsMirrorClone && !IsShadowClone) { + 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 (IsMirrorClone || IsShadowClone) { + if (cal.type == VRCAvatarDescriptor.AnimLayerType.FX) { + i++; + allLayers.Add(cal); + } + } else 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(true); + foreach (Animator anim in animators) + { + attachedAnimators.Add(anim); + animatorToTopLevelRuntime.Add(anim, this); + } + + Dictionary stageNameToValue = EarlyRefreshExpressionParameters(); + if (animator.playableGraph.IsValid()) + { + animator.playableGraph.Destroy(); + } + // 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); + 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) { + Debug.Log(vrcAnimLayer.type + " controller is null: continue."); + // i was incremented, but one of the playableMixer inputs is left unconnected. + continue; + } + allControllers[vrcAnimLayer.type] = ac; + AnimatorControllerPlayable humanAnimatorPlayable = AnimatorControllerPlayable.Create(playableGraph, ac); + PlayableBlendingState pbs = new PlayableBlendingState(); + for (int j = 0; j < humanAnimatorPlayable.GetLayerCount(); j++) + { + 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); + } + + // Keep weight 1.0 if (i < dupeOffset). + // Layers have incorrect AAP values if playable weight is 0.0... + // and the duplicate layers will be overridden later anyway by Base. + } + + LateRefreshExpressionParameters(stageNameToValue); + + // Plays the Graph. + playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + Debug.Log(this.name + " : " + GetType() + " awoken and ready to Play.", this); + playableGraph.Play(); + } + + Dictionary EarlyRefreshExpressionParameters() { + Dictionary stageNameToValue = new Dictionary(); + if (IsLocal) { + foreach (var val in Ints) { + stageNameToValue[val.stageName] = val.value; + } + foreach (var val in Floats) { + stageNameToValue[val.stageName] = val.exportedValue; + } + 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(); + return stageNameToValue; + } + void LateRefreshExpressionParameters(Dictionary stageNameToValue) { + HashSet usedparams = new HashSet(BUILTIN_PARAMETERS); + int 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 = 0.0f; + if (AvatarSyncSource == this) { + 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.exportedValue = 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++; + } + } else { + IntParam param = new IntParam(); + param.stageName = "VRCEmote"; + param.synced = true; + param.name = "VRCEmote"; + Ints.Add(param); + usedparams.Add("VRCEmote"); + FloatParam fparam = new FloatParam(); + fparam.stageName = "VRCFaceBlendH"; + fparam.synced = true; + fparam.name = "VRCFaceBlendH"; + Floats.Add(fparam); + usedparams.Add("VRCFaceBlendH"); + fparam = new FloatParam(); + fparam.stageName = "VRCFaceBlendV"; + fparam.synced = true; + fparam.name = "VRCFaceBlendV"; + Floats.Add(fparam); + usedparams.Add("VRCFaceBlendV"); + } + + //playableParamterIds + int whichcontroller = 0; + playableParamterIds.Clear(); + foreach (AnimatorControllerPlayable playable in playables) { + Dictionary parameterIndices = new Dictionary(); + playableParamterInts.Add(new Dictionary()); + playableParamterFloats.Add(new Dictionary()); + playableParamterBools.Add(new Dictionary()); + // 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.exportedValue = 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++; + } + } + + void CreateAv3MenuComponent() { + System.Type gestureManagerMenu = System.Type.GetType("GestureManagerAv3Menu"); + if (gestureManagerMenu != null) { + foreach (var comp in avadesc.gameObject.GetComponents(gestureManagerMenu)) { + UnityEngine.Object.Destroy(comp); + } + } + foreach (var comp in avadesc.gameObject.GetComponents()) { + UnityEngine.Object.Destroy(comp); + } + LyumaAv3Menu mainMenu; + if (gestureManagerMenu != null) { + mainMenu = (LyumaAv3Menu)avadesc.gameObject.AddComponent(gestureManagerMenu); + mainMenu.useLegacyMenu = legacyMenuGUI; + } else { + mainMenu = avadesc.gameObject.AddComponent(); + } + mainMenu.Runtime = this; + mainMenu.RootMenu = avadesc.expressionsMenu; + } + + + private bool isResetting; + private bool isResettingHold; + private bool isResettingSel; + void LateUpdate() { + if (ResetAndHold || (emulator != null && (!emulator.enabled || !emulator.gameObject.activeInHierarchy))) { + return; + } + if (IsMirrorClone || IsShadowClone) { + // Experimental. Attempt to reproduce the 1-frame desync in some cases between normal and mirror copy. + NormalUpdate(); + } + if(animator != null && this == AvatarSyncSource && !IsMirrorClone && !IsShadowClone) { + if (MirrorClone != null) { + MirrorClone.gameObject.SetActive(true); + MirrorClone.transform.localRotation = transform.localRotation; + MirrorClone.transform.localScale = transform.localScale; + MirrorClone.transform.position = transform.position + (DebugOffsetMirrorClone ? new Vector3(0.0f, 1.3f * avadesc.ViewPosition.y, 0.0f) : Vector3.zero); + } + if (ShadowClone != null) { + ShadowClone.gameObject.SetActive(true); + ShadowClone.transform.localRotation = transform.localRotation; + ShadowClone.transform.localScale = transform.localScale; + ShadowClone.transform.position = transform.position; + } + foreach (Transform[] allXTransforms in new Transform[][]{allMirrorTransforms, allShadowTransforms}) { + if (allXTransforms != null) { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + for(int i = 0; i < allTransforms.Length && i < allXTransforms.Length; i++) { + if (allXTransforms[i] == null || allTransforms[i] == this.transform) { + continue; + } + MeshRenderer mr = allTransforms[i].GetComponent(); + MeshRenderer xmr = allXTransforms[i].GetComponent(); + if (mr != null && xmr != null) { + for (int mri = 0; mri < mr.sharedMaterials.Length && mri < xmr.sharedMaterials.Length; mri++) { + xmr.sharedMaterials[mri] = mr.sharedMaterials[mri]; + } + } + allXTransforms[i].localPosition = allTransforms[i].localPosition; + allXTransforms[i].localRotation = allTransforms[i].localRotation; + if(allTransforms[i] == head && EnableHeadScaling) { + allXTransforms[i].localScale = new Vector3(1.0f, 1.0f, 1.0f); + } else { + allXTransforms[i].localScale = allTransforms[i].localScale; + } + bool theirs = allTransforms[i].gameObject.activeSelf; + if (allXTransforms[i].gameObject.activeSelf != theirs) { + allXTransforms[i].gameObject.SetActive(theirs); + } + } + } + } + } + } + + void FixedUpdate() { + if (Jump && !WasJump && Grounded) { + JumpingVelocity = new Vector3(0.0f, JumpPower, 0.0f); + JumpingHeight += JumpingVelocity; + Grounded = false; + } + WasJump = Jump; + if (JumpingHeight != Vector3.zero) { + JumpingHeight += JumpingVelocity; + JumpingVelocity += Physics.gravity * Time.fixedDeltaTime; + if (JumpingHeight.y <= 0.0f) { + JumpingHeight = Vector3.zero; + JumpingVelocity = Vector3.zero; + Grounded = true; + Jump = false; + WasJump = false; + } + Velocity.y = JumpingVelocity.y; + + } + } + + private bool broadcastStartNextFrame; + void OnEnable() { + if (emulator != null && emulator.WorkaroundPlayModeScriptCompile) { + ApplyOnEnableWorkaroundDelegate(); + } + if (attachedAnimators == null && AvatarSyncSource != null) { + broadcastStartNextFrame = true; + } + } + + void Update() { + if (broadcastStartNextFrame) { + Debug.Log("BROADCASTING START!"); + broadcastStartNextFrame = false; + BroadcastMessage("Start"); + } + if (emulator != null && (!emulator.enabled || !emulator.gameObject.activeInHierarchy)) { + return; + } + if (!IsMirrorClone && !IsShadowClone) { + NormalUpdate(); + } + } + + // Update is called once per frame + void NormalUpdate() + { + if (OSCConfigurationFile.OSCAvatarID == null) { + OSCConfigurationFile.OSCAvatarID = A3EOSCConfiguration.AVTR_EMULATOR_PREFIX + "Default"; + } + if ((OSCConfigurationFile.UseRealPipelineIdJSONFile && OSCConfigurationFile.OSCAvatarID.StartsWith(A3EOSCConfiguration.AVTR_EMULATOR_PREFIX)) || + (!OSCConfigurationFile.UseRealPipelineIdJSONFile && !OSCConfigurationFile.OSCAvatarID.StartsWith(A3EOSCConfiguration.AVTR_EMULATOR_PREFIX))) { + var pipelineManager = avadesc.GetComponent(); + string avatarid = pipelineManager != null ? pipelineManager.blueprintId : null; + OSCConfigurationFile.EnsureOSCJSONConfig(avadesc.expressionParameters, avatarid, this.gameObject.name); + } + if (OSCConfigurationFile.SaveOSCConfig) { + OSCConfigurationFile.SaveOSCConfig = false; + A3EOSCConfiguration.WriteJSON(OSCConfigurationFile.OSCFilePath, OSCConfigurationFile.OSCJsonConfig); + } + if (OSCConfigurationFile.LoadOSCConfig) { + OSCConfigurationFile.LoadOSCConfig = false; + OSCConfigurationFile.OSCJsonConfig = A3EOSCConfiguration.ReadJSON(OSCConfigurationFile.OSCFilePath); + } + if (OSCConfigurationFile.GenerateOSCConfig) { + OSCConfigurationFile.GenerateOSCConfig = false; + OSCConfigurationFile.OSCJsonConfig = A3EOSCConfiguration.GenerateOuterJSON(avadesc.expressionParameters, OSCConfigurationFile.OSCAvatarID, this.gameObject.name); + } + if (lastLegacyMenuGUI != legacyMenuGUI && AvatarSyncSource == this) { + lastLegacyMenuGUI = legacyMenuGUI; + foreach (var av3MenuComponent in GetComponents()) { + av3MenuComponent.useLegacyMenu = legacyMenuGUI; + } + } + if (isResettingSel) { + isResettingSel = false; + if (updateSelectionDelegate != null && AvatarSyncSource == this) { + updateSelectionDelegate(this.gameObject); + PrevAnimatorToViewLiteParamsShow0 = (char)126; + } + } + if (isResettingHold && (!ResetAvatar || !ResetAndHold)) { + ResetAndHold = ResetAvatar = false; + isResettingSel = true; + if (updateSelectionDelegate != null && AvatarSyncSource == this) { + updateSelectionDelegate(this.emulator != null ? this.emulator.gameObject : null); + PrevAnimatorToViewLiteParamsShow0 = (char)126; + } + } + if (ResetAvatar && ResetAndHold) { + return; + } + if (ResetAndHold && !ResetAvatar && !isResetting) { + ResetAvatar = true; + isResettingHold = true; + } + if (isResetting && !ResetAndHold) { + if (attachedAnimators == null) { + if (AvatarSyncSource == this) { + AvatarSyncSource = null; + } + Awake(); + isResetting = false; + isResettingHold = false; + return; + } else { + InitializeAnimator(); + } + isResetting = false; + isResettingHold = false; + } + if (PrevAnimatorToDebug != (char)(int)DebugDuplicateAnimator || ResetAvatar || attachedAnimators == null) { + 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 && AvatarSyncSource == this) { + updateSelectionDelegate(this.emulator != null ? this.emulator.gameObject : null); + } + isResetting = true; + isResettingSel = true; + return; + } + if (PrevAnimatorToViewLiteParamsShow0 == (char)127) { + updateSelectionDelegate(this); + ViewAnimatorOnlyNoParams = (VRCAvatarDescriptor.AnimLayerType)(int)126; + PrevAnimatorToViewLiteParamsShow0 = (char)(int)ViewAnimatorOnlyNoParams; + } + if ((char)(int)ViewAnimatorOnlyNoParams != PrevAnimatorToViewLiteParamsShow0) { + PrevAnimatorToViewLiteParamsShow0 = (char)127; + RuntimeAnimatorController rac = null; + allControllers.TryGetValue(ViewAnimatorOnlyNoParams, out rac); + updateSelectionDelegate(rac == null ? (UnityEngine.Object)this.emulator : (UnityEngine.Object)rac); + } + if (RefreshExpressionParams) { + RefreshExpressionParams = false; + Dictionary stageNameToValue = EarlyRefreshExpressionParameters(); + LateRefreshExpressionParameters(stageNameToValue); + } + if(this == AvatarSyncSource && !IsMirrorClone && !IsShadowClone) { + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + if (head != null) { + head.localScale = EnableHeadScaling ? new Vector3(0.0001f, 0.0001f, 0.0001f) : new Vector3(1.0f, 1.0f, 1.0f); // head bone is set to 0.0001 locally (not multiplied + } + } + if (DisableMirrorAndShadowClones && (MirrorClone != null || ShadowClone != null)) { + allMirrorTransforms = null; + allShadowTransforms = null; + GameObject.Destroy(MirrorClone.gameObject); + MirrorClone = null; + GameObject.Destroy(ShadowClone.gameObject); + ShadowClone = null; + } + if (!DisableMirrorAndShadowClones && MirrorClone == null && ShadowClone == null) { + CreateMirrorClone(); + CreateShadowClone(); + } + if (emulator != null) { + if (LastViewMirrorReflection != ViewMirrorReflection) { + emulator.ViewMirrorReflection = ViewMirrorReflection; + } else { + ViewMirrorReflection = emulator.ViewMirrorReflection; + } + LastViewMirrorReflection = ViewMirrorReflection; + if (LastViewBothRealAndMirror != ViewBothRealAndMirror) { + emulator.ViewBothRealAndMirror = ViewBothRealAndMirror; + } else { + ViewBothRealAndMirror = emulator.ViewBothRealAndMirror; + } + LastViewBothRealAndMirror = ViewBothRealAndMirror; + var osc = emulator.GetComponent(); + if (OSCController != null && EnableAvatarOSC && (!osc.openSocket || osc.avatarDescriptor != avadesc)) { + EnableAvatarOSC = false; + OSCController = null; + } + if (OSCController != null && !EnableAvatarOSC) { + osc.openSocket = false; + OSCController = null; + } + if (OSCController == null && EnableAvatarOSC) { + osc = emulator.gameObject.GetOrAddComponent(); + osc.openSocket = true; + osc.avatarDescriptor = avadesc; + osc.enabled = true; + OSCController = osc; + // updateSelectionDelegate(osc.gameObject); + } + } + + if (CreateNonLocalClone) { + CreateNonLocalClone = false; + GameObject go = GameObject.Instantiate(OriginalSourceClone.gameObject); + go.hideFlags = 0; + 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); + go.SetActive(true); + } + if (IsMirrorClone || IsShadowClone) { + NonLocalSyncInterval = 0.0f; + } else { + NonLocalSyncInterval = AvatarSyncSource.NonLocalSyncInterval; + } + if (nextUpdateTime == 0.0f) { + nextUpdateTime = Time.time + NonLocalSyncInterval; + } + bool ShouldSyncThisFrame = (AvatarSyncSource != this && (Time.time >= nextUpdateTime || NonLocalSyncInterval <= 0.0f)); + if (AvatarSyncSource != this) { + IKSyncRadialMenu = AvatarSyncSource.IKSyncRadialMenu; + LyumaAv3Menu[] menus = AvatarSyncSource.GetComponents(); + for (int i = 0; i < Ints.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Ints[i].stageName)) { + // Simulate IK sync of open gesture parameter. + if (ShouldSyncThisFrame || (IKSyncRadialMenu && menus.Length >= 1 && menus[0].IsControlIKSynced(Ints[i].name)) + || (IKSyncRadialMenu && menus.Length >= 2 && menus[1].IsControlIKSynced(Ints[i].name))) { + Ints[i].value = ClampByte(AvatarSyncSource.Ints[i].value); + } + } + } + for (int i = 0; i < Floats.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { + // Simulate IK sync of open gesture parameter. + if (ShouldSyncThisFrame || (IKSyncRadialMenu && menus.Length >= 1 && menus[0].IsControlIKSynced(Floats[i].name)) + || (IKSyncRadialMenu && menus.Length >= 2 && menus[1].IsControlIKSynced(Floats[i].name))) { + Floats[i].exportedValue = ClampAndQuantizeFloat(AvatarSyncSource.Floats[i].exportedValue); + Floats[i].value = Floats[i].exportedValue; + } + } + } + for (int i = 0; i < Bools.Count; i++) { + if (StageParamterToBuiltin.ContainsKey(Bools[i].stageName)) { + if (ShouldSyncThisFrame) { + Bools[i].value = AvatarSyncSource.Bools[i].value; + } + } + } + if (ShouldSyncThisFrame) { + nextUpdateTime = Time.time + NonLocalSyncInterval; + } + } + if (AvatarSyncSource != this) { + // Simulate more continuous "IK sync" of these parameters. + 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; + Grounded = AvatarSyncSource.Grounded; + Seated = AvatarSyncSource.Seated; + AFK = AvatarSyncSource.AFK; + TrackingType = AvatarSyncSource.TrackingType; + TrackingTypeIdx = AvatarSyncSource.TrackingTypeIdx; + TrackingTypeIdxInt = AvatarSyncSource.TrackingTypeIdxInt; + VRMode = AvatarSyncSource.VRMode; + MuteSelf = AvatarSyncSource.MuteSelf; + InStation = AvatarSyncSource.InStation; + } + for (int i = 0; i < Floats.Count; i++) { + if (Floats[i].expressionValue != Floats[i].lastExpressionValue_) { + Floats[i].exportedValue = Floats[i].expressionValue; + Floats[i].lastExpressionValue_ = Floats[i].expressionValue; + } + if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { + if (locally8bitQuantizedFloats) { + Floats[i].exportedValue = ClampAndQuantizeFloat(Floats[i].exportedValue); + } else { + Floats[i].exportedValue = ClampFloatOnly(Floats[i].exportedValue); + } + Floats[i].value = Floats[i].exportedValue; + } + } + 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] != null) + { + playableBlendingStates[sittingIndex].StartBlend(playableMixer.GetInputWeight(sittingIndex + 1), Seated ? 1f : 0f, 0.25f); + PrevSeated = Seated; + } + if (TPoseCalibration != PrevTPoseCalibration && tposeIndex >= 0 && playableBlendingStates[tposeIndex] != null) { + playableBlendingStates[tposeIndex].StartBlend(playableMixer.GetInputWeight(tposeIndex + 1), TPoseCalibration ? 1f : 0f, 0.0f); + PrevTPoseCalibration = TPoseCalibration; + } + if (IKPoseCalibration != PrevIKPoseCalibration && ikposeIndex >= 0 && playableBlendingStates[ikposeIndex] != null) { + 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 (GestureLeftWeight != OldGestureLeftWeight) { + OldGestureLeftWeight = GestureLeftWeight; + if (GestureLeftWeight < 0.01f) { + GestureLeftIdx = 0; + } + if (GestureLeftWeight > 0.01f && (GestureLeftIdx == 0 || GestureLeftWeight < 0.99f)) { + GestureLeftIdx = 1; + } + } + if (GestureRightWeight != OldGestureRightWeight) { + OldGestureRightWeight = GestureRightWeight; + if (GestureRightWeight < 0.01f) { + GestureRightIdx = 0; + } + if (GestureRightWeight > 0.01f && (GestureRightIdx == 0 || GestureRightWeight < 0.99f)) { + GestureRightIdx = 1; + } + } + 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 (GestureLeft == GestureIndex.Neutral) { + GestureLeftWeight = 0; + } else if (GestureLeft != GestureIndex.Fist) { + GestureLeftWeight = 1; + } + if (GestureRight == GestureIndex.Neutral) { + GestureRightWeight = 0; + } else if (GestureRight != GestureIndex.Fist) { + GestureRightWeight = 1; + } + 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()) { + whichcontroller++; + continue; + } + // Debug.Log("Index " + whichcontroller + " len " + playables.Count); + Dictionary 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()) { + whichcontroller++; + continue; + } + // Debug.Log("Index " + whichcontroller + " len " + playables.Count); + Dictionary parameterIndices = playableParamterIds[whichcontroller]; + Dictionary paramterInts = playableParamterInts[whichcontroller]; + Dictionary paramterFloats = playableParamterFloats[whichcontroller]; + Dictionary 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); + if (!playable.IsParameterControlledByCurve(paramid)) { + param.exportedValue = param.value; + } + } + } + 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("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("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++; + } + + if (((emulator != null && !emulator.DisableAvatarDynamicsIntegration) + || (AvatarSyncSource?.emulator != null && !AvatarSyncSource.emulator.DisableAvatarDynamicsIntegration)) && + !IsMirrorClone && !IsShadowClone) + { + assignContactParameters(avadesc.gameObject.GetComponentsInChildren()); + assignPhysBoneParameters(avadesc.gameObject.GetComponentsInChildren()); + } + + for (int i = 0; i < playableBlendingStates.Count; i++) { + var pbs = playableBlendingStates[i]; + if (pbs == null) { + continue; + } + 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)); + } + } + } + } + + float getObjectFloat(object o) { + switch (o) { + // case bool b: + // return b ? 1.0f : 0.0f; + // case int i: + // return (float)i; + // case long l: + // return (float)l; + case float f: + return f; + // case double d: + // return (float)d; + } + return 0.0f; + } + int getObjectInt(object o) { + switch (o) { + // case bool b: + // return b ? 1 : 0; + case int i: + return i; + // case long l: + // return (int)l; + // case float f: + // return (int)f; + // case double d: + // return (int)d; + } + return 0; + } + bool isObjectTrue(object o) { + switch (o) { + case bool b: + return b; + case int i: + return i == 1; + // case long l: + // return l == 1; + // case float f: + // return f == 1.0f; + // case double d: + // return d == 1.0; + } + return false; + } + + public void GetOSCDataInto(List messages) { + messages.Add(new A3ESimpleOSC.OSCMessage { + arguments = new object[1] {(object)OSCConfigurationFile.OSCAvatarID}, + path="/avatar/change", + time = new Vector2Int(-1,-1), + }); + if (OSCConfigurationFile.SendRecvAllParamsNotInJSON) { + foreach (var b in Bools) { + if (b.synced) { + messages.Add(new A3ESimpleOSC.OSCMessage { + arguments = new object[1] {(object)(int)((bool)b.value ? 1 : 0)}, + path = "/avatar/parameters/" + b.name, + time = new Vector2Int(-1,-1), + }); + } + } + foreach (var i in Ints) { + if (i.synced) { + messages.Add(new A3ESimpleOSC.OSCMessage { + arguments = new object[1] {(object)(int)i.value}, + path = "/avatar/parameters/" + i.name, + time = new Vector2Int(-1,-1), + }); + } + } + foreach (var f in Floats) { + if (f.synced) { + messages.Add(new A3ESimpleOSC.OSCMessage { + arguments = new object[1] {(object)(float)f.value}, + path = "/avatar/parameters/" + f.name, + time = new Vector2Int(-1,-1), + }); + } + } + } else { + foreach (var prop in OSCConfigurationFile.OSCJsonConfig.parameters) { + if (prop.name != null && prop.name.Length > 0 && prop.output.address != null && prop.output.address.Length > 0) { + string addr = prop.output.address; + float outputf = 0.0f; + string typ = "?"; + if (BoolToIndex.TryGetValue(prop.name, out var bidx)) { + if (!Bools[bidx].synced) { + continue; + } + outputf = Bools[bidx].value ? 1.0f : 0.0f; + typ = "bool"; + } else if (IntToIndex.TryGetValue(prop.name, out var iidx)) { + if (!Ints[iidx].synced) { + continue; + } + outputf = (float)Ints[iidx].value; + typ = "int"; + } else if (FloatToIndex.TryGetValue(prop.name, out var fidx)) { + if (!Floats[fidx].synced) { + continue; + } + outputf = Floats[fidx].value; + typ = "float"; + } else { + switch (prop.name) { + case "VelocityZ": + outputf = Velocity.z; + break; + case "VelocityY": + outputf = Velocity.y; + break; + case "VelocityX": + outputf = Velocity.x; + break; + case "InStation": + outputf = InStation ? 1.0f : 0.0f; + break; + case "Seated": + outputf = Seated ? 1.0f : 0.0f; + break; + case "AFK": + outputf = AFK ? 1.0f : 0.0f; + break; + case "Upright": + outputf = Upright; + break; + case "AngularY": + outputf = AngularY; + break; + case "Grounded": + outputf = Grounded ? 1.0f : 0.0f; + break; + case "MuteSelf": + outputf = MuteSelf ? 1.0f : 0.0f; + break; + case "VRMode": + outputf = VRMode ? 1.0f : 0.0f; + break; + case "TrackingType": + outputf = TrackingTypeIdxInt; + break; + case "GestureRightWeight": + outputf = GestureRightWeight; + break; + case "GestureRight": + outputf = GestureRightIdxInt; + break; + case "GestureLeftWeight": + outputf = GestureLeftWeight; + break; + case "GestureLeft": + outputf = GestureLeftIdxInt; + break; + case "Voice": + outputf = Voice; + break; + case "Viseme": + outputf = VisemeInt; + break; + default: + Debug.LogWarning("Unrecognized built in param"); + break; + } + } + object output; + switch (prop.output.type) { + case "Float": + output = (object)(float)outputf; + break; + case "Int": + output = (object)(int)outputf; + break; + case "Bool": + output = (object)(outputf != 0.0f); + break; + default: + Debug.LogError("Unrecognized JSON type " + prop.input.type + " for address " + addr + " for output " + typ + " parameter " + + prop.name + ". Should be \"Float\", \"Int\" or \"Bool\"."); + continue; + } + messages.Add(new A3ESimpleOSC.OSCMessage { + arguments = new object[1] {(object)output}, + path = addr, + time = new Vector2Int(-1,-1), + }); + } + } + } + } + public void processOSCInputMessage(string ParamName, object arg0) { + float argFloat = getObjectFloat(arg0); + int argInt = getObjectInt(arg0); + bool argBool = isObjectTrue(arg0); + switch (ParamName) { + case "Vertical": + Velocity.z = (3.0f + RunSpeed) * argFloat; + break; + case "Horizontal": + Velocity.x = (3.0f + RunSpeed) * (float)arg0; + break; + case "LookHorizontal": + AngularY = argFloat; + break; + case "UseAxisRight": + case "GrabAxisRight": + case "MoveHoldFB": + case "SpinHoldCwCcw": + case "SpinHoldUD": + case "SpinHoldLR": + break; + case "MoveForward": + Velocity.z = argBool ? 5.0f : 0.0f; + break; + case "MoveBackward": + Velocity.z = argBool ? -5.0f : 0.0f; + break; + case "MoveLeft": + Velocity.x = argBool ? -5.0f : 0.0f; + break; + case "MoveRight": + Velocity.x = argBool ? 5.0f : 0.0f; + break; + case "LookLeft": + AngularY = argBool ? -1.0f : 0.0f; + break; + case "LookRight": + AngularY = argBool ? 1.0f : 0.0f; + break; + case "Jump": + Jump = argBool; + break; + case "Run": + RunSpeed = argBool ? 3.0f : 0.0f; + break; + case "ComfortLeft": + case "ComfortRight": + case "DropRight": + case "UseRight": + case "GrabRight": + case "DropLeft": + case "UseLeft": + case "GrabLeft": + case "PanicButton": + case "QuickMenuToggleLeft": + case "QuickMenuToggleRight": + break; + case "Voice": + if (argBool && !MuteTogglerOn) { + MuteSelf = !MuteSelf; + } + MuteTogglerOn = argBool; + break; + default: + Debug.LogWarning("Unrecognized OSC input command " + ParamName); + break; + } + } + public void HandleOSCMessages(List messages) { + var innerProperties = new Dictionary(); + foreach (var ij in OSCConfigurationFile.OSCJsonConfig.parameters) { + if (ij.input.address != null && ij.input.address.Length > 0) { + innerProperties[ij.input.address] = ij; + } + } + foreach (var msg in messages) { + string msgPath = msg.path; + object[] arguments = msg.arguments; + if (AvatarSyncSource != this || !IsLocal || IsMirrorClone || IsShadowClone) { + return; + } + float argFloat = getObjectFloat(arguments[0]); + int argInt = getObjectInt(arguments[0]); + bool argBool = isObjectTrue(arguments[0]); + if (msgPath.StartsWith("/input/")) { + string ParamName = msgPath.Split(new char[]{'/'}, 3)[2]; + processOSCInputMessage(ParamName, arguments[0]); + } else { + string ParamName; + if (OSCConfigurationFile.SendRecvAllParamsNotInJSON) { + ParamName = msgPath.Split(new char[]{'/'}, 4)[3]; + } else if (innerProperties.ContainsKey(msgPath)) { + ParamName = innerProperties[msgPath].name; + if (innerProperties[msgPath].input.type == "Float") { + if (arguments[0].GetType() != typeof(float)) { + Debug.LogWarning("Address " + msgPath + " for parameter " + ParamName + " expected float in JSON but received " + arguments[0].GetType()); + continue; + } + } else if (innerProperties[msgPath].input.type == "Int" || innerProperties[msgPath].input.type == "Bool") { + if (arguments[0].GetType() != typeof(int) && arguments[0].GetType() != typeof(bool)) { + Debug.LogWarning("Address " + msgPath + " for parameter " + ParamName + " expected int/bool in JSON but received " + arguments[0].GetType()); + continue; + } + } else { + Debug.LogError("Unrecognized JSON type " + innerProperties[msgPath].input.type + " for address " + msgPath + " for inupt parameter " + + ParamName + " but received " + arguments[0].GetType() + ". Should be \"Float\", \"Int\" or \"Bool\"."); + continue; + } + } else { + Debug.LogWarning("Address " + msgPath + " not found for input in JSON."); + continue; + } + if (OSCController != null && OSCController.debugPrintReceivedMessages) { + Debug.Log("Recvd "+ParamName + ": " + msg); + } + if (arguments.Length > 0 && arguments[0].GetType() == typeof(bool)) { + int idx; + if (BoolToIndex.TryGetValue(ParamName, out idx)) { + Bools[idx].value = (bool)(arguments[0]); + } + if (IntToIndex.TryGetValue(ParamName, out idx)) { + Ints[idx].value = (int)(arguments[0]); + } + } + if (arguments.Length > 0 && arguments[0].GetType() == typeof(int)) { + int idx; + if (BoolToIndex.TryGetValue(ParamName, out idx)) { + Bools[idx].value = ((int)(arguments[0])) != 0; + } + if (IntToIndex.TryGetValue(ParamName, out idx)) { + Ints[idx].value = (int)(arguments[0]); + } + } + if (arguments.Length > 0 && arguments[0].GetType() == typeof(float)) { + int idx; + if (FloatToIndex.TryGetValue(ParamName, out idx)) { + Floats[idx].value = (float)(arguments[0]); + Floats[idx].exportedValue = Floats[idx].value; + } + } + } + } + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta new file mode 100644 index 00000000..fe086a2b --- /dev/null +++ b/VRCSDK3Avatars/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: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch new file mode 100644 index 00000000..825ad22b --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch @@ -0,0 +1,84 @@ +diff --git a/README.md b/README.md +index 7fe5f5e..e2a515f 100644 +--- a/README.md ++++ b/README.md +@@ -6,6 +6,18 @@ + + * Unity Package support + ++### **New features in v 2.9.9 (3.0 rc6):** ++ ++2.9.9: ++ ++* **Only compatible with the latest SDK VRCSDK3-AVATAR-2022.06.02.12.22** Please update the SDK. ++* Added support for the new Copy parameters feature. ++* Bifurcate exported and internal float values to avoid pollution from AAPs. This should match game better, as you cannot sync an AAP value over the network. ++You will not see `exportedValue` and `value` sliders for floats. Value will represent what the Animator sees, while Exported represents the value which is synced (and used for parameter Copy). ++* Please let me know if there are bugs related to the bifurcated float parameters. ++* Make buttons compact by default to avoid issue with huge buttons. ++* Remove use of Reflection, as we are breaking compatibility with older VRCSDK versions. ++ + ### **New features in v 2.9.8 (3.0 rc5):** + + 2.9.8: +diff --git a/Scripts/LyumaAv3Runtime.cs b/Scripts/LyumaAv3Runtime.cs +index acb8393..9df659a 100644 +--- a/Scripts/LyumaAv3Runtime.cs ++++ b/Scripts/LyumaAv3Runtime.cs +@@ -464,6 +464,26 @@ public class LyumaAv3Runtime : MonoBehaviour + return false; + } + ++ float getAdjustedParameterAsFloat(string paramName, bool convertRange=false, float srcMin=0.0f, float srcMax=0.0f, float dstMin=0.0f, float dstMax=0.0f) { ++ float newValue = 0; ++ int idx; ++ if (FloatToIndex.TryGetValue(paramName, out idx)) { ++ newValue = Floats[idx].exportedValue; ++ } else if (IntToIndex.TryGetValue(paramName, out idx)) { ++ newValue = (float)Ints[idx].value; ++ } else if (BoolToIndex.TryGetValue(paramName, out idx)) { ++ newValue = Bools[idx].value ? 1.0f : 0.0f; ++ } ++ if (convertRange) { ++ if (dstMax != dstMin) { ++ newValue = Mathf.Lerp(dstMin, dstMax, Mathf.Clamp01(Mathf.InverseLerp(srcMin, srcMax, newValue))); ++ } else { ++ newValue = dstMin; ++ } ++ } ++ return newValue; ++ } ++ + static LyumaAv3Runtime() { + VRCAvatarParameterDriver.OnApplySettings += (behaviour, animator) => + { +@@ -512,6 +532,9 @@ public class LyumaAv3Runtime : MonoBehaviour + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + runtime.Ints[idx].value = UnityEngine.Random.Range((int)parameter.valueMin, (int)parameter.valueMax + 1); + break; ++ case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: ++ runtime.Ints[idx].value = (int)runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); ++ break; + } + } + if (runtime.FloatToIndex.TryGetValue(actualName, out idx)) { +@@ -525,6 +548,9 @@ public class LyumaAv3Runtime : MonoBehaviour + case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: + runtime.Floats[idx].exportedValue = UnityEngine.Random.Range(parameter.valueMin, parameter.valueMax); + break; ++ case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: ++ runtime.Floats[idx].value = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); ++ break; + } + runtime.Floats[idx].value = runtime.Floats[idx].exportedValue; + } +@@ -546,6 +572,9 @@ public class LyumaAv3Runtime : MonoBehaviour + // random is *not* idempotent. + newValue = UnityEngine.Random.Range(0.0f, 1.0f) < parameter.chance; + break; ++ case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: ++ newValue = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax) > 0.0f; ++ break; + default: + continue; + } diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch.meta new file mode 100644 index 00000000..eed02d9a --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/foo.patch.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 581a0ea3b3240cd4a930b749086b1d3b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: -- cgit v1.2.3-freya