mirror of
https://github.com/hanetzer/sdraw_mods_cvr.git
synced 2025-09-03 18:39:23 +00:00
HMD mode
Head attachment Head offsets Model visibility VR compatibility
This commit is contained in:
parent
eb3f09fffd
commit
4c85c92203
8 changed files with 436 additions and 147 deletions
88
ml_lme_cvr/AssetsHandler.cs
Normal file
88
ml_lme_cvr/AssetsHandler.cs
Normal file
|
@ -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<string> ms_assets = new List<string>()
|
||||
{
|
||||
"leapmotion_controller.asset"
|
||||
};
|
||||
|
||||
static Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
|
||||
static Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Animator>();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ABI_RC.Core.Player.IndexIK>());
|
||||
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<IndexIK>());
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -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 `<GameFolder>\ChilloutVR_Data\StreamingAssets\Cohtml\UIResources\CVRTest\index.html` after div for `InteractionViveFaceTrackingStrength` menu item:
|
||||
```html
|
||||
<!--Leap Motion start-->
|
||||
<h2>Leap Motion tracking</h2>
|
||||
<div class="row-wrapper">
|
||||
<div class="option-caption">Enable tracking:</div>
|
||||
<div class="option-input">
|
||||
<div id="InteractionLeapMotionTracking" class="inp_toggle" data-current="false" data-saveOnChange="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-wrapper">
|
||||
<div class="option-caption">Desktop offset X:</div>
|
||||
<div class="option-input">
|
||||
<div id="InteractionLeapMotionTrackingDesktopX" class="inp_slider" data-min="-100" data-max="100" data-current="0" data-saveOnChange="true" data-continuousUpdate="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-wrapper">
|
||||
<div class="option-caption">Desktop offset Y:</div>
|
||||
<div class="option-input">
|
||||
<div id="InteractionLeapMotionTrackingDesktopY" class="inp_slider" data-min="-100" data-max="100" data-current="-45" data-saveOnChange="true" data-continuousUpdate="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-wrapper">
|
||||
<div class="option-caption">Desktop offset Z:</div>
|
||||
<div class="option-input">
|
||||
<div id="InteractionLeapMotionTrackingDesktopZ" class="inp_slider" data-min="-100" data-max="100" data-current="30" data-saveOnChange="true" data-continuousUpdate="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-wrapper">
|
||||
<div class="option-caption">Fingers tracking only:</div>
|
||||
<div class="option-input">
|
||||
<div id="InteractionLeapMotionTrackingFingersOnly" class="inp_toggle" data-current="false" data-saveOnChange="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!--Leap Motion end-->
|
||||
```
|
||||
* Add code from [this gist](https://gist.github.com/SDraw/543825b39cdabc3bc4fda358bc70247a) to `<GameFolder>\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.
|
||||
|
|
|
@ -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<System.Collections.Generic.List<ABI_RC.Core.Savior.CVRSettingsValue>>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@
|
|||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
@ -50,7 +55,6 @@
|
|||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
|
||||
|
@ -60,12 +64,18 @@
|
|||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>C:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AssetsHandler.cs" />
|
||||
<Compile Include="DependenciesHandler.cs" />
|
||||
<Compile Include="GestureMatcher.cs" />
|
||||
<Compile Include="LeapIK.cs" />
|
||||
|
@ -112,6 +122,9 @@
|
|||
<Link>LeapC.dll</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="resources\leapmotion_controller.asset" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\steamapps\common\ChilloutVR\Mods\</PostBuildEvent>
|
||||
|
|
BIN
ml_lme_cvr/resources/leapmotion_controller.asset
Normal file
BIN
ml_lme_cvr/resources/leapmotion_controller.asset
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue