/Assets/HoloToolkit-Gaze-210/Input/Scripts/GazeStabilizer.cs

https://bitbucket.org/andrewaac/single-image-target-detection · C# · 219 lines · 148 code · 43 blank · 28 comment · 12 complexity · c6db044dede903ffa9e7a458cdb4102a MD5 · raw file

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. namespace Academy.HoloToolkit.Unity
  5. {
  6. /// <summary>
  7. /// GazeStabilizer iterates over samples of Raycast data and
  8. /// helps stabilize the user's gaze for precision targeting.
  9. /// </summary>
  10. public class GazeStabilizer : MonoBehaviour
  11. {
  12. [Tooltip("Number of samples that you want to iterate on.")]
  13. [Range(1, 120)]
  14. public int StoredStabilitySamples = 60;
  15. [Tooltip("Position based distance away from gravity well.")]
  16. public float PositionDropOffRadius = 0.02f;
  17. [Tooltip("Direction based distance away from gravity well.")]
  18. public float DirectionDropOffRadius = 0.1f;
  19. [Tooltip("Position lerp interpolation factor.")]
  20. [Range(0.25f, 0.85f)]
  21. public float PositionStrength = 0.66f;
  22. [Tooltip("Direction lerp interpolation factor.")]
  23. [Range(0.25f, 0.85f)]
  24. public float DirectionStrength = 0.83f;
  25. [Tooltip("Stability average weight multiplier factor.")]
  26. public float StabilityAverageDistanceWeight = 2.0f;
  27. [Tooltip("Stability variance weight multiplier factor.")]
  28. public float StabilityVarianceWeight = 1.0f;
  29. // Access the below public properties from the client class to consume stable values.
  30. public Vector3 StableHeadPosition { get; private set; }
  31. public Quaternion StableHeadRotation { get; private set; }
  32. public Ray StableHeadRay { get; private set; }
  33. public struct GazeSample
  34. {
  35. public Vector3 position;
  36. public Vector3 direction;
  37. public float timestamp;
  38. };
  39. private Queue<GazeSample> stabilitySamples = new Queue<GazeSample>();
  40. private Vector3 gazePosition;
  41. private Vector3 gazeDirection;
  42. // Most recent calculated instability values.
  43. private float gazePositionInstability;
  44. private float gazeDirectionInstability;
  45. private bool gravityPointExists = false;
  46. private Vector3 gravityWellPosition;
  47. private Vector3 gravityWellDirection;
  48. // Transforms instability value into a modified drop off distance, modify with caution.
  49. private const float positionDestabilizationFactor = 0.02f;
  50. private const float directionDestabilizationFactor = 0.3f;
  51. /// <summary>
  52. /// Updates the StableHeadPosition and StableHeadRotation based on GazeSample values.
  53. /// Call this method with RaycastHit parameters to get stable values.
  54. /// </summary>
  55. /// <param name="position">Position value from a RaycastHit point.</param>
  56. /// <param name="rotation">Rotation value from a RaycastHit rotation.</param>
  57. public void UpdateHeadStability(Vector3 position, Quaternion rotation)
  58. {
  59. gazePosition = position;
  60. gazeDirection = rotation * Vector3.forward;
  61. AddGazeSample(gazePosition, gazeDirection);
  62. UpdateInstability(out gazePositionInstability, out gazeDirectionInstability);
  63. // If we don't have a gravity point, just use the gaze position.
  64. if (!gravityPointExists)
  65. {
  66. gravityWellPosition = gazePosition;
  67. gravityWellDirection = gazeDirection;
  68. gravityPointExists = true;
  69. }
  70. UpdateGravityWellPositionDirection();
  71. }
  72. private void AddGazeSample(Vector3 positionSample, Vector3 directionSample)
  73. {
  74. // Record and save sample data.
  75. GazeSample newStabilitySample;
  76. newStabilitySample.position = positionSample;
  77. newStabilitySample.direction = directionSample;
  78. newStabilitySample.timestamp = Time.time;
  79. if (stabilitySamples != null)
  80. {
  81. // Remove from front if we exceed stored samples.
  82. if (stabilitySamples.Count >= StoredStabilitySamples)
  83. {
  84. stabilitySamples.Dequeue();
  85. }
  86. stabilitySamples.Enqueue(newStabilitySample);
  87. }
  88. }
  89. private void UpdateInstability(out float positionInstability, out float directionInstability)
  90. {
  91. GazeSample mostRecentSample;
  92. float positionDeltaMin = 0.0f;
  93. float positionDeltaMax = 0.0f;
  94. float positionDeltaMean = 0.0f;
  95. float directionDeltaMin = 0.0f;
  96. float directionDeltaMax = 0.0f;
  97. float directionDeltaMean = 0.0f;
  98. float positionDelta = 0.0f;
  99. float directionDelta = 0.0f;
  100. positionInstability = 0.0f;
  101. directionInstability = 0.0f;
  102. // If we have zero or one sample, there is no instability to report.
  103. if (stabilitySamples.Count < 2)
  104. {
  105. return;
  106. }
  107. mostRecentSample = stabilitySamples.ElementAt(stabilitySamples.Count - 1);
  108. // All but most recent.
  109. for (int i = 0; i < stabilitySamples.Count - 1; ++i)
  110. {
  111. // Calculate difference between current sample and most recent sample.
  112. positionDelta = Vector3.Magnitude(stabilitySamples.ElementAt(i).position - mostRecentSample.position);
  113. directionDelta = Vector3.Angle(stabilitySamples.ElementAt(i).direction, mostRecentSample.direction) * Mathf.Deg2Rad;
  114. // Initialize max and min on first sample.
  115. if (i == 0)
  116. {
  117. positionDeltaMin = positionDelta;
  118. positionDeltaMax = positionDelta;
  119. directionDeltaMin = directionDelta;
  120. directionDeltaMax = directionDelta;
  121. }
  122. else
  123. {
  124. // Update maximum, minimum and mean differences from most recent sample.
  125. positionDeltaMin = Mathf.Min(positionDelta, positionDeltaMin);
  126. positionDeltaMax = Mathf.Max(positionDelta, positionDeltaMax);
  127. directionDeltaMin = Mathf.Min(directionDelta, directionDeltaMin);
  128. directionDeltaMax = Mathf.Max(directionDelta, directionDeltaMax);
  129. }
  130. positionDeltaMean += positionDelta;
  131. directionDeltaMean += directionDelta;
  132. }
  133. positionDeltaMean = positionDeltaMean / (stabilitySamples.Count - 1);
  134. directionDeltaMean = directionDeltaMean / (stabilitySamples.Count - 1);
  135. // Calculate stability value for Gaze position and direction. Note that stability values will be significantly different for position and
  136. // direction since the position value is based on values in meters while the direction stability is based on data in radians.
  137. positionInstability = StabilityVarianceWeight * (positionDeltaMax - positionDeltaMin) + StabilityAverageDistanceWeight * positionDeltaMean;
  138. directionInstability = StabilityVarianceWeight * (directionDeltaMax - directionDeltaMin) + StabilityAverageDistanceWeight * directionDeltaMean;
  139. }
  140. private void UpdateGravityWellPositionDirection()
  141. {
  142. float stabilityModifiedPositionDropOffDistance;
  143. float stabilityModifiedDirectionDropOffDistance;
  144. float normalizedGazeToGravityWellPosition;
  145. float normalizedGazeToGravityWellDirection;
  146. // Modify effective size of well based on gaze stability.
  147. stabilityModifiedPositionDropOffDistance = Mathf.Max(0.0f, PositionDropOffRadius - (gazePositionInstability * positionDestabilizationFactor));
  148. stabilityModifiedDirectionDropOffDistance = Mathf.Max(0.0f, DirectionDropOffRadius - (gazeDirectionInstability * directionDestabilizationFactor));
  149. // Determine how far away from the well the gaze is, if that distance is zero push the normalized value above 1.0 to
  150. // force a gravity well position update.
  151. normalizedGazeToGravityWellPosition = 2.0f;
  152. if (stabilityModifiedPositionDropOffDistance > 0.0f)
  153. {
  154. normalizedGazeToGravityWellPosition = Vector3.Magnitude(gravityWellPosition - gazePosition) / stabilityModifiedPositionDropOffDistance;
  155. }
  156. normalizedGazeToGravityWellDirection = 2.0f;
  157. if (stabilityModifiedDirectionDropOffDistance > 0.0f)
  158. {
  159. normalizedGazeToGravityWellDirection = Mathf.Acos(Vector3.Dot(gravityWellDirection, gazeDirection)) / stabilityModifiedDirectionDropOffDistance;
  160. }
  161. // Move gravity well with Gaze if necessary.
  162. if (normalizedGazeToGravityWellPosition > 1.0f)
  163. {
  164. gravityWellPosition = gazePosition - Vector3.Normalize(gazePosition - gravityWellPosition) * stabilityModifiedPositionDropOffDistance;
  165. }
  166. if (normalizedGazeToGravityWellDirection > 1.0f)
  167. {
  168. gravityWellDirection = Vector3.Normalize(gazeDirection - Vector3.Normalize(gazeDirection - gravityWellDirection) * stabilityModifiedDirectionDropOffDistance);
  169. }
  170. // Adjust direction and position towards gravity well based on configurable strengths.
  171. StableHeadPosition = Vector3.Lerp(gazePosition, gravityWellPosition, PositionStrength);
  172. StableHeadRotation = Quaternion.LookRotation(Vector3.Lerp(gazeDirection, gravityWellDirection, DirectionStrength));
  173. StableHeadRay = new Ray(StableHeadPosition, StableHeadRotation * Vector3.forward);
  174. }
  175. }
  176. }