Skip to content

Commit 7943ffd

Browse files
Stephen HodgsonStephen Hodgson
authored andcommitted
Merge branch 'master' into MSFT_Holotoolkit-master
2 parents d671fd5 + 2192cc4 commit 7943ffd

File tree

6 files changed

+214
-176
lines changed

6 files changed

+214
-176
lines changed

Assets/HoloToolkit/Input/Scripts/GazeStabilizer.cs

Lines changed: 45 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
3-
4-
using System.Collections.Generic;
5-
using System.Linq;
63
using UnityEngine;
74

85
namespace HoloToolkit.Unity
@@ -14,57 +11,43 @@ namespace HoloToolkit.Unity
1411
public class GazeStabilizer : MonoBehaviour
1512
{
1613
[Tooltip("Number of samples that you want to iterate on.")]
17-
[Range(1, 120)]
14+
[Range(40, 120)]
1815
public int StoredStabilitySamples = 60;
1916

20-
[Tooltip("Position based distance away from gravity well.")]
21-
public float PositionDropOffRadius = 0.02f;
22-
23-
[Tooltip("Direction based distance away from gravity well.")]
24-
public float DirectionDropOffRadius = 0.1f;
25-
26-
[Tooltip("Position lerp interpolation factor.")]
27-
[Range(0.25f, 0.85f)]
28-
public float PositionStrength = 0.66f;
29-
30-
[Tooltip("Direction lerp interpolation factor.")]
31-
[Range(0.25f, 0.85f)]
32-
public float DirectionStrength = 0.83f;
33-
34-
[Tooltip("Stability average weight multiplier factor.")]
35-
public float StabilityAverageDistanceWeight = 2.0f;
36-
37-
[Tooltip("Stability variance weight multiplier factor.")]
38-
public float StabilityVarianceWeight = 1.0f;
39-
4017
// Access the below public properties from the client class to consume stable values.
4118
public Vector3 StableHeadPosition { get; private set; }
4219
public Quaternion StableHeadRotation { get; private set; }
4320
public Ray StableHeadRay { get; private set; }
4421

45-
public struct GazeSample
46-
{
47-
public Vector3 Position;
48-
public Vector3 Direction;
49-
public float Timestamp;
50-
};
22+
/// <summary>
23+
/// These classes do the work of calculating standard deviation and averages for the gaze
24+
/// position and direction.
25+
/// </summary>
26+
private VectorRollingStatistics positionRollingStats = new VectorRollingStatistics();
27+
private VectorRollingStatistics directionRollingStats = new VectorRollingStatistics();
5128

52-
private LinkedList<GazeSample> stabilitySamples = new LinkedList<GazeSample>();
29+
/// <summary>
30+
/// These are the tunable parameters.
31+
/// </summary>
32+
// If the standard deviation is above these values we reset and stop stabalizing
33+
private const float positionStandardDeviationReset = 0.2f;
34+
private const float directionStandardDeviationReset = 0.1f;
5335

54-
private Vector3 gazePosition;
55-
private Vector3 gazeDirection;
36+
// We must have at least this many samples with a standard deviation below the above constants to stabalize
37+
private const int minimumSamplesRequiredToStabalize = 30;
5638

57-
// Most recent calculated instability values.
58-
private float gazePositionInstability;
59-
private float gazeDirectionInstability;
39+
// When not stabalizing this is the 'lerp' applied to the position and direction of the gaze to smooth it over time.
40+
private const float unstabalizedLerpFactor = 0.3f;
6041

61-
private bool gravityPointExists = false;
62-
private Vector3 gravityWellPosition;
63-
private Vector3 gravityWellDirection;
42+
// When stabalizing we will use the standard deviation of the position and direction to create the lerp value.
43+
// By default this value will be low and the cursor will be too sluggish, so we 'boost' it by this value.
44+
private const float stabalizedLerpBoost = 10.0f;
6445

65-
// Transforms instability value into a modified drop off distance, modify with caution.
66-
private const float positionDestabilizationFactor = 0.02f;
67-
private const float directionDestabilizationFactor = 0.3f;
46+
private void Awake()
47+
{
48+
directionRollingStats.Init(StoredStabilitySamples);
49+
positionRollingStats.Init(StoredStabilitySamples);
50+
}
6851

6952
/// <summary>
7053
/// Updates the StableHeadPosition and StableHeadRotation based on GazeSample values.
@@ -74,138 +57,30 @@ public struct GazeSample
7457
/// <param name="rotation">Rotation value from a RaycastHit rotation.</param>
7558
public void UpdateHeadStability(Vector3 position, Quaternion rotation)
7659
{
77-
gazePosition = position;
78-
gazeDirection = rotation * Vector3.forward;
79-
80-
AddGazeSample(gazePosition, gazeDirection);
81-
82-
UpdateInstability(out gazePositionInstability, out gazeDirectionInstability);
83-
84-
// If we don't have a gravity point, just use the gaze position.
85-
if (!gravityPointExists)
86-
{
87-
gravityWellPosition = gazePosition;
88-
gravityWellDirection = gazeDirection;
89-
gravityPointExists = true;
90-
}
91-
92-
UpdateGravityWellPositionDirection();
93-
}
94-
95-
private void AddGazeSample(Vector3 positionSample, Vector3 directionSample)
96-
{
97-
// Record and save sample data.
98-
GazeSample newStabilitySample;
99-
newStabilitySample.Position = positionSample;
100-
newStabilitySample.Direction = directionSample;
101-
newStabilitySample.Timestamp = Time.time;
102-
103-
if (stabilitySamples != null)
104-
{
105-
// Remove from front items if we exceed stored samples.
106-
while (stabilitySamples.Count >= StoredStabilitySamples)
107-
{
108-
stabilitySamples.RemoveFirst();
109-
}
110-
111-
stabilitySamples.AddLast(newStabilitySample);
60+
Vector3 gazePosition = position;
61+
Vector3 gazeDirection = rotation * Vector3.forward;
62+
63+
positionRollingStats.AddSample(gazePosition);
64+
directionRollingStats.AddSample(gazeDirection);
65+
66+
float lerpPower = unstabalizedLerpFactor;
67+
if (positionRollingStats.ActualSampleCount > minimumSamplesRequiredToStabalize && // we have enough samples and...
68+
(positionRollingStats.CurrentStandardDeviation > positionStandardDeviationReset || // the standard deviation of positions is high or...
69+
directionRollingStats.CurrentStandardDeviation > directionStandardDeviationReset)) // the standard deviation of directions is high
70+
{
71+
// We've detected that the user's gaze is no longer fixed, so stop stabalizing so that gaze is responsive.
72+
//Debug.LogFormat("Reset {0} {1} {2} {3}", positionRollingStats.standardDeviation, positionRollingStats.standardDeviationsAway, directionRollignStats.standardDeviation, directionRollignStats.standardDeviationsAway);
73+
positionRollingStats.Reset();
74+
directionRollingStats.Reset();
11275
}
113-
}
114-
115-
private void UpdateInstability(out float positionInstability, out float directionInstability)
116-
{
117-
positionInstability = 0.0f;
118-
directionInstability = 0.0f;
119-
120-
// If we have zero or one sample, there is no instability to report.
121-
if (stabilitySamples.Count < 2)
122-
{
123-
return;
124-
}
125-
126-
GazeSample mostRecentSample = stabilitySamples.Last.Value;
127-
128-
float positionDeltaMin = float.MaxValue;
129-
float positionDeltaMax = float.MinValue;
130-
float positionDeltaMean = 0.0f;
131-
132-
float directionDeltaMin = float.MaxValue;
133-
float directionDeltaMax = float.MinValue;
134-
float directionDeltaMean = 0.0f;
135-
136-
float positionDelta = 0.0f;
137-
float directionDelta = 0.0f;
138-
139-
foreach (GazeSample sample in stabilitySamples)
140-
{
141-
if (sample.Timestamp == mostRecentSample.Timestamp)
142-
{
143-
continue;
144-
}
145-
146-
// Calculate difference between current sample and most recent sample.
147-
positionDelta = Vector3.Magnitude(sample.Position - mostRecentSample.Position);
148-
directionDelta = Vector3.Angle(sample.Direction, mostRecentSample.Direction) * Mathf.Deg2Rad;
149-
150-
// Update maximum, minimum and mean differences from most recent sample.
151-
positionDeltaMin = Mathf.Min(positionDelta, positionDeltaMin);
152-
positionDeltaMax = Mathf.Max(positionDelta, positionDeltaMax);
153-
154-
directionDeltaMin = Mathf.Min(directionDelta, directionDeltaMin);
155-
directionDeltaMax = Mathf.Max(directionDelta, directionDeltaMax);
156-
157-
positionDeltaMean += positionDelta;
158-
directionDeltaMean += directionDelta;
159-
}
160-
161-
positionDeltaMean = positionDeltaMean / (stabilitySamples.Count - 1);
162-
directionDeltaMean = directionDeltaMean / (stabilitySamples.Count - 1);
163-
164-
// Calculate stability value for Gaze position and direction. Note that stability values will be significantly different for position and
165-
// direction since the position value is based on values in meters while the direction stability is based on data in radians.
166-
positionInstability = StabilityVarianceWeight * (positionDeltaMax - positionDeltaMin) + StabilityAverageDistanceWeight * positionDeltaMean;
167-
directionInstability = StabilityVarianceWeight * (directionDeltaMax - directionDeltaMin) + StabilityAverageDistanceWeight * directionDeltaMean;
168-
}
169-
170-
private void UpdateGravityWellPositionDirection()
171-
{
172-
float stabilityModifiedPositionDropOffDistance;
173-
float stabilityModifiedDirectionDropOffDistance;
174-
float normalizedGazeToGravityWellPosition;
175-
float normalizedGazeToGravityWellDirection;
176-
177-
// Modify effective size of well based on gaze stability.
178-
stabilityModifiedPositionDropOffDistance = Mathf.Max(0.0f, PositionDropOffRadius - (gazePositionInstability * positionDestabilizationFactor));
179-
stabilityModifiedDirectionDropOffDistance = Mathf.Max(0.0f, DirectionDropOffRadius - (gazeDirectionInstability * directionDestabilizationFactor));
180-
181-
// Determine how far away from the well the gaze is, if that distance is zero push the normalized value above 1.0 to
182-
// force a gravity well position update.
183-
normalizedGazeToGravityWellPosition = 2.0f;
184-
if (stabilityModifiedPositionDropOffDistance > 0.0f)
185-
{
186-
normalizedGazeToGravityWellPosition = Vector3.Magnitude(gravityWellPosition - gazePosition) / stabilityModifiedPositionDropOffDistance;
187-
}
188-
189-
normalizedGazeToGravityWellDirection = 2.0f;
190-
if (stabilityModifiedDirectionDropOffDistance > 0.0f)
191-
{
192-
normalizedGazeToGravityWellDirection = Mathf.Acos(Vector3.Dot(gravityWellDirection, gazeDirection)) / stabilityModifiedDirectionDropOffDistance;
193-
}
194-
195-
// Move gravity well with Gaze if necessary.
196-
if (normalizedGazeToGravityWellPosition > 1.0f)
197-
{
198-
gravityWellPosition = gazePosition - Vector3.Normalize(gazePosition - gravityWellPosition) * stabilityModifiedPositionDropOffDistance;
199-
}
200-
201-
if (normalizedGazeToGravityWellDirection > 1.0f)
76+
else if (positionRollingStats.ActualSampleCount > minimumSamplesRequiredToStabalize)
20277
{
203-
gravityWellDirection = Vector3.Normalize(gazeDirection - Vector3.Normalize(gazeDirection - gravityWellDirection) * stabilityModifiedDirectionDropOffDistance);
78+
// We've detected that the user's gaze is fairly fixed, so start stabalizing. The more fixed the gaze the less the cursor will move.
79+
lerpPower = stabalizedLerpBoost * (positionRollingStats.CurrentStandardDeviation + directionRollingStats.CurrentStandardDeviation);
20480
}
20581

206-
// Adjust direction and position towards gravity well based on configurable strengths.
207-
StableHeadPosition = Vector3.Lerp(gazePosition, gravityWellPosition, PositionStrength);
208-
StableHeadRotation = Quaternion.LookRotation(Vector3.Lerp(gazeDirection, gravityWellDirection, DirectionStrength));
82+
StableHeadPosition = Vector3.Lerp(StableHeadPosition, gazePosition, lerpPower);
83+
StableHeadRotation = Quaternion.LookRotation(Vector3.Lerp(StableHeadRotation * Vector3.forward, gazeDirection, lerpPower));
20984
StableHeadRay = new Ray(StableHeadPosition, StableHeadRotation * Vector3.forward);
21085
}
21186
}

Assets/HoloToolkit/Sharing/Scripts/Utilities/DirectPairing.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ void Start()
6363

6464
void OnDestroy()
6565
{
66-
if (this.sharingMgr != null)
66+
// SharingStage's OnDestroy() might execute first in the script order. Therefore we should check if
67+
// SharingStage.Instance still exists. Without the instance check, the execution of GetPairingManager
68+
// on a disposed sharing manager will crash the Unity Editor and application.
69+
if (SharingStage.Instance != null && this.sharingMgr != null)
6770
{
6871
PairingManager pairingMgr = sharingMgr.GetPairingManager();
6972
pairingMgr.CancelPairing(); // Safe to call, even if no pairing is in progress. Will not cause a disconnect

Assets/HoloToolkit/SpatialMapping/Scripts/SpatialMappingManager.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,26 +184,30 @@ public bool IsObserverRunning()
184184
/// </summary>
185185
public void StartObserver()
186186
{
187-
#if !UNITY_EDITOR
187+
#if UNITY_EDITOR
188+
// Allow observering if a device is present (Holographic Remoting)
189+
if(!UnityEngine.VR.VRDevice.isPresent) return;
190+
#endif
188191
if (!IsObserverRunning())
189192
{
190193
surfaceObserver.StartObserving();
191194
StartTime = Time.time;
192195
}
193-
#endif
194196
}
195197

196198
/// <summary>
197199
/// Instructs the SurfaceObserver to stop updating the SpatialMapping mesh.
198200
/// </summary>
199201
public void StopObserver()
200202
{
201-
#if !UNITY_EDITOR
203+
#if UNITY_EDITOR
204+
// Allow observering if a device is present (Holographic Remoting)
205+
if(!UnityEngine.VR.VRDevice.isPresent) return;
206+
#endif
202207
if (IsObserverRunning())
203208
{
204209
surfaceObserver.StopObserving();
205-
}
206-
#endif
210+
}
207211
}
208212

209213
/// <summary>

Assets/HoloToolkit/SpatialMapping/Scripts/SpatialMappingSource.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ protected void UpdateSurfaceObject(GameObject gameObject, int meshID)
9191
{
9292
SurfaceObject thisSurfaceObject = SurfaceObjects[i];
9393
thisSurfaceObject.ID = meshID;
94+
thisSurfaceObject.UpdateID++;
9495
SurfaceObjects[i] = thisSurfaceObject;
9596
return;
9697
}

0 commit comments

Comments
 (0)