void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData <IDictionary <TrackedHandJoint, MixedRealityPose> > eventData) { if (eventData.Handedness != Controller?.ControllerHandedness) { return; } MixedRealityHandTrackingProfile handTrackingProfile = MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.HandTrackingProfile; if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization) { // clear existing joint gameobjects / meshes foreach (var joint in joints) { Destroy(joint.Value.gameObject); } joints.Clear(); return; } foreach (TrackedHandJoint handJoint in eventData.InputData.Keys) { Transform jointTransform; if (joints.TryGetValue(handJoint, out jointTransform)) { jointTransform.position = eventData.InputData[handJoint].Position; jointTransform.rotation = eventData.InputData[handJoint].Rotation; } else { GameObject prefab = MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.HandTrackingProfile.JointPrefab; if (handJoint == TrackedHandJoint.Palm) { prefab = MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.HandTrackingProfile.PalmJointPrefab; } else if (handJoint == TrackedHandJoint.IndexTip) { prefab = MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.HandTrackingProfile.FingerTipPrefab; } GameObject jointObject; if (prefab != null) { jointObject = Instantiate(prefab); } else { jointObject = new GameObject(); } jointObject.name = handJoint.ToString() + " Proxy Transform"; jointObject.transform.position = eventData.InputData[handJoint].Position; jointObject.transform.rotation = eventData.InputData[handJoint].Rotation; jointObject.transform.parent = transform; joints.Add(handJoint, jointObject.transform); } } }
public void TestEditorOnlyChanges() { MixedRealityHandTrackingProfile profile = new MixedRealityHandTrackingProfile(); profile.HandJointVisualizationModes = SupportedApplicationModes.Editor | SupportedApplicationModes.Player; profile.HandMeshVisualizationModes = SupportedApplicationModes.Editor | SupportedApplicationModes.Player; // Since these tests run in the Unity editor, setting these to false should only clear the // SupportedApplicationModes.Editor from each of the Hand*VisualizationModes properties. profile.EnableHandJointVisualization = false; profile.EnableHandMeshVisualization = false; Assert.AreEqual(SupportedApplicationModes.Player, profile.HandJointVisualizationModes); Assert.AreEqual(SupportedApplicationModes.Player, profile.HandMeshVisualizationModes); }
/// <inheritdoc/> void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData <IDictionary <TrackedHandJoint, MixedRealityPose> > eventData) { if (eventData.InputSource.SourceId != Controller.InputSource.SourceId) { return; } Debug.Assert(eventData.Handedness == Controller.ControllerHandedness); IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; // Only runs if render skeleton joints is true MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile.HandTrackingProfile; if (handTrackingProfile != null && handTrackingProfile.EnableHandJointVisualization) { // This starts at 1 to skip over TrackedHandJoint.None. for (int i = 1; i < ArticulatedHandPose.JointCount; i++) { TrackedHandJoint handJoint = (TrackedHandJoint)i; MixedRealityPose handJointPose = eventData.InputData[handJoint]; if (skeletonJoints.TryGetValue(handJoint, out Transform skeletonJointTransform)) { skeletonJointTransform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); } else { GameObject prefab; if (handJoint == TrackedHandJoint.Palm) { prefab = inputSystem.InputSystemProfile.HandTrackingProfile.PalmJointPrefab; } else if (handJoint == TrackedHandJoint.IndexTip) { prefab = inputSystem.InputSystemProfile.HandTrackingProfile.FingerTipPrefab; } else { prefab = inputSystem.InputSystemProfile.HandTrackingProfile.JointPrefab; } GameObject jointObject; if (prefab != null) { jointObject = Instantiate(prefab); } else { jointObject = new GameObject(); } jointObject.name = handJoint.ToString() + " Proxy Transform"; jointObject.transform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); jointObject.transform.parent = transform; skeletonJoints.Add(handJoint, jointObject.transform); } } } else { // clear existing joint GameObjects / meshes foreach (var joint in skeletonJoints) { Destroy(joint.Value.gameObject); } skeletonJoints.Clear(); } // Only runs if render hand mesh is true bool renderHandmesh = handTrackingProfile != null && handTrackingProfile.EnableHandMeshVisualization; HandRenderer.enabled = renderHandmesh; if (renderHandmesh) { // Render the rigged hand mesh itself // Apply updated TrackedHandJoint pose data to the assigned transforms // This starts at 1 to skip over TrackedHandJoint.None. for (int i = 1; i < ArticulatedHandPose.JointCount; i++) { TrackedHandJoint handJoint = (TrackedHandJoint)i; MixedRealityPose handJointPose = eventData.InputData[handJoint]; if (joints.TryGetValue(handJoint, out Transform jointTransform) && jointTransform != null) { if (handJoint == TrackedHandJoint.Palm) { if (ModelPalmAtLeapWrist) { Palm.position = eventData.InputData[TrackedHandJoint.Wrist].Position; } else { Palm.position = handJointPose.Position; } Palm.rotation = handJointPose.Rotation * UserBoneRotation; } else if (handJoint == TrackedHandJoint.Wrist) { if (!ModelPalmAtLeapWrist) { Wrist.position = handJointPose.Position; } } else { // Finger joints jointTransform.rotation = handJointPose.Rotation * Reorientation(); if (DeformPosition) { jointTransform.position = handJointPose.Position; } if (ScaleLastFingerBone && (handJoint == TrackedHandJoint.ThumbDistalJoint || handJoint == TrackedHandJoint.IndexDistalJoint || handJoint == TrackedHandJoint.MiddleDistalJoint || handJoint == TrackedHandJoint.RingDistalJoint || handJoint == TrackedHandJoint.PinkyDistalJoint)) { ScaleFingerTip(eventData, jointTransform, handJoint + 1, jointTransform.position); } } } } // Update the hand material float pinchStrength = HandPoseUtils.CalculateIndexPinch(Controller.ControllerHandedness); // Hand Curl Properties: float indexFingerCurl = HandPoseUtils.IndexFingerCurl(Controller.ControllerHandedness); float middleFingerCurl = HandPoseUtils.MiddleFingerCurl(Controller.ControllerHandedness); float ringFingerCurl = HandPoseUtils.RingFingerCurl(Controller.ControllerHandedness); float pinkyFingerCurl = HandPoseUtils.PinkyFingerCurl(Controller.ControllerHandedness); if (handMaterial != null && handRendererInitialized) { float gripStrength = indexFingerCurl + middleFingerCurl + ringFingerCurl + pinkyFingerCurl; gripStrength /= 4.0f; gripStrength = gripStrength > 0.8f ? 1.0f : gripStrength; pinchStrength = Mathf.Pow(Mathf.Max(pinchStrength, gripStrength), 2.0f); if (handRenderer.sharedMaterial.HasProperty(pinchStrengthMaterialProperty)) { handRenderer.sharedMaterial.SetFloat(pinchStrengthMaterialProperty, pinchStrength); } // Only show this warning once else if (!displayedMaterialPropertyWarning) { Debug.LogWarning(String.Format("The property {0} for reacting to pinch strength was not found. A material with this property is required to visualize pinch strength.", pinchStrengthMaterialProperty)); displayedMaterialPropertyWarning = true; } } } }
/// <inheritdoc/> public override void OnHandJointsUpdated(InputEventData <IDictionary <TrackedHandJoint, MixedRealityPose> > eventData) { using (OnHandJointsUpdatedPerfMarker.Auto()) { // The base class takes care of updating all of the joint data base.OnHandJointsUpdated(eventData); // exit early and disable the rigged hand model if we've gotten a hand mesh from the underlying platform if (receivingPlatformHandMesh) { HandRenderer.enabled = false; return; } // Ensures that the hand only renders when the event data matches the controller this visualizer represents if (eventData.InputSource.SourceId != Controller.InputSource.SourceId) { return; } Debug.Assert(eventData.Handedness == Controller.ControllerHandedness); IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile.HandTrackingProfile; // Only runs if render hand mesh is true bool renderHandmesh = handTrackingProfile != null && handTrackingProfile.EnableHandMeshVisualization; HandRenderer.enabled = renderHandmesh; if (renderHandmesh) { // Render the rigged hand mesh itself // Apply updated TrackedHandJoint pose data to the assigned transforms // This starts at 1 to skip over TrackedHandJoint.None. for (int i = 1; i < ArticulatedHandPose.JointCount; i++) { TrackedHandJoint handJoint = (TrackedHandJoint)i; // Skip this hand joint if the event data doesn't have an entry for it if (!eventData.InputData.ContainsKey(handJoint)) { continue; } MixedRealityPose handJointPose = eventData.InputData[handJoint]; Transform jointTransform = riggedVisualJointsArray[i]; if (jointTransform != null) { if (handJoint == TrackedHandJoint.Palm) { if (ModelPalmAtLeapWrist) { Palm.position = eventData.InputData[TrackedHandJoint.Wrist].Position; } else { Palm.position = handJointPose.Position; } Palm.rotation = handJointPose.Rotation * UserBoneRotation; } else if (handJoint == TrackedHandJoint.Wrist) { if (!ModelPalmAtLeapWrist) { Wrist.position = handJointPose.Position; } } else { // Finger riggedVisualJointsArray jointTransform.rotation = handJointPose.Rotation * Reorientation(); if (DeformPosition) { jointTransform.position = handJointPose.Position; } if (ScaleLastFingerBone && (handJoint == TrackedHandJoint.ThumbDistalJoint || handJoint == TrackedHandJoint.IndexDistalJoint || handJoint == TrackedHandJoint.MiddleDistalJoint || handJoint == TrackedHandJoint.RingDistalJoint || handJoint == TrackedHandJoint.PinkyDistalJoint)) { ScaleFingerTip(eventData, jointTransform, handJoint + 1, jointTransform.position); } } } } // Update the hand material float pinchStrength = HandPoseUtils.CalculateIndexPinch(Controller.ControllerHandedness); // Hand Curl Properties: float indexFingerCurl = HandPoseUtils.IndexFingerCurl(Controller.ControllerHandedness); float middleFingerCurl = HandPoseUtils.MiddleFingerCurl(Controller.ControllerHandedness); float ringFingerCurl = HandPoseUtils.RingFingerCurl(Controller.ControllerHandedness); float pinkyFingerCurl = HandPoseUtils.PinkyFingerCurl(Controller.ControllerHandedness); if (handMaterial != null && handRendererInitialized) { float gripStrength = indexFingerCurl + middleFingerCurl + ringFingerCurl + pinkyFingerCurl; gripStrength /= 4.0f; gripStrength = gripStrength > 0.8f ? 1.0f : gripStrength; pinchStrength = Mathf.Pow(Mathf.Max(pinchStrength, gripStrength), 2.0f); if (handRenderer.sharedMaterial.HasProperty(pinchStrengthMaterialProperty)) { handRenderer.sharedMaterial.SetFloat(pinchStrengthMaterialProperty, pinchStrength); } // Only show this warning once else if (!displayedMaterialPropertyWarning) { Debug.LogWarning(String.Format("The property {0} for reacting to pinch strength was not found. A material with this property is required to visualize pinch strength.", pinchStrengthMaterialProperty)); displayedMaterialPropertyWarning = true; } } } } }
void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData <IDictionary <TrackedHandJoint, MixedRealityPose> > eventData) { using (OnHandJointsUpdatedPerfMarker.Auto()) { if (eventData.InputSource.SourceId != Controller.InputSource.SourceId) { return; } Debug.Assert(eventData.Handedness == Controller.ControllerHandedness); IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null; if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization) { // clear existing joint GameObjects / meshes foreach (var joint in joints) { Destroy(joint.Value.gameObject); } joints.Clear(); return; } foreach (TrackedHandJoint handJoint in eventData.InputData.Keys) { Transform jointTransform; if (joints.TryGetValue(handJoint, out jointTransform)) { jointTransform.position = eventData.InputData[handJoint].Position; jointTransform.rotation = eventData.InputData[handJoint].Rotation; } else { GameObject prefab; if (handJoint == TrackedHandJoint.None || handTrackingProfile == null) { // No visible mesh for the "None" joint prefab = null; } else if (handJoint == TrackedHandJoint.Palm) { prefab = handTrackingProfile.PalmJointPrefab; } else if (handJoint == TrackedHandJoint.IndexTip) { prefab = handTrackingProfile.FingerTipPrefab; } else { prefab = handTrackingProfile.JointPrefab; } GameObject jointObject; if (prefab != null) { jointObject = Instantiate(prefab); } else { jointObject = new GameObject(); } jointObject.name = handJoint.ToString() + " Proxy Transform"; jointObject.transform.position = eventData.InputData[handJoint].Position; jointObject.transform.rotation = eventData.InputData[handJoint].Rotation; jointObject.transform.parent = transform; joints.Add(handJoint, jointObject.transform); } } } }
void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData <IDictionary <TrackedHandJoint, MixedRealityPose> > eventData) { using (OnHandJointsUpdatedPerfMarker.Auto()) { if (eventData.InputSource.SourceId != Controller.InputSource.SourceId) { return; } Debug.Assert(eventData.Handedness == Controller.ControllerHandedness); IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null; if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization) { // clear existing joint GameObjects / meshes foreach (Transform joint in jointsArray) { if (joint != null) { Destroy(joint.gameObject); } } return; } // This starts at 1 to skip over TrackedHandJoint.None. for (int i = 1; i < ArticulatedHandPose.JointCount; i++) { TrackedHandJoint handJoint = (TrackedHandJoint)i; MixedRealityPose handJointPose = eventData.InputData[handJoint]; Transform jointTransform = jointsArray[i]; if (jointTransform != null) { jointTransform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); } else { GameObject prefab; if (handJoint == TrackedHandJoint.None || handTrackingProfile == null) { // No visible mesh for the "None" joint prefab = null; } else if (handJoint == TrackedHandJoint.Palm) { prefab = handTrackingProfile.PalmJointPrefab; } else if (handJoint == TrackedHandJoint.IndexTip) { prefab = handTrackingProfile.FingerTipPrefab; } else { prefab = handTrackingProfile.JointPrefab; } GameObject jointObject; if (prefab != null) { jointObject = Instantiate(prefab); } else { jointObject = new GameObject(); } jointObject.name = handJoint.ToString() + " Proxy Transform"; jointObject.transform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); jointObject.transform.parent = transform; jointsArray[i] = jointObject.transform; } } } }
protected virtual void UpdateHandMesh() { using (UpdateHandMeshPerfMarker.Auto()) { if (lastHandMeshInfo == null) { return; } bool newMesh = handMeshFilter == null; IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null; if (newMesh && handTrackingProfile != null) { // Create the hand mesh in the scene and assign the proper material to it if (handTrackingProfile.SystemHandMeshMaterial.IsNotNull()) { handMeshFilter = new GameObject("System Hand Mesh").EnsureComponent <MeshFilter>(); handMeshFilter.EnsureComponent <MeshRenderer>().material = handTrackingProfile.SystemHandMeshMaterial; } #pragma warning disable 0618 else if (handTrackingProfile.HandMeshPrefab.IsNotNull()) { handMeshFilter = Instantiate(handTrackingProfile.HandMeshPrefab).GetComponent <MeshFilter>(); } #pragma warning restore 0618 // Initialize the hand mesh if we generated it successfully if (handMeshFilter != null) { lastHandMeshVerticesCount = handMeshFilter.mesh.vertices.Length; handMeshFilter.transform.parent = transform; } } if (handMeshFilter != null) { Mesh mesh = handMeshFilter.mesh; bool meshChanged = false; // On some platforms, mesh length counts may change as the hand mesh is updated. // In order to update the vertices when the array sizes change, the mesh // must be cleared per instructions here: // https://docs.unity3d.com/ScriptReference/Mesh.html if (lastHandMeshVerticesCount != lastHandMeshInfo.vertices?.Length) { meshChanged = true; mesh.Clear(); } mesh.vertices = lastHandMeshInfo.vertices; mesh.normals = lastHandMeshInfo.normals; lastHandMeshVerticesCount = lastHandMeshInfo.vertices != null ? lastHandMeshInfo.vertices.Length : 0; if (newMesh || meshChanged) { mesh.triangles = lastHandMeshInfo.triangles; if (lastHandMeshInfo.uvs?.Length > 0) { mesh.uv = lastHandMeshInfo.uvs; } } if (meshChanged) { mesh.RecalculateBounds(); } handMeshFilter.transform.SetPositionAndRotation(lastHandMeshInfo.position, lastHandMeshInfo.rotation); } } }
protected virtual bool UpdateHandJoints() { using (UpdateHandJointsPerfMarker.Auto()) { if (!handJointsUpdated || MixedRealityHand.IsNull()) { return(false); } IMixedRealityInputSystem inputSystem = CoreServices.InputSystem; MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null; if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization) { // clear existing joint GameObjects / meshes foreach (Transform joint in JointsArray) { if (joint != null) { Destroy(joint.gameObject); } } JointsArray = System.Array.Empty <Transform>(); // Even though the base class isn't handling joint visualization, we still received new joints. // Return true here in case any derived classes want to update. return(true); } if (JointsArray.Length != ArticulatedHandPose.JointCount) { JointsArray = new Transform[ArticulatedHandPose.JointCount]; } // This starts at 1 to skip over TrackedHandJoint.None. for (int i = 1; i < ArticulatedHandPose.JointCount; i++) { TrackedHandJoint handJoint = (TrackedHandJoint)i; // Skip this hand joint if the event data doesn't have an entry for it if (!MixedRealityHand.TryGetJoint(handJoint, out MixedRealityPose handJointPose)) { continue; } Transform jointTransform = JointsArray[i]; if (jointTransform != null) { jointTransform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); } else { GameObject prefab; if (handJoint == TrackedHandJoint.None || handTrackingProfile == null) { // No visible mesh for the "None" joint prefab = null; } else if (handJoint == TrackedHandJoint.Palm) { prefab = handTrackingProfile.PalmJointPrefab; } else if (handJoint == TrackedHandJoint.IndexTip) { prefab = handTrackingProfile.FingerTipPrefab; } else { prefab = handTrackingProfile.JointPrefab; } GameObject jointObject; if (prefab != null) { jointObject = Instantiate(prefab); } else { jointObject = new GameObject(); } jointObject.name = handJoint.ToString() + " Proxy Transform"; jointObject.transform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation); jointObject.transform.parent = transform; JointsArray[i] = jointObject.transform; } } handJointsUpdated = false; return(true); } }
protected override void Start() { base.Start(); // Ensure hand is not visible until we can update position first time. HandRenderer.enabled = false; // Initialize joint dictionary with their corresponding joint transforms riggedVisualJointsArray[(int)TrackedHandJoint.Wrist] = Wrist; riggedVisualJointsArray[(int)TrackedHandJoint.Palm] = Palm; // Thumb riggedVisualJointsArray, first node is user assigned, note that there are only 4 riggedVisualJointsArray in the thumb if (ThumbRoot) { if (ThumbRootIsMetacarpal) { riggedVisualJointsArray[(int)TrackedHandJoint.ThumbMetacarpalJoint] = ThumbRoot; riggedVisualJointsArray[(int)TrackedHandJoint.ThumbProximalJoint] = RetrieveChild(TrackedHandJoint.ThumbMetacarpalJoint); } else { riggedVisualJointsArray[(int)TrackedHandJoint.ThumbProximalJoint] = ThumbRoot; } riggedVisualJointsArray[(int)TrackedHandJoint.ThumbDistalJoint] = RetrieveChild(TrackedHandJoint.ThumbProximalJoint); riggedVisualJointsArray[(int)TrackedHandJoint.ThumbTip] = RetrieveChild(TrackedHandJoint.ThumbDistalJoint); } // Look up index finger riggedVisualJointsArray below the index finger root joint if (IndexRoot) { if (IndexRootIsMetacarpal) { riggedVisualJointsArray[(int)TrackedHandJoint.IndexMetacarpal] = IndexRoot; riggedVisualJointsArray[(int)TrackedHandJoint.IndexKnuckle] = RetrieveChild(TrackedHandJoint.IndexMetacarpal); } else { riggedVisualJointsArray[(int)TrackedHandJoint.IndexKnuckle] = IndexRoot; } riggedVisualJointsArray[(int)TrackedHandJoint.IndexMiddleJoint] = RetrieveChild(TrackedHandJoint.IndexKnuckle); riggedVisualJointsArray[(int)TrackedHandJoint.IndexDistalJoint] = RetrieveChild(TrackedHandJoint.IndexMiddleJoint); riggedVisualJointsArray[(int)TrackedHandJoint.IndexTip] = RetrieveChild(TrackedHandJoint.IndexDistalJoint); } // Look up middle finger riggedVisualJointsArray below the middle finger root joint if (MiddleRoot) { if (MiddleRootIsMetacarpal) { riggedVisualJointsArray[(int)TrackedHandJoint.MiddleMetacarpal] = MiddleRoot; riggedVisualJointsArray[(int)TrackedHandJoint.MiddleKnuckle] = RetrieveChild(TrackedHandJoint.MiddleMetacarpal); } else { riggedVisualJointsArray[(int)TrackedHandJoint.MiddleKnuckle] = MiddleRoot; } riggedVisualJointsArray[(int)TrackedHandJoint.MiddleMiddleJoint] = RetrieveChild(TrackedHandJoint.MiddleKnuckle); riggedVisualJointsArray[(int)TrackedHandJoint.MiddleDistalJoint] = RetrieveChild(TrackedHandJoint.MiddleMiddleJoint); riggedVisualJointsArray[(int)TrackedHandJoint.MiddleTip] = RetrieveChild(TrackedHandJoint.MiddleDistalJoint); } // Look up ring finger riggedVisualJointsArray below the ring finger root joint if (RingRoot) { if (RingRootIsMetacarpal) { riggedVisualJointsArray[(int)TrackedHandJoint.RingMetacarpal] = RingRoot; riggedVisualJointsArray[(int)TrackedHandJoint.RingKnuckle] = RetrieveChild(TrackedHandJoint.RingMetacarpal); } else { riggedVisualJointsArray[(int)TrackedHandJoint.RingKnuckle] = RingRoot; } riggedVisualJointsArray[(int)TrackedHandJoint.RingMiddleJoint] = RetrieveChild(TrackedHandJoint.RingKnuckle); riggedVisualJointsArray[(int)TrackedHandJoint.RingDistalJoint] = RetrieveChild(TrackedHandJoint.RingMiddleJoint); riggedVisualJointsArray[(int)TrackedHandJoint.RingTip] = RetrieveChild(TrackedHandJoint.RingDistalJoint); } // Look up pinky riggedVisualJointsArray below the pinky root joint if (PinkyRoot) { if (PinkyRootIsMetacarpal) { riggedVisualJointsArray[(int)TrackedHandJoint.PinkyMetacarpal] = PinkyRoot; riggedVisualJointsArray[(int)TrackedHandJoint.PinkyKnuckle] = RetrieveChild(TrackedHandJoint.PinkyMetacarpal); } else { riggedVisualJointsArray[(int)TrackedHandJoint.PinkyKnuckle] = PinkyRoot; } riggedVisualJointsArray[(int)TrackedHandJoint.PinkyMiddleJoint] = RetrieveChild(TrackedHandJoint.PinkyKnuckle); riggedVisualJointsArray[(int)TrackedHandJoint.PinkyDistalJoint] = RetrieveChild(TrackedHandJoint.PinkyMiddleJoint); riggedVisualJointsArray[(int)TrackedHandJoint.PinkyTip] = RetrieveChild(TrackedHandJoint.PinkyDistalJoint); } // Give the hand mesh its own material to avoid modifying both hand materials when making property changes MixedRealityHandTrackingProfile handTrackingProfile = CoreServices.InputSystem?.InputSystemProfile.HandTrackingProfile; handMaterial = handTrackingProfile.RiggedHandMeshMaterial; Material handMaterialInstance = new Material(handMaterial); handRenderer.sharedMaterial = handMaterialInstance; handRendererInitialized = true; }