summaryrefslogtreecommitdiff
path: root/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts')
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs171
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs.meta11
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs608
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs.meta11
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs38
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs.meta11
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs242
-rw-r--r--Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs.meta11
8 files changed, 1103 insertions, 0 deletions
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs
new file mode 100644
index 0000000..3c575bc
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs
@@ -0,0 +1,171 @@
+using System.Collections.Generic;
+using UnityEngine.XR.Hands.Processing;
+
+namespace UnityEngine.XR.Hands.Samples.VisualizerSample
+{
+ /// <summary>
+ /// Example hand processor that applies transformations on the root poses to
+ /// modify the hands skeleton. Note it is possible to modify the bones
+ /// directly for more advanced use cases that are not shown here.
+ /// </summary>
+ public class HandProcessor : MonoBehaviour, IXRHandProcessor
+ {
+ /// <inheritdoc />
+ public int callbackOrder => 0;
+
+ /// <summary>
+ /// The mode to use for the sample processor.
+ /// </summary>
+ public enum ProcessorExampleMode
+ {
+ /// <summary>
+ /// No processing is applied.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Smooths the hand root pose of the left and right hands with interpolated positions
+ /// </summary>
+ Smoothing,
+
+ /// <summary>
+ /// Inverts the left and right hands.
+ /// </summary>
+ Invert
+ }
+
+ // Variables used for smoothing hand movements.
+ bool m_FirstFrame = false;
+ Vector3 m_LastLeftHandPosition;
+ Vector3 m_LastRightHandPosition;
+ Pose m_LeftHandPose = Pose.identity;
+ Pose m_RightHandPose = Pose.identity;
+
+ [SerializeField]
+ [Tooltip("The mode to use for the sample processor.")]
+ ProcessorExampleMode m_ProcessorExampleMode = ProcessorExampleMode.Smoothing;
+ ProcessorExampleMode m_LastProcessorExampleMode = ProcessorExampleMode.None;
+
+ /// <summary>
+ /// The <see cref="ProcessorExampleMode"/> to use for the sample processor.
+ /// </summary>
+ public ProcessorExampleMode processorExampleMode
+ {
+ get => m_ProcessorExampleMode;
+ set => m_ProcessorExampleMode = value;
+ }
+
+ // Smoothing factors for the left and right hands.
+ [Header("Smoothing parameters")]
+ [SerializeField]
+ [Tooltip("The smoothing factor to use when smoothing the root of the left hand in the sample processor. Use 0 for no smoothing.")]
+ float m_LeftHandSmoothingFactor = 16f;
+
+ [SerializeField]
+ [Tooltip("The smoothing factor to use when smoothing the root of the right hand in the sample processor. Use 0 for no smoothing.")]
+ float m_RightHandSmoothingFactor = 16f;
+
+ /// <inheritdoc />
+ public void ProcessJoints(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType)
+ {
+ switch (m_ProcessorExampleMode)
+ {
+ case ProcessorExampleMode.Smoothing:
+ SmoothHandsExample(subsystem, successFlags, updateType, m_LastProcessorExampleMode != m_ProcessorExampleMode);
+ break;
+
+ case ProcessorExampleMode.Invert:
+ InvertHandsExample(subsystem, successFlags, updateType);
+ break;
+ }
+
+ m_LastProcessorExampleMode = m_ProcessorExampleMode;
+ }
+
+ // Smooths the hand movements of an XRHandSubsystem by updating the root
+ // pose of the left and right hands with interpolated positions.
+ void SmoothHandsExample(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType, bool modeChanged)
+ {
+ var leftHand = subsystem.leftHand;
+ var rightHand = subsystem.rightHand;
+
+ if (leftHand.isTracked && m_LeftHandSmoothingFactor > 0)
+ {
+ var leftPose = leftHand.rootPose;
+ var currentLeftHandPosition = leftPose.position;
+ if (!m_FirstFrame && !modeChanged)
+ {
+ float tweenAmt = Time.deltaTime * m_LeftHandSmoothingFactor;
+ currentLeftHandPosition = Vector3.Lerp(m_LastLeftHandPosition, currentLeftHandPosition, tweenAmt);
+ m_LeftHandPose.position = currentLeftHandPosition;
+ m_LeftHandPose.rotation = leftPose.rotation;
+
+ leftHand.SetRootPose(m_LeftHandPose);
+ subsystem.SetCorrespondingHand(leftHand);
+ }
+ m_LastLeftHandPosition = currentLeftHandPosition;
+ }
+
+ if (rightHand.isTracked && m_RightHandSmoothingFactor > 0)
+ {
+ var rightPose = rightHand.rootPose;
+ var currentRightHandPosition = rightPose.position;
+ if (!m_FirstFrame && !modeChanged)
+ {
+ float tweenAmt = Time.deltaTime * m_RightHandSmoothingFactor;
+ currentRightHandPosition = Vector3.Lerp(m_LastRightHandPosition, currentRightHandPosition, tweenAmt);
+ m_RightHandPose.position = currentRightHandPosition;
+ m_RightHandPose.rotation = rightPose.rotation;
+
+ rightHand.SetRootPose(m_RightHandPose);
+ subsystem.SetCorrespondingHand(rightHand);
+ }
+ m_LastRightHandPosition = currentRightHandPosition;
+ }
+ }
+
+ // Call this from process joints to try inverting the user's hands.
+ void InvertHandsExample(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType)
+ {
+ var leftHand = subsystem.leftHand;
+ var leftHandPose = leftHand.rootPose;
+
+ var rightHand = subsystem.rightHand;
+ var rightHandPose = rightHand.rootPose;
+
+ if (leftHand.isTracked)
+ {
+ leftHand.SetRootPose(rightHandPose);
+ subsystem.SetCorrespondingHand(leftHand);
+
+ rightHand.SetRootPose(leftHandPose);
+ subsystem.SetCorrespondingHand(rightHand);
+ }
+ }
+
+ void Update()
+ {
+ if (m_Subsystem != null)
+ return;
+
+ SubsystemManager.GetSubsystems(s_SubsystemsReuse);
+ if (s_SubsystemsReuse.Count == 0)
+ return;
+
+ m_Subsystem = s_SubsystemsReuse[0];
+ m_Subsystem.RegisterProcessor(this);
+ }
+
+ void OnDisable()
+ {
+ if (m_Subsystem != null)
+ {
+ m_Subsystem.UnregisterProcessor(this);
+ m_Subsystem = null;
+ }
+ }
+
+ XRHandSubsystem m_Subsystem;
+ static List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
+ }
+}
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs.meta b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs.meta
new file mode 100644
index 0000000..748484b
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ac5903b776721d74786a2e43f00b949a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs
new file mode 100644
index 0000000..7d94269
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs
@@ -0,0 +1,608 @@
+using System.Collections.Generic;
+using UnityEngine.Serialization;
+
+namespace UnityEngine.XR.Hands.Samples.VisualizerSample
+{
+ // Hand rig setups can differ between platforms. In these cases, the HandVisualizer supports displaying unique hands on a per-platform basis.
+ // If you would like to customize the hand meshes that are displayed by the HandVisualizer, based on the platform you are using,
+ // you will need to replace the rigged hand mesh references assigned to the corresponding fields for that platform.
+ // For Meta Quest devices, assign your rigged hand meshes to the "m_MetaQuestLeftHandMesh" & "m_MetaQuestRightHandMesh" fields.
+ // For Android XR devices, assign your rigged hand meshes to the "m_AndroidXRLeftHandMesh" & "m_AndroidXRRightHandMesh" fields.
+ // The rigged hand meshes that are assigned for a given platform will be displayed when that platform is detected,
+ // and any other rigged hand meshes assigned for other undetected platforms will not be displayed.
+
+ /// <summary>
+ /// This component visualizes the hand joints and mesh for the left and right hands.
+ /// </summary>
+ public class HandVisualizer : MonoBehaviour
+ {
+ /// <summary>
+ /// The type of velocity to visualize.
+ /// </summary>
+ public enum VelocityType
+ {
+ /// <summary>
+ /// Visualize the linear velocity of the joint.
+ /// </summary>
+ Linear,
+
+ /// <summary>
+ /// Visualize the angular velocity of the joint.
+ /// </summary>
+ Angular,
+
+ /// <summary>
+ /// Do not visualize velocity.
+ /// </summary>
+ None,
+ }
+
+ [SerializeField]
+ [Tooltip("If this is enabled, this component will enable the Input System internal feature flag 'USE_OPTIMIZED_CONTROLS'. You must have at least version 1.5.0 of the Input System and have its backend enabled for this to take effect.")]
+ bool m_UseOptimizedControls;
+
+ [SerializeField, FormerlySerializedAs("m_LeftHandMesh")]
+ [Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the left hand.")]
+ GameObject m_MetaQuestLeftHandMesh;
+
+ [SerializeField, FormerlySerializedAs("m_RightHandMesh")]
+ [Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the right hand.")]
+ GameObject m_MetaQuestRightHandMesh;
+
+ [SerializeField]
+ [Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the left hand on Android XR devices." +
+ "<br><br><b>Instructions for how to setup and use these meshes can be found at the top of the <b>HandVisualizer.cs class</b>")]
+ GameObject m_AndroidXRLeftHandMesh;
+
+ [SerializeField]
+ [Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the right hand on Android XR devices." +
+ "<br><br><b>Instructions for how to setup and use these meshes can be found at the top of the <b>HandVisualizer.cs class</b>")]
+ GameObject m_AndroidXRRightHandMesh;
+
+ [SerializeField]
+ [Tooltip("(Optional) If this is set, the hand meshes will be assigned this material.")]
+ Material m_HandMeshMaterial;
+
+ [SerializeField]
+ [Tooltip("Tells the Hand Visualizer to draw the meshes for the hands.")]
+ bool m_DrawMeshes;
+ bool m_PreviousDrawMeshes;
+
+ /// <summary>
+ /// Tells the Hand Visualizer to draw the meshes for the hands.
+ /// </summary>
+ public bool drawMeshes
+ {
+ get => m_DrawMeshes;
+ set => m_DrawMeshes = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The prefab that will be used to visualize the joints for debugging.")]
+ GameObject m_DebugDrawPrefab;
+
+ [SerializeField]
+ [Tooltip("Tells the Hand Visualizer to draw the debug joints for the hands.")]
+ bool m_DebugDrawJoints;
+ bool m_PreviousDebugDrawJoints;
+
+ /// <summary>
+ /// Tells the Hand Visualizer to draw the debug joints for the hands.
+ /// </summary>
+ public bool debugDrawJoints
+ {
+ get => m_DebugDrawJoints;
+ set => m_DebugDrawJoints = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Prefab to use for visualizing the velocity.")]
+ GameObject m_VelocityPrefab;
+
+ [SerializeField]
+ [Tooltip("The type of velocity to visualize.")]
+ VelocityType m_VelocityType;
+ VelocityType m_PreviousVelocityType;
+
+ /// <summary>
+ /// The type of velocity to visualize.
+ /// </summary>
+ public VelocityType velocityType
+ {
+ get => m_VelocityType;
+ set => m_VelocityType = value;
+ }
+
+ XRHandSubsystem m_Subsystem;
+ HandGameObjects m_LeftHandGameObjects;
+ HandGameObjects m_RightHandGameObjects;
+
+ static readonly List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void Awake()
+ {
+#if ENABLE_INPUT_SYSTEM
+ if (m_UseOptimizedControls)
+ InputSystem.InputSystem.settings.SetInternalFeatureFlag("USE_OPTIMIZED_CONTROLS", true);
+#endif // ENABLE_INPUT_SYSTEM
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnEnable()
+ {
+ if (m_Subsystem == null)
+ return;
+
+ UpdateRenderingVisibility(m_LeftHandGameObjects, m_Subsystem.leftHand.isTracked);
+ UpdateRenderingVisibility(m_RightHandGameObjects, m_Subsystem.rightHand.isTracked);
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDisable()
+ {
+ if (m_Subsystem != null)
+ {
+ m_Subsystem.trackingAcquired -= OnTrackingAcquired;
+ m_Subsystem.trackingLost -= OnTrackingLost;
+ m_Subsystem.updatedHands -= OnUpdatedHands;
+ m_Subsystem = null;
+ }
+
+ UpdateRenderingVisibility(m_LeftHandGameObjects, false);
+ UpdateRenderingVisibility(m_RightHandGameObjects, false);
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDestroy()
+ {
+ if (m_LeftHandGameObjects != null)
+ {
+ m_LeftHandGameObjects.OnDestroy();
+ m_LeftHandGameObjects = null;
+ }
+
+ if (m_RightHandGameObjects != null)
+ {
+ m_RightHandGameObjects.OnDestroy();
+ m_RightHandGameObjects = null;
+ }
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void Update()
+ {
+ if (m_Subsystem != null && m_Subsystem.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();
+ m_Subsystem = handSubsystem;
+ foundRunningHandSubsystem = true;
+ break;
+ }
+ }
+
+ if (!foundRunningHandSubsystem)
+ return;
+
+ GameObject selectedLeftHandMesh = null, selectedRightHandMesh = null;
+ if (m_Subsystem.detectedHandMeshLayout == XRDetectedHandMeshLayout.OpenXRAndroidXR)
+ {
+ selectedLeftHandMesh = m_AndroidXRLeftHandMesh;
+ selectedRightHandMesh = m_AndroidXRRightHandMesh;
+ }
+ else
+ {
+ selectedLeftHandMesh = m_MetaQuestLeftHandMesh;
+ selectedRightHandMesh = m_MetaQuestRightHandMesh;
+ }
+
+ if (m_LeftHandGameObjects == null)
+ {
+ m_LeftHandGameObjects = new HandGameObjects(
+ Handedness.Left,
+ transform,
+ selectedLeftHandMesh,
+ m_HandMeshMaterial,
+ m_DebugDrawPrefab,
+ m_VelocityPrefab);
+ }
+
+ if (m_RightHandGameObjects == null)
+ {
+ m_RightHandGameObjects = new HandGameObjects(
+ Handedness.Right,
+ transform,
+ selectedRightHandMesh,
+ m_HandMeshMaterial,
+ m_DebugDrawPrefab,
+ m_VelocityPrefab);
+ }
+
+ UpdateRenderingVisibility(m_LeftHandGameObjects, m_Subsystem.leftHand.isTracked);
+ UpdateRenderingVisibility(m_RightHandGameObjects, m_Subsystem.rightHand.isTracked);
+
+ m_PreviousDrawMeshes = m_DrawMeshes;
+ m_PreviousDebugDrawJoints = m_DebugDrawJoints;
+ m_PreviousVelocityType = m_VelocityType;
+
+ SubscribeHandSubsystem();
+ }
+
+ void SubscribeHandSubsystem()
+ {
+ if (m_Subsystem == null)
+ return;
+
+ m_Subsystem.trackingAcquired += OnTrackingAcquired;
+ m_Subsystem.trackingLost += OnTrackingLost;
+ m_Subsystem.updatedHands += OnUpdatedHands;
+ }
+
+ void UnsubscribeHandSubsystem()
+ {
+ if (m_Subsystem == null)
+ return;
+
+ m_Subsystem.trackingAcquired -= OnTrackingAcquired;
+ m_Subsystem.trackingLost -= OnTrackingLost;
+ m_Subsystem.updatedHands -= OnUpdatedHands;
+ }
+
+ void UpdateRenderingVisibility(HandGameObjects handGameObjects, bool isTracked)
+ {
+ if (handGameObjects == null)
+ return;
+
+ handGameObjects.ToggleDrawMesh(m_DrawMeshes);
+ handGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && isTracked);
+ handGameObjects.SetVelocityType(isTracked ? m_VelocityType : VelocityType.None);
+ }
+
+ void OnTrackingAcquired(XRHand hand)
+ {
+ switch (hand.handedness)
+ {
+ case Handedness.Left:
+ UpdateRenderingVisibility(m_LeftHandGameObjects, true);
+ break;
+
+ case Handedness.Right:
+ UpdateRenderingVisibility(m_RightHandGameObjects, true);
+ break;
+ }
+ }
+
+ void OnTrackingLost(XRHand hand)
+ {
+ switch (hand.handedness)
+ {
+ case Handedness.Left:
+ UpdateRenderingVisibility(m_LeftHandGameObjects, false);
+ break;
+
+ case Handedness.Right:
+ UpdateRenderingVisibility(m_RightHandGameObjects, false);
+ break;
+ }
+ }
+
+ void OnUpdatedHands(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags, XRHandSubsystem.UpdateType updateType)
+ {
+ // We have no game logic depending on the Transforms, so early out here
+ // (add game logic before this return here, directly querying from
+ // subsystem.leftHand and subsystem.rightHand using GetJoint on each hand)
+ if (updateType == XRHandSubsystem.UpdateType.Dynamic)
+ return;
+
+ bool leftHandTracked = subsystem.leftHand.isTracked;
+ bool rightHandTracked = subsystem.rightHand.isTracked;
+
+ if (m_PreviousDrawMeshes != m_DrawMeshes)
+ {
+ m_LeftHandGameObjects.ToggleDrawMesh(m_DrawMeshes);
+ m_RightHandGameObjects.ToggleDrawMesh(m_DrawMeshes);
+ m_PreviousDrawMeshes = m_DrawMeshes;
+ }
+
+ if (m_PreviousDebugDrawJoints != m_DebugDrawJoints)
+ {
+ m_LeftHandGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && leftHandTracked);
+ m_RightHandGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && rightHandTracked);
+ m_PreviousDebugDrawJoints = m_DebugDrawJoints;
+ }
+
+ if (m_PreviousVelocityType != m_VelocityType)
+ {
+ m_LeftHandGameObjects.SetVelocityType(leftHandTracked ? m_VelocityType : VelocityType.None);
+ m_RightHandGameObjects.SetVelocityType(rightHandTracked ? m_VelocityType : VelocityType.None);
+ m_PreviousVelocityType = m_VelocityType;
+ }
+
+ m_LeftHandGameObjects.UpdateJoints(
+ subsystem.leftHand,
+ (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints) != 0,
+ m_DebugDrawJoints,
+ m_VelocityType);
+
+ m_RightHandGameObjects.UpdateJoints(
+ subsystem.rightHand,
+ (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.RightHandJoints) != 0,
+ m_DebugDrawJoints,
+ m_VelocityType);
+ }
+
+ class HandGameObjects
+ {
+ GameObject m_HandRoot;
+ GameObject m_DrawJointsParent;
+
+ GameObject[] m_DrawJoints = new GameObject[XRHandJointID.EndMarker.ToIndex()];
+ GameObject[] m_VelocityParents = new GameObject[XRHandJointID.EndMarker.ToIndex()];
+ LineRenderer[] m_Lines = new LineRenderer[XRHandJointID.EndMarker.ToIndex()];
+ JointVisualizer[] m_JointVisualizers = new JointVisualizer[XRHandJointID.EndMarker.ToIndex()];
+
+ static Vector3[] s_LinePointsReuse = new Vector3[2];
+ XRHandMeshController m_MeshController;
+ const float k_LineWidth = 0.005f;
+
+ public HandGameObjects(
+ Handedness handedness,
+ Transform parent,
+ GameObject meshPrefab,
+ Material meshMaterial,
+ GameObject debugDrawPrefab,
+ GameObject velocityPrefab)
+ {
+ void AssignJoint(
+ XRHandJointID jointId,
+ Transform jointDrivenTransform,
+ Transform drawJointsParent)
+ {
+ var jointIndex = jointId.ToIndex();
+ m_DrawJoints[jointIndex] = Instantiate(debugDrawPrefab);
+ m_DrawJoints[jointIndex].transform.parent = drawJointsParent;
+ m_DrawJoints[jointIndex].name = jointId.ToString();
+
+ m_VelocityParents[jointIndex] = Instantiate(velocityPrefab);
+ m_VelocityParents[jointIndex].transform.parent = jointDrivenTransform;
+
+ m_Lines[jointIndex] = m_DrawJoints[jointIndex].GetComponent<LineRenderer>();
+ m_Lines[jointIndex].startWidth = m_Lines[jointIndex].endWidth = k_LineWidth;
+ s_LinePointsReuse[0] = s_LinePointsReuse[1] = jointDrivenTransform.position;
+ m_Lines[jointIndex].SetPositions(s_LinePointsReuse);
+
+ if (m_DrawJoints[jointIndex].TryGetComponent<JointVisualizer>(out var jointVisualizer))
+ m_JointVisualizers[jointIndex] = jointVisualizer;
+ }
+
+ var isSceneObject = meshPrefab.scene.IsValid();
+ m_HandRoot = isSceneObject ? meshPrefab : Instantiate(meshPrefab, parent);
+ m_HandRoot.SetActive(false); // Deactivate so that added components do not run OnEnable before they are finished being set up
+
+ m_HandRoot.transform.localPosition = Vector3.zero;
+ m_HandRoot.transform.localRotation = Quaternion.identity;
+
+ var handEvents = m_HandRoot.GetComponent<XRHandTrackingEvents>();
+ if (handEvents == null)
+ {
+ handEvents = m_HandRoot.AddComponent<XRHandTrackingEvents>();
+ handEvents.updateType = XRHandTrackingEvents.UpdateTypes.Dynamic;
+ handEvents.handedness = handedness;
+ }
+
+ m_MeshController = m_HandRoot.GetComponent<XRHandMeshController>();
+ if (m_MeshController == null)
+ {
+ m_MeshController = m_HandRoot.AddComponent<XRHandMeshController>();
+ for (var childIndex = 0; childIndex < m_HandRoot.transform.childCount; ++childIndex)
+ {
+ var childTransform = m_HandRoot.transform.GetChild(childIndex);
+ if (childTransform.TryGetComponent<SkinnedMeshRenderer>(out var renderer))
+ m_MeshController.handMeshRenderer = renderer;
+ }
+
+ m_MeshController.handTrackingEvents = handEvents;
+ }
+
+ if (meshMaterial != null)
+ {
+ m_MeshController.handMeshRenderer.sharedMaterial = meshMaterial;
+ }
+
+ var skeletonDriver = m_HandRoot.GetComponent<XRHandSkeletonDriver>();
+ if (skeletonDriver == null)
+ {
+ skeletonDriver = m_HandRoot.AddComponent<XRHandSkeletonDriver>();
+ skeletonDriver.jointTransformReferences = new List<JointToTransformReference>();
+ Transform root = null;
+ for (var childIndex = 0; childIndex < m_HandRoot.transform.childCount; ++childIndex)
+ {
+ var child = m_HandRoot.transform.GetChild(childIndex);
+ if (child.gameObject.name.EndsWith(XRHandJointID.Wrist.ToString()))
+ root = child;
+ }
+
+ skeletonDriver.rootTransform = root;
+ XRHandSkeletonDriverUtility.FindJointsFromRoot(skeletonDriver);
+ skeletonDriver.InitializeFromSerializedReferences();
+ skeletonDriver.handTrackingEvents = handEvents;
+ }
+
+ m_DrawJointsParent = new GameObject();
+ m_DrawJointsParent.transform.parent = parent;
+ m_DrawJointsParent.transform.localPosition = Vector3.zero;
+ m_DrawJointsParent.transform.localRotation = Quaternion.identity;
+ m_DrawJointsParent.name = handedness + "HandDebugDrawJoints";
+
+ for (var i = 0; i < skeletonDriver.jointTransformReferences.Count; i++)
+ {
+ var jointTransformReference = skeletonDriver.jointTransformReferences[i];
+ var jointTransform = jointTransformReference.jointTransform;
+ var jointID = jointTransformReference.xrHandJointID;
+ AssignJoint(jointID, jointTransform, m_DrawJointsParent.transform);
+ }
+
+ m_HandRoot.SetActive(true);
+ }
+
+ public void OnDestroy()
+ {
+ Destroy(m_HandRoot);
+ m_HandRoot = null;
+
+ for (var jointIndex = 0; jointIndex < m_DrawJoints.Length; ++jointIndex)
+ {
+ Destroy(m_DrawJoints[jointIndex]);
+ m_DrawJoints[jointIndex] = null;
+ }
+
+ for (var jointIndex = 0; jointIndex < m_VelocityParents.Length; ++jointIndex)
+ {
+ Destroy(m_VelocityParents[jointIndex]);
+ m_VelocityParents[jointIndex] = null;
+ }
+
+ Destroy(m_DrawJointsParent);
+ m_DrawJointsParent = null;
+ }
+
+ public void ToggleDrawMesh(bool drawMesh)
+ {
+ m_MeshController.enabled = drawMesh;
+ if (!drawMesh)
+ m_MeshController.handMeshRenderer.enabled = false;
+ }
+
+ public void ToggleDebugDrawJoints(bool debugDrawJoints)
+ {
+ for (int jointIndex = 0; jointIndex < m_DrawJoints.Length; ++jointIndex)
+ {
+ ToggleRenderers<MeshRenderer>(debugDrawJoints, m_DrawJoints[jointIndex].transform);
+ m_Lines[jointIndex].enabled = debugDrawJoints;
+ }
+
+ m_Lines[0].enabled = false;
+ }
+
+ public void SetVelocityType(VelocityType velocityType)
+ {
+ for (int jointIndex = 0; jointIndex < m_VelocityParents.Length; ++jointIndex)
+ ToggleRenderers<LineRenderer>(velocityType != VelocityType.None, m_VelocityParents[jointIndex].transform);
+ }
+
+ public void UpdateJoints(
+ XRHand hand,
+ bool areJointsTracked,
+ bool debugDrawJoints,
+ VelocityType velocityType)
+ {
+ if (!areJointsTracked)
+ return;
+
+ var wristPose = Pose.identity;
+ var parentIndex = XRHandJointID.Wrist.ToIndex();
+ UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointID.Wrist), ref wristPose, ref parentIndex);
+ UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointID.Palm), ref wristPose, ref parentIndex, false);
+
+ for (var fingerIndex = (int)XRHandFingerID.Thumb;
+ fingerIndex <= (int)XRHandFingerID.Little;
+ ++fingerIndex)
+ {
+ var parentPose = wristPose;
+ var fingerId = (XRHandFingerID)fingerIndex;
+ parentIndex = XRHandJointID.Wrist.ToIndex();
+
+ var jointIndexBack = fingerId.GetBackJointID().ToIndex();
+ for (var jointIndex = fingerId.GetFrontJointID().ToIndex();
+ jointIndex <= jointIndexBack;
+ ++jointIndex)
+ {
+ UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointIDUtility.FromIndex(jointIndex)), ref parentPose, ref parentIndex);
+ }
+ }
+ }
+
+ void UpdateJoint(
+ bool debugDrawJoints,
+ VelocityType velocityType,
+ XRHandJoint joint,
+ ref Pose parentPose,
+ ref int parentIndex,
+ bool cacheParentPose = true)
+ {
+ if (joint.id == XRHandJointID.Invalid)
+ return;
+
+ var jointIndex = joint.id.ToIndex();
+ m_JointVisualizers[jointIndex].NotifyTrackingState(joint.trackingState);
+
+ if (!joint.TryGetPose(out var pose))
+ return;
+
+ m_DrawJoints[jointIndex].transform.localPosition = pose.position;
+ m_DrawJoints[jointIndex].transform.localRotation = pose.rotation;
+
+ if (debugDrawJoints && joint.id != XRHandJointID.Wrist)
+ {
+ s_LinePointsReuse[0] = m_DrawJoints[parentIndex].transform.position;
+ s_LinePointsReuse[1] = m_DrawJoints[jointIndex].transform.position;
+ m_Lines[jointIndex].SetPositions(s_LinePointsReuse);
+ }
+
+ if (cacheParentPose)
+ {
+ parentPose = pose;
+ parentIndex = jointIndex;
+ }
+
+ if (velocityType != VelocityType.None && m_VelocityParents[jointIndex].TryGetComponent<LineRenderer>(out var renderer))
+ {
+ m_VelocityParents[jointIndex].transform.localPosition = Vector3.zero;
+ m_VelocityParents[jointIndex].transform.localRotation = Quaternion.identity;
+
+ s_LinePointsReuse[0] = s_LinePointsReuse[1] = m_VelocityParents[jointIndex].transform.position;
+ if (velocityType == VelocityType.Linear)
+ {
+ if (joint.TryGetLinearVelocity(out var velocity))
+ s_LinePointsReuse[1] += velocity;
+ }
+ else if (velocityType == VelocityType.Angular)
+ {
+ if (joint.TryGetAngularVelocity(out var velocity))
+ s_LinePointsReuse[1] += 0.05f * velocity.normalized;
+ }
+
+ renderer.SetPositions(s_LinePointsReuse);
+ }
+ }
+
+ static void ToggleRenderers<TRenderer>(bool toggle, Transform rendererTransform)
+ where TRenderer : Renderer
+ {
+ if (rendererTransform.TryGetComponent<TRenderer>(out var renderer))
+ renderer.enabled = toggle;
+
+ for (var childIndex = 0; childIndex < rendererTransform.childCount; ++childIndex)
+ ToggleRenderers<TRenderer>(toggle, rendererTransform.GetChild(childIndex));
+ }
+ }
+ }
+}
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs.meta b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs.meta
new file mode 100644
index 0000000..65ca33e
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/HandVisualizer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2e9813c68d7d6f44282ace8bd2d1fd46
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs
new file mode 100644
index 0000000..d8b67e9
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs
@@ -0,0 +1,38 @@
+using UnityEngine;
+using UnityEngine.XR.Hands.Processing;
+
+namespace UnityEngine.XR.Hands.Samples.VisualizerSample
+{
+ public class JointVisualizer : MonoBehaviour
+ {
+ [SerializeField]
+ GameObject m_JointVisual;
+
+ [SerializeField]
+ Material m_HighFidelityJointMaterial;
+
+ [SerializeField]
+ Material m_LowFidelityJointMaterial;
+
+ bool m_HighFidelityJoint;
+
+ Renderer m_JointRenderer;
+
+ public void NotifyTrackingState(XRHandJointTrackingState jointTrackingState)
+ {
+ bool highFidelityJoint = (jointTrackingState & XRHandJointTrackingState.HighFidelityPose) == XRHandJointTrackingState.HighFidelityPose;
+ if (m_HighFidelityJoint == highFidelityJoint)
+ return;
+
+ m_JointRenderer.material = highFidelityJoint ? m_HighFidelityJointMaterial : m_LowFidelityJointMaterial;
+
+ m_HighFidelityJoint = highFidelityJoint;
+ }
+
+ void Start()
+ {
+ if (m_JointVisual.TryGetComponent<Renderer>(out var jointRenderer))
+ m_JointRenderer = jointRenderer;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs.meta b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs.meta
new file mode 100644
index 0000000..9f1035d
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/JointVisualizer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6e1c395ff62a3a14dbeb293298bb46bf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs
new file mode 100644
index 0000000..c673fa9
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs
@@ -0,0 +1,242 @@
+using UnityEngine.Rendering;
+using System.Collections.Generic;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace UnityEngine.XR.Hands.Samples.VisualizerSample
+{
+#if UNITY_EDITOR
+ [InitializeOnLoad]
+ static class RenderPipelineValidation
+ {
+ static RenderPipelineValidation()
+ {
+ foreach (var pipelineHandler in GetAllInstances())
+ pipelineHandler.AutoRefreshPipelineShaders();
+ }
+
+ static List<MaterialPipelineHandler> GetAllInstances()
+ {
+ var instances = new List<MaterialPipelineHandler>();
+
+ // Find all GUIDs for objects that match the type MaterialPipelineHandler
+ var guids = AssetDatabase.FindAssets("t:MaterialPipelineHandler");
+ for (int i = 0; i < guids.Length; i++)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guids[i]);
+ var asset = AssetDatabase.LoadAssetAtPath<MaterialPipelineHandler>(path);
+ if (asset != null)
+ instances.Add(asset);
+ }
+
+ return instances;
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Serializable class that contains the shader information for a material.
+ /// </summary>
+ [System.Serializable]
+ public class ShaderContainer
+ {
+ public Material material;
+ public bool useSRPShaderName = true;
+ public string scriptableRenderPipelineShaderName = "Universal Render Pipeline/Lit";
+ public Shader scriptableRenderPipelineShader;
+ public bool useBuiltinShaderName = true;
+ public string builtInPipelineShaderName = "Standard";
+ public Shader builtInPipelineShader;
+ }
+
+ /// <summary>
+ /// Scriptable object that allows for setting the shader on a material based on the current render pipeline.
+ /// Will run automatically OnEnable in the editor to set the shaders on project bootup. Can be refreshed manually with editor button.
+ /// This exists because while objects render correctly using shadergraph shaders, others do not and using the standard shader resolves various rendering issues.
+ /// </summary>
+ [CreateAssetMenu(fileName = "MaterialPipelineHandler", menuName = "XR/MaterialPipelineHandler", order = 0)]
+ public class MaterialPipelineHandler : ScriptableObject
+ {
+ [SerializeField]
+ [Tooltip("List of materials and their associated shaders.")]
+ List<ShaderContainer> m_ShaderContainers;
+
+ [SerializeField]
+ [Tooltip("If true, the shaders will be refreshed automatically when the editor opens and when this scriptable object instance is enabled.")]
+ bool m_AutoRefreshShaders = true;
+
+#if UNITY_EDITOR
+ void OnEnable()
+ {
+ if (Application.isPlaying)
+ return;
+ AutoRefreshPipelineShaders();
+ }
+#endif
+
+ public void AutoRefreshPipelineShaders()
+ {
+ if (m_AutoRefreshShaders)
+ SetPipelineShaders();
+ }
+
+ /// <summary>
+ /// Applies the appropriate shader to the materials based on the current render pipeline.
+ /// </summary>
+ public void SetPipelineShaders()
+ {
+ if (m_ShaderContainers == null)
+ return;
+
+ bool isBuiltinRenderPipeline = GraphicsSettings.currentRenderPipeline == null;
+
+ foreach (var info in m_ShaderContainers)
+ {
+ if (info.material == null)
+ continue;
+
+ // Find the appropriate shaders based on the toggle
+ Shader birpShader = info.useBuiltinShaderName ? Shader.Find(info.builtInPipelineShaderName) : info.builtInPipelineShader;
+ Shader srpShader = info.useSRPShaderName ? Shader.Find(info.scriptableRenderPipelineShaderName) : info.scriptableRenderPipelineShader;
+
+ // Determine current shader for comparison
+ Shader currentShader = info.material.shader;
+
+ // Update shader for the current render pipeline only if necessary
+ if (isBuiltinRenderPipeline && birpShader != null && currentShader != birpShader)
+ {
+ info.material.shader = birpShader;
+ MarkMaterialModified(info.material);
+ }
+ else if (!isBuiltinRenderPipeline && srpShader != null && currentShader != srpShader)
+ {
+ info.material.shader = srpShader;
+ MarkMaterialModified(info.material);
+ }
+ }
+ }
+
+ static void MarkMaterialModified(Material material)
+ {
+#if UNITY_EDITOR
+ EditorUtility.SetDirty(material);
+#endif
+ }
+ }
+
+#if UNITY_EDITOR
+ /// <summary>
+ /// Custom property drawer for the shader container class.
+ /// </summary>
+ [CustomPropertyDrawer(typeof(ShaderContainer))]
+ public class ShaderContainerDrawer : PropertyDrawer
+ {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ EditorGUI.BeginProperty(position, label, property);
+
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+ float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
+
+ SerializedProperty materialProp = property.FindPropertyRelative("material");
+ SerializedProperty useSRPShaderNameProp = property.FindPropertyRelative("useSRPShaderName");
+ SerializedProperty scriptableShaderNameProp = property.FindPropertyRelative("scriptableRenderPipelineShaderName");
+ SerializedProperty scriptableShaderProp = property.FindPropertyRelative("scriptableRenderPipelineShader");
+ SerializedProperty useShaderNameProp = property.FindPropertyRelative("useBuiltinShaderName");
+ SerializedProperty builtInNameProp = property.FindPropertyRelative("builtInPipelineShaderName");
+ SerializedProperty builtInShaderProp = property.FindPropertyRelative("builtInPipelineShader");
+
+ // Draw Material without the header.
+ position.height = singleLineHeight;
+ EditorGUI.PropertyField(position, materialProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ // SRP Shader header and fields.
+ EditorGUI.LabelField(position, "Scriptable Render Pipeline Shader", EditorStyles.boldLabel);
+ position.y += EditorGUIUtility.singleLineHeight + verticalSpacing;
+
+ EditorGUI.PropertyField(position, useSRPShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ if (useSRPShaderNameProp.boolValue)
+ {
+ EditorGUI.PropertyField(position, scriptableShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+ else
+ {
+ EditorGUI.PropertyField(position, scriptableShaderProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+
+ // Built-in Shader header and fields.
+ EditorGUI.LabelField(position, "Built-In Render Pipeline Shader", EditorStyles.boldLabel);
+ position.y += singleLineHeight + verticalSpacing;
+
+ EditorGUI.PropertyField(position, useShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ if (useShaderNameProp.boolValue)
+ {
+ EditorGUI.PropertyField(position, builtInNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+ else
+ {
+ EditorGUI.PropertyField(position, builtInShaderProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+
+ // Draw a separator line at the end.
+ position.y += verticalSpacing / 2; // Extra space for the line.
+ position.height = 1;
+ EditorGUI.DrawRect(new Rect(position.x, position.y, position.width, 1), Color.gray);
+
+ EditorGUI.EndProperty();
+ }
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ const int baseFieldCount = 4; // The Material field, the two toggles, and one for an optional field.
+ int extraLineCount = property.FindPropertyRelative("useBuiltinShaderName").boolValue ? 0 : 1;
+ extraLineCount += property.FindPropertyRelative("useSRPShaderName").boolValue ? 0 : 1;
+
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+ float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
+ float headerHeight = EditorGUIUtility.singleLineHeight; // No longer need extra height for headers.
+
+ // Calculate height for fields and headers
+ float fieldsHeight = baseFieldCount * singleLineHeight + (baseFieldCount - 1 + extraLineCount) * verticalSpacing;
+
+ // Allow space for header, separator line, and a bit of padding before the line.
+ float headersHeight = 2 * (headerHeight + verticalSpacing);
+ float separatorSpace = verticalSpacing / 2 + 1; // Additional vertical spacing and line height.
+
+ return fieldsHeight + headersHeight + separatorSpace + singleLineHeight * 1.5f;
+ }
+ }
+
+ /// <summary>
+ /// Custom editor MaterialPipelineHandler
+ /// </summary>
+ [CustomEditor(typeof(MaterialPipelineHandler)), CanEditMultipleObjects]
+ public class MaterialPipelineHandlerEditor : Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+
+ // Draw the "Refresh Shaders" button
+ if (GUILayout.Button("Refresh Shaders"))
+ {
+ foreach (var t in targets)
+ {
+ var handler = (MaterialPipelineHandler)t;
+ handler.SetPipelineShaders();
+ }
+ }
+ }
+ }
+#endif
+}
diff --git a/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs.meta b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs.meta
new file mode 100644
index 0000000..3117421
--- /dev/null
+++ b/Assets/Samples/XR Hands/1.6.0/HandVisualizer/Scripts/MaterialPipelineHandler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6772a216ed6f29c42abef904c7d6940d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: