Skip to content

Commit 8835f7e

Browse files
RogPodgekeveleigh
authored andcommitted
Removing extra raycasts from eye gaze enabled objects (#10162)
* Initial commit for improving gaze provider * Small finalizing removing EyeTrackingTarget Raycasts * removing commented out code * fixing gaps which caused unit tests to fail * updated unit tests and added some safeguards * fixed errors introduced by swapping profiles at runtime * added checks to ensure eyetracking data is valid * updated the gaze provider to update the hit point correctly * updated focus provider comments
1 parent cad5412 commit 8835f7e

File tree

4 files changed

+57
-56
lines changed

4 files changed

+57
-56
lines changed

Assets/MRTK/SDK/Features/Input/Handlers/EyeTrackingTarget.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,26 @@ public bool EyeCursorSnapToTargetCenter
170170
/// GameObject eye gaze is currently targeting, updated once per frame.
171171
/// null if no object with collider is currently being looked at.
172172
/// </summary>
173-
public static GameObject LookedAtTarget { get; private set; }
173+
public static GameObject LookedAtTarget =>
174+
(CoreServices.InputSystem != null &&
175+
CoreServices.InputSystem.EyeGazeProvider != null &&
176+
CoreServices.InputSystem.EyeGazeProvider.IsEyeTrackingEnabledAndValid) ? CoreServices.InputSystem.EyeGazeProvider.GazeTarget : null;
177+
178+
/// <summary>
179+
/// The point in space where the eye gaze hit.
180+
/// set to the origin if the EyeGazeProvider is not currently enabled
181+
/// </summary>
182+
public static Vector3 LookedAtPoint =>
183+
(CoreServices.InputSystem != null &&
184+
CoreServices.InputSystem.EyeGazeProvider != null &&
185+
CoreServices.InputSystem.EyeGazeProvider.IsEyeTrackingEnabledAndValid) ? CoreServices.InputSystem.EyeGazeProvider.HitPosition : Vector3.zero;
174186

175187
/// <summary>
176188
/// EyeTrackingTarget eye gaze is currently looking at.
177189
/// null if currently gazed at object has no EyeTrackingTarget, or if
178190
/// no object with collider is being looked at.
179191
/// </summary>
180192
public static EyeTrackingTarget LookedAtEyeTarget { get; private set; }
181-
public static Vector3 LookedAtPoint { get; private set; }
182193

183194
/// <summary>
184195
/// Most recently selected target, selected either using pointer
@@ -191,7 +202,6 @@ protected override void Start()
191202
{
192203
base.Start();
193204
IsLookedAt = false;
194-
LookedAtTarget = null;
195205
LookedAtEyeTarget = null;
196206
}
197207

@@ -252,28 +262,14 @@ private void UpdateHitTarget()
252262
lastEyeSignalUpdateTimeFromET = (CoreServices.InputSystem?.EyeGazeProvider?.Timestamp).Value;
253263
lastEyeSignalUpdateTimeLocal = DateTime.UtcNow;
254264

255-
// ToDo: Handle raycasting layers
256-
var lookRay = new Ray(
257-
CoreServices.InputSystem.EyeGazeProvider.GazeOrigin,
258-
CoreServices.InputSystem.EyeGazeProvider.GazeDirection.normalized);
259-
bool isHit = UnityEngine.Physics.Raycast(lookRay, out RaycastHit hitInfo);
260-
261-
if (isHit)
262-
{
263-
LookedAtEyeTarget = hitInfo.collider.transform.GetComponent<EyeTrackingTarget>();
264-
LookedAtTarget = hitInfo.collider.transform.gameObject;
265-
LookedAtPoint = hitInfo.point;
266-
}
267-
else
265+
if(LookedAtTarget != null)
268266
{
269-
LookedAtTarget = null;
270-
LookedAtEyeTarget = null;
267+
LookedAtEyeTarget = LookedAtTarget.GetComponent<EyeTrackingTarget>();
271268
}
272269
}
273270
}
274271
else if ((DateTime.UtcNow - lastEyeSignalUpdateTimeLocal).TotalMilliseconds > EyeTrackingTimeoutInMilliseconds)
275272
{
276-
LookedAtTarget = null;
277273
LookedAtEyeTarget = null;
278274
}
279275
}
@@ -298,7 +294,7 @@ protected void OnEyeFocusStay()
298294
protected void OnEyeFocusDwell()
299295
{
300296
IsDwelledOn = true;
301-
OnDwell.Invoke();
297+
OnDwell?.Invoke();
302298
}
303299

