using System;
using System.Collections.Generic;
using Unity.Collections;
namespace UnityEngine.XR.ARFoundation
{
///
/// Generator method for mesh geometry.
///
public static class ARPlaneMeshGenerator
{
static List s_Vertices = new();
static List s_TriangleIndices = new();
static List s_VertexNormals = new();
static Bounds s_Bounds;
static LinkedList s_UnusedVertices = new();
static LinkedListNode s_CurrentUnusedVertexNode;
static HashSet s_ReflexVertices = new();
///
/// Generates a Mesh from the given parameters. The is assumed to be a simple polygon. The algorithm uses the ear clipping algorithm with O(n) time complexity for convex polygons and O(n * m) for concave polygons where n is the total number vertices and m is the number of reflex vertices. The space complexity is O(n), scaling linearly with the number of boundary vertices.
///
/// The Mesh to write results to.
/// The vertices of the planes boundary, in plane-space.
/// True if the was generated, False otherwise.
public static bool TryGenerateMesh(Mesh mesh, NativeArray boundaryVertices)
{
if (mesh == null)
throw new ArgumentNullException(nameof(mesh));
if (boundaryVertices.Length < 3)
return false;
s_Vertices.Clear();
s_TriangleIndices.Clear();
s_VertexNormals.Clear();
s_Bounds = new();
s_UnusedVertices.Clear();
s_CurrentUnusedVertexNode = null;
s_ReflexVertices.Clear();
Initialize(boundaryVertices);
Triangulate();
mesh.Clear();
mesh.SetVertices(s_Vertices);
mesh.SetTriangles(s_TriangleIndices, 0, true);
mesh.SetNormals(s_VertexNormals);
var uvs = new List();
GenerateUVs(uvs, s_Vertices);
mesh.SetUVs(0, uvs);
return true;
}
static void GenerateUVs(ICollection uvs, IEnumerable vertices)
{
uvs.Clear();
foreach (var vertex in vertices)
{
var uv = new Vector2(vertex.x, vertex.z);
uvs.Add(uv);
}
}
static bool IsVertexReflex(Vector3 vertex, Vector3 nextVertex, Vector3 previousVertex)
{
var nextVector = nextVertex - vertex;
var previousVector = previousVertex - vertex;
var determinant = Determinant(nextVector, previousVector);
return determinant >= 0;
}
static void Initialize(NativeArray vertices)
{
var numVertices = vertices.Length;
for (int i = 0; i < numVertices; i += 1)
{
s_UnusedVertices.AddLast(i);
s_Bounds.Encapsulate(vertices[i]);
s_Vertices.Add(new(vertices[i].x, 0, vertices[i].y));
s_VertexNormals.Add(Vector3.up);
var nextIndex = (i + 1) % numVertices;
var previousIndex = (i < 1 ? numVertices : i) - 1;
var vertex = new Vector3(vertices[i].x, 0, vertices[i].y);
var nextVertex = new Vector3(vertices[nextIndex].x, 0, vertices[nextIndex].y);
var previousVertex = new Vector3(vertices[previousIndex].x, 0, vertices[previousIndex].y);
if (IsVertexReflex(vertex, nextVertex, previousVertex))
s_ReflexVertices.Add(i);
}
}
static void Triangulate()
{
s_CurrentUnusedVertexNode = s_UnusedVertices.Last;
while (s_UnusedVertices.Count > 2)
{
s_CurrentUnusedVertexNode = GetNextUnusedIndexNode(s_CurrentUnusedVertexNode);
var nextUnusedVertexNode = GetNextUnusedIndexNode(s_CurrentUnusedVertexNode);
var previousUnusedVertexNode = GetPreviousUnusedIndexNode(s_CurrentUnusedVertexNode);
if (s_ReflexVertices.Contains(s_CurrentUnusedVertexNode.Value))
continue;
var isAnyReflexVertexInsideTriangle = IsAnyReflexVertexInsideTriangle(
s_CurrentUnusedVertexNode.Value,
nextUnusedVertexNode.Value,
previousUnusedVertexNode.Value);
if (isAnyReflexVertexInsideTriangle)
continue;
s_TriangleIndices.Add(s_CurrentUnusedVertexNode.Value);
s_TriangleIndices.Add(nextUnusedVertexNode.Value);
s_TriangleIndices.Add(previousUnusedVertexNode.Value);
s_UnusedVertices.Remove(s_CurrentUnusedVertexNode);
UpdateReflexVertex(nextUnusedVertexNode);
UpdateReflexVertex(previousUnusedVertexNode);
}
}
static bool IsAnyReflexVertexInsideTriangle(
int currentIndex,
int nextIndex,
int previousIndex)
{
foreach (var index in s_ReflexVertices)
{
if (index == nextIndex || index == previousIndex)
continue;
var triangleSideA = s_Vertices[nextIndex] - s_Vertices[currentIndex];
var triangleSideB = s_Vertices[previousIndex] - s_Vertices[nextIndex];
var triangleSideC = s_Vertices[currentIndex] - s_Vertices[previousIndex];
var reflexPointVectorA = s_Vertices[index] - s_Vertices[currentIndex];
var reflexPointVectorB = s_Vertices[index] - s_Vertices[nextIndex];
var reflexPointVectorC = s_Vertices[index] - s_Vertices[previousIndex];
var triangleSideADeterminant = Determinant(triangleSideA, reflexPointVectorA);
var triangleSideBDeterminant = Determinant(triangleSideB, reflexPointVectorB);
var triangleSideCDeterminant = Determinant(triangleSideC, reflexPointVectorC);
var isADeterminantNegative = triangleSideADeterminant < 0;
var isBDeterminantNegative = triangleSideBDeterminant < 0;
var isCDeterminantNegative = triangleSideCDeterminant < 0;
if (isADeterminantNegative == isBDeterminantNegative &&
isADeterminantNegative == isCDeterminantNegative)
return true;
}
return false;
}
static void UpdateReflexVertex(LinkedListNode node)
{
if (!s_ReflexVertices.Contains(node.Value))
return;
var nextIndex = GetNextUnusedIndexNode(node).Value;
var previousIndex = GetPreviousUnusedIndexNode(node).Value;
var vertex = s_Vertices[node.Value];
var nextVertex = s_Vertices[nextIndex];
var previousVertex = s_Vertices[previousIndex];
if (IsVertexReflex(vertex, nextVertex, previousVertex))
return;
s_ReflexVertices.Remove(node.Value);
}
static LinkedListNode GetNextUnusedIndexNode(LinkedListNode node)
{
return node.Next ?? s_UnusedVertices.First;
}
static LinkedListNode GetPreviousUnusedIndexNode(LinkedListNode node)
{
return node.Previous ?? s_UnusedVertices.Last;
}
static float Determinant(Vector3 vectorA, Vector3 vectorB)
{
return vectorA.x * vectorB.z - vectorA.z * vectorB.x;
}
}
}