// 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
{
///
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);
///
/// Constructor.
///
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()?.InputSimulationProfile;
if (inputSimProfile != null)
{
holdStartDuration = inputSimProfile.HoldStartDuration;
navigationStartThreshold = inputSimProfile.NavigationStartThreshold;
}
}
///
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);
}
///
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);
}
}
}
}
}