// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #if MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) using Microsoft.MixedReality.OpenXR; using System.Collections.Generic; using UnityEngine; #endif // MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) using Microsoft.MixedReality.Toolkit.Input; using Unity.Profiling; namespace Microsoft.MixedReality.Toolkit.XRSDK.OpenXR { /// /// Provides the ability to load a hand mesh from OpenXR for the corresponding hand. /// internal class OpenXRHandMeshProvider { /// /// The user's left hand. /// public static OpenXRHandMeshProvider Left { get; } = #if MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) new OpenXRHandMeshProvider(HandMeshTracker.Left, Utilities.Handedness.Left); #else null; #endif // MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) /// /// The user's right hand. /// public static OpenXRHandMeshProvider Right { get; } = #if MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) new OpenXRHandMeshProvider(HandMeshTracker.Right, Utilities.Handedness.Right); #else null; #endif // MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) #if MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) private OpenXRHandMeshProvider(HandMeshTracker handMeshTracker, Utilities.Handedness handedness) { this.handMeshTracker = handMeshTracker; this.handedness = handedness; mesh = new Mesh(); neutralPoseMesh = new Mesh(); } private readonly HandMeshTracker handMeshTracker; private readonly Utilities.Handedness handedness; private readonly Mesh mesh; private readonly Mesh neutralPoseMesh; private readonly List vertices = new List(); private readonly List normals = new List(); private readonly List triangles = new List(); private Vector2[] handMeshUVs = null; #endif // MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) private IMixedRealityInputSource inputSource = null; /// /// Sets the that represents the current hand for this mesh. /// /// Implementation of the hand input source. public void SetInputSource(IMixedRealityInputSource inputSource) => this.inputSource = inputSource; private static readonly ProfilerMarker UpdateHandMeshPerfMarker = new ProfilerMarker($"[MRTK] {nameof(OpenXRHandMeshProvider)}.UpdateHandMesh"); /// /// Updates the hand mesh based on the current state of the hand. /// public void UpdateHandMesh() { #if MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) using (UpdateHandMeshPerfMarker.Auto()) { MixedRealityInputSystemProfile inputSystemProfile = CoreServices.InputSystem?.InputSystemProfile; MixedRealityHandTrackingProfile handTrackingProfile = inputSystemProfile != null ? inputSystemProfile.HandTrackingProfile : null; if (handTrackingProfile == null || !handTrackingProfile.EnableHandMeshVisualization) { // If hand mesh visualization is disabled make sure to clean up if we've already initialized if (handMeshUVs != null) { // Notify that hand mesh has been updated (cleared) CoreServices.InputSystem?.RaiseHandMeshUpdated(inputSource, handedness, new HandMeshInfo()); handMeshUVs = null; } return; } if (handMeshUVs == null && handMeshTracker.TryGetHandMesh(FrameTime.OnUpdate, neutralPoseMesh, HandPoseType.ReferenceOpenPalm)) { handMeshUVs = InitializeUVs(neutralPoseMesh.vertices); } if (handMeshTracker.TryGetHandMesh(FrameTime.OnUpdate, mesh) && handMeshTracker.TryLocateHandMesh(FrameTime.OnUpdate, out Pose pose)) { mesh.GetVertices(vertices); mesh.GetNormals(normals); mesh.GetTriangles(triangles, 0); HandMeshInfo handMeshInfo = new HandMeshInfo { vertices = vertices.ToArray(), normals = normals.ToArray(), triangles = triangles.ToArray(), uvs = handMeshUVs, position = MixedRealityPlayspace.TransformPoint(pose.position), rotation = MixedRealityPlayspace.Rotation * pose.rotation }; CoreServices.InputSystem?.RaiseHandMeshUpdated(inputSource, handedness, handMeshInfo); } } } private Vector2[] InitializeUVs(Vector3[] neutralPoseVertices) { if (neutralPoseVertices.Length == 0) { Debug.LogError("Loaded 0 vertices for neutralPoseVertices"); return System.Array.Empty(); } float minY = neutralPoseVertices[0].y; float maxY = minY; for (int ix = 1; ix < neutralPoseVertices.Length; ix++) { Vector3 p = neutralPoseVertices[ix]; if (p.y < minY) { minY = p.y; } else if (p.y > maxY) { maxY = p.y; } } float scale = 1.0f / (maxY - minY); Vector2[] uvs = new Vector2[neutralPoseVertices.Length]; for (int ix = 0; ix < neutralPoseVertices.Length; ix++) { Vector3 p = neutralPoseVertices[ix]; uvs[ix] = new Vector2(p.x * scale + 0.5f, (p.y - minY) * scale); } return uvs; #endif // MSFT_OPENXR && (UNITY_STANDALONE_WIN || UNITY_WSA) } } }