// Copyright (C) 2023 Nicholas Maltbie // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using nickmaltbie.OpenKCC.Environment.MovingGround; using nickmaltbie.TestUtilsUnity; using nickmaltbie.TestUtilsUnity.Tests.TestCommon; using NUnit.Framework; using UnityEngine; namespace nickmaltbie.OpenKCC.Tests.EditMode.Environment { /// /// Class to validate all types of IMovingGround implementations. /// [TestFixture] public class MovingGroundTests : TestBase { /// /// Mock untiy service for managing unity inputs. /// private MockUnityService mockUnityService; /// /// Enumerate list of supported moving ground types for testing. /// /// Enumerable list of moving ground types. public static IEnumerable MovingGroundTypes() { return new[] { typeof(MovementTracking), typeof(RigidbodyMovingGround), typeof(RelativeVelocityMovementTracking), typeof(RelativeVelocityRigidbodyTracking), }; } /// /// Validate the expected movement for moving ground displacement. /// /// Distance to move ground. /// Amount to rotate the given object. /// type fo moving ground being tested. [Test] public void Validate_MovingGround_DisplacementAndRotation( [ValueSource(nameof(TestDirections))] Vector3 move, [ValueSource(nameof(TestDirections))] Vector3 rotation, [ValueSource(nameof(MovingGroundTypes))] Type type) { Validate_MovingGroundDisplacement_Helper( move, rotation * 30, type, Vector3.zero); } /// /// Validate the NoMovementTracking object. /// [Test] public void Validate_NoMovementTracking( [ValueSource(nameof(TestDirections))] Vector3 move, [ValueSource(nameof(TestDirections))] Vector3 rotation) { Validate_MovingGroundDisplacement_Helper( move, rotation * 30, typeof(NoMovementTracking), Vector3.zero, true, Vector3.zero ); NoMovementTracking mt = CreateMovingGround(); Assert.IsFalse(mt.ShouldAttach()); Assert.AreEqual(mt.AvoidTransferMomentum(), true); Assert.AreEqual(mt.GetTransferMomentumWeight(Vector3.zero, Vector3.zero), 0.0f); Assert.AreEqual(mt.GetMovementWeight(Vector3.zero, Vector3.zero), 0.0f); } /// /// Validate the expected movement for moving ground displacement. /// /// Relative position to parent object. /// type fo moving ground being tested. [Test] public void Validate_MovingGround_RelativePosition( [ValueSource(nameof(TestDirections))] Vector3 relativePosition, [ValueSource(nameof(MovingGroundTypes))] Type type) { foreach (Vector3 translation in TestDirections()) { foreach (Vector3 rotation in TestDirections()) { Validate_MovingGroundDisplacement_Helper( translation, rotation * 30, type, relativePosition, verifyVelocity: false); } } } /// /// Validate movement tracking parameters /// /// Value of transfer momentum to be used. /// Shoudl avoid transfer momenum be enabled. [Test] public void Validate_MovementTrackingParams( [NUnit.Framework.Range(0.0f, 2.5f, 0.5f)] float transferMomentum, [Values] bool avoidTransferMomentum) { MovementTracking movementTracking = CreateMovingGround(); movementTracking.avoidTransferMomentum = avoidTransferMomentum; movementTracking.transferMomentumWeight = transferMomentum; Assert.IsTrue(movementTracking.ShouldAttach()); Assert.AreEqual(movementTracking.AvoidTransferMomentum(), avoidTransferMomentum); Assert.AreEqual(movementTracking.GetTransferMomentumWeight(Vector3.zero, Vector3.zero), transferMomentum); Assert.AreEqual(movementTracking.GetMovementWeight(Vector3.zero, Vector3.zero), 1.0f); } /// /// Validate the relative velocity for movement tracking object. /// [Test] public void Validate_RelativeVelocityMovementTracking() { RelativeVelocityMovementTracking rvtrack = CreateMovingGround(); MoveGround(rvtrack, Vector3.forward, Vector3.zero); rvtrack.minimumVelocityThreshold = 1.0f; rvtrack.maximumVelocityThreshold = 4.0f; Debug.Log($"velAtPoint: {rvtrack.GetVelocityAtPoint(Vector3.zero)}"); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 1), 1, 0.001f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 2), 1, 0.001f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 3), 0.5f, 0.25f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 5), 0, 0.001f); } /// /// Validate the relative velocity for movement tracking object. /// [Test] public void Validate_RelativeVelocityRigidbodyTracking() { RelativeVelocityRigidbodyTracking rvtrack = CreateMovingGround(); MoveGround(rvtrack, Vector3.forward, Vector3.zero); rvtrack.minimumVelocityThreshold = 1.0f; rvtrack.maximumVelocityThreshold = 4.0f; Debug.Log($"velAtPoint: {rvtrack.GetVelocityAtPoint(Vector3.zero)}"); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 1), 1, 0.001f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 2), 1, 0.001f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 3), 0.5f, 0.25f); TestUtils.AssertInBounds(rvtrack.GetMovementWeight(Vector3.zero, Vector3.forward * 5), 0, 0.001f); } /// /// Create a moving ground object of a given type and attach it to a new game object. /// /// Type of moving ground to create, must /// extend MonoBehaviour and impelment IMovingGround. /// Created moving ground behaviour. protected E CreateMovingGround() where E : MonoBehaviour, IMovingGround { return CreateMovingGround(typeof(E)) as E; } /// /// Create a moving ground object of a given type and attach it to a new game object. /// /// Type of moving ground to create, must /// extend MonoBehaviour and impelment IMovingGround. /// Created moving ground behaviour. protected IMovingGround CreateMovingGround(Type componentType) { GameObject go; RegisterGameObject(go = GameObject.CreatePrimitive(PrimitiveType.Sphere)); go.AddComponent(); var movingGround = go.AddComponent(componentType) as IMovingGround; (movingGround as RigidbodyMovingGround)?.Start(); var movementTracking = movingGround as MovementTracking; if (movementTracking != null) { mockUnityService = new MockUnityService(); movementTracking.unityService = mockUnityService; mockUnityService.deltaTime = 1; mockUnityService.fixedDeltaTime = 1; } return movingGround; } /// /// Helper function for validating moving ground displacement. /// /// Displacement of moving ground. /// Rotation of moving ground. /// Type of moving ground to create. /// Relative position of object to moving ground. /// Delta time for processing moving ground. protected void Validate_MovingGroundDisplacement_Helper( Vector3 move, Vector3 rotation, Type type, Vector3 relativePosition, bool verifyVelocity = true, Vector3? expectedDisp = null) { IMovingGround movingGround = CreateMovingGround(type); var behaviour = movingGround as MonoBehaviour; float deltaTime = 1.0f; mockUnityService.deltaTime = deltaTime; mockUnityService.fixedDeltaTime = deltaTime; MoveGround(behaviour, move, rotation, deltaTime: deltaTime); var changeAttitude = Quaternion.Euler(rotation); Vector3 deltaRotation = (changeAttitude * relativePosition) - relativePosition; Vector3 expectedDisplacement = expectedDisp ?? move + deltaRotation; if (verifyVelocity) { TestUtils.AssertInBounds( movingGround.GetVelocityAtPoint(behaviour.transform.position + relativePosition), expectedDisplacement, 0.001f); } } /// /// Move a moving ground a given distance. /// /// Mono behaviour to update. /// Distance to move. /// Distance to rotate. /// Expected time to pass, by default is 1 second. protected static void MoveGround(MonoBehaviour monoBehaviour, Vector3 move, Vector3 rotate, float deltaTime = 1.0f) { var movementTracking = monoBehaviour as MovementTracking; movementTracking?.FixedUpdate(); movementTracking?.FixedUpdate(); monoBehaviour.transform.position += move; monoBehaviour.transform.eulerAngles += rotate; Rigidbody rb = monoBehaviour.GetComponent(); if (rb != null) { rb.position = monoBehaviour.transform.position; rb.rotation = monoBehaviour.transform.rotation; rb.velocity = move / deltaTime; rb.angularVelocity = rotate * Mathf.Deg2Rad / deltaTime; } movementTracking?.FixedUpdate(); } } }