44using System . Collections . Generic ;
55using Unity . XR . CoreUtils ;
66using UnityEngine ;
7+ using UnityEngine . SubsystemsImplementation . Extensions ;
78using UnityEngine . XR ;
9+ using UnityEngine . XR . Hands ;
10+ using UnityEngine . XR . Hands . Meshing ;
11+ using UnityEngine . XR . Hands . OpenXR ;
812
913#if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
1014using Microsoft . MixedReality . OpenXR ;
@@ -33,8 +37,11 @@ public class PlatformHandMeshVisualizer : HandMeshVisualizer
3337 private bool initializedUVs = false ;
3438#endif
3539
36- private XRMeshSubsystem meshSubsystem = null ;
37- private readonly List < MeshInfo > meshInfos = new List < MeshInfo > ( ) ;
40+ // Share these among all instances to only query once per frame
41+ private static int lastUpdatedFrame = - 1 ;
42+ private static XRHandSubsystem handSubsystem = null ;
43+ private static XRHandMeshDataQueryResult result ;
44+ private XRHandSubsystem . UpdateSuccessFlags updateSuccessFlags ;
3845
3946 // The property block used to modify the wrist position property on the material
4047 private MaterialPropertyBlock propertyBlock = null ;
@@ -47,33 +54,43 @@ protected override void OnEnable()
4754 handRenderer . enabled = false ;
4855 propertyBlock ??= new MaterialPropertyBlock ( ) ;
4956
50- #if UNITY_OPENXR_PRESENT
51- if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_ANDROID_hand_mesh" ) )
57+ // If we already found our subsystem, don't search again
58+ if ( handSubsystem != null && handSubsystem . running )
5259 {
53- // If we already found our subsystem, just return
54- if ( meshSubsystem != null && meshSubsystem . running ) { return ; }
60+ updateSuccessFlags = HandNode == XRNode . LeftHand ?
61+ XRHandSubsystem . UpdateSuccessFlags . LeftHandJoints | XRHandSubsystem . UpdateSuccessFlags . LeftHandRootPose :
62+ XRHandSubsystem . UpdateSuccessFlags . RightHandJoints | XRHandSubsystem . UpdateSuccessFlags . RightHandRootPose ;
5563
56- List < XRMeshSubsystem > meshSubsystems = new List < XRMeshSubsystem > ( ) ;
57- SubsystemManager . GetSubsystems ( meshSubsystems ) ;
58- foreach ( XRMeshSubsystem subsystem in meshSubsystems )
64+ // Since the hand mesh is likely to change every frame, we
65+ // "optimize mesh for frequent updates" by marking it dynamic
66+ meshFilter . mesh . MarkDynamic ( ) ;
67+
68+ return ;
69+ }
70+
71+ List < XRHandSubsystem > subsystems = XRSubsystemHelpers . GetAllSubsystems < XRHandSubsystem > ( ) ;
72+ foreach ( XRHandSubsystem subsystem in subsystems )
73+ {
74+ if ( subsystem . GetProvider ( ) is OpenXRHandProvider provider && provider . handMeshDataSupplier != null )
5975 {
60- if ( subsystem . subsystemDescriptor . id == "AndroidXRHandMeshProvider"
61- || subsystem . subsystemDescriptor . id == "AndroidXRMeshProvider" )
62- {
63- Debug . Log ( $ "Using XR_ANDROID_hand_mesh for { HandNode } visualization.") ;
64- meshSubsystem = subsystem ;
76+ Debug . Log ( $ "Using { provider . handMeshDataSupplier . GetType ( ) } for hand visualization.") ;
77+ handSubsystem = subsystem ;
6578
66- // Since the hand mesh is likely to change every frame, we
67- // "optimize mesh for frequent updates" by marking it dynamic
68- meshFilter . mesh . MarkDynamic ( ) ;
79+ updateSuccessFlags = HandNode == XRNode . LeftHand ?
80+ XRHandSubsystem . UpdateSuccessFlags . LeftHandJoints | XRHandSubsystem . UpdateSuccessFlags . LeftHandRootPose :
81+ XRHandSubsystem . UpdateSuccessFlags . RightHandJoints | XRHandSubsystem . UpdateSuccessFlags . RightHandRootPose ;
6982
70- break ;
71- }
83+ // Since the hand mesh is likely to change every frame, we
84+ // "optimize mesh for frequent updates" by marking it dynamic
85+ meshFilter . mesh . MarkDynamic ( ) ;
86+
87+ return ;
7288 }
7389 }
74- else if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_MSFT_hand_tracking_mesh" ) )
75- {
90+
7691#if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
92+ if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_MSFT_hand_tracking_mesh" ) )
93+ {
7794 Debug . Log ( $ "Using XR_MSFT_hand_tracking_mesh for { HandNode } visualization.") ;
7895 handMeshTracker = HandNode == XRNode . LeftHand ? HandMeshTracker . Left : HandMeshTracker . Right ;
7996
@@ -85,14 +102,13 @@ protected override void OnEnable()
85102 {
86103 neutralPoseMesh = new Mesh ( ) ;
87104 }
88- #endif
105+
106+ return ;
89107 }
90- else
91108#endif
92- {
93- Debug . Log ( $ "No active hand mesh extension was found for { HandNode } visualization.") ;
94- enabled = false ;
95- }
109+
110+ Debug . Log ( $ "No active hand mesh extension was found for { HandNode } visualization.") ;
111+ enabled = false ;
96112 }
97113
98114 protected void Update ( )
@@ -107,31 +123,52 @@ protected void Update()
107123 return ;
108124 }
109125
110- if ( meshSubsystem != null
111- && meshSubsystem . running
112- && meshSubsystem . TryGetMeshInfos ( meshInfos ) )
126+ if ( handSubsystem != null && handSubsystem . running )
113127 {
114- int handMeshIndex = HandNode == XRNode . LeftHand ? 0 : 1 ;
128+ XRHandMeshDataQueryParams queryParams = new ( )
129+ {
130+ allocator = Unity . Collections . Allocator . Temp ,
131+ } ;
115132
116- MeshInfo meshInfo = meshInfos [ handMeshIndex ] ;
117- if ( meshInfo . ChangeState == MeshChangeState . Added
118- || meshInfo . ChangeState == MeshChangeState . Updated )
133+ Debug . Log ( $ "Success?? { HandNode } | { Time . frameCount } | { handSubsystem . updateSuccessFlags } | { updateSuccessFlags } | { ( handSubsystem . updateSuccessFlags & updateSuccessFlags ) != 0 } " ) ;
134+ if ( ( handSubsystem . updateSuccessFlags & updateSuccessFlags ) != 0
135+ && ( lastUpdatedFrame == Time . frameCount || handSubsystem . TryGetMeshData ( out result , ref queryParams ) ) )
119136 {
120- meshSubsystem . GenerateMeshAsync ( meshInfo . MeshId , meshFilter . mesh ,
121- null , MeshVertexAttributes . Normals , result =>
137+ lastUpdatedFrame = Time . frameCount ;
138+ XRHandMeshData handMeshData = HandNode == XRNode . LeftHand ? result . leftHand : result . rightHand ;
139+ handRenderer . enabled = true ;
140+
141+ if ( handMeshData . positions . IsCreated && handMeshData . indices . IsCreated )
142+ {
143+ meshFilter . mesh . SetVertices ( handMeshData . positions ) ;
144+ Unity . Collections . NativeArray < int > indices = handMeshData . indices ;
145+ // This API appears to return CCW triangles, but Unity expects CW triangles
146+ for ( int i = 0 ; i < indices . Length ; i += 3 )
122147 {
123- if ( result . Status == MeshGenerationStatus . Success )
124- {
125- transform . SetWorldPose ( PlayspaceUtilities . TransformPose ( new Pose ( result . Position , result . Rotation ) ) ) ;
126- transform . localScale = result . Scale ;
127-
128- handRenderer . enabled = true ;
129- }
130- else if ( result . Status != MeshGenerationStatus . GenerationAlreadyInProgress )
131- {
132- handRenderer . enabled = false ;
133- }
134- } , MeshGenerationOptions . ConsumeTransform ) ;
148+ ( indices [ i + 1 ] , indices [ i + 2 ] ) = ( indices [ i + 2 ] , indices [ i + 1 ] ) ;
149+ }
150+ meshFilter . mesh . SetIndices ( indices , MeshTopology . Triangles , 0 ) ;
151+ meshFilter . mesh . RecalculateBounds ( ) ;
152+ }
153+
154+ if ( handMeshData . uvs . IsCreated )
155+ {
156+ meshFilter . mesh . SetUVs ( 0 , handMeshData . uvs ) ;
157+ }
158+
159+ if ( handMeshData . normals . IsCreated )
160+ {
161+ meshFilter . mesh . SetNormals ( handMeshData . normals ) ;
162+ }
163+ else
164+ {
165+ meshFilter . mesh . RecalculateNormals ( ) ;
166+ }
167+
168+ if ( handMeshData . TryGetRootPose ( out Pose rootPose ) )
169+ {
170+ transform . SetWorldPose ( PlayspaceUtilities . TransformPose ( rootPose ) ) ;
171+ }
135172 }
136173 }
137174#if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
0 commit comments