// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Physics
{
///
/// GazeStabilizer iterates over samples of Raycast data and
/// helps stabilize the user's gaze for precision targeting.
///
[Serializable]
public class GazeStabilizer : BaseRayStabilizer
{
///
///Number of samples that you want to iterate on.
///
public int StoredStabilitySamples => storedStabilitySamples;
[SerializeField]
[Range(40, 120)]
[Tooltip("Number of samples that you want to iterate on.")]
private int storedStabilitySamples = 60;
///
/// The stabilized position.
///
public override Vector3 StablePosition => stablePosition;
private Vector3 stablePosition;
///
/// The stabilized rotation.
///
public override Quaternion StableRotation => stableRotation;
private Quaternion stableRotation;
///
/// The stabilized position.
///
public override Ray StableRay => stableRay;
private Ray stableRay;
///
/// Calculates standard deviation and averages for the gaze position.
///
private readonly VectorRollingStatistics positionRollingStats = new VectorRollingStatistics();
///
/// Calculates standard deviation and averages for the gaze direction.
///
private readonly VectorRollingStatistics directionRollingStats = new VectorRollingStatistics();
///
/// Tunable parameter.
/// If the standard deviation for the position is above this value, we reset and stop stabilizing.
///
private const float PositionStandardDeviationReset = 0.2f;
///
/// Tunable parameter.
/// If the standard deviation for the direction is above this value, we reset and stop stabilizing.
///
private const float DirectionStandardDeviationReset = 0.1f;
///
/// We must have at least this many samples with a standard deviation below the above constants to stabilize
///
private const int MinimumSamplesRequiredToStabilize = 30;
///
/// When not stabilizing this is the 'lerp' applied to the position and direction of the gaze to smooth it over time.
///
private const float UnstabilizedLerpFactor = 0.3f;
///
/// When stabilizing we will use the standard deviation of the position and direction to create the lerp value.
/// By default this value will be low and the cursor will be too sluggish, so we 'boost' it by this value.
///
private const float StabalizedLerpBoost = 10.0f;
public GazeStabilizer()
{
directionRollingStats.Init(storedStabilitySamples);
positionRollingStats.Init(storedStabilitySamples);
}
///
/// Updates the StablePosition and StableRotation based on GazeSample values.
/// Call this method with RaycastHit parameters to get stable values.
///
/// Position value from a RaycastHit point.
/// Direction value from a RaycastHit rotation.
public override void UpdateStability(Vector3 gazePosition, Vector3 gazeDirection)
{
positionRollingStats.AddSample(gazePosition);
directionRollingStats.AddSample(gazeDirection);
float lerpPower = UnstabilizedLerpFactor;
if (positionRollingStats.ActualSampleCount > MinimumSamplesRequiredToStabilize && // we have enough samples and...
(positionRollingStats.CurrentStandardDeviation > PositionStandardDeviationReset || // the standard deviation of positions is high or...
directionRollingStats.CurrentStandardDeviation > DirectionStandardDeviationReset)) // the standard deviation of directions is high
{
// We've detected that the user's gaze is no longer fixed, so stop stabilizing so that gaze is responsive.
// Debug.Log($"Reset {positionRollingStats.CurrentStandardDeviation} {positionRollingStats.StandardDeviationsAwayOfLatestSample} {directionRollingStats.CurrentStandardDeviation} {directionRollingStats.StandardDeviationsAwayOfLatestSample}");
positionRollingStats.Reset();
directionRollingStats.Reset();
}
else if (positionRollingStats.ActualSampleCount > MinimumSamplesRequiredToStabilize)
{
// We've detected that the user's gaze is fairly fixed, so start stabilizing. The more fixed the gaze the less the cursor will move.
lerpPower = StabalizedLerpBoost * (positionRollingStats.CurrentStandardDeviation + directionRollingStats.CurrentStandardDeviation);
}
stablePosition = Vector3.Lerp(stablePosition, gazePosition, lerpPower);
stableRotation = Quaternion.LookRotation(Vector3.Lerp(stableRotation * Vector3.forward, gazeDirection, lerpPower));
stableRay = new Ray(stablePosition, stableRotation * Vector3.forward);
}
}
}