473 lines
22 KiB
C#
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
|
|
}));
|
|
}
|
|
}
|
|
}
|