Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions org.mixedrealitytoolkit.input/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## Unreleased

### Changed

* Updated `InteractionDetector` to work across all `XRRayInteractor` and `NearFarInteractor` implementations, instead of just MRTK-specific `MRTKRayInteractor` implementations. [PR #1090](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1090)

### Deprecated

* Deprecated `HasUIHover` and `HasUISelection` from `MRTKRayInteractor` in favor of querying the underlying `TrackedDeviceModel` directly instead. [PR #1090](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1090)

## [4.0.0-pre.2] - 2025-12-05

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
using UnityEngine.XR.Interaction.Toolkit.UI;

namespace MixedReality.Toolkit.Input
{
Expand Down Expand Up @@ -85,24 +86,7 @@ public InteractionMode ModeOnSelect
}

/// <inheritdoc />
public InteractionMode ModeOnDetection => GetDetectedMode();

/// <summary>
/// Determines which mode should be set.
/// </summary>
/// <returns>The detected mode.</returns>
private InteractionMode GetDetectedMode()
{
if (interactor.hasSelection)
{
return modeOnSelect;
}
else
{
return modeOnHover;
}

}
public InteractionMode ModeOnDetection => interactor.hasSelection ? modeOnSelect : modeOnHover;

[SerializeField]
[FormerlySerializedAs("Controllers")]
Expand All @@ -122,10 +106,13 @@ public bool IsModeDetected()
{
bool isDetected = (interactor.hasHover && detectHover) || (interactor.hasSelection && detectSelect);

// Remove if/when XRI sets hasHover/Selection when their ray interactor is hovering/selecting legacy UI.
if (interactor is MRTKRayInteractor rayInteractor)
if (interactor is XRRayInteractor rayInteractor)
{
isDetected |= rayInteractor.TryGetUIModel(out TrackedDeviceModel model) && model.currentRaycast.isValid && (detectHover || (model.select && detectSelect));
}
else if (interactor is NearFarInteractor nearFarInteractor)
{
isDetected |= (rayInteractor.HasUIHover && detectHover) || (rayInteractor.HasUISelection && detectSelect);
isDetected |= nearFarInteractor.TryGetUIModel(out TrackedDeviceModel model) && model.currentRaycast.isValid && (detectHover || (model.select && detectSelect));
}

return isDetected;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,33 +63,14 @@ public GameObject ModeManagedRoot
/// <summary>
/// Is this ray currently hovering a UnityUI/Canvas element?
/// </summary>
[Obsolete("This property has been deprecated in version 4.0.0. Call " + nameof(TryGetUIModel) + " and use " + nameof(TrackedDeviceModel.currentRaycast.isValid) + " instead.")]
public bool HasUIHover => TryGetUIModel(out TrackedDeviceModel model) && model.currentRaycast.isValid;

/// <summary>
/// Is this ray currently selecting a UnityUI/Canvas element?
/// </summary>
public bool HasUISelection
{
get
{
bool hasUISelection = HasUIHover;
#pragma warning disable CS0618 // isUISelectActive is obsolete
if (forceDeprecatedInput)
{
hasUISelection &= isUISelectActive;
}
#pragma warning restore CS0618 // isUISelectActiver is obsolete
else if (uiPressInput != null)
{
hasUISelection &= uiPressInput.ReadIsPerformed();
}
else
{
hasUISelection = false;
}
return hasUISelection;
}
}
[Obsolete("This property has been deprecated in version 4.0.0. Call " + nameof(TryGetUIModel) + " and use " + nameof(TrackedDeviceModel.select) + " instead.")]
public bool HasUISelection => TryGetUIModel(out TrackedDeviceModel model) && model.select;

/// <summary>
/// Used to check if the parent controller is tracked or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public IEnumerator ProximityDetectorTest()
}

/// <summary>
/// Tests the basic Interaction detector. The controller should enter one mode during hover, another during select, and fall back to the default mode during neither
/// Tests the basic Interaction detector. The controller should enter one mode during hover, another during select, and fall back to the default mode during neither.
/// </summary>
[UnityTest]
public IEnumerator InteractionDetectorTest()
Expand All @@ -89,28 +89,40 @@ public IEnumerator InteractionDetectorTest()
yield return rightHand.AimAt(cube.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();

InteractionMode currentMode = rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnHover;
Assert.AreEqual(currentMode, rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnDetection);
ValidateInteractionModeActive(rightHandController, currentMode);
InteractionDetector interactionDetector = rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>();

InteractionMode expectedMode = interactionDetector.ModeOnHover;
Assert.AreEqual(expectedMode, interactionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandController, expectedMode);

// Select the cube and check that we're in the correct mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();
currentMode = rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnSelect;
Assert.AreEqual(currentMode, rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnDetection);
ValidateInteractionModeActive(rightHandController, currentMode);
expectedMode = interactionDetector.ModeOnSelect;
Assert.AreEqual(expectedMode, interactionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandController, expectedMode);

// move the hand far away and validate that we are in the default mode
// Release the selection and move the hand far away and validate that we are in the default mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Open);
yield return RuntimeTestUtilities.WaitForUpdates();
yield return rightHand.MoveTo(cube.transform.position + new Vector3(3.0f,0,0));
yield return rightHand.MoveTo(cube.transform.position + new Vector3(3.0f, 0, 0));
yield return RuntimeTestUtilities.WaitForUpdates();
expectedMode = InteractionModeManager.Instance.DefaultMode;
ValidateInteractionModeActive(rightHandController, expectedMode);

currentMode = InteractionModeManager.Instance.DefaultMode;
ValidateInteractionModeActive(rightHandController, currentMode);
// Put the hand into a grab state and validate that we are in the default mode, since we're not selecting an object
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();
ValidateInteractionModeActive(rightHandController, expectedMode);

// Release the grab state and validate that we are in the default mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Open);
yield return RuntimeTestUtilities.WaitForUpdates();
ValidateInteractionModeActive(rightHandController, expectedMode);
}

/// <summary>
/// Tests that mode mediation works properly.
/// Tests that mode mediation works properly.
/// </summary>
/// <remarks>
/// The interaction mode with the higher priority should be the valid one which affects the controller.
Expand All @@ -136,12 +148,14 @@ public IEnumerator ModeMediationTest()
InputTestUtilities.SetHandAnchorPoint(Handedness.Right, ControllerAnchorPoint.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();

// Moving the hand to a position where it's far ray is hovering over the cube
InteractionDetector rayInteractionDetector = rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>();

// Moving the hand to a position where its far ray is hovering over the cube
yield return rightHand.AimAt(cube.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();
InteractionMode farRayMode = rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnHover;
InteractionMode farRayMode = rayInteractionDetector.ModeOnHover;
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.AreEqual(farRayMode, rightHandController.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>().ModeOnDetection);
Assert.AreEqual(farRayMode, rayInteractionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandController, farRayMode);

// Now move the hand in range for the proximity detector
Expand All @@ -159,8 +173,10 @@ public IEnumerator ModeMediationTest()
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();

InteractionMode grabMode = rightHandController.GetComponentInChildren<GrabInteractor>().GetComponent<InteractionDetector>().ModeOnSelect;
Assert.AreEqual(grabMode, rightHandController.GetComponentInChildren<GrabInteractor>().GetComponent<InteractionDetector>().ModeOnDetection);
InteractionDetector grabInteractionDetector = rightHandController.GetComponentInChildren<GrabInteractor>().GetComponent<InteractionDetector>();

InteractionMode grabMode = grabInteractionDetector.ModeOnSelect;
Assert.AreEqual(grabMode, grabInteractionDetector.ModeOnDetection);
yield return RuntimeTestUtilities.WaitForUpdates();
ValidateInteractionModeActive(rightHandController, grabMode);
Assert.IsTrue(grabMode.Priority > nearMode.Priority);
Expand All @@ -176,7 +192,7 @@ public IEnumerator ModeMediationTest()

// Moving the hand to a position where it's far ray is hovering over the cube
yield return rightHand.MoveTo(cube.transform.position + new Vector3(0.02f, -0.1f, -0.8f));
yield return RuntimeTestUtilities.WaitForUpdates(frameCount:120);
yield return RuntimeTestUtilities.WaitForUpdates(frameCount: 120);

ValidateInteractionModeActive(rightHandController, farRayMode);
}
Expand All @@ -189,15 +205,14 @@ public IEnumerator ModeMediationTest()
private void ValidateInteractionModeActive(XRBaseController controller, InteractionMode currentMode)
{
// We construct the list of managed interactor types manually because we don't want to expose the internal controller mapping implementation to even internal use, since
// we don't want any other class to be able to modify those collections without going through the Mode Manager or it's in-editor inspector.
HashSet<System.Type> managedInteractorTypes = new HashSet<System.Type>(InteractionModeManager.Instance.PrioritizedInteractionModes.SelectMany(x => x.AssociatedTypes));
HashSet<System.Type> activeInteractorTypes = InteractionModeManager.Instance.PrioritizedInteractionModes.Find(x => x.ModeName == currentMode.Name).AssociatedTypes;
// we don't want any other class to be able to modify those collections without going through the Mode Manager or its in-editor inspector.
HashSet<Type> managedInteractorTypes = new HashSet<System.Type>(InteractionModeManager.Instance.PrioritizedInteractionModes.SelectMany(x => x.AssociatedTypes));
HashSet<Type> activeInteractorTypes = InteractionModeManager.Instance.PrioritizedInteractionModes.Find(x => x.ModeName == currentMode.Name).AssociatedTypes;

// Ensure the prox detector has actually had the desired effect of enabling/disabling interactors.
foreach (System.Type interactorType in managedInteractorTypes)
foreach (Type interactorType in managedInteractorTypes)
{
XRBaseInteractor interactor = controller.GetComponentInChildren(interactorType) as XRBaseInputInteractor;
if (interactor != null)
if (controller.GetComponentInChildren(interactorType) is XRBaseInputInteractor interactor && interactor != null)
{
Assert.AreEqual(activeInteractorTypes.Contains(interactorType), interactor.enabled);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,42 @@ public IEnumerator InteractionDetectorTest()
yield return RuntimeTestUtilities.WaitForUpdates();

TrackedPoseDriver rightHandTrackedPoseDriver = CachedTrackedPoseDriverLookup.RightHandTrackedPoseDriver;
InteractionDetector rightHandInteractionDetector = rightHandTrackedPoseDriver.transform.parent.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>();
Assert.IsTrue(rightHandTrackedPoseDriver != null, "No tracked pose driver found for right hand.");

// Moving the hand to a position where it's far ray is hovering over the cube
yield return rightHand.AimAt(cube.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();

InteractionMode currentMode = rightHandInteractionDetector.ModeOnHover;
Assert.AreEqual(currentMode, rightHandInteractionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandTrackedPoseDriver, currentMode);
InteractionDetector interactionDetector = rightHandTrackedPoseDriver.transform.parent.GetComponentInChildren<MRTKRayInteractor>().GetComponent<InteractionDetector>();

InteractionMode expectedMode = interactionDetector.ModeOnHover;
Assert.AreEqual(expectedMode, interactionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandTrackedPoseDriver, expectedMode);

// Select the cube and check that we're in the correct mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();
currentMode = rightHandInteractionDetector.ModeOnSelect;
Assert.AreEqual(currentMode, rightHandInteractionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandTrackedPoseDriver, currentMode);
expectedMode = interactionDetector.ModeOnSelect;
Assert.AreEqual(expectedMode, interactionDetector.ModeOnDetection);
ValidateInteractionModeActive(rightHandTrackedPoseDriver, expectedMode);

// move the hand far away and validate that we are in the default mode
// Release the selection and move the hand far away and validate that we are in the default mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Open);
yield return RuntimeTestUtilities.WaitForUpdates();
yield return rightHand.MoveTo(cube.transform.position + new Vector3(3.0f,0,0));
yield return rightHand.MoveTo(cube.transform.position + new Vector3(3.0f, 0, 0));
yield return RuntimeTestUtilities.WaitForUpdates();
expectedMode = InteractionModeManager.Instance.DefaultMode;
ValidateInteractionModeActive(rightHandTrackedPoseDriver, expectedMode);

currentMode = InteractionModeManager.Instance.DefaultMode;
ValidateInteractionModeActive(rightHandTrackedPoseDriver, currentMode);
// Put the hand into a grab state and validate that we are in the default mode, since we're not selecting an object
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Grab);
yield return RuntimeTestUtilities.WaitForUpdates();
ValidateInteractionModeActive(rightHandTrackedPoseDriver, expectedMode);

// Release the grab state and validate that we are in the default mode
yield return rightHand.SetHandshape(HandshapeTypes.HandshapeId.Open);
yield return RuntimeTestUtilities.WaitForUpdates();
ValidateInteractionModeActive(rightHandTrackedPoseDriver, expectedMode);
}

/// <summary>
Expand Down