// 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();
}
}
}