using System.Collections; using System.Linq; using NUnit.Framework; using UnityEngine.TestTools; using UnityEngine.TestTools.Utils; using UnityEngine.XR.Interaction.Toolkit.Filtering; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Interactors.Visuals; namespace UnityEngine.XR.Interaction.Toolkit.Tests { [TestFixture] class InteractorLineVisualTests { static readonly XRRayInteractor.LineType[] s_LineTypes = { XRRayInteractor.LineType.StraightLine, XRRayInteractor.LineType.ProjectileCurve, XRRayInteractor.LineType.BezierCurve, }; static readonly Quaternion[] k_ValidSnapRotations = { Quaternion.Euler(0f, 5f, 0f), Quaternion.Euler(5f, -5f, 0f), Quaternion.Euler(0f, 0f, 0f), }; static readonly Quaternion k_InvalidSnapRotation = Quaternion.Euler(0f, 90f, 0f); static readonly Gradient k_InvalidColorGradient = new Gradient { colorKeys = new[] { new GradientColorKey(Color.red, 0f), new GradientColorKey(Color.red, 1f) }, alphaKeys = new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }, }; static readonly Gradient k_ValidColorGradient = new Gradient { colorKeys = new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) }, alphaKeys = new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }, }; static readonly Gradient k_BlockedColorGradient = new Gradient { colorKeys = new[] { new GradientColorKey(Color.yellow, 0f), new GradientColorKey(Color.yellow, 1f) }, alphaKeys = new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }, }; [TearDown] public void TearDown() { TestUtilities.DestroyAllSceneObjects(); } [UnityTest] public IEnumerator LineVisualUsesCorrectStateVisualOptions() { TestUtilities.CreateInteractionManager(); var interactor = TestUtilities.CreateRayInteractor(); interactor.transform.position = Vector3.zero; interactor.transform.forward = Vector3.forward; var lineVisual = interactor.GetComponent(); var lineRenderer = lineVisual.GetComponent(); lineVisual.invalidColorGradient = k_InvalidColorGradient; lineVisual.validColorGradient = k_ValidColorGradient; lineVisual.blockedColorGradient = k_BlockedColorGradient; var validReticle = new GameObject("valid reticle"); lineVisual.reticle = validReticle; var blockedReticle = new GameObject("blocked reticle"); lineVisual.blockedReticle = blockedReticle; // No valid target yield return new WaitForSeconds(0.1f); lineVisual.UpdateLineVisual(); Assert.That(lineRenderer.colorGradient.Evaluate(0f), Is.EqualTo(k_InvalidColorGradient.Evaluate(0f)).Using(ColorEqualityComparer.Instance)); Assert.That(lineRenderer.colorGradient.Evaluate(1f), Is.EqualTo(k_InvalidColorGradient.Evaluate(1f)).Using(ColorEqualityComparer.Instance)); Assert.That(validReticle.activeSelf, Is.False); Assert.That(blockedReticle.activeSelf, Is.False); // Valid target exists var interactable = TestUtilities.CreateGrabInteractable(); interactable.transform.position = interactor.transform.position + interactor.transform.forward * 5.0f; yield return new WaitForSeconds(0.1f); lineVisual.UpdateLineVisual(); Assert.That(lineRenderer.colorGradient.Evaluate(0f), Is.EqualTo(k_ValidColorGradient.Evaluate(0f)).Using(ColorEqualityComparer.Instance)); Assert.That(lineRenderer.colorGradient.Evaluate(1f), Is.EqualTo(k_ValidColorGradient.Evaluate(1f)).Using(ColorEqualityComparer.Instance)); Assert.That(validReticle.activeSelf, Is.True); Assert.That(blockedReticle.activeSelf, Is.False); // Valid target exists but is not selectable var blockedFilter = new XRSelectFilterDelegate((x, y) => false); interactable.selectFilters.Add(blockedFilter); yield return new WaitForSeconds(0.1f); lineVisual.UpdateLineVisual(); Assert.That(lineRenderer.colorGradient.Evaluate(0f), Is.EqualTo(k_BlockedColorGradient.Evaluate(0f)).Using(ColorEqualityComparer.Instance)); Assert.That(lineRenderer.colorGradient.Evaluate(1f), Is.EqualTo(k_BlockedColorGradient.Evaluate(1f)).Using(ColorEqualityComparer.Instance)); Assert.That(validReticle.activeSelf, Is.False); Assert.That(blockedReticle.activeSelf, Is.True); } [UnityTest] public IEnumerator LineVisualSnapsToSnapVolume([ValueSource(nameof(s_LineTypes))] XRRayInteractor.LineType lineType) { const int numBendyLineRenderPoints = 20; var manager = TestUtilities.CreateInteractionManager(); var interactor = TestUtilities.CreateRayInteractor(); interactor.transform.position = Vector3.zero; const int sampleFrequency = 5; interactor.sampleFrequency = sampleFrequency; interactor.maxRaycastDistance = 20f; interactor.lineType = lineType; var lineVisual = interactor.GetComponent(); var lineRenderer = lineVisual.GetComponent(); yield return null; Vector3[] samplePoints = null; var isValid = interactor.GetLinePoints(ref samplePoints, out var samplePointsCount); Assert.That(isValid, Is.True); Assert.That(samplePoints, Is.Not.Null); Assert.That(samplePointsCount, Is.EqualTo(lineType == XRRayInteractor.LineType.StraightLine ? 2 : sampleFrequency)); yield return null; // Setup interactable and snap volume var interactable = TestUtilities.CreateSimpleInteractable(); interactable.transform.position = interactor.transform.position + interactor.transform.forward * 5.0f; var snapVolume = TestUtilities.CreateSnapVolume(); snapVolume.interactable = interactable; snapVolume.snapToCollider = interactable.colliders[0]; snapVolume.transform.position = interactable.transform.position; // Turn interactor away from interactable interactor.transform.rotation = k_InvalidSnapRotation; yield return null; // Confirm we're not hitting anything var isHitInfoValid = interactor.TryGetHitInfo(out var hp, out _, out var hitPositionInLine, out var isValidTarget); Assert.That(isHitInfoValid, Is.False); Assert.That(isValidTarget, Is.False); Assert.That(hitPositionInLine, Is.EqualTo(0)); Assert.That(interactor.interactablesHovered, Is.Empty); // Enable trigger interaction and snap end point on visuals interactor.raycastTriggerInteraction = QueryTriggerInteraction.Collide; lineVisual.snapEndpointIfAvailable = true; // LineRenderer.GetPositions always returns 0 when running with the -batchmode flag if (!Application.isBatchMode) { // Test various interactor rotations and ensure line render endpoint is updated to the snapped endpoint Vector3[] renderedPoints = new Vector3[numBendyLineRenderPoints]; for (var i = 0; i < k_ValidSnapRotations.Length; i++) { interactor.transform.rotation = k_ValidSnapRotations[i]; yield return new WaitForSeconds(0.1f); isHitInfoValid = interactor.TryGetHitInfo(out var hitPosition, out _, out _, out isValidTarget); Assert.That(isHitInfoValid, Is.True); Assert.That(isValidTarget, Is.True); var closestPoint = snapVolume.GetClosestPoint(hitPosition); yield return null; lineRenderer.GetPositions(renderedPoints); Assert.That(closestPoint, Is.EqualTo(renderedPoints.Last()).Using(Vector3ComparerWithEqualsOperator.Instance)); } } } } }