Skip to content

Commit cf95ee0

Browse files
RogPodgekeveleigh
andauthored
OpenXR general hand interaction support (#10538)
* OpenXR general hand interaction support * Update Assets/MRTK/Core/Definitions/Devices/ArticulatedHandDefinition.cs Co-authored-by: Kurtis <[email protected]> * Update Assets/MRTK/Providers/OpenXR/Scripts/MicrosoftArticulatedHand.cs Co-authored-by: Kurtis <[email protected]> * Update Assets/MRTK/Core/Definitions/Devices/ArticulatedHandDefinition.cs Co-authored-by: Kurtis <[email protected]> * pr feedback * applied rotation to go from grip pose to source pose * sanity check for palm joint retrieval Co-authored-by: Kurtis <[email protected]>
1 parent 30d4ee7 commit cf95ee0

File tree

6 files changed

+338
-13
lines changed

6 files changed

+338
-13
lines changed

Assets/MRTK/Core/Definitions/Devices/ArticulatedHandDefinition.cs

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ public float ExitPinchDistance
8686
}
8787
}
8888

89-
private bool isPinching = false;
90-
9189
/// <summary>
9290
/// The articulated hands default interactions.
9391
/// </summary>
@@ -120,8 +118,14 @@ public MixedRealityInteractionMapping[] DefaultInteractions
120118
new MixedRealityInputActionMapping("Teleport Pose", AxisType.DualAxis, DeviceInputType.ThumbStick),
121119
};
122120

121+
122+
// Internal calculation of what the HandRay should be
123+
// Useful as a fallback for hand ray data
124+
protected virtual IHandRay HandRay { get; } = new HandRay();
125+
123126
/// <summary>
124127
/// Calculates whether the current pose allows for pointing/distant interactions.
128+
/// Equivalent to the HandRay's ShouldShowRay implementation <see cref="HandRay.ShouldShowRay"/>
125129
/// </summary>
126130
public bool IsInPointingPose
127131
{
@@ -168,13 +172,19 @@ protected bool IsInTeleportPose
168172

169173
Transform cameraTransform = mainCamera.transform;
170174

175+
Vector3 palmNormal = -palmPose.Up;
171176
// We check if the palm up is roughly in line with the camera up
172-
return Vector3.Dot(-palmPose.Up, cameraTransform.up) > 0.6f
177+
return Vector3.Dot(palmNormal, cameraTransform.up) > 0.6f
173178
// Thumb must be extended, and middle must be grabbing
174179
&& !isThumbGrabbing && isMiddleGrabbing;
175180
}
176181
}
177182

183+
/// <summary>
184+
/// A bool tracking whether the hand definition is pinch or not
185+
/// </summary>
186+
private bool isPinching = false;
187+
178188
/// <summary>
179189
/// Calculates whether the current the current joint pose is selecting (air tap gesture).
180190
/// </summary>
@@ -206,12 +216,31 @@ public bool IsPinching
206216
}
207217
}
208218

219+
public bool IsGrabbing => isIndexGrabbing && isMiddleGrabbing;
220+
209221
private bool isIndexGrabbing;
210222
private bool isMiddleGrabbing;
211223
private bool isThumbGrabbing;
212224

225+
// Velocity internal states
226+
private float deltaTimeStart;
227+
private const int velocityUpdateInterval = 6;
228+
private int frameOn = 0;
229+
230+
private readonly Vector3[] velocityPositionsCache = new Vector3[velocityUpdateInterval];
231+
private readonly Vector3[] velocityNormalsCache = new Vector3[velocityUpdateInterval];
232+
private Vector3 velocityPositionsSum = Vector3.zero;
233+
private Vector3 velocityNormalsSum = Vector3.zero;
234+
235+
public Vector3 AngularVelocity { get; protected set; }
236+
237+
public Vector3 Velocity { get; protected set; }
238+
239+
213240
private static readonly ProfilerMarker UpdateHandJointsPerfMarker = new ProfilerMarker("[MRTK] ArticulatedHandDefinition.UpdateHandJoints");
214241

