示例#1
0
        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);
                }
            }
        }
示例#2
0
        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);
        }
示例#3
0
        /// <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;
                    }
                }
            }
        }
示例#4
0
        /// <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;
                        }
                    }
                }
            }
        }
示例#5
0
        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);
                    }
                }
            }
        }
示例#6
0
        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);
            }
        }
示例#9
0
        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;
        }