44using System . Collections . Generic ;
55using Unity . XR . CoreUtils ;
66using UnityEngine ;
7- using UnityEngine . SubsystemsImplementation . Extensions ;
87using UnityEngine . XR ;
9- using UnityEngine . XR . Hands ;
10- using UnityEngine . XR . Hands . Meshing ;
11- using UnityEngine . XR . Hands . OpenXR ;
128
139#if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
1410using Microsoft . MixedReality . OpenXR ;
@@ -37,11 +33,8 @@ public class PlatformHandMeshVisualizer : HandMeshVisualizer
3733 private bool initializedUVs = false ;
3834#endif
3935
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 ;
36+ private XRMeshSubsystem meshSubsystem = null ;
37+ private readonly List < MeshInfo > meshInfos = new List < MeshInfo > ( ) ;
4538
4639 // The property block used to modify the wrist position property on the material
4740 private MaterialPropertyBlock propertyBlock = null ;
@@ -54,60 +47,40 @@ protected override void OnEnable()
5447 handRenderer . enabled = false ;
5548 propertyBlock ??= new MaterialPropertyBlock ( ) ;
5649
57- if ( handSubsystem != null )
50+ #if UNITY_OPENXR_PRESENT
51+ if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_ANDROID_hand_mesh" ) )
5852 {
59- updateSuccessFlags = HandNode == XRNode . LeftHand ?
60- XRHandSubsystem . UpdateSuccessFlags . LeftHandJoints | XRHandSubsystem . UpdateSuccessFlags . LeftHandRootPose :
61- XRHandSubsystem . UpdateSuccessFlags . RightHandJoints | XRHandSubsystem . UpdateSuccessFlags . RightHandRootPose ;
62-
63- // Since the hand mesh is likely to change every frame, we
64- // "optimize mesh for frequent updates" by marking it dynamic
65- meshFilter . mesh . MarkDynamic ( ) ;
66-
67- return ;
68- }
69-
70- List < XRHandSubsystem > subsystems = XRSubsystemHelpers . GetAllSubsystems < XRHandSubsystem > ( ) ;
71- foreach ( XRHandSubsystem subsystem in subsystems )
72- {
73- if ( subsystem . GetProvider ( ) is OpenXRHandProvider provider && provider . handMeshDataSupplier != null )
53+ List < XRMeshSubsystem > meshSubsystems = new List < XRMeshSubsystem > ( ) ;
54+ SubsystemManager . GetSubsystems ( meshSubsystems ) ;
55+ foreach ( XRMeshSubsystem subsystem in meshSubsystems )
7456 {
75- Debug . Log ( $ "Using { provider . handMeshDataSupplier . GetType ( ) } for hand visualization.") ;
76- handSubsystem = subsystem ;
77-
78- updateSuccessFlags = HandNode == XRNode . LeftHand ?
79- XRHandSubsystem . UpdateSuccessFlags . LeftHandJoints | XRHandSubsystem . UpdateSuccessFlags . LeftHandRootPose :
80- XRHandSubsystem . UpdateSuccessFlags . RightHandJoints | XRHandSubsystem . UpdateSuccessFlags . RightHandRootPose ;
81-
82- // Since the hand mesh is likely to change every frame, we
83- // "optimize mesh for frequent updates" by marking it dynamic
84- meshFilter . mesh . MarkDynamic ( ) ;
85-
86- return ;
57+ if ( subsystem . subsystemDescriptor . id == "AndroidXRHandMeshProvider"
58+ || subsystem . subsystemDescriptor . id == "AndroidXRMeshProvider" )
59+ {
60+ Debug . Log ( $ "Using XR_ANDROID_hand_mesh for { HandNode } visualization.") ;
61+ meshSubsystem = subsystem ;
62+ break ;
63+ }
8764 }
8865 }
89-
90- #if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
91- if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_MSFT_hand_tracking_mesh" ) )
66+ else if ( UnityEngine . XR . OpenXR . OpenXRRuntime . IsExtensionEnabled ( "XR_MSFT_hand_tracking_mesh" ) )
9267 {
68+ #if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
9369 Debug . Log ( $ "Using XR_MSFT_hand_tracking_mesh for { HandNode } visualization.") ;
9470 handMeshTracker = HandNode == XRNode . LeftHand ? HandMeshTracker . Left : HandMeshTracker . Right ;
9571
96- // Since the hand mesh is likely to change every frame, we
97- // "optimize mesh for frequent updates" by marking it dynamic
98- meshFilter . mesh . MarkDynamic ( ) ;
99-
10072 if ( neutralPoseMesh == null )
10173 {
10274 neutralPoseMesh = new Mesh ( ) ;
10375 }
104-
105- return ;
76+ #endif
10677 }
78+ else
10779#endif
108-
109- Debug . Log ( $ "No active hand mesh extension was found for { HandNode } visualization.") ;
110- enabled = false ;
80+ {
81+ Debug . Log ( $ "No active hand mesh extension was found for { HandNode } visualization.") ;
82+ enabled = false ;
83+ }
11184 }
11285
11386 protected void Update ( )
@@ -122,52 +95,25 @@ protected void Update()
12295 return ;
12396 }
12497
125- if ( handSubsystem != null && handSubsystem . running )
98+ if ( meshSubsystem != null
99+ && meshSubsystem . running
100+ && meshSubsystem . TryGetMeshInfos ( meshInfos ) )
126101 {
127- XRHandMeshDataQueryParams queryParams = new ( )
128- {
129- allocator = Unity . Collections . Allocator . Temp ,
130- } ;
102+ int handMeshIndex = HandNode == XRNode . LeftHand ? 0 : 1 ;
131103
132- Debug . Log ( $ "Success?? { HandNode } | { Time . frameCount } | { handSubsystem . updateSuccessFlags } | { updateSuccessFlags } | { ( handSubsystem . updateSuccessFlags & updateSuccessFlags ) != 0 } " ) ;
133- if ( ( handSubsystem . updateSuccessFlags & updateSuccessFlags ) != 0
134- && ( lastUpdatedFrame == Time . frameCount || handSubsystem . TryGetMeshData ( out result , ref queryParams ) ) )
104+ MeshInfo meshInfo = meshInfos [ handMeshIndex ] ;
105+ if ( meshInfo . ChangeState == MeshChangeState . Added
106+ || meshInfo . ChangeState == MeshChangeState . Updated )
135107 {
136- lastUpdatedFrame = Time . frameCount ;
137- XRHandMeshData handMeshData = HandNode == XRNode . LeftHand ? result . leftHand : result . rightHand ;
138- handRenderer . enabled = true ;
139-
140- if ( handMeshData . positions . IsCreated && handMeshData . indices . IsCreated )
141- {
142- meshFilter . mesh . SetVertices ( handMeshData . positions ) ;
143- Unity . Collections . NativeArray < int > indices = handMeshData . indices ;
144- // This API appears to return CCW triangles, but Unity expects CW triangles
145- for ( int i = 0 ; i < indices . Length ; i += 3 )
146- {
147- ( indices [ i + 1 ] , indices [ i + 2 ] ) = ( indices [ i + 2 ] , indices [ i + 1 ] ) ;
148- }
149- meshFilter . mesh . SetIndices ( indices , MeshTopology . Triangles , 0 ) ;
150- meshFilter . mesh . RecalculateBounds ( ) ;
151- }
152-
153- if ( handMeshData . uvs . IsCreated )
154- {
155- meshFilter . mesh . SetUVs ( 0 , handMeshData . uvs ) ;
156- }
108+ meshSubsystem . GenerateMeshAsync ( meshInfo . MeshId , meshFilter . mesh ,
109+ null , MeshVertexAttributes . Normals , result => { } ) ;
157110
158- if ( handMeshData . normals . IsCreated )
159- {
160- meshFilter . mesh . SetNormals ( handMeshData . normals ) ;
161- }
162- else
163- {
164- meshFilter . mesh . RecalculateNormals ( ) ;
165- }
111+ handRenderer . enabled = true ;
166112
167- if ( handMeshData . TryGetRootPose ( out Pose rootPose ) )
168- {
169- transform . SetWorldPose ( PlayspaceUtilities . TransformPose ( rootPose ) ) ;
170- }
113+ // This hand mesh is provided pre-translated from the world origin,
114+ // so we want to ensure the mesh is "centered" at the world origin
115+ PlayspaceUtilities . XROrigin . CameraFloorOffsetObject . transform . GetPositionAndRotation ( out Vector3 position , out Quaternion rotation ) ;
116+ transform . SetPositionAndRotation ( position , rotation ) ;
171117 }
172118 }
173119#if MROPENXR_PRESENT && ( UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_ANDROID )
0 commit comments