242+
#region Hand Definition Update functions
243+
215244
/// <summary>
216245
/// Updates the current hand joints with new data.
217246
/// </summary>
@@ -257,6 +286,10 @@ public void UpdateCurrentIndexPose(MixedRealityInteractionMapping interactionMap
257286

258287
private static readonly ProfilerMarker UpdateCurrentTeleportPosePerfMarker = new ProfilerMarker("[MRTK] ArticulatedHandDefinition.UpdateCurrentTeleportPose");
259288

289+
/// <summary>
290+
/// Updates the MixedRealityInteractionMapping with the lastest teleport pose status and fires an event when appropriate
291+
/// </summary>
292+
/// <param name="interactionMapping">The teleport action's interaction mapping.</param>
260293
public void UpdateCurrentTeleportPose(MixedRealityInteractionMapping interactionMapping)
261294
{
262295
using (UpdateCurrentTeleportPosePerfMarker.Auto())
@@ -311,5 +344,80 @@ public void UpdateCurrentTeleportPose(MixedRealityInteractionMapping interaction
311344
previousReadyToTeleport = isReadyForTeleport;
312345
}
313346
}
347+
348+
/// <summary>
349+
/// Updates the MixedRealityInteractionMapping with the lastest pointer pose status and fires a corresponding pose event.
350+
/// </summary>
351+
/// <param name="interactionMapping">The pointer pose's interaction mapping.</param>
352+
public void UpdatePointerPose(MixedRealityInteractionMapping interactionMapping)
353+
{
354+
if (!unityJointPoses.TryGetValue(TrackedHandJoint.IndexKnuckle, out var knucklePose)) return;
355+
if (!unityJointPoses.TryGetValue(TrackedHandJoint.Palm, out var palmPose)) return;
356+
357+
Vector3 rayPosition = knucklePose.Position;
358+
Vector3 palmNormal = -palmPose.Up;
359+
360+
HandRay.Update(rayPosition, palmNormal, CameraCache.Main.transform, Handedness);
361+
Ray ray = HandRay.Ray;
362+
363+
MixedRealityPose pointerPose = new MixedRealityPose();
364+
pointerPose.Position = ray.origin;
365+
pointerPose.Rotation = Quaternion.LookRotation(ray.direction);
366+
367+
// Update the interaction data source
368+
interactionMapping.PoseData = pointerPose;
369+
370+
// If our value changed raise it
371+
if (interactionMapping.Changed)
372+
{
373+
// Raise input system event if it's enabled
374+
CoreServices.InputSystem?.RaisePoseInputChanged(InputSource, Handedness, interactionMapping.MixedRealityInputAction, pointerPose);
375+
}
376+
}
377+
378+
/// <summary>
379+
/// Updates the hand definition with its velocity
380+
/// </summary>
381+
public void UpdateVelocity()
382+
{
383+
if (unityJointPoses.TryGetValue(TrackedHandJoint.Palm, out var currentPalmPose))
384+
{
385+
Vector3 palmPosition = currentPalmPose.Position;
386+
Vector3 palmNormal = -currentPalmPose.Up;
387+
388+
if (frameOn < velocityUpdateInterval)
389+
{
390+
velocityPositionsCache[frameOn] = palmPosition;
391+
velocityPositionsSum += velocityPositionsCache[frameOn];
392+
velocityNormalsCache[frameOn] = palmNormal;
393+
velocityNormalsSum += velocityNormalsCache[frameOn];
394+
}
395+
else
396+
{
397+
int frameIndex = frameOn % velocityUpdateInterval;
398+
399+
float deltaTime = Time.unscaledTime - deltaTimeStart;
400+
401+
Vector3 newPositionsSum = velocityPositionsSum - velocityPositionsCache[frameIndex] + palmPosition;
402+
Vector3 newNormalsSum = velocityNormalsSum - velocityNormalsCache[frameIndex] + palmNormal;
403+
404+
Velocity = (newPositionsSum - velocityPositionsSum) / deltaTime / velocityUpdateInterval;
405+
406+
Quaternion rotation = Quaternion.FromToRotation(velocityNormalsSum / velocityUpdateInterval, newNormalsSum / velocityUpdateInterval);
407+
Vector3 rotationRate = rotation.eulerAngles * Mathf.Deg2Rad;
408+
AngularVelocity = rotationRate / deltaTime;
409+
410+
velocityPositionsCache[frameIndex] = palmPosition;
411+
velocityNormalsCache[frameIndex] = palmNormal;
412+
velocityPositionsSum = newPositionsSum;
413+
velocityNormalsSum = newNormalsSum;
414+
}
415+
416+
deltaTimeStart = Time.unscaledTime;
417+
frameOn++;
418+
}
419+
}
420+
421+
#endregion
314422
}
315423
}

