mixedreality/com.microsoft.mixedreality..../Services/InputSystem/GazePointerVisibilityStateM...

110 lines
4.6 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// <para>Has different behavior depending on whether or not eye gaze or head gaze in use - see comments on
/// GazePointerState for more details.</para>
/// </remarks>
public class GazePointerVisibilityStateMachine : IMixedRealitySpeechHandler
{
private enum GazePointerState
{
/// <summary>
/// When the application starts up, the gaze pointer should be active.
/// </summary>
Initial,
/// <summary>
/// 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.
/// </summary>
GazePointerActive,
/// <summary>
/// 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.
/// </summary>
GazePointerInactive
}
private GazePointerState gazePointerState = GazePointerState.Initial;
private bool activateGazeKeywordIsSet = false;
private bool eyeGazeValid = false;
/// <summary>
/// Whether the state machine is currently in a state where the gaze pointer should be active.
/// </summary>
public bool IsGazePointerActive => gazePointerState != GazePointerState.GazePointerInactive;
/// <summary>
/// Updates the state machine based on the number of near pointers, the number of far pointers,
/// and whether or not eye gaze is valid.
/// </summary>
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;
}
}
/// <summary>
/// Flags user intention to re activate eye or head based gaze cursor
/// </summary>
public void OnSpeechKeywordRecognized(SpeechEventData eventData)
{
if (eventData.Command.Keyword.Equals("select", StringComparison.CurrentCultureIgnoreCase))
{
activateGazeKeywordIsSet = true;
}
}
}
}