/*
* 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
}
}