/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2024. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ namespace Leap { using LeapInternal; using System; using System.Linq; using System.Threading; using UnityEngine; /// /// The Controller class is your main interface to the Leap Motion Controller. /// /// Create an instance of this Controller class to access frames of tracking /// data and configuration information.Frame data can be polled at any time /// using the Controller.Frame() function.Call frame() or frame(0) to get the /// most recent frame.Set the history parameter to a positive integer to access /// previous frames.A controller stores up to 60 frames in its frame history. /// /// /// Polling is an appropriate strategy for applications which already have an /// intrinsic update loop, such as a game. You can also subscribe to the FrameReady /// event to get tracking frames through an event delegate. /// /// If the current thread implements a SynchronizationContext that contains a message /// loop, events are posted to that threads message loop. Otherwise, events are called /// on an independent thread and applications must perform any needed synchronization /// or marshalling of data between threads. Note that Unity3D does not create an /// appropriate SynchronizationContext object. Typically, event handlers cannot access /// any Unity objects. /// /// @since 1.0 /// public class Controller : IController { Connection _connection; bool _disposed = false; string _serverNamespace = "Leap Service"; /// /// The SynchronizationContext used for dispatching events. /// /// By default the synchronization context of the thread creating the controller /// instance is used. You can change the context if desired. /// public SynchronizationContext EventContext { get { return _connection.EventContext; } set { _connection.EventContext = value; } } /// /// Dispatched when the connection is initialized (but not necessarily connected). /// /// Can be dispatched more than once, if connection is restarted. /// @since 3.0 /// public event EventHandler Init { add { if (_hasInitialized) value(this, new LeapEventArgs(LeapEvent.EVENT_INIT)); _init += value; } remove { _init -= value; } } private bool _hasInitialized = false; private EventHandler _init; /// /// Dispatched when the connection to the service is established. /// @since 3.0 /// public event EventHandler Connect { add { _connection.LeapConnection += value; } remove { _connection.LeapConnection -= value; } } private EventHandler _connect; /// /// Dispatched if the connection to the service is lost. /// @since 3.0 /// public event EventHandler Disconnect { add { _connection.LeapConnectionLost += value; } remove { _connection.LeapConnectionLost -= value; } } /// /// Dispatched when a tracking frame is ready. /// @since 3.0 /// public event EventHandler FrameReady { add { _connection.LeapFrame += value; } remove { _connection.LeapFrame -= value; } } /// /// Dispatched when an internal tracking frame is ready. /// @since 3.0 /// public event EventHandler InternalFrameReady { add { _connection.LeapInternalFrame += value; } remove { _connection.LeapInternalFrame -= value; } } /// /// Dispatched when a Leap Motion device is connected. /// @since 3.0 /// public event EventHandler Device { add { _connection.LeapDevice += value; } remove { _connection.LeapDevice -= value; } } /// /// Dispatched when a Leap Motion device is disconnected. /// @since 3.0 /// public event EventHandler DeviceLost { add { _connection.LeapDeviceLost += value; } remove { _connection.LeapDeviceLost -= value; } } /// /// Dispatched when a Leap device fails to initialize. /// @since 3.0 /// public event EventHandler DeviceFailure { add { _connection.LeapDeviceFailure += value; } remove { _connection.LeapDeviceFailure -= value; } } /// /// Dispatched when the system generates a loggable event. /// @since 3.0 /// public event EventHandler LogMessage { add { _connection.LeapLogEvent += value; } remove { _connection.LeapLogEvent -= value; } } /// /// Dispatched when a policy changes. /// @since 3.0 /// public event EventHandler PolicyChange { add { _connection.LeapPolicyChange += value; } remove { _connection.LeapPolicyChange -= value; } } /// /// Dispatched when a configuration setting changes. /// @since 3.0 /// [Obsolete("Config is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")] public event EventHandler ConfigChange { add { _connection.LeapConfigChange += value; } remove { _connection.LeapConfigChange -= value; } } /// /// Dispatched when the image distortion map changes. /// The distortion map can change when the Leap device switches orientation, /// or a new device becomes active. /// @since 3.0 /// public event EventHandler DistortionChange { add { _connection.LeapDistortionChange += value; } remove { _connection.LeapDistortionChange -= value; } } /// /// Dispatched when the service drops a tracking frame. /// public event EventHandler DroppedFrame { add { _connection.LeapDroppedFrame += value; } remove { _connection.LeapDroppedFrame -= value; } } /// /// Dispatched when an unrequested image is ready. /// @since 4.0 /// public event EventHandler ImageReady { add { _connection.LeapImage += value; } remove { _connection.LeapImage -= value; } } /// /// Dispatched whenever a thread wants to start profiling for a custom thread. /// The event is always dispatched from the thread itself. /// /// The event data will contain the name of the thread, as well as an array of /// all possible profiling blocks that could be entered on that thread. /// /// @since 4.0 /// public event Action BeginProfilingForThread { add { _connection.LeapBeginProfilingForThread += value; } remove { _connection.LeapBeginProfilingForThread -= value; } } /// /// Dispatched whenever a thread is finished profiling. The event is always /// dispatched from the thread itself. /// /// @since 4.0 /// public event Action EndProfilingForThread { add { _connection.LeapEndProfilingForThread += value; } remove { _connection.LeapEndProfilingForThread -= value; } } /// /// Dispatched whenever a thread enters a profiling block. The event is always /// dispatched from the thread itself. /// /// The event data will contain the name of the profiling block. /// /// @since 4.0 /// public event Action BeginProfilingBlock { add { _connection.LeapBeginProfilingBlock += value; } remove { _connection.LeapBeginProfilingBlock -= value; } } /// /// Dispatched whenever a thread ends a profiling block. The event is always /// dispatched from the thread itself. /// /// The event data will contain the name of the profiling block. /// /// @since 4.0 /// public event Action EndProfilingBlock { add { _connection.LeapEndProfilingBlock += value; } remove { _connection.LeapEndProfilingBlock -= value; } } /// /// Dispatched when point mapping change events are generated by the service. /// @since 4.0 /// public event EventHandler PointMappingChange { add { _connection.LeapPointMappingChange += value; } remove { _connection.LeapPointMappingChange -= value; } } /// /// Dispatched when a new HeadPose is available. /// public event EventHandler HeadPoseChange { add { _connection.LeapHeadPoseChange += value; } remove { _connection.LeapHeadPoseChange -= value; } } /// /// Dispatched when a Fiducial Marker has been tracked. /// public event EventHandler FiducialPose { add { _connection.LeapFiducialPose += value; } remove { _connection.LeapFiducialPose -= value; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } _connection.Dispose(); _disposed = true; } /// /// Constructs a Controller object. /// /// The default constructor uses a connection key of 0. /// /// @since 1.0 /// public Controller() : this(0) { } /// /// Constructs a Controller object using the specified connection key. /// /// All controller instances using the same key will use the same connection /// to the service. In general, an application should not use more than one connection /// for all its controllers. Each connection keeps its own cache of frames and images. /// /// @param connectionKey An identifier specifying the connection to use. If a /// connection with the specified key already exists, that connection is used. /// Otherwise, a new connection is created. /// @since 3.0 /// public Controller(int connectionKey, string serverNamespace = "Leap Service", bool supportsMultipleDevices = true) { _connection = Connection.GetConnection(new Connection.Key(connectionKey, serverNamespace)); _connection.EventContext = SynchronizationContext.Current; if (_connection.IsRunning) _hasInitialized = true; _connection.LeapInit += OnInit; _connection.LeapConnection += OnConnect; _connection.LeapConnectionLost += OnDisconnect; _serverNamespace = serverNamespace; StartConnection(); } /// /// Starts the connection. /// /// A connection starts automatically when created, but you can /// use this function to restart the connection after stopping it. /// /// @since 3.0 /// public void StartConnection() { _connection.Start(_serverNamespace); } /// /// Stops the connection. /// /// No more frames or other events are received from a stopped connection. You can /// restart with StartConnection(). /// /// @since 3.0 /// public void StopConnection() { _connection.Stop(); } /// /// Reports whether your application has a connection to the Leap Motion /// daemon/service. Can be true even if the Leap Motion hardware is not available. /// @since 1.2 /// public bool IsServiceConnected { get { return _connection.IsServiceConnected; } } /// /// Returns the current Leap service version information. /// public LEAP_VERSION ServiceVersion { get { return _connection.GetCurrentServiceVersion(); } } /// /// Checks whether a minimum or required tracking service version is installed. /// Gets the currently installed service version from the connection and checks whether /// the argument minServiceVersion is smaller or equal to it /// /// The minimum service version to check against /// The connection /// True if the version of the running service is equal to or less than the minimum version specified public static bool CheckRequiredServiceVersion(LEAP_VERSION minServiceVersion, Connection connection) { LEAP_VERSION currentServiceVersion = connection.GetCurrentServiceVersion(); // check that minServiceVersion is smaller or equal to the current service version if (minServiceVersion.major < currentServiceVersion.major) return true; else if (minServiceVersion.major == currentServiceVersion.major) { if (minServiceVersion.minor < currentServiceVersion.minor) return true; else if (minServiceVersion.minor == currentServiceVersion.minor && minServiceVersion.patch <= currentServiceVersion.patch) return true; } return false; } /// /// Checks whether a minimum or required tracking service version is installed. /// Gets the currently installed service version from the connection and checks whether /// the argument minServiceVersion is smaller or equal to it /// /// The minimum service version to check against /// True if the version of the running service is equal to or less than the minimum version specified public bool CheckRequiredServiceVersion(LEAP_VERSION minServiceVersion) { return Controller.CheckRequiredServiceVersion(minServiceVersion, _connection); } /// /// Requests setting and clearing policy flags on a specific device /// /// A request to change a policy is subject to user approval and a policy /// can be changed by the user at any time (using the Leap Motion settings dialog). /// The desired policy flags must be set every time an application runs. /// /// Policy changes are completed asynchronously and, because they are subject /// to user approval or system compatibility checks, may not complete successfully. Call /// Controller.IsPolicySet() after a suitable interval to test whether /// the change was accepted. /// @since 2.1.6 (5.4.4 for specific device) /// public void SetAndClearPolicy(PolicyFlag set, PolicyFlag clear, Device device = null) { _connection.SetAndClearPolicy(set, clear, device); } /// /// Requests setting a policy on a specific device /// /// A request to change a policy is subject to user approval and a policy /// can be changed by the user at any time (using the Leap Motion settings dialog). /// The desired policy flags must be set every time an application runs. /// /// Policy changes are completed asynchronously and, because they are subject /// to user approval or system compatibility checks, may not complete successfully. Call /// Controller.IsPolicySet() after a suitable interval to test whether /// the change was accepted. /// @since 2.1.6 (5.4.4 for specific device) /// public void SetPolicy(PolicyFlag policy, Device device = null) { _connection.SetPolicy(policy, device); } /// /// Requests clearing a policy on a specific device /// /// Policy changes are completed asynchronously and, because they are subject /// to user approval or system compatibility checks, may not complete successfully. Call /// Controller.IsPolicySet() after a suitable interval to test whether /// the change was accepted. /// @since 2.1.6 (5.4.4 for specific device) /// public void ClearPolicy(PolicyFlag policy, Device device = null) { _connection.ClearPolicy(policy, device); } /// /// Gets the active setting for a specific device. /// /// Keep in mind that setting a policy flag is asynchronous, so changes are /// not effective immediately after calling setPolicyFlag(). In addition, a /// policy request can be declined by the user. You should always set the /// policy flags required by your application at startup and check that the /// policy change request was successful after an appropriate interval. /// /// If the controller object is not connected to the Leap Motion software, then the default /// state for the selected policy is returned. /// /// @since 2.1.6 (5.4.4 for specific device) /// public bool IsPolicySet(PolicyFlag policy, Device device = null) { return _connection.IsPolicySet(policy, device); } /// /// Checks if the specified device is available. /// /// Device availability is determined by checking it has active policy flags set against it /// via its connection. /// public bool IsDeviceAvailable(Device device = null) { return _connection.IsDeviceAvailable(device); } /// /// Send a specific set of hints, if this does not include previously set ones, they will be cleared. /// /// The hints you wish to send /// An optional specific Device, otherwise the first found will be used public void RequestHandTrackingHints(string[] hints, Device device = null) { if (device == null) { device = Devices.ActiveDevices.FirstOrDefault(); } _connection.RequestHandTrackingHintsOnDevice(device.Handle, hints); } /// /// In most cases you should get Frame objects using the LeapProvider.CurrentFrame /// property. The data in Frame objects taken directly from a Leap.Controller instance /// is still in the Leap Motion frame of reference and will not match the hands /// displayed in a Unity scene. /// /// Returns a frame of tracking data from the Leap Motion software. Use the optional /// history parameter to specify which frame to retrieve. Call frame() or /// frame(0) to access the most recent frame; call frame(1) to access the /// previous frame, and so on. If you use a history value greater than the /// number of stored frames, then the controller returns an empty frame. /// /// @param history The age of the frame to return, counting backwards from /// the most recent frame (0) into the past and up to the maximum age (59). /// @returns The specified frame; or, if no history parameter is specified, /// the newest frame. If a frame is not available at the specified history /// position, an invalid Frame is returned. /// @since 1.0 /// public Frame Frame(int history = 0) { Frame frame = new Frame(); Frame(frame, history); return frame; } /// /// Identical to Frame(history) but instead of constructing a new frame and returning /// it, the user provides a frame object to be filled with data instead. /// public void Frame(Frame toFill, int history = 0) { LEAP_TRACKING_EVENT trackingEvent; _connection.Frames.Get(out trackingEvent, history); toFill.CopyFrom(ref trackingEvent); } /// /// Returns the timestamp of a recent tracking frame. Use the /// optional history parameter to specify how many frames in the past /// to retrieve the timestamp. Leave the history parameter as /// it's default value to return the timestamp of the most recent /// tracked frame. /// public long FrameTimestamp(int history = 0) { LEAP_TRACKING_EVENT trackingEvent; _connection.Frames.Get(out trackingEvent, history); return trackingEvent.info.timestamp; } /// /// Returns the frame object with all hands transformed by the specified /// transform matrix. /// public Frame GetTransformedFrame(LeapTransform trs, int history = 0) { return new Frame().CopyFrom(Frame(history)).Transform(trs); } /// /// Returns the Frame at the specified time, interpolating the data between existing frames, if necessary. /// public Frame GetInterpolatedFrame(Int64 time, Device device = null) { return _connection.GetInterpolatedFrame(time, device); } /// /// Returns the Frame at the specified time, interpolating the data between existing frames, if necessary. /// public Frame GetInterpolatedFrame(Int64 time) { return GetInterpolatedFrame(time, null); } /// /// Fills the Frame with data taken at the specified time, interpolating the data between existing frames, if necessary. /// public void GetInterpolatedFrame(Frame toFill, Int64 time, Device device = null) { _connection.GetInterpolatedFrame(toFill, time, device); } /// /// Returns the Head pose at the specified time, interpolating the data between existing frames, if necessary. /// public LEAP_HEAD_POSE_EVENT GetInterpolatedHeadPose(Int64 time) { return _connection.GetInterpolatedHeadPose(time); } public void GetInterpolatedHeadPose(ref LEAP_HEAD_POSE_EVENT toFill, Int64 time) { _connection.GetInterpolatedHeadPose(ref toFill, time); } /// /// Subscribes to the events coming from an individual device /// /// If this is not called, only the primary device will be subscribed. /// Will automatically unsubscribe the primary device if this is called /// on a secondary device, but not a primary one. /// /// @since 4.1 /// public void SubscribeToDeviceEvents(Device device) { _connection.SubscribeToDeviceEvents(device); } /// /// Unsubscribes from the events coming from an individual device /// /// This can be called safely, even if the device has not been subscribed. /// /// @since 4.1 /// public void UnsubscribeFromDeviceEvents(Device device) { _connection.UnsubscribeFromDeviceEvents(device); } /// /// Subscribes to the events coming from all devices /// /// @since 4.1 /// public void SubscribeToAllDevices() { for (int i = 1; i < Devices.Count; i++) { _connection.SubscribeToDeviceEvents(Devices[i]); } } /// /// Unsubscribes from the events coming from all devices /// /// @since 4.1 /// public void UnsubscribeFromAllDevices() { for (int i = 1; i < Devices.Count; i++) { _connection.UnsubscribeFromDeviceEvents(Devices[i]); } } public void TelemetryProfiling(ref LEAP_TELEMETRY_DATA telemetryData) { _connection.TelemetryProfiling(ref telemetryData); } public UInt64 TelemetryGetNow() { return LeapC.TelemetryGetNow(); } public void GetPointMapping(ref PointMapping pointMapping) { _connection.GetPointMapping(ref pointMapping); } /// /// This is a special variant of GetInterpolatedFrameFromTime, for use with special /// features that only require the position and orientation of the palm positions, and do /// not care about pose data or any other data. /// /// You must specify the id of the hand that you wish to get a transform for. If you specify /// an id that is not present in the interpolated frame, the output transform will be the /// identity transform. /// public void GetInterpolatedLeftRightTransform(Int64 time, Int64 sourceTime, int leftId, int rightId, Device device, out LeapTransform leftTransform, out LeapTransform rightTransform) { _connection.GetInterpolatedLeftRightTransform(time, sourceTime, leftId, rightId, device, out leftTransform, out rightTransform); } /// /// This is a special variant of GetInterpolatedFrameFromTime, for use with special /// features that only require the position and orientation of the palm positions, and do /// not care about pose data or any other data. /// /// You must specify the id of the hand that you wish to get a transform for. If you specify /// an id that is not present in the interpolated frame, the output transform will be the /// identity transform. /// public void GetInterpolatedLeftRightTransform(Int64 time, Int64 sourceTime, int leftId, int rightId, out LeapTransform leftTransform, out LeapTransform rightTransform) { GetInterpolatedLeftRightTransform(time, sourceTime, leftId, rightId, null, out leftTransform, out rightTransform); } public void GetInterpolatedFrameFromTime(Frame toFill, Int64 time, Int64 sourceTime, Device device = null) { _connection.GetInterpolatedFrameFromTime(toFill, time, sourceTime, device); } public UnityEngine.Matrix4x4 LeapExtrinsicCameraMatrix(Image.CameraType camera, Device device) { return _connection.LeapExtrinsicCameraMatrix(camera, device); } public UnityEngine.Vector3 RectilinearToPixel(Image.CameraType camera, UnityEngine.Vector3 ray) { return _connection.RectilinearToPixel(camera, ray); } public UnityEngine.Vector3 RectilinearToPixelEx(Image.CameraType camera, UnityEngine.Vector3 ray, Device device) { return _connection.RectilinearToPixelEx(device.Handle, camera, ray); } public UnityEngine.Vector3 PixelToRectilinearEx(Image.CameraType camera, UnityEngine.Vector3 pixel, Device device) { return _connection.PixelToRectilinearEx(device.Handle, camera, pixel); } /// /// Returns a timestamp value as close as possible to the current time. /// Values are in microseconds, as with all the other timestamp values. /// /// @since 2.2.7 /// public long Now() { return LeapC.GetNow(); } /// /// Reports whether this Controller is connected to the Leap Motion service and /// the Leap Motion hardware is plugged in. /// /// When you first create a Controller object, isConnected() returns false. /// After the controller finishes initializing and connects to the Leap Motion /// software and if the Leap Motion hardware is plugged in, isConnected() returns true. /// /// You can either handle the onConnect event using a Listener instance or /// poll the isConnected() function if you need to wait for your /// application to be connected to the Leap Motion software before performing some other /// operation. /// /// @since 1.0 /// public bool IsConnected { get { return IsServiceConnected && Devices.Count > 0; } } /// /// Returns a Config object, which you can use to query the Leap Motion system for /// configuration information. /// /// @since 1.0 /// [Obsolete("Config is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")] public Config Config { get { return null; } } /// /// The list of currently attached and recognized Leap Motion controller devices. /// /// The Device objects in the list describe information such as the range and /// tracking volume. /// /// Currently, the Leap Motion Controller only allows a single active device at a time, /// however there may be multiple devices physically attached and listed here. Any active /// device(s) are guaranteed to be listed first, however order is not determined beyond that. /// /// @since 1.0 /// public DeviceList Devices { get { return _connection.Devices; } } /// /// A list of any Leap Motion hardware devices that are physically connected to /// the client computer, but are not functioning correctly. The list contains /// FailedDevice objects containing the pnpID and the reason for failure. No /// other device information is available. /// /// @since 3.0 /// public FailedDeviceList FailedDevices() { return _connection.FailedDevices; } /// /// The supported controller policies. /// /// The supported policy flags are: /// /// **POLICY_BACKGROUND_FRAMES** -- requests that your application receives frames /// when it is not the foreground application for user input. /// /// The background frames policy determines whether an application /// receives frames of tracking data while in the background. By /// default, the Leap Motion software only sends tracking data to the foreground application. /// Only applications that need this ability should request the background /// frames policy. The "Allow Background Apps" checkbox must be enabled in the /// Leap Motion Control Panel or this policy will be denied. /// /// **POLICY_OPTIMIZE_HMD** -- request that the tracking be optimized for head-mounted /// tracking. /// /// The optimize HMD policy improves tracking in situations where the Leap /// Motion hardware is attached to a head-mounted display. This policy is /// not granted for devices that cannot be mounted to an HMD, such as /// Leap Motion controllers embedded in a laptop or keyboard. /// /// Some policies can be denied if the user has disabled the feature on /// their Leap Motion control panel. /// /// @since 1.0 /// public enum PolicyFlag { /// /// The default policy. /// POLICY_DEFAULT = 0, /// /// Receive background frames. /// POLICY_BACKGROUND_FRAMES = (1 << 0), /// /// Allow streaming images. /// POLICY_IMAGES = (1 << 1), /// /// Optimize the tracking for head-mounted device. /// POLICY_OPTIMIZE_HMD = (1 << 2), /// /// Allow pausing and unpausing of the Leap Motion service. /// POLICY_ALLOW_PAUSE_RESUME = (1 << 3), /// /// Allow streaming map point /// POLICY_MAP_POINTS = (1 << 7), /// /// Optimize the tracking for screen-top device. /// @since 5.0.0 /// POLICY_OPTIMIZE_SCREENTOP = (1 << 8), } protected virtual void OnInit(object sender, LeapEventArgs eventArgs) { _hasInitialized = true; } protected virtual void OnConnect(object sender, ConnectionEventArgs eventArgs) { } protected virtual void OnDisconnect(object sender, ConnectionLostEventArgs eventArgs) { _hasInitialized = false; } } }