344 lines
13 KiB
C#
344 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using UnityEngine;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Input
|
|
{
|
|
[MixedRealityController(
|
|
SupportedControllerType.GGVHand,
|
|
new[] { Handedness.Left, Handedness.Right, Handedness.None })]
|
|
public class SimulatedGestureHand : SimulatedHand
|
|
{
|
|
/// <inheritdoc />
|
|
public override ControllerSimulationMode SimulationMode => ControllerSimulationMode.HandGestures;
|
|
|
|
private bool initializedFromProfile = false;
|
|
private MixedRealityInputAction holdAction = MixedRealityInputAction.None;
|
|
private MixedRealityInputAction navigationAction = MixedRealityInputAction.None;
|
|
private MixedRealityInputAction manipulationAction = MixedRealityInputAction.None;
|
|
private MixedRealityInputAction selectAction = MixedRealityInputAction.None;
|
|
private bool useRailsNavigation = false;
|
|
float holdStartDuration = 0.0f;
|
|
float navigationStartThreshold = 0.0f;
|
|
|
|
private float SelectDownStartTime = 0.0f;
|
|
private bool holdInProgress = false;
|
|
private bool manipulationInProgress = false;
|
|
private bool navigationInProgress = false;
|
|
private Vector3 currentRailsUsed = Vector3.one;
|
|
private Vector3 currentPosition = Vector3.zero;
|
|
private Vector3 cumulativeDelta = Vector3.zero;
|
|
private MixedRealityPose currentGripPose = MixedRealityPose.ZeroIdentity;
|
|
|
|
private Vector3 NavigationDelta => new Vector3(
|
|
Mathf.Clamp(cumulativeDelta.x, -1.0f, 1.0f) * currentRailsUsed.x,
|
|
Mathf.Clamp(cumulativeDelta.y, -1.0f, 1.0f) * currentRailsUsed.y,
|
|
Mathf.Clamp(cumulativeDelta.z, -1.0f, 1.0f) * currentRailsUsed.z);
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
public SimulatedGestureHand(
|
|
TrackingState trackingState,
|
|
Handedness controllerHandedness,
|
|
IMixedRealityInputSource inputSource = null,
|
|
MixedRealityInteractionMapping[] interactions = null)
|
|
: base(trackingState, controllerHandedness, inputSource, interactions, new SimpleHandDefinition(controllerHandedness))
|
|
{ }
|
|
|
|
/// Lazy-init settings based on profile.
|
|
/// This cannot happen in the constructor because the profile may not exist yet.
|
|
private void EnsureProfileSettings()
|
|
{
|
|
if (initializedFromProfile)
|
|
{
|
|
return;
|
|
}
|
|
initializedFromProfile = true;
|
|
|
|
MixedRealityGesturesProfile gestureProfile = null;
|
|
MixedRealityInputSystemProfile inputSystemProfile = CoreServices.InputSystem?.InputSystemProfile;
|
|
if (inputSystemProfile != null)
|
|
{
|
|
gestureProfile = inputSystemProfile.GesturesProfile;
|
|
}
|
|
if (gestureProfile != null)
|
|
{
|
|
for (int i = 0; i < gestureProfile.Gestures.Length; i++)
|
|
{
|
|
var gesture = gestureProfile.Gestures[i];
|
|
switch (gesture.GestureType)
|
|
{
|
|
case GestureInputType.Hold:
|
|
holdAction = gesture.Action;
|
|
break;
|
|
case GestureInputType.Manipulation:
|
|
manipulationAction = gesture.Action;
|
|
break;
|
|
case GestureInputType.Navigation:
|
|
navigationAction = gesture.Action;
|
|
break;
|
|
case GestureInputType.Select:
|
|
selectAction = gesture.Action;
|
|
break;
|
|
}
|
|
}
|
|
|
|
useRailsNavigation = gestureProfile.UseRailsNavigation;
|
|
}
|
|
|
|
MixedRealityInputSimulationProfile inputSimProfile = CoreServices.GetInputSystemDataProvider<IInputSimulationService>()?.InputSimulationProfile;
|
|
if (inputSimProfile != null)
|
|
{
|
|
holdStartDuration = inputSimProfile.HoldStartDuration;
|
|
navigationStartThreshold = inputSimProfile.NavigationStartThreshold;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void UpdateHandJoints(SimulatedHandData handData)
|
|
{
|
|
for (int i = 0; i < ArticulatedHandPose.JointCount; i++)
|
|
{
|
|
TrackedHandJoint handJoint = (TrackedHandJoint)i;
|
|
|
|
if (!jointPoses.ContainsKey(handJoint))
|
|
{
|
|
jointPoses.Add(handJoint, handData.Joints[i]);
|
|
}
|
|
else
|
|
{
|
|
jointPoses[handJoint] = handData.Joints[i];
|
|
}
|
|
}
|
|
|
|
CoreServices.InputSystem?.RaiseHandJointsUpdated(InputSource, ControllerHandedness, jointPoses);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void UpdateInteractions(SimulatedHandData handData)
|
|
{
|
|
EnsureProfileSettings();
|
|
|
|
Vector3 lastPosition = currentPosition;
|
|
currentPosition = jointPoses[TrackedHandJoint.IndexTip].Position;
|
|
cumulativeDelta += currentPosition - lastPosition;
|
|
currentGripPose.Position = currentPosition;
|
|
|
|
if (lastPosition != currentPosition)
|
|
{
|
|
CoreServices.InputSystem?.RaiseSourcePositionChanged(InputSource, this, currentPosition);
|
|
}
|
|
|
|
for (int i = 0; i < Interactions?.Length; i++)
|
|
{
|
|
switch (Interactions[i].InputType)
|
|
{
|
|
case DeviceInputType.SpatialGrip:
|
|
Interactions[i].PoseData = currentGripPose;
|
|
if (Interactions[i].Changed)
|
|
{
|
|
CoreServices.InputSystem?.RaisePoseInputChanged(InputSource, ControllerHandedness, Interactions[i].MixedRealityInputAction, currentGripPose);
|
|
}
|
|
break;
|
|
case DeviceInputType.Select:
|
|
Interactions[i].BoolData = handData.IsPinching;
|
|
if (Interactions[i].Changed)
|
|
{
|
|
if (Interactions[i].BoolData)
|
|
{
|
|
CoreServices.InputSystem?.RaiseOnInputDown(InputSource, ControllerHandedness, Interactions[i].MixedRealityInputAction);
|
|
|
|
SelectDownStartTime = Time.time;
|
|
cumulativeDelta = Vector3.zero;
|
|
}
|
|
else
|
|
{
|
|
CoreServices.InputSystem?.RaiseOnInputUp(InputSource, ControllerHandedness, Interactions[i].MixedRealityInputAction);
|
|
|
|
// Stop active gestures
|
|
TryCompleteSelect();
|
|
TryCompleteHold();
|
|
TryCompleteManipulation();
|
|
TryCompleteNavigation();
|
|
}
|
|
}
|
|
else if (Interactions[i].BoolData)
|
|
{
|
|
if (manipulationInProgress)
|
|
{
|
|
UpdateManipulation();
|
|
}
|
|
if (navigationInProgress)
|
|
{
|
|
UpdateNavigation();
|
|
}
|
|
|
|
if (cumulativeDelta.magnitude > navigationStartThreshold)
|
|
{
|
|
TryCancelHold();
|
|
TryStartNavigation();
|
|
TryStartManipulation();
|
|
}
|
|
else if (Time.time >= SelectDownStartTime + holdStartDuration)
|
|
{
|
|
TryStartHold();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool TryStartHold()
|
|
{
|
|
if (!holdInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureStarted(this, holdAction);
|
|
holdInProgress = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryCompleteHold()
|
|
{
|
|
if (holdInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCompleted(this, holdAction);
|
|
holdInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryCancelHold()
|
|
{
|
|
if (holdInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCanceled(this, holdAction);
|
|
holdInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryStartManipulation()
|
|
{
|
|
if (!manipulationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureStarted(this, manipulationAction);
|
|
manipulationInProgress = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void UpdateManipulation()
|
|
{
|
|
if (manipulationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureUpdated(this, manipulationAction, cumulativeDelta);
|
|
}
|
|
}
|
|
|
|
private bool TryCompleteManipulation()
|
|
{
|
|
if (manipulationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCompleted(this, manipulationAction, cumulativeDelta);
|
|
manipulationInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryCancelManipulation()
|
|
{
|
|
if (manipulationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCanceled(this, manipulationAction);
|
|
manipulationInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryCompleteSelect()
|
|
{
|
|
if (!manipulationInProgress && !holdInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCompleted(this, selectAction);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryStartNavigation()
|
|
{
|
|
if (!navigationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureStarted(this, navigationAction);
|
|
navigationInProgress = true;
|
|
|
|
currentRailsUsed = Vector3.one;
|
|
UpdateNavigationRails();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void UpdateNavigation()
|
|
{
|
|
if (navigationInProgress)
|
|
{
|
|
UpdateNavigationRails();
|
|
CoreServices.InputSystem?.RaiseGestureUpdated(this, navigationAction, NavigationDelta);
|
|
}
|
|
}
|
|
|
|
private bool TryCompleteNavigation()
|
|
{
|
|
if (navigationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCompleted(this, navigationAction, NavigationDelta);
|
|
navigationInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryCancelNavigation()
|
|
{
|
|
if (navigationInProgress)
|
|
{
|
|
CoreServices.InputSystem?.RaiseGestureCanceled(this, navigationAction);
|
|
navigationInProgress = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// If rails are used, test the delta for largest component and limit navigation to that axis
|
|
private void UpdateNavigationRails()
|
|
{
|
|
if (useRailsNavigation && currentRailsUsed == Vector3.one)
|
|
{
|
|
if (Mathf.Abs(cumulativeDelta.x) >= navigationStartThreshold)
|
|
{
|
|
currentRailsUsed = new Vector3(1, 0, 0);
|
|
}
|
|
else if (Mathf.Abs(cumulativeDelta.y) > navigationStartThreshold)
|
|
{
|
|
currentRailsUsed = new Vector3(0, 1, 0);
|
|
}
|
|
else if (Mathf.Abs(cumulativeDelta.z) > navigationStartThreshold)
|
|
{
|
|
currentRailsUsed = new Vector3(0, 0, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |