// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; namespace Microsoft.MixedReality.Toolkit.Input { /// /// Helper class for managing the visibility of the gaze pointer to match windows mixed reality and HoloLens 2 /// When application starts, gaze pointer is visible. Then when articulate hands / motion controllers /// appear, hide the gaze cursor. Whenever user says "select", make the gaze cursor appear. /// /// /// Has different behavior depending on whether or not eye gaze or head gaze in use - see comments on /// GazePointerState for more details. /// public class GazePointerVisibilityStateMachine : IMixedRealitySpeechHandler { private enum GazePointerState { /// /// When the application starts up, the gaze pointer should be active. /// Initial, /// /// If head gaze is in use, then the gaze pointer is active when no hands are visible, after "select". /// If eye gaze is use, then the gaze pointer is active when no far pointers are active. /// GazePointerActive, /// /// If head gaze is in use, then the gaze pointer is inactive as soon as motion controller or /// articulated hand pointers appear. /// If eye gaze is in use, then the gaze pointer is inactive when far pointers are active. /// GazePointerInactive } private GazePointerState gazePointerState = GazePointerState.Initial; private bool activateGazeKeywordIsSet = false; private bool eyeGazeValid = false; /// /// Whether the state machine is currently in a state where the gaze pointer should be active. /// public bool IsGazePointerActive => gazePointerState != GazePointerState.GazePointerInactive; /// /// Updates the state machine based on the number of near pointers, the number of far pointers, /// and whether or not eye gaze is valid. /// public void UpdateState(int numNearPointersActive, int numFarPointersActive, int numFarPointersWithoutCursorActive, bool isEyeGazeValid) { if (eyeGazeValid != isEyeGazeValid) { activateGazeKeywordIsSet = false; eyeGazeValid = isEyeGazeValid; } bool canGazeCursorShow = numFarPointersActive == 0 && numNearPointersActive == 0; switch (gazePointerState) { case GazePointerState.Initial: if (!canGazeCursorShow) { // There is some pointer other than the gaze pointer in the scene, assume // this is from a motion controller or articulated hand, and that we should // hide the gaze pointer gazePointerState = GazePointerState.GazePointerInactive; } break; case GazePointerState.GazePointerActive: if (!canGazeCursorShow) { activateGazeKeywordIsSet = false; gazePointerState = GazePointerState.GazePointerInactive; } break; case GazePointerState.GazePointerInactive: // Go from inactive to active if we say the word "select" if (activateGazeKeywordIsSet) { activateGazeKeywordIsSet = false; gazePointerState = GazePointerState.GazePointerActive; } if (canGazeCursorShow && numFarPointersWithoutCursorActive > 0) { activateGazeKeywordIsSet = false; gazePointerState = GazePointerState.GazePointerActive; } break; default: break; } } /// /// Flags user intention to re activate eye or head based gaze cursor /// public void OnSpeechKeywordRecognized(SpeechEventData eventData) { if (eventData.Command.Keyword.Equals("select", StringComparison.CurrentCultureIgnoreCase)) { activateGazeKeywordIsSet = true; } } } }