// 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); } } } } }