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; } } }