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