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

850 lines
41 KiB
C#

using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Gravity;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Jump;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Turning;
namespace UnityEngine.XR.Interaction.Toolkit.Tests
{
[TestFixture]
class LocomotionTests
{
static readonly MatchOrientation[] s_MatchOrientations =
{
MatchOrientation.WorldSpaceUp,
MatchOrientation.TargetUp,
MatchOrientation.TargetUpAndForward,
};
static readonly MatchOrientation[] s_DirectionalMatchOrientations =
{
MatchOrientation.WorldSpaceUp,
MatchOrientation.TargetUp,
};
static readonly XRRayInteractor.HitDetectionType[] s_HitDetectionTypes =
{
XRRayInteractor.HitDetectionType.Raycast,
XRRayInteractor.HitDetectionType.SphereCast,
XRRayInteractor.HitDetectionType.ConeCast,
};
[TearDown]
public void TearDown()
{
TestUtilities.DestroyAllSceneObjects();
}
[UnityTest]
public IEnumerator LocomotionStateChanges()
{
var mediator = TestUtilities.CreateLocomotionMediatorWithXROrigin();
var provider1 = TestUtilities.CreateMockLocomotionProvider(mediator);
var provider2 = TestUtilities.CreateMockLocomotionProvider(mediator);
var provider3 = TestUtilities.CreateMockLocomotionProvider(mediator);
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Idle));
yield return new WaitForFixedUpdate();
yield return null;
// Frame 1:
// Provider 1: Idle -> Moving
// Provider 2: Idle -> Preparing
// Provider 3: Idle -> Preparing
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider1.isLocomotionActive, Is.False);
Assert.That(provider2.isLocomotionActive, Is.False);
Assert.That(provider3.isLocomotionActive, Is.False);
var provider1StartedLocomotion = false;
var provider2StartedLocomotion = false;
var provider3StartedLocomotion = false;
provider1.locomotionStarted += provider => provider1StartedLocomotion = true;
provider2.locomotionStarted += provider => provider2StartedLocomotion = true;
provider3.locomotionStarted += provider => provider3StartedLocomotion = true;
provider1.InvokeTryStartLocomotionImmediately();
provider2.InvokeTryPrepareLocomotion();
provider3.InvokeTryPrepareLocomotion();
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Moving));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Preparing));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Preparing));
Assert.That(provider1.isLocomotionActive, Is.True);
Assert.That(provider2.isLocomotionActive, Is.True);
Assert.That(provider3.isLocomotionActive, Is.True);
Assert.That(provider1StartedLocomotion, Is.True);
Assert.That(provider2StartedLocomotion, Is.False);
Assert.That(provider3StartedLocomotion, Is.False);
// Frame 2:
// Provider 1: Moving -> Moving
// Provider 2: Preparing -> Moving
// Provider 3: Preparing -> Preparing
provider2.FinishPreparation();
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Moving));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Moving));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Preparing));
Assert.That(provider2.isLocomotionActive, Is.True);
Assert.That(provider2StartedLocomotion, Is.True);
Assert.That(provider3StartedLocomotion, Is.False);
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Moving));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Moving));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Preparing));
// Frame 3:
// Provider 1: Moving -> Ended
// Provider 2: Moving -> Ended
// Provider 3: Preparing -> Ended
var provider1EndedLocomotion = false;
var provider2EndedLocomotion = false;
var provider3EndedLocomotion = false;
provider1.locomotionEnded += provider => provider1EndedLocomotion = true;
provider2.locomotionEnded += provider => provider2EndedLocomotion = true;
provider3.locomotionEnded += provider => provider3EndedLocomotion = true;
provider1.InvokeTryEndLocomotion();
provider2.InvokeTryEndLocomotion();
provider3.InvokeTryEndLocomotion();
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Ended));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Ended));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Ended));
Assert.That(provider1.isLocomotionActive, Is.False);
Assert.That(provider2.isLocomotionActive, Is.False);
Assert.That(provider3.isLocomotionActive, Is.False);
Assert.That(provider1EndedLocomotion, Is.True);
Assert.That(provider2EndedLocomotion, Is.True);
Assert.That(provider3EndedLocomotion, Is.True);
// Frame 4:
// Provider 1: Ended -> Preparing
// Provider 2: Ended -> Idle
// Provider 3: Ended -> Idle
provider1.InvokeTryPrepareLocomotion();
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(provider1.locomotionState, Is.EqualTo(LocomotionState.Preparing));
Assert.That(provider2.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider3.locomotionState, Is.EqualTo(LocomotionState.Idle));
Assert.That(provider1.isLocomotionActive, Is.True);
Assert.That(provider2.isLocomotionActive, Is.False);
Assert.That(provider3.isLocomotionActive, Is.False);
}
[UnityTest]
public IEnumerator ProviderCanOnlyPrepareLocomotionWhenLocomotionInactive()
{
var mediator = TestUtilities.CreateLocomotionMediatorWithXROrigin();
var provider = TestUtilities.CreateMockLocomotionProvider(mediator);
// Can prepare if Idle
Assert.That(provider.InvokeTryPrepareLocomotion(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Preparing));
// Cannot prepare if Preparing
Assert.That(provider.InvokeTryPrepareLocomotion(), Is.False);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Preparing));
provider.FinishPreparation();
yield return new WaitForFixedUpdate();
yield return null;
// Cannot prepare if Moving
Assert.That(provider.InvokeTryPrepareLocomotion(), Is.False);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Moving));
provider.InvokeTryEndLocomotion();
// Can prepare if Ended
Assert.That(provider.InvokeTryPrepareLocomotion(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Preparing));
}
[UnityTest]
public IEnumerator ProviderCanOnlyStartLocomotionWhenNotMoving()
{
var mediator = TestUtilities.CreateLocomotionMediatorWithXROrigin();
var provider = TestUtilities.CreateMockLocomotionProvider(mediator);
// Can start if Idle
Assert.That(provider.InvokeTryStartLocomotionImmediately(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Moving));
// Cannot start if Moving
Assert.That(provider.InvokeTryStartLocomotionImmediately(), Is.False);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Moving));
provider.InvokeTryEndLocomotion();
// Can start if Ended
Assert.That(provider.InvokeTryStartLocomotionImmediately(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Moving));
provider.InvokeTryEndLocomotion();
yield return new WaitForFixedUpdate();
yield return null;
provider.InvokeTryPrepareLocomotion();
// Can start if Preparing
Assert.That(provider.InvokeTryStartLocomotionImmediately(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Moving));
}
[UnityTest]
public IEnumerator ProviderCanOnlyEndLocomotionWhenLocomotionActive()
{
var mediator = TestUtilities.CreateLocomotionMediatorWithXROrigin();
var provider = TestUtilities.CreateMockLocomotionProvider(mediator);
provider.InvokeTryPrepareLocomotion();
// Can end if Preparing
Assert.That(provider.InvokeTryEndLocomotion(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Ended));
provider.InvokeTryStartLocomotionImmediately();
// Can end if Moving
Assert.That(provider.InvokeTryEndLocomotion(), Is.True);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Ended));
// Cannot end if Ended
Assert.That(provider.InvokeTryEndLocomotion(), Is.False);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Ended));
yield return new WaitForFixedUpdate();
yield return null;
// Cannot end if Idle
Assert.That(provider.InvokeTryEndLocomotion(), Is.False);
Assert.That(provider.locomotionState, Is.EqualTo(LocomotionState.Idle));
}
[UnityTest]
public IEnumerator ProviderCanOnlyQueueTransformationWhenMoving()
{
var mediator = TestUtilities.CreateLocomotionMediatorWithXROrigin();
var provider = TestUtilities.CreateMockLocomotionProvider(mediator);
var beforeStepLocomotionInvoked = false;
var transformationApplied = false;
provider.beforeStepLocomotion += locomotionProvider => beforeStepLocomotionInvoked = true;
provider.delegateTransformation.transformation += body => transformationApplied = true;
// Cannot queue if Idle
Assert.That(provider.InvokeTryQueueTransformation(), Is.False);
provider.InvokeTryPrepareLocomotion();
// Cannot queue if Preparing
Assert.That(provider.InvokeTryQueueTransformation(), Is.False);
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(beforeStepLocomotionInvoked, Is.False);
Assert.That(transformationApplied, Is.False);
provider.InvokeTryStartLocomotionImmediately();
// Can queue if Moving
Assert.That(provider.InvokeTryQueueTransformation(), Is.True);
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(beforeStepLocomotionInvoked, Is.True);
Assert.That(transformationApplied, Is.True);
beforeStepLocomotionInvoked = false;
transformationApplied = false;
provider.InvokeTryEndLocomotion();
// Cannot queue if Ending
Assert.That(provider.InvokeTryQueueTransformation(), Is.False);
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(beforeStepLocomotionInvoked, Is.False);
Assert.That(transformationApplied, Is.False);
}
[UnityTest]
public IEnumerator TeleportToAreaWithSphereCastOverlapIsBlocked([ValueSource(nameof(s_HitDetectionTypes))] XRRayInteractor.HitDetectionType hitDetectionType)
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.StraightLine;
interactor.hitDetectionType = hitDetectionType;
interactor.sphereCastRadius = 1.5f;
// Create teleportation area
var teleArea = TestUtilities.CreateTeleportAreaPlane();
teleArea.interactionManager = manager;
teleArea.teleportationProvider = teleProvider;
teleArea.matchOrientation = MatchOrientation.TargetUp;
// Pitch the interactor down so that the cast will hit the teleport area
interactor.transform.position = Vector3.up;
interactor.transform.Rotate(Vector3.right, 45f, Space.Self);
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleArea }));
var teleportingInvoked = false;
teleArea.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input so teleport is triggered
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(interactor.isSelectActive, Is.True);
Assert.That(interactor.TryGetCurrent3DRaycastHit(out var hit, out var hitIndex), Is.True);
Assert.That(hitIndex, Is.EqualTo(1));
if (hitDetectionType == XRRayInteractor.HitDetectionType.SphereCast)
{
// The interactor's sphere cast overlaps with the teleport area, so the cast does not return a point.
Assert.That(hit.distance, Is.EqualTo(0f));
Assert.That(hit.point, Is.EqualTo(Vector3.zero));
}
else
{
// The interactor is 1 unit above the teleport area aiming down at a 45-degree angle,
// so the distance to the teleport area should be sqrt(2) units since each side of the
// triangle is 1 unit.
Assert.That(hit.distance, Is.EqualTo(Mathf.Sqrt(2f)).Within(1e-5f));
Assert.That(hit.point, Is.EqualTo(Vector3.forward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
Assert.That(teleArea.IsSelected(interactor), hitDetectionType != XRRayInteractor.HitDetectionType.SphereCast ? Is.True : Is.False);
Assert.That(teleportingInvoked, Is.False);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(interactor.isSelectActive, Is.False);
Assert.That(teleArea.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, hitDetectionType != XRRayInteractor.HitDetectionType.SphereCast ? Is.True : Is.False);
}
[UnityTest]
public IEnumerator TeleportToAnchorWithStraightLine([ValueSource(nameof(s_MatchOrientations))] MatchOrientation matchOrientation)
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.StraightLine;
// Create teleportation anchor
var teleAnchor = TestUtilities.CreateTeleportAnchorPlane();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = matchOrientation;
// Set teleportation anchor plane in the forward direction of controller
teleAnchor.transform.position = interactor.transform.forward + Vector3.down;
teleAnchor.transform.Rotate(-45f, 0f, 0f, Space.World);
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleAnchor }));
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input so teleport is triggered
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.True);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.True);
// Wait a frame for the queued teleport request to be executed
yield return null;
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
if (matchOrientation == MatchOrientation.TargetUp ||
matchOrientation == MatchOrientation.TargetUpAndForward)
{
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(teleAnchor.transform.up).Using(Vector3ComparerWithEqualsOperator.Instance));
var projectedCameraForward = Vector3.ProjectOnPlane(xrOrigin.Camera.transform.forward, teleAnchor.transform.up);
Assert.That(projectedCameraForward.normalized, Is.EqualTo(teleAnchor.transform.forward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
else if (matchOrientation == MatchOrientation.WorldSpaceUp)
{
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(Vector3.up).Using(Vector3ComparerWithEqualsOperator.Instance), "XR Origin up vector");
Assert.That(xrOrigin.Camera.transform.forward, Is.EqualTo(Vector3.forward).Using(Vector3ComparerWithEqualsOperator.Instance), "Projected forward");
}
}
[UnityTest]
public IEnumerator TeleportToAnchorWithStraightLineAndFilterByHitNormal()
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.StraightLine;
// Create teleportation anchor with plane as child so the hit normal can be misaligned with the anchor's up
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
plane.name = "plane";
var planeTrans = plane.transform;
var teleAnchorTrans = new GameObject("teleportation anchor").transform;
planeTrans.SetParent(teleAnchorTrans);
planeTrans.Rotate(-45, 0, 0, Space.World);
teleAnchorTrans.position = interactor.transform.forward + Vector3.down;
var teleAnchor = teleAnchorTrans.gameObject.AddComponent<TeleportationAnchor>();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = MatchOrientation.TargetUpAndForward;
teleAnchor.filterSelectionByHitNormal = true;
teleAnchor.upNormalToleranceDegrees = 30f;
var cameraTrans = xrOrigin.Camera.transform;
var originalCameraPosition = cameraTrans.position;
var originalCameraForward = cameraTrans.forward;
var originTrans = xrOrigin.transform;
var originalOriginUp = originTrans.up;
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleAnchor }));
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input, but teleport should not occur
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.False);
// Wait a frame for any queued teleport request to be executed (which shouldn't happen)
yield return null;
Assert.That(cameraTrans.position, Is.EqualTo(originalCameraPosition).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(originTrans.up, Is.EqualTo(originalOriginUp).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(cameraTrans.forward, Is.EqualTo(originalCameraForward).Using(Vector3ComparerWithEqualsOperator.Instance));
// Now increase the normal tolerance and try teleporting again
teleAnchor.upNormalToleranceDegrees = 50f;
// Manually drive the select input so teleport is triggered
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.True);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.True);
// Wait a frame for the queued teleport request to be executed
yield return null;
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(teleAnchor.transform.up).Using(Vector3ComparerWithEqualsOperator.Instance));
var projectedCameraForward = Vector3.ProjectOnPlane(xrOrigin.Camera.transform.forward, teleAnchor.transform.up);
Assert.That(projectedCameraForward.normalized, Is.EqualTo(teleAnchor.transform.forward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator TeleportToAnchorWithProjectile()
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.ProjectileCurve;
// create teleportation anchor
var teleAnchor = TestUtilities.CreateTeleportAnchorPlane();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = MatchOrientation.TargetUp;
// Set teleportation anchor plane
teleAnchor.transform.position = interactor.transform.forward + Vector3.down;
teleAnchor.transform.Rotate(-90f, 0f, 0f, Space.World);
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleAnchor }));
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input so teleport is triggered
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.True);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.True);
// Wait a frame for the queued teleport request to be executed
yield return null;
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(teleAnchor.transform.up).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator TeleportToAnchorWithBezierCurve()
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.BezierCurve;
// Create teleportation anchor
var teleAnchor = TestUtilities.CreateTeleportAnchorPlane();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = MatchOrientation.TargetUp;
// Set teleportation anchor plane
teleAnchor.transform.position = interactor.transform.forward + Vector3.down;
teleAnchor.transform.Rotate(-90f, 0f, 0f, Space.World);
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleAnchor }));
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input so teleport is triggered
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.True);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.True);
// Wait a frame for the queued teleport request to be executed
yield return null;
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(teleAnchor.transform.up).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator TeleportToAnchorWithStraightLineAndDirectionalInput([ValueSource(nameof(s_DirectionalMatchOrientations))] MatchOrientation matchOrientation)
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Interactor
var interactor = TestUtilities.CreateRayInteractor();
interactor.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform);
interactor.selectActionTrigger = XRBaseInputInteractor.InputTriggerType.State;
interactor.lineType = XRRayInteractor.LineType.StraightLine;
// Fake directional input by manually rotating the attach transform
var attachTransform = interactor.attachTransform;
attachTransform.Rotate(Vector3.up, 30f);
// Create teleportation anchor
var teleAnchor = TestUtilities.CreateTeleportAnchorPlane();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = matchOrientation;
teleAnchor.matchDirectionalInput = true;
// Set teleportation anchor plane in the forward direction of controller
teleAnchor.transform.position = interactor.transform.forward + Vector3.down;
teleAnchor.transform.Rotate(-45f, 0f, 0f, Space.World);
// Wait for Physics update for hit
yield return new WaitForFixedUpdate();
yield return null;
var validTargets = new List<IXRInteractable>();
manager.GetValidTargets(interactor, validTargets);
Assert.That(validTargets, Is.EqualTo(new[] { teleAnchor }));
// Calculate expected forward direction AFTER rotating attach transform but BEFORE the controller performs the fake teleportation
var planeNormal = matchOrientation == MatchOrientation.WorldSpaceUp ? Vector3.up : teleAnchor.transform.up;
var expectedForward = Vector3.ProjectOnPlane(attachTransform.forward, planeNormal).normalized;
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually drive the select input so teleport is triggered
interactor.selectInput.inputSourceMode = XRInputButtonReader.InputSourceMode.ManualValue;
interactor.selectInput.QueueManualState(true, 1f);
yield return null;
Assert.That(interactor.selectInput.ReadValue(), Is.EqualTo(1f));
Assert.That(interactor.logicalSelectState.active, Is.True);
Assert.That(interactor.isSelectActive, Is.True);
Assert.That(teleAnchor.IsSelected(interactor), Is.True);
interactor.selectInput.QueueManualState(false, 0f);
yield return null;
Assert.That(interactor.selectInput.ReadValue(), Is.EqualTo(0f));
Assert.That(interactor.logicalSelectState.active, Is.False);
Assert.That(interactor.isSelectActive, Is.False);
Assert.That(teleAnchor.IsSelected(interactor), Is.False);
Assert.That(teleportingInvoked, Is.True);
// Wait a frame for the queued teleport request to be executed
yield return null;
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(planeNormal).Using(Vector3ComparerWithEqualsOperator.Instance), "XR Origin up vector");
if (matchOrientation == MatchOrientation.WorldSpaceUp)
{
Assert.That(xrOrigin.Camera.transform.forward, Is.EqualTo(expectedForward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
else if (matchOrientation == MatchOrientation.TargetUp)
{
var projectedCameraForward = Vector3.ProjectOnPlane(xrOrigin.Camera.transform.forward, planeNormal);
Assert.That(projectedCameraForward.normalized, Is.EqualTo(expectedForward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
}
[UnityTest]
public IEnumerator TeleportToAnchorManually()
{
var manager = TestUtilities.CreateInteractionManager();
var xrOrigin = TestUtilities.CreateXROrigin();
// Config teleportation on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
var teleProvider = xrOrigin.gameObject.AddComponent<TeleportationProvider>();
teleProvider.mediator = mediator;
// Create teleportation anchor
var teleAnchor = TestUtilities.CreateTeleportAnchorPlane();
teleAnchor.interactionManager = manager;
teleAnchor.teleportationProvider = teleProvider;
teleAnchor.matchOrientation = MatchOrientation.TargetUpAndForward;
teleAnchor.transform.position = new Vector3(0f, -1f, 1f);
teleAnchor.transform.Rotate(-45f, 0f, 0f, Space.World);
var teleportingInvoked = false;
teleAnchor.teleporting.AddListener(_ => teleportingInvoked = true);
// Manually trigger teleport to the anchor
teleAnchor.RequestTeleport();
// Wait a frame for the queued teleport request to be executed
yield return null;
Assert.That(teleportingInvoked, Is.True);
var cameraPosAdjustment = xrOrigin.Origin.transform.up * xrOrigin.CameraInOriginSpaceHeight;
Assert.That(xrOrigin.Camera.transform.position, Is.EqualTo(teleAnchor.transform.position + cameraPosAdjustment).Using(Vector3ComparerWithEqualsOperator.Instance));
Assert.That(xrOrigin.Origin.transform.up, Is.EqualTo(teleAnchor.transform.up).Using(Vector3ComparerWithEqualsOperator.Instance));
var projectedCameraForward = Vector3.ProjectOnPlane(xrOrigin.Camera.transform.forward, teleAnchor.transform.up);
Assert.That(projectedCameraForward.normalized, Is.EqualTo(teleAnchor.transform.forward).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator SnapTurn()
{
var xrOrigin = TestUtilities.CreateXROrigin();
// Config snap turn on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
mediator.xrOrigin = xrOrigin;
var snapProvider = xrOrigin.gameObject.AddComponent<SnapTurnProvider>();
snapProvider.mediator = mediator;
var turnAmount = snapProvider.turnAmount;
snapProvider.rightHandTurnInput.inputSourceMode = XRInputValueReader.InputSourceMode.ManualValue;
snapProvider.rightHandTurnInput.manualValue = Vector2.right;
yield return null;
Assert.That(xrOrigin.transform.rotation.eulerAngles, Is.EqualTo(new Vector3(0f, turnAmount, 0f)).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator SnapTurnAround()
{
var xrOrigin = TestUtilities.CreateXROrigin();
// Config snap turn on XR Origin
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
mediator.xrOrigin = xrOrigin;
var snapProvider = xrOrigin.gameObject.AddComponent<SnapTurnProvider>();
snapProvider.mediator = mediator;
snapProvider.rightHandTurnInput.inputSourceMode = XRInputValueReader.InputSourceMode.ManualValue;
snapProvider.rightHandTurnInput.manualValue = Vector2.down;
yield return null;
Assert.That(xrOrigin.transform.rotation.eulerAngles, Is.EqualTo(new Vector3(0f, 180f, 0f)).Using(Vector3ComparerWithEqualsOperator.Instance));
}
[UnityTest]
public IEnumerator Jump()
{
var xrOrigin = TestUtilities.CreateXROrigin();
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
mediator.xrOrigin = xrOrigin;
var gravityProvider = xrOrigin.gameObject.AddComponent<GravityProvider>();
gravityProvider.mediator = mediator;
var jumpProvider = xrOrigin.gameObject.AddComponent<JumpProvider>();
jumpProvider.mediator = mediator;
jumpProvider.unlimitedInAirJumps = true;
var initialHeight = xrOrigin.Origin.transform.position.y;
jumpProvider.Jump();
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(xrOrigin.Origin.transform.position.y, Is.GreaterThan(initialHeight));
}
[UnityTest]
public IEnumerator Gravity()
{
var xrOrigin = TestUtilities.CreateXROrigin();
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
mediator.xrOrigin = xrOrigin;
var gravityProvider = xrOrigin.gameObject.AddComponent<GravityProvider>();
gravityProvider.mediator = mediator;
var initialHeight = xrOrigin.Origin.transform.position.y;
yield return new WaitForFixedUpdate();
yield return null;
Assert.That(xrOrigin.Origin.transform.position.y, Is.LessThan(initialHeight));
}
}
}