Assets/MRTK/Providers/Oculus/XRSDK/MRTK-Quest/Scripts/Input/Controllers/OculusHand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ public void UpdateController(OVRHand hand, OVRSkeleton ovrSkeleton, Transform tr
134134
currentPointerPose.Rotation = Quaternion.LookRotation(pointerForward, pointerUp);
135135

136136
currentGripPose = jointPoses[TrackedHandJoint.Palm];
137-
138137
CoreServices.InputSystem?.RaiseSourcePoseChanged(InputSource, this, currentGripPose);
139138

140139
UpdateVelocity();

Assets/MRTK/Providers/OpenXR/Scripts/MicrosoftArticulatedHand.cs

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public MicrosoftArticulatedHand(TrackingState trackingState, Handedness controll
4444
private Quaternion currentPointerRotation = Quaternion.identity;
4545
private MixedRealityPose currentPointerPose = MixedRealityPose.ZeroIdentity;
4646

47+
// The rotation offset between the reported grip pose of a hand and the palm joint orientation.
48+
// These values were calculated by comparing the platform's reported grip pose and palm pose.
49+
private static readonly Quaternion rightPalmOffset = Quaternion.Inverse(new Quaternion(Mathf.Sqrt(0.125f), Mathf.Sqrt(0.125f), -Mathf.Sqrt(1.5f) / 2.0f, Mathf.Sqrt(1.5f) / 2.0f));
50+
private static readonly Quaternion leftPalmOffset = Quaternion.Inverse(new Quaternion(Mathf.Sqrt(0.125f), -Mathf.Sqrt(0.125f), Mathf.Sqrt(1.5f) / 2.0f, Mathf.Sqrt(1.5f) / 2.0f));
51+
4752
#region IMixedRealityHand Implementation
4853

4954
/// <inheritdoc/>
@@ -54,8 +59,18 @@ public MicrosoftArticulatedHand(TrackingState trackingState, Handedness controll
5459
/// <inheritdoc/>
5560
public override bool IsInPointingPose => handDefinition.IsInPointingPose;
5661

62+
protected bool IsPinching => handDefinition.IsPinching;
63+
64+
// Pinch was also used as grab, we want to allow hand-curl grab not just pinch.
65+
// Determine pinch and grab separately
66+
protected bool IsGrabbing => handDefinition.IsGrabbing;
67+
5768
private static readonly ProfilerMarker UpdateControllerPerfMarker = new ProfilerMarker("[MRTK] MicrosoftArticulatedHand.UpdateController");
5869

70+
// This bool is used to track whether or not we are recieving device data from the platform itself
71+
// If we aren't we will attempt to infer some common input actions from the hand joint data (i.e. the pinch gesture, pointer positions etc)
72+
private bool receivingDeviceInputs = false;
73+
5974
/// <summary>
6075
/// The OpenXR plug-in uses extensions to expose all possible data, which might be surfaced through multiple input devices.
6176
/// This method is overridden to account for multiple input devices.
@@ -76,20 +91,87 @@ public override void UpdateController(InputDevice inputDevice)
7691
if (inputDevice.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 _))
7792
{
7893
base.UpdateController(inputDevice);
94+
95+
// We've gotten device data from the platform, don't attempt to infer other input actions
96+
// from the hand joint data
97+
receivingDeviceInputs = true;
7998
}
8099
else
81100
{
82101
UpdateHandData(inputDevice);
83102

103+
// Updating the Index finger pose right after getting the hand data
104+
// regardless of whether device data is present
84105
for (int i = 0; i < Interactions?.Length; i++)
85106
{
86-
switch (Interactions[i].AxisType)
107+
var interactionMapping = Interactions[i];
108+
switch (interactionMapping.InputType)
87109
{
88-
case AxisType.SixDof:
89-
UpdatePoseData(Interactions[i], inputDevice);
110+
case DeviceInputType.IndexFinger:
111+
handDefinition?.UpdateCurrentIndexPose(interactionMapping);
90112
break;
91113
}
92114
}
115+
116+
// If we aren't getting device data, infer input actions, velocity, etc from hand joint data
117+
if (!receivingDeviceInputs)
118+
{
119+
for (int i = 0; i < Interactions?.Length; i++)
120+
{
121+
var interactionMapping = Interactions[i];
122+
switch (interactionMapping.InputType)
123+
{
124+
case DeviceInputType.SpatialGrip:
125+
if (TryGetJoint(TrackedHandJoint.Palm, out MixedRealityPose currentGripPose))
126+
{
127+
interactionMapping.PoseData = currentGripPose;
128+
129+
if (interactionMapping.Changed)
130+
{
131+
CoreServices.InputSystem?.RaisePoseInputChanged(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction, currentGripPose);
132+
133+
// Spatial Grip is also used as the basis for the source pose when device data is not provided
134+
// We need to rotate it by an offset to properly represent the source pose.
135+
MixedRealityPose CurrentControllerPose = currentGripPose;
136+
CurrentControllerPose.Rotation *= (ControllerHandedness == Handedness.Left ? leftPalmOffset : rightPalmOffset);
137+
138+
CoreServices.InputSystem?.RaiseSourcePoseChanged(InputSource, this, CurrentControllerPose);
139+
IsPositionAvailable = IsRotationAvailable = true;
140+
}
141+
}
142+
break;
143+
case DeviceInputType.Select:
144+
case DeviceInputType.TriggerPress:
145+
case DeviceInputType.GripPress:
146+
interactionMapping.BoolData = IsPinching || IsGrabbing;
147+
148+
if (interactionMapping.Changed)
149+
{
150+
if (interactionMapping.BoolData)
151+
{
152+
CoreServices.InputSystem?.RaiseOnInputDown(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction);
153+
}
154+
else
155+
{
156+
CoreServices.InputSystem?.RaiseOnInputUp(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction);
157+
}
158+
}
159+
break;
160+
case DeviceInputType.SpatialPointer:
161+
handDefinition?.UpdatePointerPose(interactionMapping);
162+
break;
163+
// Gotta do this only for non-AR devices
164+
case DeviceInputType.ThumbStick:
165+
handDefinition?.UpdateCurrentTeleportPose(interactionMapping);
166+
break;
167+
}
168+
}
169+
170+
// Update the controller velocity based on the hand definition's calculations
171+
handDefinition?.UpdateVelocity();
172+
Velocity = (handDefinition?.Velocity).Value;
173+
AngularVelocity = (handDefinition?.AngularVelocity).Value;
174+
}
93175
}
94176
}
95177
}
@@ -186,9 +268,6 @@ protected override void UpdatePoseData(MixedRealityInteractionMapping interactio
186268
{
187269
switch (interactionMapping.InputType)
188270
{
189-
case DeviceInputType.IndexFinger:
190-
handDefinition?.UpdateCurrentIndexPose(interactionMapping);
191-
break;
192271
case DeviceInputType.SpatialPointer:
193272
if (inputDevice.TryGetFeatureValue(CustomUsages.PointerPosition, out currentPointerPosition))
194273
{

0 commit comments

Comments
 (0)