UnityGame/Library/PackageCache/com.unity.ai.navigation/Tests/Editor/NavMeshLinkEditorTests.cs

488 lines
21 KiB
C#
Raw Permalink Normal View History

2024-10-27 10:53:47 +03:00
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools.Utils;
using Matrix4x4 = UnityEngine.Matrix4x4;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
using From = Unity.AI.Navigation.Editor.Tests.NavMeshLinkEditorTests.LinkEndType;
using To = Unity.AI.Navigation.Editor.Tests.NavMeshLinkEditorTests.LinkEndType;
// Note: To pause and inspect the state during these editor tests,
// run them in playmode from the NavMeshLinkEditorTestsInPlaymode class
// in the Unity.AI.Navigation.Editor.Tests.InPlaymode namespace.
namespace Unity.AI.Navigation.Editor.Tests
{
public class NavMeshLinkEditorTests
{
List<UnityEngine.Object> m_TestObjects = new();
GameObject m_LinkGameObject;
GameObject m_Start;
GameObject m_End;
NavMeshLink m_Link;
NavMeshLink m_LinkSibling1;
NavMeshLink m_LinkSibling2;
static readonly Vector3EqualityComparer k_DefaultThreshold = Vector3EqualityComparer.Instance;
GameObject CreateTestObject(string name, params Type[] components)
{
var go = new GameObject(name, components);
m_TestObjects.Add(go);
return go;
}
[OneTimeSetUp]
public void OneTimeSetup()
{
m_LinkGameObject = new GameObject("Link");
m_Link = m_LinkGameObject.AddComponent<NavMeshLink>();
m_Start = new GameObject("Start");
m_End = new GameObject("End");
// To debug, add these components, only to show icons for them in the scene
//m_Start.AddComponent<NavMeshSurface>().enabled = false;
//m_End.AddComponent<NavMeshModifier>().enabled = false;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (m_LinkGameObject != null)
UnityEngine.Object.DestroyImmediate(m_LinkGameObject);
if (m_Start != null)
UnityEngine.Object.DestroyImmediate(m_Start);
if (m_End != null)
UnityEngine.Object.DestroyImmediate(m_End);
}
[SetUp]
public void Setup()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
m_Link.transform.localScale = Vector3.one;
m_Link.startPoint = Vector3.left;
m_Link.endPoint = Vector3.right;
}
}
[TearDown]
public void TearDown()
{
if (m_LinkSibling1 != null)
UnityEngine.Object.DestroyImmediate(m_LinkSibling1);
if (m_LinkSibling2 != null)
UnityEngine.Object.DestroyImmediate(m_LinkSibling2);
foreach (var obj in m_TestObjects)
{
if (obj != null)
UnityEngine.Object.DestroyImmediate(obj);
}
m_TestObjects.Clear();
}
protected static readonly Vector3[] k_ReverseDirectionPositions =
{ Vector3.zero, new(1f, 2f, 3f), new(1f, -2f, 3f) };
protected static readonly Quaternion[] k_ReverseDirectionOrientations =
{ Quaternion.identity, new(0f, 0.7071067812f, 0f, 0.7071067812f) };
protected static readonly Vector3[] k_ReverseDirectionScales =
{ Vector3.one, new(0.5f, 1f, 2f), new(0.5f, -1f, 2f) };
[Test]
public void ReverseDirection_SwapsStartAndEndPoints(
[ValueSource(nameof(k_ReverseDirectionPositions))]
Vector3 position,
[ValueSource(nameof(k_ReverseDirectionOrientations))]
Quaternion orientation,
[ValueSource(nameof(k_ReverseDirectionScales))]
Vector3 scale
)
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.transform.SetPositionAndRotation(position, orientation);
m_Link.transform.localScale = scale;
m_Link.startPoint = new Vector3(2f, 0f, 0f);
m_Link.endPoint = new Vector3(0f, 0f, 2f);
}
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startPoint, m_Link.endPoint),
Is.EqualTo((new Vector3(0f, 0f, 2f), new Vector3(2f, 0f, 0f))),
"Start and end points did not swap."
);
}
[Test]
public void ReverseDirection_SwapsStartAndEndPoints_TargetTransformsDoNotAffect(
[ValueSource(nameof(k_ReverseDirectionPositions))]
Vector3 position,
[ValueSource(nameof(k_ReverseDirectionOrientations))]
Quaternion orientation,
[ValueSource(nameof(k_ReverseDirectionScales))]
Vector3 scale
)
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startTransform = CreateTestObject("Start").transform;
m_Link.endTransform = CreateTestObject("End").transform;
m_Link.transform.SetPositionAndRotation(position, orientation);
m_Link.transform.localScale = scale;
m_Link.startPoint = new Vector3(2f, 0f, 0f);
m_Link.endPoint = new Vector3(0f, 0f, 2f);
}
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startPoint, m_Link.endPoint),
Is.EqualTo((new Vector3(0f, 0f, 2f), new Vector3(2f, 0f, 0f))),
"Start and end points did not swap."
);
}
[Test]
public void ReverseDirection_SwapsStartAndEndTransforms()
{
var start = m_Link.startTransform = CreateTestObject("Start").transform;
var end = m_Link.endTransform = CreateTestObject("End").transform;
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startTransform, m_Link.endTransform),
Is.EqualTo((end, start)),
"Start and end transform did not swap."
);
}
[Test]
public void ReverseDirection_OneTransformIsNotSet_SwapsStartAndEndTransforms()
{
var start = m_Link.startTransform = CreateTestObject("Start").transform;
var end = m_Link.endTransform;
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startTransform, m_Link.endTransform),
Is.EqualTo((end, start)),
"Start and end transform did not swap."
);
}
static readonly Vector3 k_Offset101 = new(1, 1, 1);
static readonly Vector3 k_Offset103 = new(1, 1, 3);
static readonly Quaternion k_DoNotRotate = Quaternion.identity;
static readonly Quaternion k_FlipToRight = Quaternion.Euler(0, 0, -90);
static readonly Quaternion k_UpSideDown = Quaternion.Euler(0, 0, 180);
//TestCaseData( startType, endType, transformRotation,
// expectedPosition, expectedForward)
protected static readonly TestCaseData[] k_PointsOnly =
{
new(From.Point, To.Point, k_DoNotRotate,
new Vector3(1, 1, 2), Vector3.back),
new(From.Point, To.Point, k_UpSideDown,
new Vector3(-1, -1, 2), Vector3.back),
};
protected static readonly TestCaseData[] k_PointAndTransforms =
{
new(From.Point, To.Transform, k_DoNotRotate,
new Vector3(1, 2, 2), Vector3.back),
new(From.Point, To.Transform, k_FlipToRight,
new Vector3(1, 1, 2), Quaternion.Euler(-116.565f, 0, -90) * Vector3.forward),
new(From.Transform, To.Point, k_DoNotRotate,
new Vector3(1, 2, 2), Vector3.back),
new(From.Transform, To.Point, k_FlipToRight,
new Vector3(1, 1, 2), Quaternion.Euler(116.565f, 0, -90) * Vector3.forward),
new(From.Transform, To.Transform, k_DoNotRotate,
new Vector3(1, 3, 2), Vector3.back),
new(From.Transform, To.Transform, k_UpSideDown,
new Vector3(1, 3, 2), Vector3.back),
};
protected static readonly TestCaseData[] k_ChildTransforms =
{
new(From.TransformChild, To.TransformChild, k_FlipToRight,
new Vector3(3, -1, 2), Vector3.back),
};
void ConfigureLinkForTest(From startType, To endType, Quaternion transformRotation)
{
m_Start.transform.position = k_Offset103 + 2f * Vector3.up;
m_End.transform.position = k_Offset101 + 2f * Vector3.up;
m_Start.transform.parent = startType == From.TransformChild ? m_Link.transform : null;
m_End.transform.parent = endType == To.TransformChild ? m_Link.transform : null;
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = startType != From.Point ? m_Start.transform : null;
m_Link.endTransform = endType != To.Point ? m_End.transform : null;
m_Link.transform.rotation = transformRotation;
}
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_MovesTransformInTheMiddle(
From startType, To endType, Quaternion transformRotation,
Vector3 expectedPosition, Vector3 _)
{
ConfigureLinkForTest(startType, endType, transformRotation);
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.position, Is.EqualTo(expectedPosition).Using(k_DefaultThreshold),
"The Link object should be in the middle between the endpoints.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[Description("When the endpoints remain at the same world position it means that the local points must have been adjusted correctly.")]
public void AlignTransformToEndPoints_EndpointsWorldPositionsRemainUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialEndpointsMatrix = LocalToWorldUnscaled(m_Link);
var initialStartWorld = initialEndpointsMatrix.MultiplyPoint3x4(m_Link.startPoint);
var initialEndWorld = initialEndpointsMatrix.MultiplyPoint3x4(m_Link.endPoint);
var initialStartLocal = m_Link.startPoint;
var initialEndLocal = m_Link.endPoint;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assume.That(m_Link.startPoint, Is.Not.EqualTo(initialStartLocal).Using(k_DefaultThreshold),
"The local position of Start point should have been adjusted.");
Assert.That(m_Link.endPoint, Is.Not.EqualTo(initialEndLocal).Using(k_DefaultThreshold),
"The local position of End point should have been adjusted.");
var endpointsMatrix = LocalToWorldUnscaled(m_Link);
var startWorld = endpointsMatrix.MultiplyPoint3x4(m_Link.startPoint);
var endWorld = endpointsMatrix.MultiplyPoint3x4(m_Link.endPoint);
Assert.That(startWorld, Is.EqualTo(initialStartWorld).Using(k_DefaultThreshold),
"The world position of Start should remain unchanged.");
Assert.That(endWorld, Is.EqualTo(initialEndWorld).Using(k_DefaultThreshold),
"The world position of End should remain unchanged.");
}
static Matrix4x4 LocalToWorldUnscaled(NavMeshLink link)
{
return Matrix4x4.TRS(link.transform.position, link.transform.rotation, Vector3.one);
}
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_EndsTransformsRemainUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialStartPosition = m_Link.startTransform != null ? m_Link.startTransform.position : Vector3.negativeInfinity;
var initialEndPosition = m_Link.endTransform != null ? m_Link.endTransform.position : Vector3.negativeInfinity;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
if (m_Link.startTransform != null)
Assert.That(m_Link.startTransform.position, Is.EqualTo(initialStartPosition).Using(k_DefaultThreshold),
"The Link start transform should not have moved.");
if (m_Link.endTransform != null)
Assert.That(m_Link.endTransform.position, Is.EqualTo(initialEndPosition).Using(k_DefaultThreshold),
"The Link end transform should not have moved.");
}
[Test]
[Explicit("Functionality not implemented yet for child game objects")]
public void AlignTransformToEndPoints_ChildGameObjectsRetainWorldPositions()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = null;
m_Link.endTransform = null;
m_Link.transform.rotation = k_FlipToRight;
}
var child1 = CreateTestObject("Child 1").GetComponent<Transform>();
var child2 = CreateTestObject("Child 2").GetComponent<Transform>();
var grandchild = CreateTestObject("Grandchild").GetComponent<Transform>();
child2.rotation = Quaternion.Euler(0, 90, 0);
child1.parent = m_Link.transform;
child2.parent = m_Link.transform;
grandchild.parent = child2.transform;
child1.position = new Vector3(2, 1, 3);
child2.position = new Vector3(-0.5f, 0.6f, 0.7f);
grandchild.position = new Vector3(-3, 2, 1);
var child1Earlier = child1.position;
var child2Earlier = child2.position;
var grandchildEarlier = grandchild.position;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(child1.position, Is.EqualTo(child1Earlier).Using(k_DefaultThreshold),
"Child object 1 should not have moved.");
Assert.That(child2.position, Is.EqualTo(child2Earlier).Using(k_DefaultThreshold),
"Child object 2 should not have moved.");
Assert.That(grandchild.position, Is.EqualTo(grandchildEarlier).Using(k_DefaultThreshold),
"Grandchild object should not have moved.");
}
[Test]
public void AlignTransformToEndPoints_EndpointsWorldPositionsRemainUnchanged_InSiblingLinks()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = null;
m_Link.endTransform = null;
m_Link.transform.rotation = k_FlipToRight;
}
m_LinkSibling1 = m_Link.gameObject.AddComponent<NavMeshLink>();
m_LinkSibling2 = m_Link.gameObject.AddComponent<NavMeshLink>();
m_LinkSibling1.startPoint = m_Link.endPoint;
m_LinkSibling1.endPoint = m_Link.startPoint;
m_LinkSibling2.startPoint = m_Link.endPoint;
m_LinkSibling2.endPoint = m_Link.startPoint;
var initialStartLocal = m_Link.startPoint;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assume.That(m_Link.startPoint, Is.Not.EqualTo(initialStartLocal).Using(k_DefaultThreshold));
Assert.That(m_LinkSibling1.startPoint, Is.EqualTo(m_Link.endPoint).Using(k_DefaultThreshold),
"The sibling 1 Start point should have been adjusted.");
Assert.That(m_LinkSibling1.endPoint, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold),
"The sibling 1 End point should have been adjusted.");
Assert.That(m_LinkSibling2.startPoint, Is.EqualTo(m_Link.endPoint).Using(k_DefaultThreshold),
"The sibling 2 Start point should have been adjusted.");
Assert.That(m_LinkSibling2.endPoint, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold),
"The sibling 2 End point should have been adjusted.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_UpVectorRemainsUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialUp = m_Link.transform.up;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.up, Is.EqualTo(initialUp).Using(k_DefaultThreshold),
"The Link's up vector should remain unchanged.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_OrientsForwardVectorFromStartToEndInXZPlane(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 expectedForward)
{
ConfigureLinkForTest(startType, endType, transformRotation);
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.forward, Is.EqualTo(expectedForward).Using(k_DefaultThreshold),
"The Link's forward vector should point from the start towards the end of the link.");
}
static readonly Vector3 k_ForwardRightDiagonal = Quaternion.Euler(0, 45, 0) * Vector3.forward;
static readonly Quaternion k_RotatedX90 = Quaternion.Euler(90, 0, 0);
static readonly Quaternion k_RotatedY180 = Quaternion.Euler(0, 180, 0);
static readonly Quaternion k_RotatedZ90 = Quaternion.Euler(0, 0, 90);
static readonly Quaternion k_RotatedZ180 = Quaternion.Euler(0, 0, 180);
protected static readonly TestCaseData[] k_RotateToChangeLinkDirectionLocally =
{
new TestCaseData(Quaternion.identity, Vector3.right, Vector3.one)
.SetName("Link aligned to World axes"),
new TestCaseData(k_RotatedX90, Vector3.zero, new Vector3(1, 1, -1))
.SetName("Endpoints following the Up direction")
.SetDescription("The right vector cannot be properly defined in this case"),
new TestCaseData(k_RotatedY180, Vector3.forward, new Vector3(-1, 1, -1))
.SetName("Endpoints following Right direction"),
new TestCaseData(k_RotatedZ90, Vector3.right, new Vector3(1, -1, 1))
.SetName("Link tipped to the left"),
new TestCaseData(k_RotatedZ180, k_ForwardRightDiagonal, new Vector3(-1, -1, 1))
.SetName("Link rotated freely")
.SetDescription("The rotation has been chosen to produce a Right vector easy to identify and verify."),
};
[TestCaseSource(nameof(k_RotateToChangeLinkDirectionLocally))]
[Description("The link's direction changes because the end transform moves in local space when the game object rotates.")]
public void CalcLinkRight_ReturnsLocalRightIn2D(
Quaternion transformRotation,
Vector3 expectedLocalRight, Vector3 expectedEnd)
{
m_Link.transform.SetPositionAndRotation(Vector3.one, transformRotation);
m_Link.transform.localScale = 2f * Vector3.one;
m_End.transform.position = 2f * Vector3.one;
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = new Vector3(1, -1, -1);
m_Link.endTransform = m_End.transform;
m_Link.startTransform = null;
m_Link.endPoint = Vector3.negativeInfinity;
}
var linkRight = NavMeshLinkEditor.GetLocalDirectionRight(m_Link, out var localStart, out var localEnd);
Assume.That(localStart, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold), "Wrong local Start reported.");
Assume.That(localEnd, Is.EqualTo(expectedEnd).Using(k_DefaultThreshold), "Wrong local End reported.");
Assert.That(linkRight, Is.EqualTo(expectedLocalRight).Using(k_DefaultThreshold),
"Wrong Right vector relative to the direction from start to end.");
}
public enum LinkEndType
{
Point,
Transform,
TransformChild
}
}
}