/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Oculus.Interaction.Input; using UnityEngine; namespace Oculus.Interaction.GrabAPI { /// /// The class provides functionality to detect finger grabs using multiple implementations. /// It distinguishes between pinch and palm grabs and allows customization of the grab detectors through dependency injection, using the and methods. /// public class HandGrabAPI : MonoBehaviour { [SerializeField, Interface(typeof(IHand))] private UnityEngine.Object _hand; /// /// Gets the implementation associated with this instance. /// This property is crucial for accessing hand-specific data required for grab detection. /// public IHand Hand { get; private set; } [SerializeField, Interface(typeof(IHmd)), Optional] private UnityEngine.Object _hmd; public IHmd Hmd { get; private set; } = null; private IFingerAPI _fingerPinchGrabAPI = null; private IFingerAPI _fingerPalmGrabAPI = null; private bool _started = false; protected virtual void Awake() { Hand = _hand as IHand; Hmd = _hmd as IHmd; } protected virtual void Start() { this.BeginStart(ref _started); this.AssertField(Hand, nameof(Hand)); #if ISDK_OPENXR_HAND if (_fingerPinchGrabAPI == null) { _fingerPinchGrabAPI = new PinchGrabAPI(Hmd); } if (_fingerPalmGrabAPI == null) { _fingerPalmGrabAPI = new PalmGrabAPI(); } #else if (_fingerPinchGrabAPI == null) { _fingerPinchGrabAPI = new FingerPinchGrabAPI(Hmd); } if (_fingerPalmGrabAPI == null) { _fingerPalmGrabAPI = new FingerPalmGrabAPI(); } #endif this.EndStart(ref _started); } private void OnEnable() { if (_started) { Hand.WhenHandUpdated += OnHandUpdated; } } private void OnDisable() { if (_started) { Hand.WhenHandUpdated -= OnHandUpdated; } } private void OnHandUpdated() { _fingerPinchGrabAPI.Update(Hand); _fingerPalmGrabAPI.Update(Hand); } /// /// Returns the flags indicating which fingers are currently performing a pinch grab. /// /// A value representing the fingers involved in pinch grabs. public HandFingerFlags HandPinchGrabbingFingers() { return HandGrabbingFingers(_fingerPinchGrabAPI); } /// /// Returns the flags indicating which fingers are currently performing a palm grab. /// /// A value representing the fingers involved in palm grabs. public HandFingerFlags HandPalmGrabbingFingers() { return HandGrabbingFingers(_fingerPalmGrabAPI); } private HandFingerFlags HandGrabbingFingers(IFingerAPI fingerAPI) { HandFingerFlags grabbingFingers = HandFingerFlags.None; for (int i = 0; i < Constants.NUM_FINGERS; i++) { HandFinger finger = (HandFinger)i; bool isGrabbing = fingerAPI.GetFingerIsGrabbing(finger); if (isGrabbing) { grabbingFingers |= (HandFingerFlags)(1 << i); } } return grabbingFingers; } /// /// Determines if the hand is currently performing a pinch grab according to the specified rules. /// /// The defining required and optional fingers for the grab. /// True if the hand meets the pinch grab conditions; otherwise, false. public bool IsHandPinchGrabbing(in GrabbingRule fingers) { HandFingerFlags pinchFingers = HandPinchGrabbingFingers(); return IsSustainingGrab(fingers, pinchFingers); } /// /// Determines if the hand is currently performing a palm grab according to the specified rules. /// /// The defining required and optional fingers for the grab. /// True if the hand meets the palm grab conditions; otherwise, false. public bool IsHandPalmGrabbing(in GrabbingRule fingers) { HandFingerFlags palmFingers = HandPalmGrabbingFingers(); return IsSustainingGrab(fingers, palmFingers); } /// /// Determines if the grab condition is sustained based on the specified grabbing rules and current grabbing fingers. /// /// The specifying required and optional fingers. /// The current state of fingers that are grabbing. /// True if the grab condition is sustained; otherwise, false. public bool IsSustainingGrab(in GrabbingRule fingers, HandFingerFlags grabbingFingers) { bool anyHolding = false; for (int i = 0; i < Constants.NUM_FINGERS; i++) { HandFinger finger = (HandFinger)i; HandFingerFlags fingerFlag = (HandFingerFlags)(1 << i); bool isFingerGrabbing = (grabbingFingers & fingerFlag) != 0; if (fingers[finger] == FingerRequirement.Required) { anyHolding |= isFingerGrabbing; if (fingers.UnselectMode == FingerUnselectMode.AnyReleased && !isFingerGrabbing) { return false; } if (fingers.UnselectMode == FingerUnselectMode.AllReleased && isFingerGrabbing) { return true; } } else if (fingers[finger] == FingerRequirement.Optional) { anyHolding |= isFingerGrabbing; } } return anyHolding; } /// /// Determine whether the state of any of the finger pinches have changed this frame to /// the target pinching state (on/off). /// /// Finger to check. /// /// True if any finger's pinch state has changed according to the rules; otherwise, false. /// public bool IsHandSelectPinchFingersChanged(in GrabbingRule fingers) { return IsHandSelectFingersChanged(fingers, _fingerPinchGrabAPI); } /// /// Determines whether the state of any of the finger grabs have changed this frame to /// the target grabbing state (on/off). /// /// Finger to check. /// True if any finger's grab state has changed according to the rules; otherwise, false. public bool IsHandSelectPalmFingersChanged(in GrabbingRule fingers) { return IsHandSelectFingersChanged(fingers, _fingerPalmGrabAPI); } /// /// Determines whether the state of any of the finger pinches have changed this frame to /// the target pinching state (on/off). /// /// Finger to check. /// True if any finger's pinch state has changed according to the rules; otherwise, false. public bool IsHandUnselectPinchFingersChanged(in GrabbingRule fingers) { return IsHandUnselectFingersChanged(fingers, _fingerPinchGrabAPI); } /// /// Determines whether the state of any of the finger grabs have changed this frame to /// the target grabbing state (on/off). /// /// Finger to check. /// True if any finger's grab state has changed according to the rules; otherwise, false. public bool IsHandUnselectPalmFingersChanged(in GrabbingRule fingers) { return IsHandUnselectFingersChanged(fingers, _fingerPalmGrabAPI); } private bool IsHandSelectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI) { bool selectsWithOptionals = fingers.SelectsWithOptionals; bool anyFingerBeganGrabbing = false; for (int i = 0; i < Constants.NUM_FINGERS; i++) { HandFinger finger = (HandFinger)i; if (fingers[finger] == FingerRequirement.Required) { if (!fingerAPI.GetFingerIsGrabbing(finger)) { return false; } if (fingerAPI.GetFingerIsGrabbingChanged(finger, true)) { anyFingerBeganGrabbing = true; } } else if (selectsWithOptionals && fingers[finger] == FingerRequirement.Optional) { if (fingerAPI.GetFingerIsGrabbingChanged(finger, true)) { return true; } } } return anyFingerBeganGrabbing; } private bool IsHandUnselectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI) { bool isAnyFingerGrabbing = false; bool anyFingerStoppedGrabbing = false; bool selectsWithOptionals = fingers.SelectsWithOptionals; for (int i = 0; i < Constants.NUM_FINGERS; i++) { HandFinger finger = (HandFinger)i; if (fingers[finger] == FingerRequirement.Ignored) { continue; } isAnyFingerGrabbing |= fingerAPI.GetFingerIsGrabbing(finger); if (fingers[finger] == FingerRequirement.Required) { if (fingerAPI.GetFingerIsGrabbingChanged(finger, false)) { anyFingerStoppedGrabbing = true; if (fingers.UnselectMode == FingerUnselectMode.AnyReleased) { return true; } } } else if (fingers[finger] == FingerRequirement.Optional) { if (fingerAPI.GetFingerIsGrabbingChanged(finger, false)) { anyFingerStoppedGrabbing = true; if (fingers.UnselectMode == FingerUnselectMode.AnyReleased && selectsWithOptionals) { return true; } } } } return !isAnyFingerGrabbing && anyFingerStoppedGrabbing; } /// /// Calculates the center position of the pinch grab based on the wrist offset. /// /// The world position of the pinch center. public Vector3 GetPinchCenter() { Vector3 localOffset = Vector3.zero; if (_fingerPinchGrabAPI != null) { localOffset = _fingerPinchGrabAPI.GetWristOffsetLocal(); } return WristOffsetToWorldPoint(localOffset); } /// /// Calculates the center position of the palm grab based on the wrist offset. /// /// The world position of the palm center. public Vector3 GetPalmCenter() { Vector3 localOffset = Vector3.zero; if (_fingerPalmGrabAPI != null) { localOffset = _fingerPalmGrabAPI.GetWristOffsetLocal(); } return WristOffsetToWorldPoint(localOffset); } private Vector3 WristOffsetToWorldPoint(Vector3 localOffset) { if (!Hand.GetJointPose(HandJointId.HandWristRoot, out Pose wristPose)) { return localOffset * Hand.Scale; } return wristPose.position + wristPose.rotation * localOffset * Hand.Scale; } /// /// Retrieves the overall score of how well the hand is performing a pinch grab based on specified rules. /// /// The rules defining required and optional fingers for the pinch grab. /// Indicates whether to include currently pinching fingers in the score calculation. /// A float representing the pinch grab score, where higher values indicate a stronger grab. public float GetHandPinchScore(in GrabbingRule fingers, bool includePinching = true) { return GetHandGrabScore(fingers, includePinching, _fingerPinchGrabAPI); } /// /// Retrieves the overall score of how well the hand is performing a palm grab based on specified rules. /// /// The defining required and optional fingers for the palm grab. /// Indicates whether to include currently grabbing fingers in the score calculation. /// A float representing the palm grab score, where higher values indicate a stronger grab. public float GetHandPalmScore(in GrabbingRule fingers, bool includeGrabbing = true) { return GetHandGrabScore(fingers, includeGrabbing, _fingerPalmGrabAPI); } /// /// Retrieves the strength of the pinch grab for a specific finger. /// /// The to check the pinch strength for. /// A float representing the pinch strength, where higher values indicate a stronger pinch. public float GetFingerPinchStrength(HandFinger finger) { return _fingerPinchGrabAPI.GetFingerGrabScore(finger); } /// /// Retrieves the percentage of completion for a pinch gesture for a specific finger. /// /// The to check the pinch percentage for. /// A float representing the percentage of the pinch completion. public float GetFingerPinchPercent(HandFinger finger) { if (_fingerPinchGrabAPI is FingerPinchGrabAPI) { FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI; return pinchGrab.GetFingerPinchPercent(finger); } Debug.LogWarning("GetFingerPinchPercent is not applicable"); return -1; } /// /// Retrieves the distance between the thumb and the specified finger during a pinch gesture. /// /// The to measure the distance from the thumb. /// A float representing the distance between the thumb and the specified finger during a pinch. public float GetFingerPinchDistance(HandFinger finger) { if (_fingerPinchGrabAPI is FingerPinchGrabAPI) { FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI; return pinchGrab.GetFingerPinchDistance(finger); } Debug.LogWarning("GetFingerPinchDistance is not applicable"); return -1; } /// /// Retrieves the strength of the palm grab for a specific finger. /// /// The to check the palm grab strength for. /// A float representing the palm grab strength, where higher values indicate a stronger grab. public float GetFingerPalmStrength(HandFinger finger) { return _fingerPalmGrabAPI.GetFingerGrabScore(finger); } private float GetHandGrabScore(in GrabbingRule fingers, bool includeGrabbing, IFingerAPI fingerAPI) { float requiredMin = 1.0f; float optionalMax = 0f; bool anyRequired = false; bool usesOptionals = fingers.SelectsWithOptionals; for (int i = 0; i < Constants.NUM_FINGERS; i++) { HandFinger finger = (HandFinger)i; if (!includeGrabbing && fingerAPI.GetFingerIsGrabbing((HandFinger)i)) { continue; } if (fingers[finger] == FingerRequirement.Ignored) { continue; } if (fingers[finger] == FingerRequirement.Optional) { optionalMax = Mathf.Max(optionalMax, fingerAPI.GetFingerGrabScore(finger)); } else if (fingers[finger] == FingerRequirement.Required) { anyRequired = true; requiredMin = Mathf.Min(requiredMin, fingerAPI.GetFingerGrabScore(finger)); } } return usesOptionals ? optionalMax : anyRequired ? requiredMin : 0f; } public void SetPinchGrabParam(PinchGrabParam paramId, float paramVal) { FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI; if (pinchGrab != null) { pinchGrab.SetPinchGrabParam(paramId, paramVal); } } public float GetPinchGrabParam(PinchGrabParam paramId) { FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI; if (pinchGrab != null) { return pinchGrab.GetPinchGrabParam(paramId); } return 0; } /// /// Checks if a specific finger is currently grabbing. /// /// The finger to check for grabbing status. /// True if the specified finger is grabbing; otherwise, false. public bool GetFingerIsGrabbing(HandFinger finger) { return _fingerPinchGrabAPI.GetFingerIsGrabbing(finger); } public bool GetFingerIsPalmGrabbing(HandFinger finger) { return _fingerPalmGrabAPI.GetFingerIsGrabbing(finger); } #region Inject /// /// Injects custom implementations for the . This method facilitates unit testing. /// /// The custom implementation to inject. public void InjectAllHandGrabAPI(IHand hand) { InjectHand(hand); } public void InjectHand(IHand hand) { _hand = hand as UnityEngine.Object; Hand = hand; } public void InjectOptionalHmd(IHmd hmd) { Hmd = hmd; _hmd = hmd as UnityEngine.Object; } /// /// Injects an optional custom implementation for the pinch grab API. /// /// The custom pinch grab to inject. public void InjectOptionalFingerPinchAPI(IFingerAPI fingerPinchAPI) { _fingerPinchGrabAPI = fingerPinchAPI; } /// /// Injects an optional custom implementation for the palm grab API. /// /// The custom palm grab to inject. public void InjectOptionalFingerGrabAPI(IFingerAPI fingerGrabAPI) { _fingerPalmGrabAPI = fingerGrabAPI; } #endregion } }