// %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% using System; using System.Linq; using System.Text; using System.Runtime.InteropServices; using UnityEngine.XR.MagicLeap.Native; namespace UnityEngine.XR.MagicLeap { public static partial class InputSubsystem { public static partial class Extensions { public static partial class Controller { public static State GetState() { var device = InputSubsystem.Utils.FindMagicLeapDevice(InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.TrackedDevice); byte[] stateData = new byte[Marshal.SizeOf()]; if (device.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Controller.State, stateData)) { IntPtr ptr = Marshal.AllocHGlobal(stateData.Length); Marshal.Copy(stateData, 0, ptr, stateData.Length); var nativeState = Marshal.PtrToStructure(ptr); Marshal.FreeHGlobal(ptr); return new State(nativeState); } return default; } /// /// Exposed callback for controller trigger event. /// public static bool AttachTriggerListener(Action triggerCallback) { if (!_callbacksSet) { var result = SetCallbacks(); if (result != MLResult.Code.Ok) return false; _callbacksSet = true; } _onTriggerEvent += triggerCallback; return _callbacksSet; } public static void RemoveTriggerListener(Action triggerCallback) { _onTriggerEvent -= triggerCallback; } private static event Action _onTriggerEvent; /// /// Sets the callbacks for controller input events. /// private static MLResult SetCallbacks() { var callbacks = NativeBindings.MLInputControllerCallbacksEx.Create(); var result = NativeBindings.MLInputSetControllerCallbacksEx(MagicLeapXrProviderNativeBindings.GetInputHandle(), ref callbacks, IntPtr.Zero); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLInputSetControllerCallbacksEx)); return MLResult.Create(result); } /// /// The maximum number of controller touchpad touches that are supported. /// public const uint MaxControllerTouchpadTouches = 2; /// /// Flag used to mark whenever callbacks for controller inputs are already set. /// private static bool _callbacksSet; /// /// The calibration accuracy levels for controller. /// public enum CalibrationAccuracy : uint { /// /// The calibration accuracy is bad. /// Bad = 0, /// /// The calibration accuracy is low. /// Low = 1, /// /// The calibration accuracy is medium. /// Medium = 2, /// /// The calibration accuracy is high. /// High = 3, } public enum MLInputControllerType { None, Device } public enum MLInputControllerHand { None, Left, Right, Both } public enum MLInputControllerButton { None, Bumper, Menu, Count } /// /// Trigger events types. /// public enum MLInputControllerTriggerEvent { /// /// This is used when trigger is pulled down, and the normalized value is > 0. /// Pull, /// /// /This is used when trigger is fully released, and the normalized value is 0. /// Release, /// /// This is used when trigger is pulled and released within a short duration. /// Click, /// /// This is used when trigger is pulled and held for a longer duration. /// Hold } /// /// Contains information about the current state of an input controller. /// public struct State { internal State(NativeBindings.MLInputControllerStateEx nativeStruct) { Hand = nativeStruct.Hand; TouchesPositionAndForce = new Vector3[nativeStruct.TouchesPositionAndForce.Length]; TriggerNormalized = nativeStruct.TriggerNormalized; ButtonStates = nativeStruct.ButtonStates.Select(b => b > 0).ToArray(); IsTouchesActive = nativeStruct.IsTouchesActive.Select(b => b > 0).ToArray(); IsConnected = nativeStruct.IsConnected; TouchpadGestureData = nativeStruct.TouchpadGestureData; TouchpadGestureState = nativeStruct.TouchpadGestureState; HardwareIndex = nativeStruct.HardwareIndex; } public MLInputControllerHand Hand; /// /// Current touch position (x,y) and force (z). Position is in the [-1.0,1.0] range and force is in the [0.0,1.0] range. /// public Vector3[] TouchesPositionAndForce; /// /// Normalized trigger value [0.0,1.0] /// public float TriggerNormalized; public bool[] ButtonStates; public bool[] IsTouchesActive; public bool IsConnected; public TouchpadGesture.Data TouchpadGestureData; public TouchpadGesture.State TouchpadGestureState; public byte HardwareIndex; public override string ToString() => $"Hand: {Hand}, TouchesPositionAndForce:\n{string.Join(',',TouchesPositionAndForce)}, TriggerNormalized: { TriggerNormalized}, " + $"ButtonStates:\n{string.Join(',', ButtonStates)}, IsTouchesActive:\n{string.Join(',',IsTouchesActive)}, IsConnected: {IsConnected}, " + $"TouchpadGestureData: {TouchpadGestureData}, TouchpadGestureState: {TouchpadGestureState}, HardwareIndex: {HardwareIndex}"; } internal class NativeBindings : MagicLeapNativeBindings { /// /// Contains information about the current state of an input controller. /// [StructLayout(LayoutKind.Sequential)] public struct MLInputControllerStateEx { public uint Version; public MLInputControllerType Type; public MLInputControllerHand Hand; /// /// Current touch position (x,y) and force (z). Position is in the [-1.0,1.0] range and force is in the [0.0,1.0] range. /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)MaxControllerTouchpadTouches)] public Vector3[] TouchesPositionAndForce; /// /// Normalized trigger value [0.0,1.0] /// public float TriggerNormalized; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = (int)MLInputControllerButton.Count)] public byte[] ButtonStates; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = (int)MaxControllerTouchpadTouches)] public byte[] IsTouchesActive; [MarshalAs(UnmanagedType.I1)] public bool IsConnected; public TouchpadGesture.Data TouchpadGestureData; public TouchpadGesture.State TouchpadGestureState; public byte HardwareIndex; } /// /// Callback structure for . /// public delegate void OnTouchpadGestureEndDelegate(ushort controllerId, IntPtr touchpadGesture, IntPtr data); /// /// Callback structure for . /// public delegate void OnTouchpadGestureDelegate(ushort controllerId, IntPtr touchpadGesture, IntPtr data); /// /// Callback structure for . /// public delegate void OnTouchpadGestureContinueDelegate(ushort controllerId, IntPtr touchpadGesture, IntPtr data); /// /// Callback structure for . /// public delegate void OnButtonDownDelegate(ushort controllerId, MLInputControllerButton button, IntPtr data); /// /// Callback structure for . /// public delegate void OnButtonUpDelegate(ushort controllerId, MLInputControllerButton button, IntPtr data); /// /// Callback structure for . /// public delegate void OnButtonClickDelegate(ushort controllerId, MLInputControllerButton button, IntPtr data); /// /// Callback structure for . /// public delegate void OnTriggerDelegate(ushort controllerId, MLInputControllerTriggerEvent @event, float depth, IntPtr data); /// /// Callback structure for . /// public delegate void OnConnectDelegate(ushort controllerId, IntPtr data); /// /// Callback structure for . /// public delegate void OnDisconnectDelegate(ushort controllerId, IntPtr data); /// /// A structure containing information about the connected devices. /// [StructLayout(LayoutKind.Sequential)] public struct MLInputConnectedDevicesList { /// /// Version of this structure. /// public uint Version; /// /// Number of connected controllers. /// public uint ConnectedControllerCount; /// /// Pointer to the array of connected controller IDs. /// public IntPtr ConnectedControllerIds; }; /// /// A structure containing callbacks for input controller events. The final parameter to all the callbacks is a void *, /// which will point to whatever payload data the user provides in MLInputSetControllerCallbacksEx. Individual callbacks which /// are not required by the client can be NULL. This structure must be initialized by calling /// MLInputControllerCallbacksExInit() before use. /// [StructLayout(LayoutKind.Sequential)] public struct MLInputControllerCallbacksEx { /// /// Version of this structure. /// public uint Version; /// /// This callback will be invoked whenever a touch gesture is detected. This callback will be called for both discrete and /// continuous gestures. /// public OnTouchpadGestureDelegate OnTouchpadGesture; /// /// This callback will be invoked whenever a continuation of a touch gesture is detected. This callback will be called only /// for continuous gestures. /// public OnTouchpadGestureContinueDelegate OnTouchpadGestureContinue; /// /// This callback will be invoked whenever a continuous touch gesture ends. This callback will be called only for /// continuous gestures. /// public OnTouchpadGestureEndDelegate OnTouchpadGestureEnd; /// /// This callback will be invoked whenever a controller button is pressed. This callback will be called only for discrete /// gestures. /// public OnButtonDownDelegate OnButtonDown; /// /// This callback will be invoked whenever a controller button is released. /// public OnButtonUpDelegate OnButtonUp; /// /// This callback will be invoked whenever a controller button is pressed and released within a short duration. /// public OnButtonClickDelegate OnButtonClick; /// /// This callback will be invoked whenever a controller trigger state is changed. /// public OnTriggerDelegate OnTrigger; /// /// This callback will be invoked whenever a controller is connected. /// public OnConnectDelegate OnConnect; /// /// This callback will be invoked whenever a controller is disconnected. /// public OnDisconnectDelegate OnDisconnect; /// /// Create and return an initialized version of this struct. /// public static MLInputControllerCallbacksEx Create() { return new MLInputControllerCallbacksEx { Version = 3, OnTrigger = NativeBindings.OnTrigger }; } }; /// /// Sets the callbacks for controller input events. /// [DllImport(MLInputDll, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLInputSetControllerCallbacksEx(ulong Handle, ref MLInputControllerCallbacksEx Callbacks, IntPtr UserData); /// /// Gets the device IDs of all connected devices. /// [DllImport(MLInputDll, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLInputGetConnectedDevices(ulong Handle, IntPtr InoutDevices); /// /// Releases the contents of #MLInputConnectedDevicesList populated by #MLInputGetConnectedDevices. /// [DllImport(MLInputDll, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLInputReleaseConnectedDevicesList(ulong Handle, IntPtr Devices); /// /// This callback will be invoked whenever a controller trigger state is changed. /// [AOT.MonoPInvokeCallback(typeof(OnTriggerDelegate))] private static void OnTrigger(ushort controllerId, MLInputControllerTriggerEvent triggerEvent, float depth, IntPtr data) { _onTriggerEvent?.Invoke(controllerId, triggerEvent, depth); } } } } } }