304300
protected void OnEyeFocusStop()

Assets/MRTK/Services/InputSystem/FocusProvider.cs

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,6 @@ private bool IsSetupValid
169169
}
170170
}
171171

172-
/// <summary>
173-
/// GazeProvider is a little special, so we keep track of it even if it's not a registered pointer. For the sake
174-
/// of StabilizationPlaneModifier and potentially other components that care where the user's looking, we need
175-
/// to do a gaze raycast even if gaze isn't used for focus.
176-
/// </summary>
177-
private PointerData gazeProviderPointingData;
178-
private PointerHitResult gazeHitResult;
179-
180172
/// <summary>
181173
/// Cached <see href="https://docs.unity3d.com/ScriptReference/Vector3.html">Vector3</see> reference to the new raycast position.
182174
/// </summary>
@@ -569,31 +561,42 @@ public override void Update()
569561

570562
private static readonly ProfilerMarker UpdateGazeProviderPerfMarker = new ProfilerMarker("[MRTK] FocusProvider.UpdateGazeProvider");
571563

564+
/// <summary>
565+
/// GazeProvider is a little special, so we keep track of it even if it's not a registered pointer. For the sake
566+
/// of StabilizationPlaneModifier and potentially other components that care where the user's looking, we need
567+
/// to do a gaze raycast even if gaze isn't used for focus.
568+
/// </summary>
569+
private PointerEventData gazeProviderPointingData;
570+
private PointerHitResult gazeHitResult;
571+
572572
/// <summary>
573573
/// Updates the gaze raycast provider even in scenarios where gaze isn't used for focus
574574
/// </summary>
575575
private void UpdateGazeProvider()
576576
{
577577
using (UpdateGazeProviderPerfMarker.Auto())
578578
{
579-
// The gaze hit result may be populated from previous raycasts this frame, only recompute
579+
// The gaze hit result may be populated from the UpdatePointers call. If it has not, then perform
580580
// another raycast if it's not populated
581581
if (gazeHitResult == null)
582582
{
583-
if (gazeProviderPointingData?.Pointer != null)
583+
IMixedRealityPointer gazePointer = CoreServices.InputSystem.GazeProvider?.GazePointer;
584+
// Check that the gazePointer isn't null and that it has been properly registered as a pointer.
585+
if (gazePointer != null && gazeProviderPointingData != null)
584586
{
585587
// get 3d hit
588+
// This is unneccessary since the gaze pointer has been registered normally along with the other pointers(?)
586589
hitResult3d.Clear();
587590
var raycastProvider = CoreServices.InputSystem.RaycastProvider;
588-
LayerMask[] prioritizedLayerMasks = (gazeProviderPointingData.Pointer.PrioritizedLayerMasksOverride ?? FocusLayerMasks);
589-
QueryScene(gazeProviderPointingData.Pointer, raycastProvider, prioritizedLayerMasks,
591+
LayerMask[] prioritizedLayerMasks = (gazePointer.PrioritizedLayerMasksOverride ?? FocusLayerMasks);
592+
QueryScene(gazePointer, raycastProvider, prioritizedLayerMasks,
590593
hitResult3d, maxQuerySceneResults, focusIndividualCompoundCollider);
591594

592595
if (shouldUseGraphicsRaycast)
593596
{
594597
// get ui hit
595598
hitResultUi.Clear();
596-
RaycastGraphics(gazeProviderPointingData.Pointer, gazeProviderPointingData.GraphicEventData, prioritizedLayerMasks, hitResultUi);
599+
RaycastGraphics(gazePointer, gazeProviderPointingData, prioritizedLayerMasks, hitResultUi);
597600
}
598601

599602
// set gaze hit according to distance and prioritization layer mask
@@ -605,7 +608,10 @@ private void UpdateGazeProvider()
605608
}
606609
}
607610

608-
CoreServices.InputSystem.GazeProvider.UpdateGazeInfoFromHit(gazeHitResult.raycastHit);
611+
if (!CoreServices.InputSystem.GazeProvider.IsNull())
612+
{
613+
CoreServices.InputSystem.GazeProvider.UpdateGazeInfoFromHit(gazeHitResult.raycastHit);
614+
}
609615

610616
// Zero out value after every use to ensure the hit result is updated every frame.
611617
gazeHitResult = null;
@@ -835,11 +841,12 @@ private void RegisterPointers(IMixedRealityInputSource inputSource)
835841
RegisterPointer(inputSource.Pointers[i]);
836842

837843
// Special Registration for Gaze
844+
// Refreshes gazeProviderPointingData to a new reference to the current EventSystem
838845
if (!CoreServices.InputSystem.GazeProvider.IsNull()
839846
&& inputSource.SourceId == CoreServices.InputSystem.GazeProvider.GazeInputSource.SourceId
840847
&& gazeProviderPointingData == null)
841848
{
842-
gazeProviderPointingData = new PointerData(inputSource.Pointers[i]);
849+
gazeProviderPointingData = new PointerEventData(EventSystem.current);
843850
}
844851
}
845852
}
@@ -1084,7 +1091,8 @@ private void UpdatePointer(PointerData pointerData)
10841091
pointerData.UpdateHit(hit);
10851092

10861093
// set gaze hit result - make sure to include unity ui hits
1087-
if (gazeProviderPointingData?.Pointer != null && pointerData.Pointer.PointerId == gazeProviderPointingData.Pointer.PointerId)
1094+
var gazePointer = CoreServices.InputSystem.GazeProvider.GazePointer;
1095+
if (gazePointer != null && pointerData.Pointer.PointerId == gazePointer.PointerId)
10881096
{
10891097
gazeHitResult = hit;
10901098
}
@@ -1161,7 +1169,7 @@ private void ReconcilePointers()
11611169
{
11621170
using (ReconcilePointersPerfMarker.Auto())
11631171
{
1164-
var gazePointer = gazeProviderPointingData?.Pointer as GenericPointer;
1172+
var gazePointer = CoreServices.InputSystem.GazeProvider?.GazePointer as GenericPointer;
11651173
NumFarPointersActive = 0;
11661174
NumNearPointersActive = 0;
11671175
int numFarPointersWithoutCursorActive = 0;
@@ -1559,18 +1567,19 @@ public void OnSourceLost(SourceStateEventData eventData)
15591567

15601568
pointerMediators.Remove(eventData.SourceId);
15611569

1570+
15621571
for (var i = 0; i < eventData.InputSource.Pointers.Length; i++)
15631572
{
1573+
var gazePointer = CoreServices.InputSystem.GazeProvider?.GazePointer;
15641574
// Special unregistration for Gaze
1565-
if (gazeProviderPointingData?.Pointer != null && eventData.InputSource.Pointers[i].PointerId == gazeProviderPointingData.Pointer.PointerId)
1575+
if (gazePointer != null && eventData.InputSource.Pointers[i].PointerId == gazePointer.PointerId)
15661576
{
1567-
// If the source lost is the gaze input source, then reset it.
1577+
// If the source lost is the gaze input source, clear gazeProviderPointingData.
15681578
if (eventData.InputSource.SourceId == CoreServices.InputSystem.GazeProvider?.GazeInputSource.SourceId)
15691579
{
1570-
gazeProviderPointingData.ResetFocusedObjects();
15711580
gazeProviderPointingData = null;
15721581
}
1573-
// Otherwise, don't unregister the gaze pointer, since the gaze input source is still active.
1582+
// Otherwise, don't clear gazeProviderPointingData, since the gaze input source is still active.
15741583
else
15751584
{
15761585
continue;

Assets/MRTK/Services/InputSystem/GazeProvider.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ internal void SetGazeInputSourceParent(IMixedRealityInputSource gazeInputSource)
210210
private static readonly ProfilerMarker OnPreSceneQueryPerfMarker = new ProfilerMarker("[MRTK] InternalGazePointer.OnPreSceneQuery");
211211

212212
/// <inheritdoc />
213+
/// On pre-scene query, the gaze pointer will set up it's raycast ray to use either the eye gaze ray or the head gaze ray, depending on IsEyeTrackingEnabledAndValid
213214
public override void OnPreSceneQuery()
214215
{
215216
using (OnPreSceneQueryPerfMarker.Auto())
@@ -252,8 +253,6 @@ public override void OnPreSceneQuery()
252253

253254
Vector3 endPoint = newGazeOrigin + (newGazeNormal * pointerExtent);
254255
Rays[0].UpdateRayStep(ref newGazeOrigin, ref endPoint);
255-
256-
gazeProvider.HitPosition = Rays[0].Origin + (gazeProvider.lastHitDistance * Rays[0].Direction);
257256
}
258257
}
259258

@@ -550,6 +549,12 @@ private async void RaiseSourceDetected()
550549
public void UpdateGazeInfoFromHit(MixedRealityRaycastHit raycastHit)
551550
{
552551
HitInfo = raycastHit;
552+
553+
if (IsEyeTrackingEnabledAndValid)
554+
{
555+
UpdateEyeGaze(null, GazePointer.Rays[0], DateTime.UtcNow);
556+
}
557+
553558
if (raycastHit.transform != null)
554559
{
555560
GazeTarget = raycastHit.transform.gameObject;

Assets/MRTK/Tests/PlayModeTests/InputSystem/EyeTrackingTargetTest.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public class EyeTrackingTargetTest : BasePlayModeTests
3333
// do any kind of setup here that can't be done in playmode
3434
public override IEnumerator Setup()
3535
{
36-
yield return base.Setup();
36+
var eyeTrackingProfile = AssetDatabase.LoadAssetAtPath(eyeTrackingConfigurationProfilePath, typeof(MixedRealityToolkitConfigurationProfile)) as MixedRealityToolkitConfigurationProfile;
37+
PlayModeTestUtilities.Setup(eyeTrackingProfile);
3738
TestUtilities.PlayspaceToOriginLookingForward();
3839
yield return null;
3940
}
@@ -46,17 +47,6 @@ public override IEnumerator Setup()
4647
[UnityTest]
4748
public IEnumerator TestEyeTrackingTarget()
4849
{
49-
// Eye tracking configuration profile should set eye based gaze
50-
var profile = AssetDatabase.LoadAssetAtPath(eyeTrackingConfigurationProfilePath, typeof(MixedRealityToolkitConfigurationProfile)) as MixedRealityToolkitConfigurationProfile;
51-
MixedRealityToolkit.Instance.ResetConfiguration(profile);
52-
53-
// Reset view to origin
54-
MixedRealityPlayspace.PerformTransformation(p =>
55-
{
56-
p.position = Vector3.zero;
57-
p.LookAt(Vector3.forward);
58-
});
59-
6050
string targetName = "eyetrackingTargetObject";
6151

6252
var eyetrackingTargetObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
@@ -74,8 +64,9 @@ public IEnumerator TestEyeTrackingTarget()
7464

7565
var isEyeGazeActive = InputRayUtils.TryGetEyeGazeRay(out var eyegazeRay);
7666
Assert.True(isEyeGazeActive);
77-
yield return null;
67+
yield return PlayModeTestUtilities.WaitForInputSystemUpdate();
7868
Assert.True(EyeTrackingTarget.LookedAtTarget != null);
69+
Assert.True(EyeTrackingTarget.LookedAtEyeTarget != null);
7970
Assert.True(EyeTrackingTarget.LookedAtEyeTarget.name == targetName);
8071
}
8172
#endregion

0 commit comments

Comments
 (0)