Could not find Hand Subsystem

Hi there,

since the oob solution for two pinch input (second pinch is only detected when hand is moved) isn’t working really well for us, i started experimenting with custom hand tracking.

Sadly I can not get Unitys MixedReality Scene from the poly spatial samples to work. I get a LogError saying Could not find Hand Subsystem from PinchSpawn.cs:91.


I tried to test the input with the XR Device Simulator in Editor, just wanted to see if I can get reliable pinches.

Thanks for any help!

1 Like

I struggled a bit with this awhile back, and ended up modeling my code after the HandsVisualizer that comes with the XRHands package. I have an entire working sample app I can share, but it needs updating to the latest release. Here’s an excerpt of the main bits that access the hand subsystem and expose the positions of each joint:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Hands;
using UnityEngine.InputSystem;


public class SNHandTracking_XRHands : SNHandTracking
{

   XRHandSubsystem _handSys = null;
   bool _leftHandTracking = false;
   bool _rightHandTracking = false;

   Vector3[] _jointPosLeft = new Vector3[(uint)SNHandJoint.JOINTS_MAX];
   Vector3[] _jointPosRight = new Vector3[(uint)SNHandJoint.JOINTS_MAX];
   Quaternion[] _jointRotLeft = new Quaternion[(uint)SNHandJoint.JOINTS_MAX];
   Quaternion[] _jointRotRight = new Quaternion[(uint)SNHandJoint.JOINTS_MAX];

   void Awake()
   {
      //make input system work faster?
      #if ENABLE_INPUT_SYSTEM
      InputSystem.settings.SetInternalFeatureFlag("USE_OPTIMIZED_CONTROLS", true);
      #endif // ENABLE_INPUT_SYSTEM

      for (int i = 0; i < _jointPosLeft.Length; i++)
         _jointPosLeft[i] = Vector3.zero;
      for (int i = 0; i < _jointPosRight.Length; i++)
         _jointPosRight[i] = Vector3.zero;
      for (int i = 0; i < _jointRotLeft.Length; i++)
         _jointRotLeft[i] = Quaternion.identity;
      for (int i = 0; i < _jointRotRight.Length; i++)
         _jointRotRight[i] = Quaternion.identity;
   }

   public override bool GetIsTracking(Hand h)
   {
      return (h == Hand.Left) ? _leftHandTracking : _rightHandTracking;
   }


   public override Vector3 GetPosition(Hand h, SNHandJoint j) 
   {
      if (h == Hand.Left)
         return _jointPosLeft[(uint)j];
      else
         return _jointPosRight[(uint)j];
   }
   
   public override Quaternion GetRotation(Hand h, SNHandJoint j) 
   {
      if (h == Hand.Left)
         return _jointRotLeft[(uint)j];
      else
         return _jointRotRight[(uint)j];
   }

   void _OnTrackingAcquired(XRHand hand)
   {
      if (hand.handedness == Handedness.Left)
         _leftHandTracking = true;
      else if (hand.handedness == Handedness.Right)
         _rightHandTracking = true;

      Debug.Log("TRACKING ACQUIRED for hand " + hand.handedness.ToString() +  " at " + Time.time);
   }

   void _OnTrackingLost(XRHand hand)
   {
      if (hand.handedness == Handedness.Left)
         _leftHandTracking = false;
      else if (hand.handedness == Handedness.Right)
         _rightHandTracking = false;

      Debug.Log("TRACKING LOST for hand " + hand.handedness.ToString() + " at " + Time.time);
   }

   void _OnUpdatedHands(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags, XRHandSubsystem.UpdateType updateType)
   {
      _RefreshData(subsystem);
   }

   void _SubscribeHandSubsystem()
   {
      if (_handSys == null)
         return;

      _handSys.trackingAcquired += _OnTrackingAcquired;
      _handSys.trackingLost += _OnTrackingLost;
      _handSys.updatedHands += _OnUpdatedHands;

      Debug.Log("SUBSCRIBE hand subsystem at " + Time.time);
   }

   void _UnsubscribeHandSubsystem()
   {
      if (_handSys == null)
         return;

      _handSys.trackingAcquired -= _OnTrackingAcquired;
      _handSys.trackingLost -= _OnTrackingLost;
      _handSys.updatedHands -= _OnUpdatedHands;

      Debug.Log("UNSUBSCRIBE hand subsystem at " + Time.time);
   }

