/*
* 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;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
using UnityEngine;
namespace Meta.XR.MRUtilityKit.Tests
{
public static class TestUtilities
{
// Use the triangulated area as a proxy to ensure the triangulation worked as expected
internal static float CalculateTriangulatedArea(Vector2[] vertices, int[] indices)
{
float area = 0f;
for (int i = 0; i < indices.Length; i += 3)
{
var p1 = vertices[indices[i]];
var p2 = vertices[indices[i + 1]];
var p3 = vertices[indices[i + 2]];
var triangleArea = CalculateTriangleArea(p1, p2, p3);
Assert.GreaterOrEqual(triangleArea, 0f);
area += triangleArea;
}
return area;
}
private static float CalculateTriangleArea(Vector2 p1, Vector2 p2, Vector2 p3)
{
return (p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2.0f;
}
///
/// Calculates the total area of a triangulated mesh using the vertices and indices.
///
/// An array of representing the vertices of the mesh.
/// An array of integers representing the indices of the mesh triangles.
/// The total area of the mesh.
internal static float CalculateTriangulatedArea(Vector3[] vertices, int[] indices)
{
float area = 0f;
for (int i = 0; i < indices.Length; i += 3)
{
var p1 = vertices[indices[i]];
var p2 = vertices[indices[i + 1]];
var p3 = vertices[indices[i + 2]];
var triangleArea = CalculateTriangleArea(p1, p2, p3);
Assert.GreaterOrEqual(triangleArea, 0f);
area += triangleArea;
}
return area;
}
///
/// Calculates the area of a triangle given three vertices in 3D space.
///
/// The first vertex of the triangle.
/// The second vertex of the triangle.
/// The third vertex of the triangle.
/// The area of the triangle.
private static float CalculateTriangleArea(Vector3 p1, Vector3 p2, Vector3 p3)
{
// Calculate the vectors for two sides of the triangle
Vector3 u = p2 - p1;
Vector3 v = p3 - p1;
// Calculate the cross product of the two vectors
Vector3 crossProduct = Vector3.Cross(u, v);
// The area of the triangle is half the magnitude of the cross product
float area = crossProduct.magnitude * 0.5f;
return area;
}
}
#region COMPARERS
internal class Vector3ArrayComparer : IEqualityComparer
{
public bool Equals(Vector3[] x, Vector3[] y)
{
if (x == null || y == null)
{
return x == y;
}
if (x.Length != y.Length)
{
return false;
}
var setX = new HashSet(x);
return setX.SetEquals(y);
}
public int GetHashCode(Vector3[] obj)
{
return 0;
}
}
internal class IntArrayComparer : IEqualityComparer
{
public bool Equals(int[] x, int[] y)
{
if (x == null || y == null)
{
return x == y;
}
if (x.Length != y.Length)
{
return false;
}
var setX = new HashSet(x);
return setX.SetEquals(y);
}
public int GetHashCode(int[] obj)
{
return 0;
}
}
#endregion COMPARERS
#region JSON_CONVERTERS
internal class IntArrayConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, int[] value, JsonSerializer serializer)
{
var array = value;
writer.WriteStartArray();
foreach (var item in array)
{
writer.WriteValue(item);
}
writer.WriteEndArray();
}
public override int[] ReadJson(JsonReader reader, Type objectType, int[] existingValue, bool hasExistingValue,
JsonSerializer serial)
{
if (reader.TokenType == JsonToken.StartArray)
{
var list = new List();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
{
return list.ToArray();
}
list.Add((int)(long)reader.Value);
}
}
throw new JsonReaderException("Expected start of array.");
}
}
public class Vector3Converter : JsonConverter
{
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteStartArray();
// Disable indentation to make it more compact
var prevFormatting = writer.Formatting;
writer.Formatting = Formatting.None;
writer.WriteValue(value.x);
writer.WriteValue(value.y);
writer.WriteValue(value.z);
writer.WriteEndArray();
writer.Formatting = prevFormatting;
}
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
Vector3 result = new()
{
x = (float)reader.ReadAsDouble(),
y = (float)reader.ReadAsDouble(),
z = (float)reader.ReadAsDouble()
};
reader.Read();
if (reader.TokenType != JsonToken.EndArray)
{
throw new Exception("Expected end of array");
}
return result;
}
}
internal class Vector3ArrayConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, Vector3[] value, JsonSerializer serializer)
{
var array = value;
writer.WriteStartArray();
foreach (var item in array)
{
var prevFormatting = writer.Formatting;
writer.WriteStartArray();
writer.Formatting = Formatting.None;
writer.WriteValue(item.x);
writer.WriteValue(item.y);
writer.WriteValue(item.z);
writer.WriteEndArray();
writer.Formatting = prevFormatting;
}
writer.WriteEndArray();
}
public override Vector3[] ReadJson(JsonReader reader, Type objectType, Vector3[] existingValue,
bool hasExistingValue, JsonSerializer serial)
{
if (reader.TokenType == JsonToken.StartArray)
{
var list = new List();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
{
return list.ToArray();
}
Vector3 result = new()
{
x = (float)reader.ReadAsDouble(),
y = (float)reader.ReadAsDouble(),
z = (float)reader.ReadAsDouble()
};
reader.Read();
if (reader.TokenType != JsonToken.EndArray)
{
throw new Exception("Expected end of array");
}
list.Add(result);
}
}
throw new JsonReaderException("Expected start of array.");
}
}
internal class SceneNavigationConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, SceneNavigation value, JsonSerializer serializer)
{
var sceneNavigation = value;
writer.WriteStartObject();
writer.WritePropertyName("BuildOnSceneLoad");
serializer.Serialize(writer, sceneNavigation.BuildOnSceneLoaded);
writer.WritePropertyName("CollectGeometry");
writer.WriteValue(sceneNavigation.CollectGeometry);
writer.WritePropertyName("CollectObjects");
serializer.Serialize(writer, sceneNavigation.CollectObjects);
writer.WritePropertyName("AgentRadius");
writer.WriteValue(sceneNavigation.AgentRadius);
writer.WritePropertyName("AgentHeight");
serializer.Serialize(writer, sceneNavigation.AgentHeight);
writer.WritePropertyName("AgentClimb");
writer.WriteValue(sceneNavigation.AgentClimb);
writer.WritePropertyName("AgentMaxSlope");
serializer.Serialize(writer, sceneNavigation.AgentMaxSlope);
writer.WritePropertyName("NavigableSurfaces");
writer.WriteValue(sceneNavigation.NavigableSurfaces);
writer.WritePropertyName("SceneObstacles");
serializer.Serialize(writer, sceneNavigation.SceneObstacles);
writer.WritePropertyName("Layers");
writer.WriteValue(sceneNavigation.Layers);
writer.WritePropertyName("AgentIndex");
writer.WriteValue(sceneNavigation.AgentIndex);
writer.WritePropertyName("UseSceneData");
writer.WriteValue(sceneNavigation.UseSceneData);
writer.WritePropertyName("CustomAgent");
writer.WriteValue(sceneNavigation.CustomAgent);
writer.WritePropertyName("OverrideVoxelSize");
writer.WriteValue(sceneNavigation.OverrideVoxelSize);
writer.WritePropertyName("VoxelSize");
writer.WriteValue(sceneNavigation.VoxelSize);
writer.WritePropertyName("OverrideTileSize");
writer.WriteValue(sceneNavigation.OverrideTileSize);
writer.WritePropertyName("TileSize");
writer.WriteValue(sceneNavigation.TileSize);
writer.WriteEndObject();
}
public override SceneNavigation ReadJson(JsonReader reader, Type objectType,
SceneNavigation existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
var sceneNavigation = new SceneNavigation();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
if (reader.Value != null)
{
var propertyName = reader.Value.ToString();
switch (propertyName)
{
case "BuildOnSceneLoaded":
reader.Read();
var BuildOnSceneLoaded = reader.Value.ToString();
sceneNavigation.BuildOnSceneLoaded = Enum.Parse(BuildOnSceneLoaded);
break;
case "CollectGeometry":
reader.Read();
var CollectGeometry = reader.Value.ToString();
sceneNavigation.BuildOnSceneLoaded = Enum.Parse(CollectGeometry);
break;
case "CollectObjects":
reader.Read();
var CollectObjects = reader.Value.ToString();
sceneNavigation.BuildOnSceneLoaded = Enum.Parse(CollectObjects);
break;
case "AgentRadius":
reader.Read();
sceneNavigation.AgentRadius = serializer.Deserialize(reader);
break;
case "AgentHeight":
reader.Read();
sceneNavigation.AgentHeight = serializer.Deserialize(reader);
break;
case "AgentClimb":
reader.Read();
sceneNavigation.AgentClimb = serializer.Deserialize(reader);
break;
case "AgentMaxSlope":
reader.Read();
sceneNavigation.AgentMaxSlope = serializer.Deserialize(reader);
break;
case "NavigableSurfaces":
reader.Read();
sceneNavigation.NavigableSurfaces =
serializer.Deserialize(reader);
break;
case "SceneObstacles":
reader.Read();
sceneNavigation.SceneObstacles = serializer.Deserialize(reader);
break;
case "Layers":
reader.Read();
sceneNavigation.Layers.value = serializer.Deserialize(reader);
break;
case "AgentIndex":
reader.Read();
sceneNavigation.AgentIndex = serializer.Deserialize(reader);
break;
case "UseSceneData":
reader.Read();
sceneNavigation.UseSceneData = serializer.Deserialize(reader);
break;
case "CustomAgent":
reader.Read();
sceneNavigation.CustomAgent = serializer.Deserialize(reader);
break;
case "OverrideVoxelSize":
reader.Read();
sceneNavigation.OverrideVoxelSize = serializer.Deserialize(reader);
break;
case "VoxelSize":
reader.Read();
sceneNavigation.VoxelSize = serializer.Deserialize(reader);
break;
case "OverrideTileSize":
reader.Read();
sceneNavigation.OverrideTileSize = serializer.Deserialize(reader);
break;
case "TileSize":
reader.Read();
sceneNavigation.TileSize = serializer.Deserialize(reader);
break;
}
}
}
else if (reader.TokenType == JsonToken.EndObject)
{
break;
}
}
return sceneNavigation;
}
}
internal class GridSliceResizerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, GridSliceResizer value, JsonSerializer serializer)
{
var gridSliceResizer = value;
writer.WriteStartObject();
writer.WritePropertyName("PivotOffset");
serializer.Serialize(writer, gridSliceResizer.PivotOffset);
writer.WritePropertyName("ScalingX");
writer.WriteValue(gridSliceResizer.ScalingX.ToString());
writer.WritePropertyName("BorderXNegative");
serializer.Serialize(writer, gridSliceResizer.BorderXNegative);
writer.WritePropertyName("BorderXPositive");
serializer.Serialize(writer, gridSliceResizer.BorderXPositive);
writer.WritePropertyName("ScalingY");
writer.WriteValue(gridSliceResizer.ScalingY.ToString());
writer.WritePropertyName("BorderYNegative");
serializer.Serialize(writer, gridSliceResizer.BorderYNegative);
writer.WritePropertyName("BorderYPositive");
serializer.Serialize(writer, gridSliceResizer.BorderYPositive);
writer.WritePropertyName("ScalingZ");
writer.WriteValue(gridSliceResizer.ScalingZ.ToString());
writer.WritePropertyName("BorderZNegative");
serializer.Serialize(writer, gridSliceResizer.BorderZNegative);
writer.WritePropertyName("BorderZPositive");
serializer.Serialize(writer, gridSliceResizer.BorderZPositive);
writer.WritePropertyName("StretchCenter");
writer.WriteValue((int)gridSliceResizer.StretchCenter);
writer.WriteEndObject();
}
public override GridSliceResizer ReadJson(JsonReader reader, Type objectType, GridSliceResizer existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
var gridSliceResizer = new GridSliceResizer();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
var propertyName = reader.Value.ToString();
switch (propertyName)
{
case "PivotOffset":
reader.Read();
gridSliceResizer.PivotOffset = serializer.Deserialize(reader);
break;
case "ScalingX":
reader.Read();
var scalingXString = reader.Value.ToString();
gridSliceResizer.ScalingX = Enum.Parse(scalingXString);
break;
case "BorderXNegative":
reader.Read();
gridSliceResizer.BorderXNegative = serializer.Deserialize(reader);
break;
case "BorderXPositive":
reader.Read();
gridSliceResizer.BorderXPositive = serializer.Deserialize(reader);
break;
case "ScalingY":
reader.Read();
var scalingYString = reader.Value.ToString();
gridSliceResizer.ScalingY = Enum.Parse(scalingYString);
break;
case "BorderYNegative":
reader.Read();
gridSliceResizer.BorderYNegative = serializer.Deserialize(reader);
break;
case "BorderYPositive":
reader.Read();
gridSliceResizer.BorderYPositive = serializer.Deserialize(reader);
break;
case "ScalingZ":
reader.Read();
var scalingZString = reader.Value.ToString();
gridSliceResizer.ScalingZ = Enum.Parse(scalingZString);
break;
case "BorderZNegative":
reader.Read();
gridSliceResizer.BorderZNegative = serializer.Deserialize(reader);
break;
case "BorderZPositive":
reader.Read();
gridSliceResizer.BorderZPositive = serializer.Deserialize(reader);
break;
case "StretchCenter":
reader.Read();
var stretchCenter = reader.Value.ToString();
gridSliceResizer.StretchCenter =
Enum.Parse(stretchCenter);
break;
}
}
else if (reader.TokenType == JsonToken.EndObject)
{
break;
}
}
return gridSliceResizer;
}
}
#endregion JSON_CONVERTERS
}