private float DuckPinchMetric(Leap.Hand hand) { Vector3 thumbDistal = hand.GetThumb().bones[3].PrevJoint.ToVector3(); Vector3 thumbTip = hand.GetThumb().TipPosition.ToVector3(); Vector3 indexMetacarpal = hand.GetIndex().bones[0].PrevJoint.ToVector3(); Vector3 indexProximal = hand.GetIndex().bones[1].PrevJoint.ToVector3(); Vector3 indexTip = hand.GetIndex().TipPosition.ToVector3(); Vector3 middleMetacarpal = hand.GetMiddle().bones[0].PrevJoint.ToVector3(); Vector3 middleTip = hand.GetMiddle().bones[3].PrevJoint.ToVector3(); Vector3 ringMetacarpal = hand.GetMiddle().bones[0].PrevJoint.ToVector3(); Vector3 ringTip = hand.GetRing().bones[3].PrevJoint.ToVector3(); // Project all except thumb onto a plane Vector3 projectionPlane = Vector3.Cross(hand.GetIndex().bones[2].Direction.ToVector3(), hand.PalmarAxis()).normalized; if (hand.IsRight) { // convert to left-handed-rule for Unity projectionPlane *= -1f; } Vector3 planeOrigin = indexProximal; indexProximal = Vector3.zero; indexMetacarpal = Vector3.ProjectOnPlane(indexMetacarpal - planeOrigin, projectionPlane) + planeOrigin; indexTip = Vector3.ProjectOnPlane(indexTip - planeOrigin, projectionPlane) + planeOrigin; middleMetacarpal = Vector3.ProjectOnPlane(middleMetacarpal - planeOrigin, projectionPlane) + planeOrigin; middleTip = Vector3.ProjectOnPlane(middleTip - planeOrigin, projectionPlane) + planeOrigin; ringMetacarpal = Vector3.ProjectOnPlane(ringMetacarpal - planeOrigin, projectionPlane) + planeOrigin; ringTip = Vector3.ProjectOnPlane(ringTip - planeOrigin, projectionPlane) + planeOrigin; // Limit thumb positions float thumbDistalOverlap = Vector3.Dot(thumbDistal - planeOrigin, projectionPlane); if (thumbDistalOverlap < 0) { thumbDistal += thumbDistalOverlap * projectionPlane; } float thumbTipOverlap = Vector3.Dot(thumbTip - planeOrigin, projectionPlane); if (thumbTipOverlap < 0) { thumbTip += thumbTipOverlap * projectionPlane; } float indexMetric = SegmentDisplacement.SegmentToSegmentDistance(indexMetacarpal, indexTip, thumbDistal, thumbTip); float middleMetric = SegmentDisplacement.SegmentToSegmentDistance(middleMetacarpal, middleTip, thumbDistal, thumbTip); float ringMetric = SegmentDisplacement.SegmentToSegmentDistance(ringMetacarpal, ringTip, thumbDistal, thumbTip); float wipMetric = (indexMetric + middleMetric + ringMetric) / (3.0f); metric = Mathf.Max(0f, wipMetric - 0.01f); return(metric); }
bool CheckHandForActivation(Leap.Hand hand, bool wasEligibleLastCheck) { bool shouldActivate = false; float latestPinchDistance = GetCustomPinchDistance(hand); Vector3 palmDir = hand.PalmarAxis(); Vector3 middleDir = hand.GetMiddle().bones[1].Direction.ToVector3(); float signedMiddlePalmAngle = Vector3.SignedAngle(palmDir, middleDir, hand.RadialAxis()); if (hand.IsLeft) { signedMiddlePalmAngle *= -1f; } Vector3 ringDir = hand.GetRing().bones[1].Direction.ToVector3(); float signedRingPalmAngle = Vector3.SignedAngle(palmDir, ringDir, hand.RadialAxis()); if (hand.IsLeft) { signedRingPalmAngle *= -1f; } Vector3 indexDir = hand.GetIndex().bones[1].Direction.ToVector3(); float indexPalmAngle = Vector3.Angle(indexDir, palmDir); Vector3 thumbDir = hand.GetThumb().bones[2].Direction.ToVector3(); float thumbPalmAngle = Vector3.Angle(thumbDir, palmDir); // Eligibility checks-- necessary, but not sufficient conditions to start // a pinch, suitable for e.g. visual feedback on whether the gesture is // "able to occur" or "about to occur." if ( ((!wasEligibleLastCheck && signedMiddlePalmAngle >= minPalmMiddleAngle) || (wasEligibleLastCheck && signedMiddlePalmAngle >= minPalmMiddleAngle * ringMiddleSafetyHysteresisMult) || !requireMiddleAndRingSafetyPinch) && ((!wasEligibleLastCheck && signedRingPalmAngle >= minPalmRingAngle) || (wasEligibleLastCheck && signedRingPalmAngle >= minPalmRingAngle * ringMiddleSafetyHysteresisMult) || !requireMiddleAndRingSafetyPinch) // Index angle (eligibility state only) && ((!wasEligibleLastCheck && indexPalmAngle < maxIndexAngleForEligibilityActivation) || (wasEligibleLastCheck && indexPalmAngle < maxIndexAngleForEligibilityDeactivation)) // Thumb angle (eligibility state only) && ((!wasEligibleLastCheck && thumbPalmAngle < maxThumbAngleForEligibilityActivation) || (wasEligibleLastCheck && thumbPalmAngle < maxThumbAngleForEligibilityDeactivation)) // Must cross pinch threshold from a non-pinching / non-fist pose. && (!requiresRepinch) ) { // Conceptually, this should be true when all but the most essential // parameters for the gesture are satisfied, so the user can be notified // that the gesture is imminent. _isGestureEligible = true; } #region Update Pinch Strength // Update global "pinch strength". // If the gesture is eligible, we'll have a non-zero pinch strength. if (_isGestureEligible) { _latestPinchStrength = latestPinchDistance.Map(0f, pinchActivateDistance, 1f, 0f); } else { _latestPinchStrength = 0f; } #endregion #region Check: Pinch Distance if (_isGestureEligible // Absolute pinch strength. && (latestPinchDistance < pinchActivateDistance) ) { shouldActivate = true; } #endregion #region Hysteresis for Failed Pinches // "requiresRepinch" prevents a closed-finger configuration from beginning // a pinch when the index and thumb never actually actively close from a // valid position -- think, closed-fist to safety-pinch, as opposed to // open-hand to safety-pinch -- without introducing any velocity-based // requirement. if (latestPinchDistance < pinchActivateDistance && !shouldActivate) { requiresRepinch = true; } if (requiresRepinch && latestPinchDistance > failedPinchResetDistance) { requiresRepinch = false; } #endregion return(shouldActivate); }