/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using UnityEngine;
using NUnit.Framework;
using UnityEngine.AI;
using UnityEngine.TestTools;
namespace Meta.XR.MRUtilityKit.Tests
{
public class SceneNavigationTests : MRUKTestBase
{
///
/// A struct that can serialized to json to quickly load/save preconfigured SceneNavigation and it's results.
///
internal struct SceneNavigationTestConfig
{
[JsonProperty("SceneNavigation")] public SceneNavigation sceneNavigation;
}
private readonly JsonSerializerSettings _serializerSettings = new()
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.None,
Converters = new List
{
new IntArrayConverter(),
new Vector3ArrayConverter(),
new Vector3Converter(),
new SceneNavigationConverter()
}
};
private SceneNavigation _sceneNav;
private GameObject _sceneNavGameObject;
private EffectMesh _effectMesh;
private const float expectedCustomAgent = 24.29f;
private const float expectedDefaultAgent = 17.09f;
private const float expectedWithin = 0.01f;
private const float expectedSceneDataCustomAgentGlobalMesh = 5.74f;
private const float expectedBuiltInCustomAgentGlobalMesh = 5.55f;
private const string _sceneDataCustomAgent =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":921,""SceneObstacles"":73624,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":false,""CustomAgent"":true,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
private const string _sceneDataDefaultAgent =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":921,""SceneObstacles"":73624,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":false,""CustomAgent"":false,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
private const string _builtInDefaultAgent =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":921,""SceneObstacles"":73624,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":false,""CustomAgent"":false,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
private const string _builtInCustomAgent =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":921,""SceneObstacles"":73624,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":false,""CustomAgent"":true,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
private const string _sceneDataCustomAgentGlobalMesh =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":16384,""SceneObstacles"":0,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":true,""CustomAgent"":true,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
private const string _builtInCustomAgentGlobalMesh =
@"{""SceneNavigation"":{""BuildOnSceneLoad"":0,""CollectGeometry"":1,""CollectObjects"":2,""AgentRadius"":0.05,""AgentHeight"":2.0,""AgentClimb"":0.2,""AgentMaxSlope"":14.0,""NavigableSurfaces"":921,""SceneObstacles"":73624,""Layers"":-1,""AgentIndex"":0,""connectRoomsInNavMesh"":true,""UseSceneData"":false,""CustomAgent"":true,""OverrideVoxelSize"":false,""VoxelSize"":0.05,""OverrideTileSize"":false,""TileSize"":5}}";
///
/// Utility method to quickly export a SceneNavigationTestConfig from a scene.
/// Set up the SceneNavigationTests scene and serialize the scenarion that needs to be tested.
///
/// The name given to the exported json file.
private void SerializeSceneNavigationTestConfig(string jsonFileName)
{
var sceneNavigationTestConfig = new SceneNavigationTestConfig()
{
sceneNavigation = _sceneNav
};
var json = JsonConvert.SerializeObject(sceneNavigationTestConfig, _serializerSettings);
using var writer =
new StreamWriter(Path.Combine(Application.persistentDataPath, jsonFileName + ".json"));
{
writer.WriteLine(json);
}
}
///
/// Sets up the SceneNavigation instance so that it matches an exported conviguration
///
/// The serialized SceneNavigationTestConfig.
/// The resized mesh to verify.
private void SetUpSceneNavigationTest(string sceneNavigationTestConfigJson)
{
var sceneNavMeshConfig =
JsonConvert.DeserializeObject(sceneNavigationTestConfigJson,
_serializerSettings);
_sceneNav.BuildOnSceneLoaded = sceneNavMeshConfig.sceneNavigation.BuildOnSceneLoaded;
_sceneNav.CollectGeometry = sceneNavMeshConfig.sceneNavigation.CollectGeometry;
_sceneNav.CollectObjects = sceneNavMeshConfig.sceneNavigation.CollectObjects;
_sceneNav.AgentRadius = sceneNavMeshConfig.sceneNavigation.AgentRadius;
_sceneNav.AgentHeight = sceneNavMeshConfig.sceneNavigation.AgentHeight;
_sceneNav.AgentClimb = sceneNavMeshConfig.sceneNavigation.AgentClimb;
_sceneNav.AgentMaxSlope = sceneNavMeshConfig.sceneNavigation.AgentMaxSlope;
_sceneNav.NavigableSurfaces = sceneNavMeshConfig.sceneNavigation.NavigableSurfaces;
_sceneNav.SceneObstacles = sceneNavMeshConfig.sceneNavigation.SceneObstacles;
_sceneNav.AgentIndex = sceneNavMeshConfig.sceneNavigation.AgentIndex;
_sceneNav.UseSceneData = sceneNavMeshConfig.sceneNavigation.UseSceneData;
_sceneNav.CustomAgent = sceneNavMeshConfig.sceneNavigation.CustomAgent;
_sceneNav.OverrideVoxelSize = sceneNavMeshConfig.sceneNavigation.OverrideVoxelSize;
_sceneNav.VoxelSize = sceneNavMeshConfig.sceneNavigation.VoxelSize;
_sceneNav.OverrideTileSize = sceneNavMeshConfig.sceneNavigation.OverrideTileSize;
_sceneNav.TileSize = sceneNavMeshConfig.sceneNavigation.TileSize;
}
[UnitySetUp]
public IEnumerator SetUp()
{
yield return LoadScene("Packages/com.meta.xr.mrutilitykit/Tests/SceneNavigationTests.unity", false);
_sceneNav = Object.FindFirstObjectByType();
_effectMesh = Object.FindFirstObjectByType();
}
[UnityTearDown]
public IEnumerator TearDown()
{
yield return UnloadScene();
}
[UnityTest]
public IEnumerator NavMeshBuild_BuiltIn_CustomAgent()
{
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromPrefabAndWait(MRUK.Instance.SceneSettings.RoomPrefabs[0]);
SetUpSceneNavigationTest(_builtInCustomAgent);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
yield return null;
Assert.IsTrue(triangulation.vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedCustomAgent, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
[UnityTest]
public IEnumerator NavMeshBuild_BuiltIn_DefaultAgent()
{
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromPrefabAndWait(MRUK.Instance.SceneSettings.RoomPrefabs[0]);
SetUpSceneNavigationTest(_builtInDefaultAgent);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
yield return null;
Assert.IsTrue(NavMesh.CalculateTriangulation().vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedDefaultAgent, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
[UnityTest]
public IEnumerator NavMeshBuild_UseSceneData_CustomAgent()
{
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromPrefabAndWait(MRUK.Instance.SceneSettings.RoomPrefabs[0]);
SetUpSceneNavigationTest(_sceneDataCustomAgent);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
Assert.IsTrue(triangulation.vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedCustomAgent, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
[UnityTest]
public IEnumerator NavMeshBuild_UseSceneData_DefaultAgent()
{
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromPrefabAndWait(MRUK.Instance.SceneSettings.RoomPrefabs[0]);
SetUpSceneNavigationTest(_sceneDataDefaultAgent);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
Assert.IsTrue(NavMesh.CalculateTriangulation().vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedDefaultAgent, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
[UnityTest]
public IEnumerator NavMeshBuild_UseSceneData_CustomAgent_GlobalMesh()
{
_effectMesh.Labels = MRUKAnchor.SceneLabels.GLOBAL_MESH;
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromJsonStringAndWait(MRUK.Instance.SceneSettings.SceneJsons[0].ToString());
SetUpSceneNavigationTest(_sceneDataCustomAgentGlobalMesh);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
Assert.IsTrue(triangulation.vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedSceneDataCustomAgentGlobalMesh, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
[UnityTest]
public IEnumerator NavMeshBuild_BuiltIn_CustomAgent_GlobalMesh()
{
_effectMesh.Labels = MRUKAnchor.SceneLabels.GLOBAL_MESH;
_effectMesh.DestroyMesh();
_sceneNav.RemoveNavMeshData();
yield return LoadSceneFromJsonStringAndWait(MRUK.Instance.SceneSettings.SceneJsons[0].ToString());
SetUpSceneNavigationTest(_builtInCustomAgentGlobalMesh);
_sceneNav.BuildSceneNavMesh();
yield return null;
var triangulation = NavMesh.CalculateTriangulation();
Assert.IsTrue(NavMesh.CalculateTriangulation().vertices.Length > 0, "NavMesh should have vertices.");
var triangulatedArea =
TestUtilities.CalculateTriangulatedArea(triangulation.vertices, triangulation.indices);
Assert.That(expectedBuiltInCustomAgentGlobalMesh, Is.EqualTo(triangulatedArea).Within(expectedWithin),
"Expected triangulated area should be be equal to the actual NavMesh triangulated area within a tolerance of 0.001");
}
}
}