/// <summary>
        /// Forces the given object to be released by any hands currently holding it.  Will return true
        /// only if there was at least one hand holding the object.
        /// </summary>
        public bool ReleaseObject(IInteractionBehaviour graspedObject)
        {
            if (!_graspedBehaviours.Remove(graspedObject))
            {
                return(false);
            }

            foreach (var interactionHand in _idToInteractionHand.Values)
            {
                if (interactionHand.graspedObject == graspedObject)
                {
                    if (interactionHand.isUntracked)
                    {
                        interactionHand.MarkTimeout();
                    }
                    else
                    {
                        if (_graspingEnabled)
                        {
                            INTERACTION_HAND_RESULT result = new INTERACTION_HAND_RESULT();
                            result.classification = ManipulatorMode.Contact;
                            result.handFlags      = HandResultFlags.ManipulatorMode;
                            result.instanceHandle = new INTERACTION_SHAPE_INSTANCE_HANDLE();
                            InteractionC.OverrideHandResult(ref _scene, (uint)interactionHand.hand.Id, ref result);
                        }
                        interactionHand.ReleaseObject();
                    }
                }
            }

            return(true);
        }
        protected virtual INTERACTION_HAND_RESULT getHandResults(InteractionHand hand)
        {
            if (!_graspingEnabled)
            {
                INTERACTION_HAND_RESULT result = new INTERACTION_HAND_RESULT();
                result.classification = ManipulatorMode.Contact;
                result.handFlags      = HandResultFlags.ManipulatorMode;
                result.instanceHandle = new INTERACTION_SHAPE_INSTANCE_HANDLE();
                return(result);
            }

            INTERACTION_HAND_RESULT handResult;

            InteractionC.GetHandResult(ref _scene,
                                       (uint)hand.hand.Id,
                                       out handResult);
            return(handResult);
        }
        protected virtual void updateInteractionStateChanges(Frame frame)
        {
            var hands = frame.Hands;

            INTERACTION_HAND_RESULT handResult = new INTERACTION_HAND_RESULT();

            //First loop through all the hands and get their classifications from the engine
            for (int i = 0; i < hands.Count; i++)
            {
                Hand hand = hands[i];

                bool handResultForced = false;

                //Get the InteractionHand associated with this hand id
                InteractionHand interactionHand;
                if (!_idToInteractionHand.TryGetValue(hand.Id, out interactionHand))
                {
                    //First we see if there is an untracked interactionHand that can be re-connected using this one
                    InteractionHand untrackedInteractionHand = null;
                    foreach (var pair in _idToInteractionHand)
                    {
                        //If the old ieHand is untracked, and the handedness matches, we re-connect it
                        if (pair.Value.isUntracked && pair.Value.hand.IsLeft == hand.IsLeft)
                        {
                            untrackedInteractionHand = pair.Value;
                            break;
                        }
                    }

                    if (untrackedInteractionHand != null)
                    {
                        //If we found an untrackedIeHand, use it!
                        interactionHand = untrackedInteractionHand;
                        //Remove the old id from the mapping
                        _idToInteractionHand.Remove(untrackedInteractionHand.hand.Id);
                        _idToInteractionHand[hand.Id] = interactionHand;

                        try {
                            //This also dispatched InteractionObject.OnHandRegainedTracking()
                            interactionHand.RegainTracking(hand);

                            if (interactionHand.graspedObject == null)
                            {
                                continue;
                            }

                            // NotifyHandRegainedTracking() did not throw, continue on to NotifyHandsHoldPhysics().
                            dispatchOnHandsHolding(hands, interactionHand.graspedObject, isPhysics: true);
                        } catch (Exception e) {
                            _activityManager.NotifyMisbehaving(interactionHand.graspedObject);
                            Debug.LogException(e);
                            continue;
                        }

                        //Override the existing classification to force the hand to grab the old object
                        handResultForced          = true;
                        handResult.classification = ManipulatorMode.Grasp;
                        handResult.handFlags      = HandResultFlags.ManipulatorMode;
                        handResult.instanceHandle = interactionHand.graspedObject.ShapeInstanceHandle;

                        if (_graspingEnabled)
                        {
                            InteractionC.OverrideHandResult(ref _scene, (uint)hand.Id, ref handResult);
                        }
                    }
                    else
                    {
                        //Otherwise just create a new one
                        interactionHand = new InteractionHand(hand);
                        _idToInteractionHand[hand.Id] = interactionHand;
                    }
                }

                if (!handResultForced)
                {
                    handResult = getHandResults(interactionHand);
                }

                interactionHand.UpdateHand(hand);

                if (!interactionHand.isUserGrasp)
                {
                    switch (handResult.classification)
                    {
                    case ManipulatorMode.Grasp:
                    {
                        IInteractionBehaviour interactionBehaviour;
                        if (_instanceHandleToBehaviour.TryGetValue(handResult.instanceHandle, out interactionBehaviour))
                        {
                            if (interactionHand.graspedObject == null)
                            {
                                if (!interactionBehaviour.IsBeingGrasped)
                                {
                                    _graspedBehaviours.Add(interactionBehaviour);
                                }

                                try {
                                    interactionHand.GraspObject(interactionBehaviour, isUserGrasp: false);

                                    //the grasp callback might have caused the object to become ungrasped
                                    //the component might have also destroyed itself!
                                    if (interactionHand.graspedObject == interactionBehaviour && interactionBehaviour != null)
                                    {
                                        dispatchOnHandsHolding(hands, interactionBehaviour, isPhysics: true);
                                    }
                                } catch (Exception e) {
                                    _activityManager.NotifyMisbehaving(interactionBehaviour);
                                    Debug.LogException(e);
                                    continue;
                                }
                            }
                        }
                        else
                        {
                            Debug.LogError("Recieved a hand result with an unkown handle " + handResult.instanceHandle.handle);
                        }
                        break;
                    }

                    case ManipulatorMode.Contact:
                    {
                        if (interactionHand.graspedObject != null)
                        {
                            if (interactionHand.graspedObject.GraspingHandCount == 1)
                            {
                                _graspedBehaviours.Remove(interactionHand.graspedObject);
                            }

                            try {
                                interactionHand.ReleaseObject();
                            } catch (Exception e) {
                                _activityManager.NotifyMisbehaving(interactionHand.graspedObject);
                                Debug.LogException(e);
                                continue;
                            }
                        }
                        break;
                    }

                    default:
                        throw new InvalidOperationException("Unexpected classification " + handResult.classification);
                    }
                }
            }

            //Loop through all ieHands to check for timeouts and loss of tracking
            foreach (var pair in _idToInteractionHand)
            {
                var id     = pair.Key;
                var ieHand = pair.Value;

                float handAge = Time.unscaledTime - ieHand.lastTimeUpdated;
                //Check to see if the hand is at least 1 frame old
                //We assume it has become untracked if this is the case
                if (handAge > 0)
                {
                    //If the hand isn't grasping anything, just remove it
                    if (ieHand.graspedObject == null)
                    {
                        _handIdsToRemove.Add(id);
                        continue;
                    }

                    //If is isn't already marked as untracked, mark it as untracked
                    if (!ieHand.isUntracked)
                    {
                        try {
                            //This also dispatches InteractionObject.OnHandLostTracking()
                            ieHand.MarkUntracked();
                        } catch (Exception e) {
                            _activityManager.NotifyMisbehaving(ieHand.graspedObject);
                            Debug.LogException(e);
                        }
                    }

                    //If the age is longer than the timeout, we also remove it from the list
                    if (handAge >= ieHand.maxSuspensionTime)
                    {
                        _handIdsToRemove.Add(id);

                        try {
                            if (ieHand.graspedObject.GraspingHandCount == 1)
                            {
                                _graspedBehaviours.Remove(ieHand.graspedObject);
                            }

                            //This also dispatched InteractionObject.OnHandTimeout()
                            ieHand.MarkTimeout();
                        } catch (Exception e) {
                            _activityManager.NotifyMisbehaving(ieHand.graspedObject);
                            Debug.LogException(e);
                        }
                    }
                }
            }

            //Loop through the stale ids and remove them from the map
            for (int i = 0; i < _handIdsToRemove.Count; i++)
            {
                _idToInteractionHand.Remove(_handIdsToRemove[i]);
            }
            _handIdsToRemove.Clear();
        }
    protected virtual void updateInteractionStateChanges(Frame frame) {
      var hands = frame.Hands;

      INTERACTION_HAND_RESULT handResult = new INTERACTION_HAND_RESULT();

      //First loop through all the hands and get their classifications from the engine
      for (int i = 0; i < hands.Count; i++) {
        Hand hand = hands[i];

        bool handResultForced = false;

        //Get the InteractionHand associated with this hand id
        InteractionHand interactionHand;
        if (!_idToInteractionHand.TryGetValue(hand.Id, out interactionHand)) {

          //First we see if there is an untracked interactionHand that can be re-connected using this one
          InteractionHand untrackedInteractionHand = null;
          foreach (var pair in _idToInteractionHand) {
            //If the old ieHand is untracked, and the handedness matches, we re-connect it
            if (pair.Value.isUntracked && pair.Value.hand.IsLeft == hand.IsLeft) {
              untrackedInteractionHand = pair.Value;
              break;
            }
          }

          if (untrackedInteractionHand != null) {
            //If we found an untrackedIeHand, use it!
            interactionHand = untrackedInteractionHand;
            //Remove the old id from the mapping
            _idToInteractionHand.Remove(untrackedInteractionHand.hand.Id);
            _idToInteractionHand[hand.Id] = interactionHand;

            try {
              //This also dispatched InteractionObject.OnHandRegainedTracking()
              interactionHand.RegainTracking(hand);

              if (interactionHand.graspedObject == null) {
                continue;
              }

              // NotifyHandRegainedTracking() did not throw, continue on to NotifyHandsHoldPhysics().
              dispatchOnHandsHolding(hands, interactionHand.graspedObject, isPhysics: true);
            } catch (Exception e) {
              _activityManager.NotifyMisbehaving(interactionHand.graspedObject);
              Debug.LogException(e);
              continue;
            }

            //Override the existing classification to force the hand to grab the old object
            handResultForced = true;
            handResult.classification = ManipulatorMode.Grasp;
            handResult.handFlags = HandResultFlags.ManipulatorMode;
            handResult.instanceHandle = interactionHand.graspedObject.ShapeInstanceHandle;

            if (_graspingEnabled) {
              InteractionC.OverrideHandResult(ref _scene, (uint)hand.Id, ref handResult);
            }
          } else {
            //Otherwise just create a new one
            interactionHand = new InteractionHand(hand);
            _idToInteractionHand[hand.Id] = interactionHand;
          }
        }

        if (!handResultForced) {
          handResult = getHandResults(interactionHand);
        }

        interactionHand.UpdateHand(hand);

        if (!interactionHand.isUserGrasp) {
          switch (handResult.classification) {
            case ManipulatorMode.Grasp:
              {
                IInteractionBehaviour interactionBehaviour;
                if (_instanceHandleToBehaviour.TryGetValue(handResult.instanceHandle, out interactionBehaviour)) {
                  if (interactionHand.graspedObject == null) {
                    if (!interactionBehaviour.IsBeingGrasped) {
                      _graspedBehaviours.Add(interactionBehaviour);
                    }

                    try {
                      interactionHand.GraspObject(interactionBehaviour, isUserGrasp: false);

                      //the grasp callback might have caused the object to become ungrasped
                      //the component might have also destroyed itself!
                      if (interactionHand.graspedObject == interactionBehaviour && interactionBehaviour != null) {
                        dispatchOnHandsHolding(hands, interactionBehaviour, isPhysics: true);
                      }
                    } catch (Exception e) {
                      _activityManager.NotifyMisbehaving(interactionBehaviour);
                      Debug.LogException(e);
                      continue;
                    }
                  }
                } else {
                  Debug.LogError("Recieved a hand result with an unkown handle " + handResult.instanceHandle.handle);
                }
                break;
              }
            case ManipulatorMode.Contact:
              {
                if (interactionHand.graspedObject != null) {
                  if (interactionHand.graspedObject.GraspingHandCount == 1) {
                    _graspedBehaviours.Remove(interactionHand.graspedObject);
                  }

                  try {
                    interactionHand.ReleaseObject();
                  } catch (Exception e) {
                    _activityManager.NotifyMisbehaving(interactionHand.graspedObject);
                    Debug.LogException(e);
                    continue;
                  }
                }
                break;
              }
            default:
              throw new InvalidOperationException("Unexpected classification " + handResult.classification);
          }
        }
      }

      //Loop through all ieHands to check for timeouts and loss of tracking
      foreach (var pair in _idToInteractionHand) {
        var id = pair.Key;
        var ieHand = pair.Value;

        float handAge = Time.unscaledTime - ieHand.lastTimeUpdated;
        //Check to see if the hand is at least 1 frame old
        //We assume it has become untracked if this is the case
        if (handAge > 0) {
          //If the hand isn't grasping anything, just remove it
          if (ieHand.graspedObject == null) {
            _handIdsToRemove.Add(id);
            continue;
          }

          //If is isn't already marked as untracked, mark it as untracked
          if (!ieHand.isUntracked) {
            try {
              //This also dispatches InteractionObject.OnHandLostTracking()
              ieHand.MarkUntracked();
            } catch (Exception e) {
              _activityManager.NotifyMisbehaving(ieHand.graspedObject);
              Debug.LogException(e);
            }
          }

          //If the age is longer than the timeout, we also remove it from the list
          if (handAge >= ieHand.maxSuspensionTime) {
            _handIdsToRemove.Add(id);

            try {
              if (ieHand.graspedObject.GraspingHandCount == 1) {
                _graspedBehaviours.Remove(ieHand.graspedObject);
              }

              //This also dispatched InteractionObject.OnHandTimeout()
              ieHand.MarkTimeout();
            } catch (Exception e) {
              _activityManager.NotifyMisbehaving(ieHand.graspedObject);
              Debug.LogException(e);
            }
          }
        }
      }

      //Loop through the stale ids and remove them from the map
      for (int i = 0; i < _handIdsToRemove.Count; i++) {
        _idToInteractionHand.Remove(_handIdsToRemove[i]);
      }
      _handIdsToRemove.Clear();
    }
    /// <summary>
    /// Forces the given object to be released by any hands currently holding it.  Will return true
    /// only if there was at least one hand holding the object.
    /// </summary>
    public bool ReleaseObject(IInteractionBehaviour graspedObject) {
      if (!_graspedBehaviours.Remove(graspedObject)) {
        return false;
      }

      foreach (var interactionHand in _idToInteractionHand.Values) {
        if (interactionHand.graspedObject == graspedObject) {
          if (interactionHand.isUntracked) {
            interactionHand.MarkTimeout();
          } else {
            if (_graspingEnabled) {
              INTERACTION_HAND_RESULT result = new INTERACTION_HAND_RESULT();
              result.classification = ManipulatorMode.Contact;
              result.handFlags = HandResultFlags.ManipulatorMode;
              result.instanceHandle = new INTERACTION_SHAPE_INSTANCE_HANDLE();
              InteractionC.OverrideHandResult(ref _scene, (uint)interactionHand.hand.Id, ref result);
            }
            interactionHand.ReleaseObject();
          }
        }
      }

      return true;
    }
    protected virtual INTERACTION_HAND_RESULT getHandResults(InteractionHand hand) {
      if (!_graspingEnabled) {
        INTERACTION_HAND_RESULT result = new INTERACTION_HAND_RESULT();
        result.classification = ManipulatorMode.Contact;
        result.handFlags = HandResultFlags.ManipulatorMode;
        result.instanceHandle = new INTERACTION_SHAPE_INSTANCE_HANDLE();
        return result;
      }

      INTERACTION_HAND_RESULT handResult;
      InteractionC.GetHandResult(ref _scene,
                                     (uint)hand.hand.Id,
                                 out handResult);
      return handResult;
    }