Advertisement
Guest User

LeapServiceProvider.cs

a guest
Jun 23rd, 2019
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 23.71 KB | None | 0 0
  1. /******************************************************************************
  2.  * Copyright (C) Leap Motion, Inc. 2011-2018.                                 *
  3.  * Leap Motion proprietary and confidential.                                  *
  4.  *                                                                            *
  5.  * Use subject to the terms of the Leap Motion SDK Agreement available at     *
  6.  * https://developer.leapmotion.com/sdk_agreement, or another agreement       *
  7.  * between Leap Motion and you, your company or other organization.           *
  8.  ******************************************************************************/
  9.  
  10. /* Modified
  11.  * by: Zacryon
  12.  * Date: 23.06.2019
  13.  * Notes: Added a delay functionality. Modifications not marked.
  14.  */
  15.  
  16. using System;
  17. using System.Collections.Generic;
  18. using UnityEngine;
  19.  
  20. namespace Leap.Unity
  21. {
  22.     using Attributes;
  23.    
  24.     /// <summary>
  25.     /// The LeapServiceProvider provides tracked Leap Hand data and images from the device
  26.     /// via the Leap service running on the client machine.
  27.     /// </summary>
  28.     public class LeapServiceProvider : LeapProvider
  29.     {
  30.         #region Delay parameters and additional events
  31.         [Tooltip("Starts the hand tracking in delay mode if enabled.")]
  32.         public bool enableDelay = false;
  33.         [Tooltip("Delay is not supported for interpolation mode yet.\n" +
  34.                  "If you use the interpolation feature the delay settings will be ignored.")]
  35.         public bool _useInterpolation = false;
  36.         [Tooltip("Delay duration in milliseconds. Supports float point values as well.")]
  37.         public double delayInMilliseconds = 350;
  38.         #endregion
  39.  
  40.         #region Constants
  41.  
  42.         /// <summary>
  43.         /// Converts nanoseconds to seconds.
  44.         /// </summary>
  45.         protected const double NS_TO_S = 1e-6;
  46.  
  47.         /// <summary>
  48.         /// Converts seconds to nanoseconds.
  49.         /// </summary>
  50.         protected const double S_TO_NS = 1e6;
  51.  
  52.         /// <summary>
  53.         /// The transform array used for late-latching.
  54.         /// </summary>
  55.         protected const string HAND_ARRAY_GLOBAL_NAME = "_LeapHandTransforms";
  56.  
  57.         /// <summary>
  58.         /// The maximum number of times the provider will
  59.         /// attempt to reconnect to the service before giving up.
  60.         /// </summary>
  61.         protected const int MAX_RECONNECTION_ATTEMPTS = 5;
  62.  
  63.         /// <summary>
  64.         /// The number of frames to wait between each
  65.         /// reconnection attempt.
  66.         /// </summary>
  67.         protected const int RECONNECTION_INTERVAL = 180;
  68.  
  69.         #endregion
  70.  
  71.         #region Inspector
  72.  
  73.         public enum FrameOptimizationMode
  74.         {
  75.             None,
  76.             ReuseUpdateForPhysics,
  77.             ReusePhysicsForUpdate,
  78.         }
  79.         [Tooltip("When enabled, the provider will only calculate one leap frame instead of two.")]
  80.         [SerializeField]
  81.         protected FrameOptimizationMode _frameOptimization = FrameOptimizationMode.None;
  82.  
  83.         public enum PhysicsExtrapolationMode
  84.         {
  85.             None,
  86.             Auto,
  87.             Manual
  88.         }
  89.         [Tooltip("The mode to use when extrapolating physics.\n" +
  90.                  " None - No extrapolation is used at all.\n" +
  91.                  " Auto - Extrapolation is chosen based on the fixed timestep.\n" +
  92.                  " Manual - Extrapolation time is chosen manually by the user.")]
  93.         [SerializeField]
  94.         protected PhysicsExtrapolationMode _physicsExtrapolation = PhysicsExtrapolationMode.Auto;
  95.  
  96.         [Tooltip("The amount of time (in seconds) to extrapolate the physics data by.")]
  97.         [SerializeField]
  98.         protected float _physicsExtrapolationTime = 1.0f / 90.0f;
  99.  
  100. #if UNITY_2017_3_OR_NEWER
  101.         [Tooltip("When checked, profiling data from the LeapCSharp worker thread will be used to populate the UnityProfiler.")]
  102.         [EditTimeOnly]
  103. #else
  104.     [Tooltip("Worker thread profiling requires a Unity version of 2017.3 or greater.")]
  105.     [Disable]
  106. #endif
  107.         [SerializeField]
  108.         protected bool _workerThreadProfiling = false;
  109.  
  110.         #endregion
  111.  
  112.         #region Internal Settings & Memory
  113.  
  114.         // Extrapolate on Android to compensate for the latency introduced by its graphics
  115.         // pipeline.
  116. #if UNITY_ANDROID && !UNITY_EDITOR
  117.     protected int ExtrapolationAmount = 15;
  118.     protected int BounceAmount = 70;
  119. #else
  120.         protected int ExtrapolationAmount = 0;
  121.         protected int BounceAmount = 0;
  122. #endif
  123.  
  124.         protected Controller _leapController;
  125.         protected bool _isDestroyed;
  126.  
  127.         protected SmoothedFloat _fixedOffset = new SmoothedFloat();
  128.         protected SmoothedFloat _smoothedTrackingLatency = new SmoothedFloat();
  129.         protected long _unityToLeapOffset;
  130.  
  131.         protected Frame _untransformedUpdateFrame;
  132.         protected Frame _transformedUpdateFrame;
  133.         protected Frame _untransformedFixedFrame;
  134.         protected Frame _transformedFixedFrame;
  135.  
  136.         #endregion
  137.  
  138.         #region Edit-time Frame Data
  139.  
  140.         private Action<Device> _onDeviceSafe;
  141.         /// <summary>
  142.         /// A utility event to get a callback whenever a new device is connected to the service.
  143.         /// This callback will ALSO trigger a callback upon subscription if a device is already
  144.         /// connected.
  145.         ///
  146.         /// For situations with multiple devices OnDeviceSafe will be dispatched once for each device.
  147.         /// </summary>
  148.         public event Action<Device> OnDeviceSafe
  149.         {
  150.             add
  151.             {
  152.                 if (_leapController != null && _leapController.IsConnected)
  153.                 {
  154.                     foreach (var device in _leapController.Devices)
  155.                     {
  156.                         value(device);
  157.                     }
  158.                 }
  159.                 _onDeviceSafe += value;
  160.             }
  161.             remove
  162.             {
  163.                 _onDeviceSafe -= value;
  164.             }
  165.         }
  166.  
  167. #if UNITY_EDITOR
  168.         private Frame _backingUntransformedEditTimeFrame = null;
  169.         private Frame _untransformedEditTimeFrame
  170.         {
  171.             get
  172.             {
  173.                 if (_backingUntransformedEditTimeFrame == null)
  174.                 {
  175.                     _backingUntransformedEditTimeFrame = new Frame();
  176.                 }
  177.                 return _backingUntransformedEditTimeFrame;
  178.             }
  179.         }
  180.         private Frame _backingEditTimeFrame = null;
  181.         private Frame _editTimeFrame
  182.         {
  183.             get
  184.             {
  185.                 if (_backingEditTimeFrame == null)
  186.                 {
  187.                     _backingEditTimeFrame = new Frame();
  188.                 }
  189.                 return _backingEditTimeFrame;
  190.             }
  191.         }
  192.  
  193.         private Dictionary<TestHandFactory.TestHandPose, Hand> _cachedLeftHands
  194.           = new Dictionary<TestHandFactory.TestHandPose, Hand>();
  195.         private Hand _editTimeLeftHand
  196.         {
  197.             get
  198.             {
  199.                 Hand cachedHand;
  200.                 if (_cachedLeftHands.TryGetValue(editTimePose, out cachedHand))
  201.                 {
  202.                     return cachedHand;
  203.                 }
  204.                 else
  205.                 {
  206.                     cachedHand = TestHandFactory.MakeTestHand(isLeft: true, pose: editTimePose);
  207.                     _cachedLeftHands[editTimePose] = cachedHand;
  208.                     return cachedHand;
  209.                 }
  210.             }
  211.         }
  212.  
  213.         private Dictionary<TestHandFactory.TestHandPose, Hand> _cachedRightHands
  214.           = new Dictionary<TestHandFactory.TestHandPose, Hand>();
  215.         private Hand _editTimeRightHand
  216.         {
  217.             get
  218.             {
  219.                 Hand cachedHand;
  220.                 if (_cachedRightHands.TryGetValue(editTimePose, out cachedHand))
  221.                 {
  222.                     return cachedHand;
  223.                 }
  224.                 else
  225.                 {
  226.                     cachedHand = TestHandFactory.MakeTestHand(isLeft: false, pose: editTimePose);
  227.                     _cachedRightHands[editTimePose] = cachedHand;
  228.                     return cachedHand;
  229.                 }
  230.             }
  231.         }
  232.  
  233. #endif
  234.  
  235.         #endregion
  236.  
  237.         #region LeapProvider Implementation
  238.  
  239.         public override Frame CurrentFrame
  240.         {
  241.             get
  242.             {
  243. #if UNITY_EDITOR
  244.                 if (!Application.isPlaying)
  245.                 {
  246.                     _editTimeFrame.Hands.Clear();
  247.                     _untransformedEditTimeFrame.Hands.Clear();
  248.                     _untransformedEditTimeFrame.Hands.Add(_editTimeLeftHand);
  249.                     _untransformedEditTimeFrame.Hands.Add(_editTimeRightHand);
  250.                     transformFrame(_untransformedEditTimeFrame, _editTimeFrame);
  251.                     return _editTimeFrame;
  252.                 }
  253. #endif
  254.                 if (_frameOptimization == FrameOptimizationMode.ReusePhysicsForUpdate)
  255.                 {
  256.                     return _transformedFixedFrame;
  257.                 }
  258.                 else
  259.                 {
  260.                     return _transformedUpdateFrame;
  261.                 }
  262.             }
  263.         }
  264.  
  265.         public override Frame CurrentFixedFrame
  266.         {
  267.             get
  268.             {
  269. #if UNITY_EDITOR
  270.                 if (!Application.isPlaying)
  271.                 {
  272.                     _editTimeFrame.Hands.Clear();
  273.                     _untransformedEditTimeFrame.Hands.Clear();
  274.                     _untransformedEditTimeFrame.Hands.Add(_editTimeLeftHand);
  275.                     _untransformedEditTimeFrame.Hands.Add(_editTimeRightHand);
  276.                     transformFrame(_untransformedEditTimeFrame, _editTimeFrame);
  277.                     return _editTimeFrame;
  278.                 }
  279. #endif
  280.                 if (_frameOptimization == FrameOptimizationMode.ReuseUpdateForPhysics)
  281.                 {
  282.                     return _transformedUpdateFrame;
  283.                 }
  284.                 else
  285.                 {
  286.                     return _transformedFixedFrame;
  287.                 }
  288.             }
  289.         }
  290.  
  291.         #endregion
  292.  
  293.         #region Unity Events
  294.  
  295.         protected virtual void Reset()
  296.         {
  297.             editTimePose = TestHandFactory.TestHandPose.DesktopModeA;
  298.         }
  299.  
  300.         protected virtual void Awake()
  301.         {
  302.             _fixedOffset.delay = 0.4f;
  303.             _smoothedTrackingLatency.SetBlend(0.99f, 0.0111f);
  304.         }
  305.  
  306.         protected virtual void Start()
  307.         {
  308.             createController();
  309.             _transformedUpdateFrame = new Frame();
  310.             _transformedFixedFrame = new Frame();
  311.             _untransformedUpdateFrame = new Frame();
  312.             _untransformedFixedFrame = new Frame();
  313.         }
  314.  
  315.         protected virtual void Update()
  316.         {
  317.             if (_workerThreadProfiling)
  318.             {
  319.                 LeapProfiling.Update();
  320.             }
  321.  
  322.             if (!checkConnectionIntegrity()) { return; }
  323.  
  324. #if UNITY_EDITOR
  325.             if (UnityEditor.EditorApplication.isCompiling)
  326.             {
  327.                 UnityEditor.EditorApplication.isPlaying = false;
  328.                 Debug.LogWarning("Unity hot reloading not currently supported. Stopping Editor Playback.");
  329.                 return;
  330.             }
  331. #endif
  332.  
  333.             _fixedOffset.Update(Time.time - Time.fixedTime, Time.deltaTime);
  334.  
  335.             if (_frameOptimization == FrameOptimizationMode.ReusePhysicsForUpdate)
  336.             {
  337.                 DispatchUpdateFrameEvent(_transformedFixedFrame);
  338.                 return;
  339.             }
  340.  
  341.             if (_useInterpolation)
  342.             {
  343. #if !UNITY_ANDROID || UNITY_EDITOR
  344.                 _smoothedTrackingLatency.value = Mathf.Min(_smoothedTrackingLatency.value, 30000f);
  345.                 _smoothedTrackingLatency.Update((float)(_leapController.Now() - _leapController.FrameTimestamp()), Time.deltaTime);
  346. #endif
  347.                 long timestamp = CalculateInterpolationTime() + (ExtrapolationAmount * 1000);
  348.                 _unityToLeapOffset = timestamp - (long)(Time.time * S_TO_NS);
  349.  
  350.                 _leapController.GetInterpolatedFrameFromTime(_untransformedUpdateFrame, timestamp, CalculateInterpolationTime() - (BounceAmount * 1000));
  351.             }
  352.             else
  353.             {
  354.                 _leapController.Frame(_untransformedUpdateFrame);
  355.             }
  356.  
  357.             if (_untransformedUpdateFrame != null)
  358.             {
  359.                 transformFrame(_untransformedUpdateFrame, _transformedUpdateFrame);
  360.  
  361.                 DispatchUpdateFrameEvent(_transformedUpdateFrame);
  362.             }
  363.         }
  364.  
  365.         protected virtual void FixedUpdate()
  366.         {
  367.             if (_frameOptimization == FrameOptimizationMode.ReuseUpdateForPhysics)
  368.             {
  369.                 DispatchFixedFrameEvent(_transformedUpdateFrame);
  370.                 return;
  371.             }
  372.  
  373.             if (_useInterpolation)
  374.             {
  375.  
  376.                 long timestamp;
  377.                 switch (_frameOptimization)
  378.                 {
  379.                     case FrameOptimizationMode.None:
  380.                         // By default we use Time.fixedTime to ensure that our hands are on the same
  381.                         // timeline as Update.  We add an extrapolation value to help compensate
  382.                         // for latency.
  383.                         float extrapolatedTime = Time.fixedTime + CalculatePhysicsExtrapolation();
  384.                         timestamp = (long)(extrapolatedTime * S_TO_NS) + _unityToLeapOffset;
  385.                         break;
  386.                     case FrameOptimizationMode.ReusePhysicsForUpdate:
  387.                         // If we are re-using physics frames for update, we don't even want to care
  388.                         // about Time.fixedTime, just grab the most recent interpolated timestamp
  389.                         // like we are in Update.
  390.                         timestamp = CalculateInterpolationTime() + (ExtrapolationAmount * 1000);
  391.                         break;
  392.                     default:
  393.                         throw new System.InvalidOperationException(
  394.                           "Unexpected frame optimization mode: " + _frameOptimization);
  395.                 }
  396.                 _leapController.GetInterpolatedFrame(_untransformedFixedFrame, timestamp);
  397.  
  398.             }
  399.             else
  400.             {
  401.                 _leapController.Frame(_untransformedFixedFrame);
  402.             }
  403.  
  404.             if (_untransformedFixedFrame != null)
  405.             {
  406.                 transformFrame(_untransformedFixedFrame, _transformedFixedFrame);
  407.  
  408.                 DispatchFixedFrameEvent(_transformedFixedFrame);
  409.             }
  410.         }
  411.  
  412.         protected virtual void OnDestroy()
  413.         {
  414.             destroyController();
  415.             _isDestroyed = true;
  416.         }
  417.  
  418.         protected virtual void OnApplicationPause(bool isPaused)
  419.         {
  420.             if (_leapController != null)
  421.             {
  422.                 if (isPaused)
  423.                 {
  424.                     _leapController.StopConnection();
  425.                 }
  426.                 else
  427.                 {
  428.                     _leapController.StartConnection();
  429.                 }
  430.             }
  431.         }
  432.  
  433.         protected virtual void OnApplicationQuit()
  434.         {
  435.             destroyController();
  436.             _isDestroyed = true;
  437.         }
  438.  
  439.         public float CalculatePhysicsExtrapolation()
  440.         {
  441.             switch (_physicsExtrapolation)
  442.             {
  443.                 case PhysicsExtrapolationMode.None:
  444.                     return 0;
  445.                 case PhysicsExtrapolationMode.Auto:
  446.                     return Time.fixedDeltaTime;
  447.                 case PhysicsExtrapolationMode.Manual:
  448.                     return _physicsExtrapolationTime;
  449.                 default:
  450.                     throw new System.InvalidOperationException(
  451.                       "Unexpected physics extrapolation mode: " + _physicsExtrapolation);
  452.             }
  453.         }
  454.  
  455.         #endregion
  456.  
  457.         #region Public API
  458.  
  459.         /// <summary>
  460.         /// Returns the Leap Controller instance.
  461.         /// </summary>
  462.         public Controller GetLeapController()
  463.         {
  464. #if UNITY_EDITOR
  465.             // Null check to deal with hot reloading.
  466.             if (!_isDestroyed && _leapController == null)
  467.             {
  468.                 createController();
  469.             }
  470. #endif
  471.             return _leapController;
  472.         }
  473.  
  474.         /// <summary>
  475.         /// Returns true if the Leap Motion hardware is plugged in and this application is
  476.         /// connected to the Leap Motion service.
  477.         /// </summary>
  478.         public bool IsConnected()
  479.         {
  480.             return GetLeapController().IsConnected;
  481.         }
  482.  
  483.         /// <summary>
  484.         /// Retransforms hand data from Leap space to the space of the Unity transform.
  485.         /// This is only necessary if you're moving the LeapServiceProvider around in a
  486.         /// custom script and trying to access Hand data from it directly afterward.
  487.         /// </summary>
  488.         public void RetransformFrames()
  489.         {
  490.             transformFrame(_untransformedUpdateFrame, _transformedUpdateFrame);
  491.             transformFrame(_untransformedFixedFrame, _transformedFixedFrame);
  492.         }
  493.  
  494.         /// <summary>
  495.         /// Copies property settings from this LeapServiceProvider to the target
  496.         /// LeapXRServiceProvider where applicable. Does not modify any XR-specific settings
  497.         /// that only exist on the LeapXRServiceProvider.
  498.         /// </summary>
  499.         public void CopySettingsToLeapXRServiceProvider(
  500.             LeapXRServiceProvider leapXRServiceProvider)
  501.         {
  502.             leapXRServiceProvider._frameOptimization = _frameOptimization;
  503.             leapXRServiceProvider._physicsExtrapolation = _physicsExtrapolation;
  504.             leapXRServiceProvider._physicsExtrapolationTime = _physicsExtrapolationTime;
  505.             leapXRServiceProvider._workerThreadProfiling = _workerThreadProfiling;
  506.         }
  507.  
  508.         #region Delay event handlers
  509.         /// <summary>
  510.         /// Event handler, which enables the delay mode. Does not use any arguments.
  511.         /// </summary>
  512.         public void EnableDelayHandler(System.Object sender, EventArgs evtArgs)
  513.         {
  514.             if (_leapController == null)
  515.                 createController();
  516.  
  517.             enableDelay = true;
  518.             _leapController.SetDelayActive(enableDelay);
  519.         }
  520.         /// <summary>
  521.         /// Event handler, which disables the delay mode. Does not use any arguments.
  522.         /// </summary>
  523.         public void DisableDelayHandler(System.Object sender, EventArgs evtArgs)
  524.         {
  525.             enableDelay = false;
  526.             if (_leapController != null)
  527.                 _leapController.SetDelayActive(enableDelay);
  528.         }
  529.         #endregion
  530.  
  531.         #endregion
  532.  
  533.         #region Internal Methods
  534.  
  535.         protected virtual long CalculateInterpolationTime(bool endOfFrame = false)
  536.         {
  537. #if UNITY_ANDROID && !UNITY_EDITOR
  538.       return _leapController.Now() - 16000;
  539. #else
  540.             if (_leapController != null)
  541.             {
  542.                 return _leapController.Now() - (long)_smoothedTrackingLatency.value;
  543.             }
  544.             else
  545.             {
  546.                 return 0;
  547.             }
  548. #endif
  549.         }
  550.  
  551.         /// <summary>
  552.         /// Initializes Leap Motion policy flags.
  553.         /// </summary>
  554.         protected virtual void initializeFlags()
  555.         {
  556.             if (_leapController == null)
  557.             {
  558.                 return;
  559.             }
  560.  
  561.             _leapController.ClearPolicy(Controller.PolicyFlag.POLICY_DEFAULT);
  562.         }
  563.  
  564.         /// <summary>
  565.         /// Creates an instance of a Controller, initializing its policy flags and
  566.         /// subscribing to its connection event.
  567.         /// Also sets the delay mode and value if provided.
  568.         /// Triggers an OnControllerCreated event as soon as the controller was instantiated.
  569.         /// </summary>
  570.         protected void createController()
  571.         {
  572.             if (_leapController != null)
  573.             {
  574.                 return;
  575.             }
  576.  
  577.             _leapController = new Controller(enableDelay, delayInMilliseconds);
  578.             _leapController.Device += (s, e) => {
  579.                 if (_onDeviceSafe != null)
  580.                 {
  581.                     _onDeviceSafe(e.Device);
  582.                 }
  583.             };
  584.  
  585.             if (_leapController.IsConnected)
  586.             {
  587.                 initializeFlags();
  588.             }
  589.             else
  590.             {
  591.                 _leapController.Device += onHandControllerConnect;
  592.             }
  593.  
  594.             if (_workerThreadProfiling)
  595.             {
  596.                 //A controller will report profiling statistics for the duration of it's lifetime
  597.                 //so these events will never be unsubscribed from.
  598.                 _leapController.EndProfilingBlock += LeapProfiling.EndProfilingBlock;
  599.                 _leapController.BeginProfilingBlock += LeapProfiling.BeginProfilingBlock;
  600.  
  601.                 _leapController.EndProfilingForThread += LeapProfiling.EndProfilingForThread;
  602.                 _leapController.BeginProfilingForThread += LeapProfiling.BeginProfilingForThread;
  603.             }
  604.         }
  605.  
  606.         /// <summary>
  607.         /// Stops the connection for the existing instance of a Controller, clearing old
  608.         /// policy flags and resetting the Controller to null.
  609.         /// Triggers an OnControllerDestroyed shortly before the controller is completely destroyed.
  610.         /// </summary>
  611.         protected void destroyController()
  612.         {
  613.             if (_leapController != null)
  614.             {
  615.                 if (_leapController.IsConnected)
  616.                 {
  617.                     _leapController.ClearPolicy(Controller.PolicyFlag.POLICY_OPTIMIZE_HMD);
  618.                 }
  619.                 _leapController.StopConnection();
  620.                 _leapController = null;
  621.             }
  622.         }
  623.  
  624.         private int _framesSinceServiceConnectionChecked = 0;
  625.         private int _numberOfReconnectionAttempts = 0;
  626.         /// <summary>
  627.         /// Checks whether this provider is connected to a service;
  628.         /// If it is not, attempt to reconnect at regular intervals
  629.         /// for MAX_RECONNECTION_ATTEMPTS
  630.         /// </summary>
  631.         protected bool checkConnectionIntegrity()
  632.         {
  633.             if (_leapController.IsServiceConnected)
  634.             {
  635.                 _framesSinceServiceConnectionChecked = 0;
  636.                 _numberOfReconnectionAttempts = 0;
  637.                 return true;
  638.             }
  639.             else if (_numberOfReconnectionAttempts < MAX_RECONNECTION_ATTEMPTS)
  640.             {
  641.                 _framesSinceServiceConnectionChecked++;
  642.  
  643.                 if (_framesSinceServiceConnectionChecked > RECONNECTION_INTERVAL)
  644.                 {
  645.                     _framesSinceServiceConnectionChecked = 0;
  646.                     _numberOfReconnectionAttempts++;
  647.  
  648.                     Debug.LogWarning("Leap Service not connected; attempting to reconnect for try " +
  649.                                      _numberOfReconnectionAttempts + "/" + MAX_RECONNECTION_ATTEMPTS +
  650.                                      "...", this);
  651.                     using (new ProfilerSample("Reconnection Attempt"))
  652.                     {
  653.                         destroyController();
  654.                         createController();
  655.                     }
  656.                 }
  657.             }
  658.             return false;
  659.         }
  660.  
  661.         protected void onHandControllerConnect(object sender, LeapEventArgs args)
  662.         {
  663.             initializeFlags();
  664.  
  665.             if (_leapController != null)
  666.             {
  667.                 _leapController.Device -= onHandControllerConnect;
  668.             }
  669.         }
  670.  
  671.         protected virtual void transformFrame(Frame source, Frame dest)
  672.         {
  673.             dest.CopyFrom(source).Transform(transform.GetLeapMatrix());
  674.         }
  675.  
  676.         #endregion
  677.  
  678.     }
  679.  
  680. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement