// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2019-2022) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
namespace UnityEngine.XR.MagicLeap
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
public partial class InputSubsystem
{
public static partial class Extensions
{
public static class MLHandTracking
{
///
/// The max number of key points to track.
///
public const int MaxKeyPoints = 28;
///
/// Represents if a hand is the right or left hand.
///
public enum HandType
{
///
/// Left hand.
///
Left,
///
/// Right hand.
///
Right
}
public enum KeyPoints
{
Thumb_Tip = 0,
Thumb_IP,
Thumb_MCP,
Thumb_CMC,
Index_Tip,
Index_DIP,
Index_PIP,
Index_MCP,
Middle_Tip,
Middle_DIP,
Middle_PIP,
Middle_MCP,
Ring_Tip,
Ring_DIP,
Ring_PIP,
Ring_MCP,
Pinky_Tip,
Pinky_DIP,
Pinky_PIP,
Pinky_MCP,
Wrist_Center,
Wrist_Ulnar,
Wrist_Radial,
Hand_Center,
Index_CMC,
Middle_CMC,
Ring_CMC,
Pinky_CMC
}
public enum KeyPointLocation
{
Thumb = 0,
Index = 4,
Middle = 8,
Ring = 12,
Pinky = 16,
Wrist = 20,
Center = 23,
FifthBone = 24
}
public static void StartTracking() => MagicLeapXrProviderNativeBindings.StartHandTracking();
public static void StopTracking() => MagicLeapXrProviderNativeBindings.StopHandTracking();
///
/// By default the keypoints data is updated twice. To turn this off
/// set enable to false to potentially improve performance. This is not
/// recommended if keypoints are visual in the app as it will
/// significantly decrease the smoothness of visuals.
///
public static void SetPreRenderHandUpdate(bool enable = true)
{
NativeBindings.SetPreRenderPoseUpdate(enable);
}
public static bool TryGetKeyPointsMask(InputDevice handDevice, out bool[] keyPointsMask) => NativeBindings.TryGetKeyPointsMask(handDevice, out keyPointsMask);
public static string GetKeyPointName(KeyPointLocation location, int keyPointIndex) => NativeBindings.GetKeyPointName(location, keyPointIndex);
public static bool GetKeyPointStatus(InputDevice handDevice, KeyPointLocation location, int keyPointIndex) => NativeBindings.GetKeyPointStatus(handDevice, location, keyPointIndex);
internal static class NativeBindings
{
private static byte[] allocatedKeyPointsMaskData = new byte[Marshal.SizeOf()];
public static bool TryGetKeyPointsMask(InputDevice handDevice, out bool[] keyPointsMask)
{
if (!handDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.KeyPointsMask, allocatedKeyPointsMaskData))
goto Failure;
try
{
IntPtr ptr = Marshal.AllocHGlobal(allocatedKeyPointsMaskData.Length);
Marshal.Copy(allocatedKeyPointsMaskData, 0, ptr, allocatedKeyPointsMaskData.Length);
var nativeStruct = Marshal.PtrToStructure(ptr);
Marshal.FreeHGlobal(ptr);
keyPointsMask = new bool[MaxKeyPoints];
for (int i = 0; i < MaxKeyPoints; i++)
{
keyPointsMask[i] = Convert.ToBoolean(nativeStruct.Mask[i]);
}
return true;
}
catch (Exception e)
{
Debug.LogError("TryGetKeyPointsMask failed with the exception: " + e);
goto Failure;
}
Failure:
keyPointsMask = new bool[MaxKeyPoints];
return false;
}
public static string GetKeyPointName(KeyPointLocation location, int keyPointIndex)
{
int keypointEnum = (int)location + keyPointIndex;
if ((int)location > 0 && keyPointIndex == 4)
{
keypointEnum = FifthBoneKeypointValue(location);
location = KeyPointLocation.FifthBone;
}
if (!CheckKeypointEnumValid(location, keypointEnum))
{
return "Invalid KeyPoint";
}
return Enum.GetName(typeof(KeyPoints), keypointEnum);
}
public static bool GetKeyPointStatus(InputDevice handDevice, KeyPointLocation location, int keyPointIndex)
{
if (TryGetKeyPointsMask(handDevice, out bool[] handKeyPoints))
{
int keypointEnum = (int)location + keyPointIndex;
if ((int)location > 0 && keyPointIndex == 4)
{
keypointEnum = FifthBoneKeypointValue(location);
location = KeyPointLocation.FifthBone;
}
if (!CheckKeypointEnumValid(location, keypointEnum))
{
return false;
}
return handKeyPoints[keypointEnum];
}
return false;
}
private static bool CheckKeypointEnumValid(KeyPointLocation location, int keypointEnum)
{
if (keypointEnum < 0 || keypointEnum >= MaxKeyPoints)
{
return false;
}
Array enumValues = Enum.GetValues(typeof(KeyPointLocation));
int locationValue = (int)location;
int nextValue = MaxKeyPoints;
for (int i = 0; i < enumValues.Length - 1; i++)
{
if ((int)enumValues.GetValue(i) == locationValue)
{
nextValue = (int)enumValues.GetValue(i + 1);
break;
}
}
if (keypointEnum >= nextValue)
{
return false;
}
return true;
}
// Should not be used for Thumb.
private static int FifthBoneKeypointValue(KeyPointLocation location)
{
switch (location)
{
case KeyPointLocation.Index:
return (int)KeyPointLocation.FifthBone;
case KeyPointLocation.Middle:
return ((int)KeyPointLocation.FifthBone + 1);
case KeyPointLocation.Ring:
return ((int)KeyPointLocation.FifthBone + 2);
case KeyPointLocation.Pinky:
return ((int)KeyPointLocation.FifthBone + 3);
default:
return (int)location;
}
}
///
/// Native call for pre render Keypoints update.
///
/// bool to determine if pre render pose update should happen.
[DllImport(MagicLeapXrProviderNativeBindings.MagicLeapXrProviderDll, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetPreRenderPoseUpdate(bool enable);
[StructLayout(LayoutKind.Sequential)]
public readonly struct KeyPointsMask
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = (int)MaxKeyPoints)]
public readonly byte[] Mask;
}
}
}
}
}
}