From 4c85c922031adb508cc6f945635842be944dfa51 Mon Sep 17 00:00:00 2001 From: SDraw Date: Tue, 22 Mar 2022 13:06:10 +0300 Subject: [PATCH] HMD mode Head attachment Head offsets Model visibility VR compatibility --- ml_lme_cvr/AssetsHandler.cs | 88 ++++++++ ml_lme_cvr/LeapIK.cs | 22 +- ml_lme_cvr/LeapTracked.cs | 49 +++-- ml_lme_cvr/Main.cs | 169 ++++++++++++---- ml_lme_cvr/README.md | 52 +---- ml_lme_cvr/Settings.cs | 188 ++++++++++++++---- ml_lme_cvr/ml_lme_cvr.csproj | 15 +- .../resources/leapmotion_controller.asset | Bin 0 -> 6703 bytes 8 files changed, 436 insertions(+), 147 deletions(-) create mode 100644 ml_lme_cvr/AssetsHandler.cs create mode 100644 ml_lme_cvr/resources/leapmotion_controller.asset diff --git a/ml_lme_cvr/AssetsHandler.cs b/ml_lme_cvr/AssetsHandler.cs new file mode 100644 index 0000000..3fc4ad2 --- /dev/null +++ b/ml_lme_cvr/AssetsHandler.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using UnityEngine; + +namespace ml_lme_cvr +{ + static class AssetsHandler + { + static readonly List ms_assets = new List() + { + "leapmotion_controller.asset" + }; + + static Dictionary ms_loadedAssets = new Dictionary(); + static Dictionary ms_loadedObjects = new Dictionary(); + + public static void Load() + { + Assembly l_assembly = Assembly.GetExecutingAssembly(); + string l_assemblyName = l_assembly.GetName().Name; + + foreach(string l_assetName in ms_assets) + { + try + { + Stream l_assetStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_assetName); + if(l_assetStream != null) + { + MemoryStream l_memorySteam = new MemoryStream((int)l_assetStream.Length); + l_assetStream.CopyTo(l_memorySteam); + AssetBundle l_assetBundle = AssetBundle.LoadFromMemory(l_memorySteam.ToArray(), 0); + if(l_assetBundle != null) + { + l_assetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; + ms_loadedAssets.Add(l_assetName, l_assetBundle); + } + else + MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset"); + } + else + MelonLoader.MelonLogger.Warning("Unable to get bundled '" + l_assetName + "' asset stream"); + } + catch(System.Exception e) + { + MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset, reason: " + e.Message); + } + } + } + + public static GameObject GetAsset(string p_name) + { + GameObject l_result = null; + if(ms_loadedObjects.ContainsKey(p_name)) + { + l_result = Object.Instantiate(ms_loadedObjects[p_name]); + l_result.SetActive(true); + l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset; + } + else + { + foreach(var l_pair in ms_loadedAssets) + { + if(l_pair.Value.Contains(p_name)) + { + GameObject l_bundledObject = (GameObject)l_pair.Value.LoadAsset(p_name, typeof(GameObject)); + if(l_bundledObject != null) + { + ms_loadedObjects.Add(p_name, l_bundledObject); + l_result = Object.Instantiate(l_bundledObject); + l_result.SetActive(true); + l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset; + } + break; + } + } + } + return l_result; + } + + public static void Unload() + { + foreach(var l_pair in ms_loadedAssets) + Object.Destroy(l_pair.Value); + ms_loadedAssets.Clear(); + } + } +} diff --git a/ml_lme_cvr/LeapIK.cs b/ml_lme_cvr/LeapIK.cs index fc5fdbe..76d8a20 100644 --- a/ml_lme_cvr/LeapIK.cs +++ b/ml_lme_cvr/LeapIK.cs @@ -13,6 +13,12 @@ namespace ml_lme_cvr Transform m_leftHand = null; Transform m_rightHand = null; + float m_leftHandWeight = 1f; + float m_rightHandWeight = 1f; + + bool m_leftHandVisible = false; + bool m_rightHandVisible = false; + void Start() { m_animator = this.GetComponent(); @@ -24,16 +30,18 @@ namespace ml_lme_cvr { if(m_leftHand != null) { - m_animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1f); - m_animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1f); + m_leftHandWeight = Mathf.Lerp(m_leftHandWeight, m_leftHandVisible ? 1f : 0f, 0.25f); + m_animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, m_leftHandWeight); + m_animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, m_leftHandWeight); m_animator.SetIKPosition(AvatarIKGoal.LeftHand, m_leftHand.position); m_animator.SetIKRotation(AvatarIKGoal.LeftHand, m_leftHand.rotation); } if(m_rightHand != null) { - m_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f); - m_animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f); + m_rightHandWeight = Mathf.Lerp(m_rightHandWeight, m_rightHandVisible ? 1f : 0f, 0.25f); + m_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, m_rightHandWeight); + m_animator.SetIKRotationWeight(AvatarIKGoal.RightHand, m_rightHandWeight); m_animator.SetIKPosition(AvatarIKGoal.RightHand, m_rightHand.position); m_animator.SetIKRotation(AvatarIKGoal.RightHand, m_rightHand.rotation); } @@ -49,5 +57,11 @@ namespace ml_lme_cvr m_leftHand = p_left; m_rightHand = p_right; } + + public void SetHandsVisibility(bool p_left, bool p_right) + { + m_leftHandVisible = p_left; + m_rightHandVisible = p_right; + } } } diff --git a/ml_lme_cvr/LeapTracked.cs b/ml_lme_cvr/LeapTracked.cs index add19e3..dba0f15 100644 --- a/ml_lme_cvr/LeapTracked.cs +++ b/ml_lme_cvr/LeapTracked.cs @@ -1,8 +1,10 @@ -using UnityEngine; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using UnityEngine; namespace ml_lme_cvr { - [RequireComponent(typeof(ABI_RC.Core.Player.IndexIK))] + [RequireComponent(typeof(IndexIK))] [DisallowMultipleComponent] class LeapTracked : MonoBehaviour { @@ -11,7 +13,8 @@ namespace ml_lme_cvr bool m_calibrated = false; Animator m_animator = null; - ABI_RC.Core.Player.IndexIK m_indexIK = null; + IndexIK m_indexIK = null; + LeapIK m_leapIK = null; Transform m_leftHand = null; Transform m_rightHand = null; @@ -26,7 +29,7 @@ namespace ml_lme_cvr m_indexIK.Recalibrate(); m_indexIK.activeControl = m_enabled; - ABI_RC.Core.Savior.CVRInputManager.Instance.individualFingerTracking = m_enabled; + CVRInputManager.Instance.individualFingerTracking = m_enabled; m_calibrated = true; } @@ -46,7 +49,7 @@ namespace ml_lme_cvr m_indexIK.Recalibrate(); m_calibrated = true; } - ABI_RC.Core.Savior.CVRInputManager.Instance.individualFingerTracking = true; + CVRInputManager.Instance.individualFingerTracking = true; } } else @@ -54,7 +57,7 @@ namespace ml_lme_cvr if((m_indexIK != null) && m_calibrated) { m_indexIK.activeControl = false; - ABI_RC.Core.Savior.CVRInputManager.Instance.individualFingerTracking = false; + CVRInputManager.Instance.individualFingerTracking = false; } } @@ -89,17 +92,13 @@ namespace ml_lme_cvr m_leapIK.SetHands(p_left, p_right); } - public void Reset() - { - m_calibrated = false; - m_animator = null; - m_leapIK = null; - } - public void UpdateTracking(GestureMatcher.GesturesData p_gesturesData) { if(m_enabled && (m_indexIK != null)) { + if(m_leapIK != null) + m_leapIK.SetHandsVisibility(p_gesturesData.m_handsPresenses[0], p_gesturesData.m_handsPresenses[1]); + if(p_gesturesData.m_handsPresenses[0]) { m_indexIK.leftThumbCurl = p_gesturesData.m_leftFingersBends[0]; @@ -108,13 +107,13 @@ namespace ml_lme_cvr m_indexIK.leftRingCurl = p_gesturesData.m_leftFingersBends[3]; m_indexIK.leftPinkyCurl = p_gesturesData.m_leftFingersBends[4]; - if(ABI_RC.Core.Savior.CVRInputManager.Instance != null) + if(CVRInputManager.Instance != null) { - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlLeftThumb = p_gesturesData.m_leftFingersBends[0]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlLeftIndex = p_gesturesData.m_leftFingersBends[1]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlLeftMiddle = p_gesturesData.m_leftFingersBends[2]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlLeftRing = p_gesturesData.m_leftFingersBends[3]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlLeftPinky = p_gesturesData.m_leftFingersBends[4]; + CVRInputManager.Instance.fingerCurlLeftThumb = p_gesturesData.m_leftFingersBends[0]; + CVRInputManager.Instance.fingerCurlLeftIndex = p_gesturesData.m_leftFingersBends[1]; + CVRInputManager.Instance.fingerCurlLeftMiddle = p_gesturesData.m_leftFingersBends[2]; + CVRInputManager.Instance.fingerCurlLeftRing = p_gesturesData.m_leftFingersBends[3]; + CVRInputManager.Instance.fingerCurlLeftPinky = p_gesturesData.m_leftFingersBends[4]; } } @@ -126,13 +125,13 @@ namespace ml_lme_cvr m_indexIK.rightRingCurl = p_gesturesData.m_rightFingersBends[3]; m_indexIK.rightPinkyCurl = p_gesturesData.m_rightFingersBends[4]; - if(ABI_RC.Core.Savior.CVRInputManager.Instance != null) + if(CVRInputManager.Instance != null) { - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlRightThumb = p_gesturesData.m_rightFingersBends[0]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlRightIndex = p_gesturesData.m_rightFingersBends[1]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlRightMiddle = p_gesturesData.m_rightFingersBends[2]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlRightRing = p_gesturesData.m_rightFingersBends[3]; - ABI_RC.Core.Savior.CVRInputManager.Instance.fingerCurlRightPinky = p_gesturesData.m_rightFingersBends[4]; + CVRInputManager.Instance.fingerCurlRightThumb = p_gesturesData.m_rightFingersBends[0]; + CVRInputManager.Instance.fingerCurlRightIndex = p_gesturesData.m_rightFingersBends[1]; + CVRInputManager.Instance.fingerCurlRightMiddle = p_gesturesData.m_rightFingersBends[2]; + CVRInputManager.Instance.fingerCurlRightRing = p_gesturesData.m_rightFingersBends[3]; + CVRInputManager.Instance.fingerCurlRightPinky = p_gesturesData.m_rightFingersBends[4]; } } } diff --git a/ml_lme_cvr/Main.cs b/ml_lme_cvr/Main.cs index 7a0ef5a..721e1c2 100644 --- a/ml_lme_cvr/Main.cs +++ b/ml_lme_cvr/Main.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using ABI_RC.Core.Player; +using UnityEngine; namespace ml_lme_cvr { @@ -14,11 +15,9 @@ namespace ml_lme_cvr GameObject m_leapTrackingRoot = null; GameObject[] m_leapHands = null; + GameObject m_leapControllerModel = null; LeapTracked m_leapTracked = null; - Transform m_vrRig = null; - Transform m_desktopRig = null; - public override void OnApplicationStart() { ms_instance = this; @@ -29,6 +28,11 @@ namespace ml_lme_cvr Settings.EnabledChange += this.OnSettingsEnableChange; Settings.DesktopOffsetChange += this.OnSettingsDesktopOffsetChange; Settings.FingersOnlyChange += this.OnSettingsFingersOptionChange; + Settings.ModelVisibilityChange += this.OnSettingsModelVisibilityChange; + Settings.HmdModeChange += this.OnSettingsHmdModeChange; + Settings.RootAngleChange += this.OnSettingsRootAngleChange; + Settings.HeadAttachChange += this.OnSettingsHeadAttachChange; + Settings.HeadOffsetChange += this.OnSettingsHeadOffsetChange; m_leapController = new Leap.Controller(); m_gesturesData = new GestureMatcher.GesturesData(); @@ -37,7 +41,7 @@ namespace ml_lme_cvr // Patches HarmonyInstance.Patch( - typeof(ABI_RC.Core.Player.PlayerSetup).GetMethod(nameof(ABI_RC.Core.Player.PlayerSetup.SetupAvatar)), + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)), null, new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnAvatarSetup), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) ); @@ -47,25 +51,20 @@ namespace ml_lme_cvr System.Collections.IEnumerator CreateTrackingObjects() { - while(ABI_RC.Core.Player.PlayerSetup.Instance == null) - yield return null; + AssetsHandler.Load(); - while(m_vrRig == null) - { - m_vrRig = ABI_RC.Core.Player.PlayerSetup.Instance.transform.Find("[CameraRigVR]"); + while(PlayerSetup.Instance == null) yield return null; - } - - while(m_desktopRig == null) - { - m_desktopRig = ABI_RC.Core.Player.PlayerSetup.Instance.transform.Find("[CameraRigDesktop]"); + while(PlayerSetup.Instance.desktopCameraRig == null) + yield return null; + while(PlayerSetup.Instance.desktopCamera == null) + yield return null; + while(PlayerSetup.Instance.vrCameraRig == null) + yield return null; + while(PlayerSetup.Instance.vrCamera == null) yield return null; - } m_leapTrackingRoot = new GameObject("[LeapRoot]"); - m_leapTrackingRoot.transform.parent = m_desktopRig; - m_leapTrackingRoot.transform.localPosition = new Vector3(0f, -0.45637f, 0.35f); - m_leapTrackingRoot.transform.localRotation = Quaternion.identity; for(int i = 0; i < GestureMatcher.GesturesData.ms_handsCount; i++) { @@ -75,7 +74,22 @@ namespace ml_lme_cvr m_leapHands[i].transform.localRotation = Quaternion.identity; } + m_leapControllerModel = AssetsHandler.GetAsset("assets/models/leapmotion/leap_motion_1_0.obj"); + if(m_leapControllerModel != null) + { + m_leapControllerModel.name = "LeapModel"; + m_leapControllerModel.transform.parent = m_leapTrackingRoot.transform; + m_leapControllerModel.transform.localPosition = Vector3.zero; + m_leapControllerModel.transform.localRotation = Quaternion.identity; + } + Settings.Reload(); + + OnSettingsEnableChange(); + OnSettingsFingersOptionChange(); + OnSettingsModelVisibilityChange(); + OnSettingsHmdModeChange(); + OnSettingsHeadAttachChange(); // Includes offsets and parenting } public override void OnUpdate() @@ -89,28 +103,33 @@ namespace ml_lme_cvr for(int i = 0; i < GestureMatcher.GesturesData.ms_handsCount; i++) { - if(m_gesturesData.m_handsPresenses[i] && (m_leapHands[i] != null)) + if((m_leapHands[i] != null) && m_gesturesData.m_handsPresenses[i]) { Vector3 l_pos = m_gesturesData.m_handsPositons[i]; Quaternion l_rot = m_gesturesData.m_handsRotations[i]; - ReorientateLeapToUnity(ref l_pos, ref l_rot, false); + ReorientateLeapToUnity(ref l_pos, ref l_rot, Settings.HmdMode); m_leapHands[i].transform.localPosition = l_pos; m_leapHands[i].transform.localRotation = l_rot; } } - - if(m_leapTracked != null) - m_leapTracked.UpdateTracking(m_gesturesData); } + + if(m_leapTracked != null) + m_leapTracked.UpdateTracking(m_gesturesData); } } + // Settings changes void OnSettingsEnableChange() { if(Settings.Enabled) { m_leapController.StartConnection(); - m_leapController.SetAndClearPolicy(Leap.Controller.PolicyFlag.POLICY_DEFAULT, Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP | Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD); + m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP); + if(Settings.HmdMode) + m_leapController.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD); + else + m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD); } else m_leapController.StopConnection(); @@ -121,10 +140,12 @@ namespace ml_lme_cvr void OnSettingsDesktopOffsetChange() { - if((m_leapTrackingRoot != null) && (m_vrRig != null)) + if((m_leapTrackingRoot != null) && !Settings.HeadAttach) { - m_leapTrackingRoot.transform.localPosition = new Vector3(Settings.DesktopOffsetX, Settings.DesktopOffsetY, Settings.DesktopOffsetZ) * m_vrRig.transform.localScale.x; - m_leapTrackingRoot.transform.localScale = m_vrRig.transform.localScale; + if(!PlayerSetup.Instance._inVr) + m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x; + else + m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset; } } @@ -134,14 +155,92 @@ namespace ml_lme_cvr m_leapTracked.SetFingersOnly(Settings.FingersOnly); } - static void OnAvatarSetup(ref ABI_RC.Core.Player.PlayerSetup __instance) + void OnSettingsModelVisibilityChange() { - if(__instance != null && __instance == ABI_RC.Core.Player.PlayerSetup.Instance) + if(m_leapControllerModel != null) + m_leapControllerModel.SetActive(Settings.ModelVisibility); + } + + void OnSettingsHmdModeChange() + { + if(m_leapController != null) { - ms_instance?.OnLocalPlayerAvatarSetup(__instance._animator, __instance.GetComponent()); + m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP); + if(Settings.HmdMode) + m_leapController.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD); + else + m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD); + } + + if(m_leapControllerModel != null) + m_leapControllerModel.transform.localRotation = (Settings.HmdMode ? Quaternion.Euler(270f, 180f, 0f) : Quaternion.identity); + } + + void OnSettingsRootAngleChange() + { + if(m_leapTrackingRoot != null) + m_leapTrackingRoot.transform.localRotation = Quaternion.Euler(Settings.RootAngle, 0f, 0f); + } + + void OnSettingsHeadAttachChange() + { + if(m_leapTrackingRoot != null) + { + if(Settings.HeadAttach) + { + if(!PlayerSetup.Instance._inVr) + { + m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.desktopCamera.transform; + m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x; + m_leapTrackingRoot.transform.localScale = PlayerSetup.Instance.vrCameraRig.transform.localScale; + } + else + { + m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.vrCamera.transform; + m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset; + m_leapTrackingRoot.transform.localScale = Vector3.one; + } + } + else + { + if(!PlayerSetup.Instance._inVr) + { + m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.desktopCameraRig.transform; + m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x; + m_leapTrackingRoot.transform.localScale = PlayerSetup.Instance.vrCameraRig.transform.localScale; + } + else + { + m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.vrCameraRig.transform; + m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset; + m_leapTrackingRoot.transform.localScale = Vector3.one; + } + } + + m_leapTrackingRoot.transform.localRotation = Quaternion.Euler(Settings.RootAngle, 0f, 0f); } } - void OnLocalPlayerAvatarSetup(Animator p_animator, ABI_RC.Core.Player.IndexIK p_indexIK) + + void OnSettingsHeadOffsetChange() + { + if((m_leapTrackingRoot != null) && Settings.HeadAttach) + { + if(!PlayerSetup.Instance._inVr) + m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x; + else + m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset; + } + } + + // Patches + static void OnAvatarSetup(ref PlayerSetup __instance) + { + if(__instance != null && __instance == PlayerSetup.Instance) + { + ms_instance?.OnLocalPlayerAvatarSetup(__instance._animator, __instance.GetComponent()); + } + } + void OnLocalPlayerAvatarSetup(Animator p_animator, IndexIK p_indexIK) { if(m_leapTracked != null) Object.DestroyImmediate(m_leapTracked); @@ -152,11 +251,7 @@ namespace ml_lme_cvr m_leapTracked.SetHands(m_leapHands[0].transform, m_leapHands[1].transform); m_leapTracked.SetFingersOnly(Settings.FingersOnly); - if((m_leapTrackingRoot != null) && (m_vrRig != null)) - { - m_leapTrackingRoot.transform.localPosition = new Vector3(Settings.DesktopOffsetX, Settings.DesktopOffsetY, Settings.DesktopOffsetZ) * m_vrRig.transform.localScale.x; - m_leapTrackingRoot.transform.localScale = m_vrRig.transform.localScale; - } + OnSettingsHeadAttachChange(); } static void ReorientateLeapToUnity(ref Vector3 p_pos, ref Quaternion p_rot, bool p_hmd) diff --git a/ml_lme_cvr/README.md b/ml_lme_cvr/README.md index 014aaba..b47e739 100644 --- a/ml_lme_cvr/README.md +++ b/ml_lme_cvr/README.md @@ -6,56 +6,16 @@ This mod allows you to use your Leap Motion controller for hands and fingers vis * Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader) * Get [latest release DLL](../../../releases/latest): * Put `ml_lme_cvr.dll` in `Mods` folder of game -* Add code section below in `\ChilloutVR_Data\StreamingAssets\Cohtml\UIResources\CVRTest\index.html` after div for `InteractionViveFaceTrackingStrength` menu item: -```html - -

Leap Motion tracking

-
-
Enable tracking:
-
-
-
-
- -
-
Desktop offset X:
-
-
-
-
- -
-
Desktop offset Y:
-
-
-
-
- -
-
Desktop offset Z:
-
-
-
-
- -
-
Fingers tracking only:
-
-
-
-
- -``` +* Add code from [this gist](https://gist.github.com/SDraw/543825b39cdabc3bc4fda358bc70247a) to `\ChilloutVR_Data\StreamingAssets\Cohtml\UIResources\CVRTest\index.html` after div for `InteractionViveFaceTrackingStrength` menu item (near line 1188) # Usage ## Settings Available mod's settings in `Settings - Implementation - Leap Motion Tracking`: * **Enable tracking:** enable hands tracking from Leap Motion data, disabled by default. +* **HMD mode:** force Leap Motion to use head-mounted orientation mode, disabled by default. * **Desktop offset X/Y/Z:** offset position for body attachment, (0, -45, 30) by default. +* **Attach to head:** attach hands transformation to head instead of body, disabled by default. +* **Head offset X/Y/Z:** offset position for head attachment (`Attach to head` is **`true`**), (0, -30, 15) by default. +* **Offset angle:** rotation around X axis, useful for neck mounts, 0 by default. * **Fingers tracking only:** apply only fingers tracking, disabled by default. - -# Notes -* Only desktop mode is implemented, VR mode is in development. -* Head attachment isn't implemented, in development. -* Root rotation isn't implemented, in development. -* Model visibility isn't implemented, in development. +* **Model visibility:** show Leap Motion controller model, useful for tracking visualizing, disabled by default. diff --git a/ml_lme_cvr/Settings.cs b/ml_lme_cvr/Settings.cs index fe42daf..0033ae6 100644 --- a/ml_lme_cvr/Settings.cs +++ b/ml_lme_cvr/Settings.cs @@ -1,4 +1,8 @@ -namespace ml_lme_cvr +using ABI_RC.Core.Savior; +using System; +using UnityEngine; + +namespace ml_lme_cvr { static class Settings { @@ -8,87 +12,186 @@ "InteractionLeapMotionTrackingDesktopX", "InteractionLeapMotionTrackingDesktopY", "InteractionLeapMotionTrackingDesktopZ", - "InteractionLeapMotionTrackingFingersOnly" + "InteractionLeapMotionTrackingFingersOnly", + "InteractionLeapMotionTrackingModel", + "InteractionLeapMotionTrackingHmd", + "InteractionLeapMotionTrackingAngle", + "InteractionLeapMotionTrackingHead", + "InteractionLeapMotionTrackingHeadX", + "InteractionLeapMotionTrackingHeadY", + "InteractionLeapMotionTrackingHeadZ" }; static bool ms_enabled = false; - static float ms_desktopOffsetX = 0f; - static float ms_desktopOffsetY = -0.45f; - static float ms_desktopOffsetZ = 0.3f; + static Vector3 ms_desktopOffset = new Vector3(0f, -0.45f, 0.3f); static bool ms_fingersOnly = false; + static bool ms_modelVisibility = false; + static bool ms_hmdMode = false; + static float ms_rootAngle = 0f; + static bool ms_headAttach = false; + static Vector3 ms_headOffset = new Vector3(0f, 0f, 0f); static bool ms_initialized = false; - static public event System.Action EnabledChange; - static public event System.Action DesktopOffsetChange; - static public event System.Action FingersOnlyChange; + static public event Action EnabledChange; + static public event Action DesktopOffsetChange; + static public event Action FingersOnlyChange; + static public event Action ModelVisibilityChange; + static public event Action HmdModeChange; + static public event Action RootAngleChange; + static public event Action HeadAttachChange; + static public event Action HeadOffsetChange; public static void Init(HarmonyLib.Harmony p_instance) { p_instance.Patch( - typeof(ABI_RC.Core.Savior.CVRSettings).GetMethod(nameof(ABI_RC.Core.Savior.CVRSettings.LoadSerializedSettings)), + typeof(CVRSettings).GetMethod(nameof(CVRSettings.LoadSerializedSettings)), new HarmonyLib.HarmonyMethod(typeof(Settings).GetMethod(nameof(BeforeSettingsLoad), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)), null ); } - static void BeforeSettingsLoad(ref ABI_RC.Core.Savior.CVRSettings __instance) + static void BeforeSettingsLoad(ref CVRSettings __instance) { if(!ms_initialized && __instance != null) { var l_settings = HarmonyLib.Traverse.Create(__instance)?.Field("_settings")?.GetValue>(); if(l_settings != null) { - l_settings.Add(new ABI_RC.Core.Savior.CVRSettingsBool(ms_defaultSettings[0], false)); - l_settings.Add(new ABI_RC.Core.Savior.CVRSettingsInt(ms_defaultSettings[1], 0)); - l_settings.Add(new ABI_RC.Core.Savior.CVRSettingsInt(ms_defaultSettings[2], -45)); - l_settings.Add(new ABI_RC.Core.Savior.CVRSettingsInt(ms_defaultSettings[3], 30)); - l_settings.Add(new ABI_RC.Core.Savior.CVRSettingsBool(ms_defaultSettings[4], false)); + l_settings.Add(new CVRSettingsBool(ms_defaultSettings[0], false)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[1], 0)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[2], -45)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[3], 30)); + l_settings.Add(new CVRSettingsBool(ms_defaultSettings[4], false)); + l_settings.Add(new CVRSettingsBool(ms_defaultSettings[5], false)); + l_settings.Add(new CVRSettingsBool(ms_defaultSettings[6], false)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[7], 0)); + l_settings.Add(new CVRSettingsBool(ms_defaultSettings[8], false)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[9], 0)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[10], 0)); + l_settings.Add(new CVRSettingsInt(ms_defaultSettings[11], 0)); } // Changes events + + // Enable tracking __instance.settingBoolChanged.AddListener((name, value) => { if(name == ms_defaultSettings[0]) { - Settings.Reload(); + ms_enabled = value; EnabledChange?.Invoke(); } }); + // Desktop offsets __instance.settingIntChanged.AddListener((name, value) => { - for(int i=1; i <= 3; i++) + for(int i = 1; i <= 3; i++) { if(name == ms_defaultSettings[i]) { - Settings.Reload(); + ms_desktopOffset = new Vector3( + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[1]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[2]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[3]) + ) * 0.01f; DesktopOffsetChange?.Invoke(); break; } } }); + // Fingers tracking only __instance.settingBoolChanged.AddListener((name, value) => { if(name == ms_defaultSettings[4]) { - Settings.Reload(); + ms_fingersOnly = value; FingersOnlyChange?.Invoke(); } }); + // Model visibility + __instance.settingBoolChanged.AddListener((name, value) => + { + if(name == ms_defaultSettings[5]) + { + ms_modelVisibility = value; + ModelVisibilityChange?.Invoke(); + } + }); + + // HMD mode + __instance.settingBoolChanged.AddListener((name, value) => + { + if(name == ms_defaultSettings[6]) + { + ms_hmdMode = value; + HmdModeChange?.Invoke(); + } + }); + + // Root angle + __instance.settingIntChanged.AddListener((name, value) => + { + if(name == ms_defaultSettings[7]) + { + ms_rootAngle = value; + RootAngleChange?.Invoke(); + } + }); + + // Head attach + __instance.settingBoolChanged.AddListener((name, value) => + { + if(name == ms_defaultSettings[8]) + { + ms_headAttach = value; + HeadAttachChange?.Invoke(); + } + }); + + // Head offset + __instance.settingIntChanged.AddListener((name, value) => + { + for(int i = 9; i <= 11; i++) + { + if(name == ms_defaultSettings[i]) + { + ms_headOffset = new Vector3( + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[9]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[10]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[11]) + ) * 0.01f; + HeadOffsetChange?.Invoke(); + break; + } + } + }); + ms_initialized = true; } } static public void Reload() { - ms_enabled = ABI_RC.Core.Savior.MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[0]); - ms_desktopOffsetX = ABI_RC.Core.Savior.MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[1]) * 0.01f; - ms_desktopOffsetY = ABI_RC.Core.Savior.MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[2]) * 0.01f; - ms_desktopOffsetZ = ABI_RC.Core.Savior.MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[3]) * 0.01f; - ms_fingersOnly = ABI_RC.Core.Savior.MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[4]); + ms_enabled = MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[0]); + ms_desktopOffset = new Vector3( + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[1]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[2]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[3]) + ) * 0.01f; + ms_fingersOnly = MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[4]); + ms_modelVisibility = MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[5]); + ms_hmdMode = MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[6]); + ms_rootAngle = MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[7]); + ms_headAttach = MetaPort.Instance.settings.GetSettingsBool(ms_defaultSettings[8]); + ms_headOffset = new Vector3( + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[9]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[10]), + MetaPort.Instance.settings.GetSettingInt(ms_defaultSettings[11]) + ) * 0.01f; } public static bool Enabled @@ -96,22 +199,39 @@ get => ms_enabled; } - public static float DesktopOffsetX + public static Vector3 DesktopOffset { - get => ms_desktopOffsetX; - } - public static float DesktopOffsetY - { - get => ms_desktopOffsetY; - } - public static float DesktopOffsetZ - { - get => ms_desktopOffsetZ; + get => ms_desktopOffset; } public static bool FingersOnly { get => ms_fingersOnly; } + + public static bool ModelVisibility + { + get => ms_modelVisibility; + } + + public static bool HmdMode + { + get => ms_hmdMode; + } + + public static float RootAngle + { + get => ms_rootAngle; + } + + public static bool HeadAttach + { + get => ms_headAttach; + } + + public static Vector3 HeadOffset + { + get => ms_headOffset; + } } } diff --git a/ml_lme_cvr/ml_lme_cvr.csproj b/ml_lme_cvr/ml_lme_cvr.csproj index fdb5a61..eb42178 100644 --- a/ml_lme_cvr/ml_lme_cvr.csproj +++ b/ml_lme_cvr/ml_lme_cvr.csproj @@ -32,6 +32,11 @@ MinimumRecommendedRules.ruleset + + False + C:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll + False + C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll False @@ -50,7 +55,6 @@ - C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll @@ -60,12 +64,18 @@ C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll False + + False + C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll + False + C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll False + @@ -112,6 +122,9 @@ LeapC.dll + + + copy /y "$(TargetPath)" "C:\Games\Steam\steamapps\common\ChilloutVR\Mods\ diff --git a/ml_lme_cvr/resources/leapmotion_controller.asset b/ml_lme_cvr/resources/leapmotion_controller.asset new file mode 100644 index 0000000000000000000000000000000000000000..b4b06eb59c678310b8ef79c203605d3bce442c0b GIT binary patch literal 6703 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