/* * 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 System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; namespace Oculus.Interaction.PoseDetection { public class JointDeltaConfig { public readonly int InstanceID; public readonly IEnumerable JointIDs; public JointDeltaConfig(int instanceID, IEnumerable jointIDs) { InstanceID = instanceID; JointIDs = jointIDs; } } /// /// Provides tracking and calculation of joint position and rotation deltas between frames for hand tracking. /// This interface enables monitoring of joint movement and rotation changes, which is essential for gesture and motion detection. /// See for an example implementation. /// public interface IJointDeltaProvider { /// /// Retrieves the position delta between the current frame and previous frame for a specified joint. /// /// The to query /// The world-space position difference between frames /// True if valid delta data is available, false otherwise bool GetPositionDelta(HandJointId joint, out Vector3 delta); /// /// Retrieves the rotation delta between the current frame and previous frame for a specified joint. /// /// The to query /// The world-space rotation difference between frames /// True if valid delta data is available, false otherwise bool GetRotationDelta(HandJointId joint, out Quaternion delta); /// /// Registers a configuration for tracking specific hand joints in a Joint Delta Provider. /// /// The specifying which joints to track void RegisterConfig(JointDeltaConfig config); /// /// Unregisters an existing joint tracking configuration from a Joint Delta Provider. /// /// The to remove from tracking void UnRegisterConfig(JointDeltaConfig config); } public class JointDeltaProvider : MonoBehaviour, IJointDeltaProvider { private class PoseData { public bool IsValid = false; public Pose Pose = Pose.identity; } [SerializeField, Interface(typeof(IHand))] private UnityEngine.Object _hand; private IHand Hand; private Dictionary _poseDataCache = new Dictionary(); private HashSet _trackedJoints = new HashSet(); private Dictionary> _requestors = new Dictionary>(); private int PrevDataIndex => 1 - CurDataIndex; private int CurDataIndex = 0; private int _lastUpdateDataVersion; protected bool _started = false; /// /// Get the delta position between the previous pose and current pose /// /// The joint for which to retrieve data /// The position delta between poses in world space /// True if data available public bool GetPositionDelta(HandJointId joint, out Vector3 delta) { UpdateData(); PoseData prevPose = _poseDataCache[joint][PrevDataIndex]; PoseData curPose = _poseDataCache[joint][CurDataIndex]; if (!prevPose.IsValid || !curPose.IsValid) { delta = Vector3.zero; return false; } delta = curPose.Pose.position - prevPose.Pose.position; return true; } /// /// Get the delta rotation between the previous pose and current pose /// /// The joint for which to retrieve data /// The rotation delta between poses in world space /// True if data available public bool GetRotationDelta(HandJointId joint, out Quaternion delta) { UpdateData(); PoseData prevPose = _poseDataCache[joint][PrevDataIndex]; PoseData curPose = _poseDataCache[joint][CurDataIndex]; if (!prevPose.IsValid || !curPose.IsValid) { delta = Quaternion.identity; return false; } delta = curPose.Pose.rotation * Quaternion.Inverse(prevPose.Pose.rotation); return true; } /// /// Get the previous frame's pose /// /// The joint for which to retrieve data /// The previous pose /// True if data available public bool GetPrevJointPose(HandJointId joint, out Pose pose) { UpdateData(); PoseData poseData = _poseDataCache[joint][PrevDataIndex]; pose = poseData.Pose; return poseData.IsValid; } public void RegisterConfig(JointDeltaConfig config) { bool containsKeyAlready = _requestors.ContainsKey(config.InstanceID); this.AssertIsTrue(!containsKeyAlready, $"Trying to register multiple configs with the same id"); _requestors.Add(config.InstanceID, new List(config.JointIDs)); // Check if any new joints added, if so then add to cache foreach (var joint in config.JointIDs) { if (!_poseDataCache.ContainsKey(joint)) { _poseDataCache.Add(joint, new PoseData[2] { new PoseData(), new PoseData() }); // New joint tracked, so write current data PoseData toWrite = _poseDataCache[joint][CurDataIndex]; toWrite.IsValid = Hand.GetJointPose(joint, out toWrite.Pose); } } } public void UnRegisterConfig(JointDeltaConfig config) { _requestors.Remove(config.InstanceID); } protected virtual void Awake() { Hand = _hand as IHand; } protected virtual void Start() { this.BeginStart(ref _started); this.AssertField(Hand, nameof(Hand)); this.EndStart(ref _started); } protected virtual void OnEnable() { if (_started) { Hand.WhenHandUpdated += UpdateData; } } protected virtual void OnDisable() { if (_started) { Hand.WhenHandUpdated -= UpdateData; } } private void UpdateData() { if (Hand.CurrentDataVersion <= _lastUpdateDataVersion) { return; } _lastUpdateDataVersion = Hand.CurrentDataVersion; // Swap read and write indices each data version CurDataIndex = 1 - CurDataIndex; // Only fetch pose data for currently tracked joints _trackedJoints.Clear(); foreach (var key in _requestors.Keys) { IList joints = _requestors[key]; _trackedJoints.UnionWithNonAlloc(joints); } // Fetch pose data for tracked joints, and // invalidate data for untracked joints foreach (var joint in _poseDataCache.Keys) { PoseData toWrite = _poseDataCache[joint][CurDataIndex]; toWrite.IsValid = _trackedJoints.Contains(joint) && Hand.GetJointPose(joint, out toWrite.Pose); } } } }