   void _RefreshData(XRHandSubsystem subsystem)
   {
      if (subsystem == null)
         return;

      _rightHandTracking = subsystem.rightHand.isTracked;
      _leftHandTracking = subsystem.leftHand.isTracked;

      _UpdateHandData(subsystem.leftHand, ref _jointPosLeft, ref _jointRotLeft);
      _UpdateHandData(subsystem.rightHand, ref _jointPosRight, ref _jointRotRight);

      //Debug.Log("UPDATE hand subsystem at " + Time.time);
   }

   void _UpdateHandData(XRHand hand, ref Vector3[] posData, ref Quaternion[] rotData)
   {
      Pose rootPose = hand.rootPose;

      for(int i = 0; i < posData.Length; i++)
      {
         SNHandJoint hj = (SNHandJoint)i;
         XRHandJointID xrj = _ToXRHandJoint(hj);

         var handJoint = hand.GetJoint(xrj);
         Pose pose;
         handJoint.TryGetPose(out pose);
         if(pose != null)
         {
            //Pose t = pose.GetTransformedBy(rootPose);
            //posData[i] = t.position;
            //rotData[i] = t.rotation;

            //Debug.Log("Update Joint " + hj.ToString() + " pos(" + pose.position + ") finalPos(" + posData[i] + ") at " + Time.time);
            posData[i] = pose.position;
            rotData[i] = pose.rotation;
         }         
      }
   }

   public override bool GetIsJointSupported(SNHandJoint j)
   {
      if ((j == SNHandJoint.Thumb0) || (j == SNHandJoint.Pinky0))
         return false;

      return true;
   }

   //convert from our enum to their enum
   XRHandJointID _ToXRHandJoint(SNHandJoint j)
   {
      switch(j)
      {
         case SNHandJoint.Wrist:     return XRHandJointID.Wrist;
         case SNHandJoint.Thumb0:    return XRHandJointID.Palm; //technically should be trapezium bone
         case SNHandJoint.Thumb1:    return XRHandJointID.ThumbMetacarpal;
         case SNHandJoint.Thumb2:    return XRHandJointID.ThumbProximal;
         case SNHandJoint.Thumb3:    return XRHandJointID.ThumbDistal;
         case SNHandJoint.ThumbTip:  return XRHandJointID.ThumbTip;
         case SNHandJoint.Index1:    return XRHandJointID.IndexProximal;
         case SNHandJoint.Index2:    return XRHandJointID.IndexIntermediate;
         case SNHandJoint.Index3:    return XRHandJointID.IndexDistal;
         case SNHandJoint.IndexTip:  return XRHandJointID.IndexTip;
         case SNHandJoint.Middle1:   return XRHandJointID.MiddleProximal;
         case SNHandJoint.Middle2:   return XRHandJointID.MiddleIntermediate;
         case SNHandJoint.Middle3:   return XRHandJointID.MiddleDistal;
         case SNHandJoint.MiddleTip: return XRHandJointID.MiddleTip;
         case SNHandJoint.Ring1:     return XRHandJointID.RingProximal;
         case SNHandJoint.Ring2:     return XRHandJointID.RingIntermediate;
         case SNHandJoint.Ring3:     return XRHandJointID.RingDistal;
         case SNHandJoint.RingTip:   return XRHandJointID.RingTip;
         case SNHandJoint.Pinky0:    return XRHandJointID.LittleMetacarpal;
         case SNHandJoint.Pinky1:    return XRHandJointID.LittleProximal;
         case SNHandJoint.Pinky2:    return XRHandJointID.LittleIntermediate;
         case SNHandJoint.Pinky3:    return XRHandJointID.LittleDistal;
         case SNHandJoint.PinkyTip:  return XRHandJointID.LittleTip;
         default:  return XRHandJointID.EndMarker;
      }
   }


   static readonly List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
   protected override void Update()
   {
      base.Update();

      //got hand subsystem, will handle everything in its callbacks
      if (_handSys != null && _handSys.running)
         return;

      SubsystemManager.GetSubsystems(s_SubsystemsReuse);
      var foundRunningHandSubsystem = false;
      for (var i = 0; i < s_SubsystemsReuse.Count; ++i)
      {
         var handSubsystem = s_SubsystemsReuse[i];
         if (handSubsystem.running)
         {
            _UnsubscribeHandSubsystem();
            _handSys = handSubsystem;
            Debug.Log("Found hand subsystem at " + Time.time);
            foundRunningHandSubsystem = true;
            break;
         }
      }

      if (!foundRunningHandSubsystem)
         return;

      _RefreshData(_handSys);

      _SubscribeHandSubsystem();
   }
}

3 Likes

Thanks for sharing!