VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Tests/Runtime/DirectInteractorTests.cs
2025-07-29 13:45:50 +03:00

473 lines
22 KiB
C#

using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.TestTools;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
namespace UnityEngine.XR.Interaction.Toolkit.Tests
{
[TestFixture]
class DirectInteractorTests
{
[TearDown]
public void TearDown()
{
TestUtilities.DestroyAllSceneObjects();
}
[UnityTest]
public IEnumerator DirectInteractorCanHoverInteractable()
{
var manager = TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var directInteractor = TestUtilities.CreateDirectInteractor();
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(directInteractor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(directInteractor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(directInteractor.hasHover, Is.True);
}
[UnityTest]
public IEnumerator DirectInteractorHandlesUnregisteredInteractable()
{
var manager = TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var directInteractor = TestUtilities.CreateDirectInteractor();
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(directInteractor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
directInteractor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(directInteractor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Object.Destroy(interactable);
yield return null;
Assert.That(interactable == null, Is.True);
manager.GetValidTargets(directInteractor, validTargets);
Assert.That(validTargets, Is.Empty);
directInteractor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.Empty);
Assert.That(directInteractor.interactablesHovered, Is.Empty);
}
[UnityTest]
public IEnumerator DirectInteractorCanSelectInteractable()
{
TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var directInteractor = TestUtilities.CreateDirectInteractor();
var controllerRecorder = TestUtilities.CreateControllerRecorder(directInteractor, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(float.MaxValue, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
controllerRecorder.isPlaying = true;
controllerRecorder.visitEachFrame = true;
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(directInteractor.interactablesSelected, Is.EqualTo(new[] { interactable }));
}
[UnityTest]
public IEnumerator DirectInteractorHandlesCanceledInteractable()
{
var manager = TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var directInteractor = TestUtilities.CreateDirectInteractor();
var controllerRecorder = TestUtilities.CreateControllerRecorder(directInteractor, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(float.MaxValue, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
controllerRecorder.isPlaying = true;
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(directInteractor.interactablesSelected, Is.EqualTo(new[] { interactable }));
IXRSelectInteractor canceledInteractor = null;
IXRSelectInteractable canceledInteractable = null;
interactable.selectExited.AddListener(args => canceledInteractor = args.isCanceled ? args.interactorObject : null);
directInteractor.selectExited.AddListener(args => canceledInteractable = args.isCanceled ? args.interactableObject : null);
Object.Destroy(interactable);
yield return null;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse -- overloaded Object operator==
Assert.That(interactable == null, Is.True);
Assert.That(directInteractor.interactablesSelected, Is.Empty);
Assert.That(canceledInteractor, Is.SameAs(directInteractor));
Assert.That(canceledInteractable, Is.SameAs(interactable));
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(directInteractor, validTargets);
Assert.That(validTargets, Is.Empty);
Assert.That(directInteractor.interactablesHovered, Is.Empty);
}
[UnityTest]
public IEnumerator DirectInteractorCanGainAndLoseFocusOfInteractable()
{
TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var group = TestUtilities.CreateInteractionGroup();
var directInteractor = TestUtilities.CreateDirectInteractor();
group.AddGroupMember(directInteractor);
var controllerRecorder = TestUtilities.CreateControllerRecorder(directInteractor, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.2f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.3f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
});
controllerRecorder.isPlaying = true;
controllerRecorder.visitEachFrame = true;
while (controllerRecorder.isPlaying)
{
yield return new WaitForSeconds(0.1f);
}
// Selection has come and gone - resulting in a focus of the grab interactable
Assert.That(group.focusInteractable, Is.EqualTo(interactable));
controllerRecorder.isPlaying = false;
Object.Destroy(controllerRecorder);
yield return null;
var offset = new Vector3(10.0f, 0.0f, 0.0f);
controllerRecorder = TestUtilities.CreateControllerRecorder(directInteractor, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, offset, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, offset, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.2f, offset, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.3f, offset, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
controllerRecorder.isPlaying = true;
controllerRecorder.visitEachFrame = true;
while (controllerRecorder.isPlaying)
{
yield return new WaitForSeconds(0.1f);
}
// Selection attempted again with no object present, resulting in loss of focus
Assert.That(group.focusInteractable, Is.EqualTo(null));
}
[UnityTest]
public IEnumerator DirectInteractorCanPassToAnother()
{
TestUtilities.CreateInteractionManager();
var interactable = TestUtilities.CreateGrabInteractable();
var directInteractor1 = TestUtilities.CreateDirectInteractor();
directInteractor1.name = "directInteractor1";
var controllerRecorder1 = TestUtilities.CreateControllerRecorder(directInteractor1, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(float.MaxValue, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
var directInteractor2 = TestUtilities.CreateDirectInteractor();
directInteractor2.name = "directInteractor2";
var controllerRecorder2 = TestUtilities.CreateControllerRecorder(directInteractor2, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
false, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.1f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(float.MaxValue, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
controllerRecorder1.isPlaying = true;
controllerRecorder1.visitEachFrame = true;
// Wait for Physics update for trigger collision
yield return new WaitForFixedUpdate();
yield return new WaitForSeconds(0.1f);
// directInteractor1 grabs the interactable
Assert.That(interactable.interactorsSelecting, Is.EqualTo(new[] { directInteractor1 }), "In first frame, controller 1 should grab the interactable.");
controllerRecorder2.isPlaying = true;
controllerRecorder2.visitEachFrame = true;
// Wait for the proper interaction that signifies the handoff
yield return new WaitForSeconds(0.1f);
// directInteractor2 grabs the interactable from directInteractor1
Assert.That(interactable.interactorsSelecting, Is.EqualTo(new[] { directInteractor2 }), "In second frame, controller 2 should grab the interactable.");
}
[UnityTest]
public IEnumerator DirectInteractorReportsValidTargetWhenInteractableRegisteredAfterContact()
{
TestUtilities.CreateInteractionManager();
var interactor = TestUtilities.CreateDirectInteractor();
var interactable = TestUtilities.CreateGrabInteractable();
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
var controllerRecorder = TestUtilities.CreateControllerRecorder(interactor, (recording) =>
{
recording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
recording.AddRecordingFrameNonAlloc(new XRControllerState(float.MaxValue, Vector3.zero, Quaternion.identity, InputTrackingState.All, true,
true, false, false));
});
controllerRecorder.isPlaying = true;
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(interactor.interactablesSelected, Is.EqualTo(new[] { interactable }));
Assert.That(interactable.interactorsSelecting, Is.EqualTo(new[] { interactor }));
var validTargets = new List<IXRInteractable>();
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
// Disable the Interactable so it will be removed as a valid target.
interactable.enabled = false;
yield return null;
Assert.That(interactor.interactablesSelected, Is.Empty);
Assert.That(interactable.interactorsSelecting, Is.Empty);
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.Empty);
// Re-enable the Interactable. It should not be required to leave and enter the collider to be selected again.
interactable.enabled = true;
yield return null;
Assert.That(interactor.interactablesSelected, Is.EqualTo(new[] { interactable }));
Assert.That(interactable.interactorsSelecting, Is.EqualTo(new[] { interactor }));
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
}
[UnityTest]
public IEnumerator DirectInteractorUpdatesStayedCollidersOnDisablingInteractableCollider()
{
TestUtilities.CreateInteractionManager();
var interactor = TestUtilities.CreateDirectInteractor();
var interactable = TestUtilities.CreateGrabInteractable();
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(interactor, Is.Not.Null);
// Check that the interactor is hovering the interactable.
var validTargets = new List<IXRInteractable>();
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
// Disable the collider component.
interactable.GetComponent<SphereCollider>().enabled = false;
yield return new WaitForFixedUpdate();
yield return null;
// Since interactable's collider is disabled, it should not show up in the list of valid targets.
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.Empty);
Assert.That(interactor.interactablesHovered, Is.Empty);
yield return new WaitForFixedUpdate();
// Additional test: re-enable the collider component, the interactor should re-hover it.
interactable.GetComponent<SphereCollider>().enabled = true;
yield return new WaitForFixedUpdate();
yield return null;
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
}
[UnityTest]
public IEnumerator DirectInteractorUpdatesStayedCollidersOnDeactivatingInteractableObject()
{
TestUtilities.CreateInteractionManager();
var interactor = TestUtilities.CreateDirectInteractor();
var interactable = TestUtilities.CreateGrabInteractable();
yield return new WaitForFixedUpdate();
yield return null;
// Check that the interactable is a valid target of and can be hovered by the interactor.
var validTargets = new List<IXRInteractable>();
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
// De-activate the interactable's gameObject.
interactable.gameObject.SetActive(false);
// Test to make sure that the interactable gameObject would no longer be a valid target
// after its gameObject is set to disable.
yield return new WaitForFixedUpdate();
yield return null;
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.Empty);
Assert.That(interactor.hasHover, Is.False);
yield return new WaitForFixedUpdate();
// Re-enabling the interactable gameObject, the interactor should re-hover it.
interactable.gameObject.SetActive(true);
yield return new WaitForFixedUpdate();
yield return null;
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
}
[UnityTest]
public IEnumerator DirectInteractorUpdatesStayedCollidersOnDestroyingInteractableObject()
{
TestUtilities.CreateInteractionManager();
var interactor = TestUtilities.CreateDirectInteractor();
var interactable = TestUtilities.CreateGrabInteractable();
yield return new WaitForFixedUpdate();
yield return null;
// Check that the interactable is a valid target of and can be hovered by the interactor.
var validTargets = new List<IXRInteractable>();
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
// Destroy the interactable's gameObject.
Object.Destroy(interactable.gameObject);
// Test to make sure that the interactable gameObject would no longer be a valid target.
yield return new WaitForFixedUpdate();
yield return null;
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.Empty);
Assert.That(interactor.hasHover, Is.False);
yield return new WaitForFixedUpdate();
// Additional test: re-spawning the interactable gameObject, the interactor should re-hover it.
interactable = TestUtilities.CreateGrabInteractable();
yield return new WaitForFixedUpdate();
yield return null;
interactor.GetValidTargets(validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.interactablesHovered, Is.EqualTo(new[] { interactable }));
Assert.That(interactor.hasHover, Is.True);
}
[UnityTest]
public IEnumerator DirectInteractorCanUseTargetFilter()
{
TestUtilities.CreateInteractionManager();
var interactor = TestUtilities.CreateDirectInteractor();
var interactable = TestUtilities.CreateGrabInteractable();
yield return new WaitForFixedUpdate();
yield return null;
var filter = new MockTargetFilter();
Assert.That(filter.callbackExecution, Is.EqualTo(new List<TargetFilterCallback>()));
// Link the filter
interactor.targetFilter = filter;
Assert.That(interactor.targetFilter, Is.EqualTo(filter));
Assert.That(filter.callbackExecution, Is.EqualTo(new List<TargetFilterCallback>
{
TargetFilterCallback.Link
}));
// Process the filter
var validTargets = new List<IXRInteractable>();
interactor.GetValidTargets(validTargets);
Assert.That(interactor.targetFilter, Is.EqualTo(filter));
Assert.That(validTargets, Is.EqualTo(new[] { interactable }));
Assert.That(filter.callbackExecution, Is.EqualTo(new List<TargetFilterCallback>
{
TargetFilterCallback.Link,
TargetFilterCallback.Process
}));
// Disable the filter and check if it will no longer be processed
filter.canProcess = false;
interactor.GetValidTargets(validTargets);
Assert.That(filter.callbackExecution, Is.EqualTo(new List<TargetFilterCallback>
{
TargetFilterCallback.Link,
TargetFilterCallback.Process
}));
// Unlink the filter
interactor.targetFilter = null;
Assert.That(interactor.targetFilter, Is.EqualTo(null));
Assert.That(filter.callbackExecution, Is.EqualTo(new List<TargetFilterCallback>
{
TargetFilterCallback.Link,
TargetFilterCallback.Process,
TargetFilterCallback.Unlink
}));
}
}
}