using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public enum SlicedMeshSubmesh { Default = 0, CutFace = 1 } /// /// Data structure used for storing mesh data during the fragmenting process /// public class FragmentData { /// /// Vertex buffer for the non-cut mesh faces /// public List Vertices; /// /// Vertex buffer for the cut mesh faces /// public List CutVertices; /// /// Index buffer for each submesh /// public List[] Triangles; /// /// List of edges constraints for the cut-face triangulation /// public List Constraints; /// /// Map between vertex indices in the source mesh and new indices for the sliced mesh /// public int[] IndexMap; /// /// The bounds of the vertex data (must manually call UpdateBounds() to update) /// public Bounds Bounds; /// /// Gets the total number of triangles across all sub meshes /// /// public int triangleCount { get { int count = 0; for (int i = 0; i < this.Triangles.Length; i++) { count += this.Triangles[i].Count; } return count; } } /// /// Gets the total number of vertices in the mesh /// /// public int vertexCount { get { return this.Vertices.Count + this.CutVertices.Count; } } /// /// Initializes a new sliced mesh /// /// The name of the mesh /// Vertex count used to initialize lists. Initializing lists to approximate size reduces resizes and GC. /// Triangle count used to initialize lists. Initializing lists to approximate size reduces resizes and GC. public FragmentData(int vertexCount, int triangleCount) { this.Vertices = new List(vertexCount); this.CutVertices = new List(vertexCount / 10); // Store triangles for each submesh separately this.Triangles = new List[] { new List(triangleCount), new List(triangleCount / 10) }; this.Constraints = new List(); this.IndexMap = new int[vertexCount]; } /// /// Creates a new sliced mesh dataset from source mesh data /// /// The source mesh data. public FragmentData(Mesh mesh) { var positions = mesh.vertices; var normals = mesh.normals; var uv = mesh.uv; this.Vertices = new List(mesh.vertexCount); this.CutVertices = new List(mesh.vertexCount / 10); this.Constraints = new List(); this.IndexMap = new int[positions.Length]; // Add mesh vertices for (int i = 0; i < positions.Length; i++) { this.Vertices.Add(new MeshVertex(positions[i], normals[i], uv[i])); } // Only meshes with one submesh are currently supported this.Triangles = new List[2]; this.Triangles[0] = new List(mesh.GetTriangles(0)); if (mesh.subMeshCount >= 2) { this.Triangles[1] = new List(mesh.GetTriangles(1)); } else { this.Triangles[1] = new List(mesh.triangles.Length / 10); } this.CalculateBounds(); } /// /// Adds a new cut face vertex /// /// The vertex position /// The vertex normal /// The vertex UV coordinates /// Returns the index of the vertex in the cutVertices array public void AddCutFaceVertex(Vector3 position, Vector3 normal, Vector2 uv) { var vertex = new MeshVertex(position, normal, uv); // Add the vertex to both the normal mesh vertex data and the cut face vertex data // The vertex on the cut face will have different normal/uv coordinates which are // populated with the correct values later in the triangulation process. this.Vertices.Add(vertex); this.CutVertices.Add(vertex); } /// /// Adds a new vertex to this mesh that is mapped to the source mesh /// /// Vertex data /// Index of the vertex in the source mesh public void AddMappedVertex(MeshVertex vertex, int sourceIndex) { this.Vertices.Add(vertex); this.IndexMap[sourceIndex] = this.Vertices.Count - 1; } /// /// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indexes of the /// vertices relative to this mesh's list of vertices; no mapping is performed. /// /// Index of the first vertex /// Index of the second vertex /// Index of the third vertex /// The sub-mesh to add the triangle to public void AddTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh) { this.Triangles[(int)subMesh].Add(v1); this.Triangles[(int)subMesh].Add(v2); this.Triangles[(int)subMesh].Add(v3); } /// /// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indices of the /// vertices in the original mesh. These vertices are mapped to the indices in the sliced mesh. /// /// Index of the first vertex /// Index of the second vertex /// Index of the third vertex /// The sub-mesh to add the triangle to public void AddMappedTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh) { this.Triangles[(int)subMesh].Add(IndexMap[v1]); this.Triangles[(int)subMesh].Add(IndexMap[v2]); this.Triangles[(int)subMesh].Add(IndexMap[v3]); } /// /// Finds coincident vertices on the cut face and welds them together. /// public void WeldCutFaceVertices() { // Temporary array containing the unique (welded) vertices // Initialize capacity to current number of cut vertices to prevent // unnecessary reallocations List weldedVerts = new List(CutVertices.Count); // We also keep track of the index mapping between the skipped vertices // and the index of the welded vertex so we can update the edges int[] indexMap = new int[CutVertices.Count]; // Number of welded vertices in the temp array int k = 0; // Loop through each vertex, identifying duplicates. Must compare directly // because floating point inconsistencies cause a hash table to be unreliable // for vertices that are very close together but not directly coincident for(int i = 0; i < CutVertices.Count; i++) { bool duplicate = false; for(int j = 0; j < weldedVerts.Count; j++) { if (CutVertices[i].position == weldedVerts[j].position) { indexMap[i] = j; duplicate = true; break; } } if (!duplicate) { weldedVerts.Add(CutVertices[i]); indexMap[i] = k; k++; } } // Update the edges for(int i = 0; i < Constraints.Count; i++) { var edge = Constraints[i]; edge.v1 = indexMap[edge.v1]; edge.v2 = indexMap[edge.v2]; } weldedVerts.TrimExcess(); // Update the cut vertices this.CutVertices = new List(weldedVerts); } /// /// Gets the triangles for the specified sub mesh /// /// The index of the submesh /// public int[] GetTriangles(int subMeshIndex) { return this.Triangles[subMeshIndex].ToArray(); } /// /// Calculates the bounds of the mesh data /// public void CalculateBounds() { float vertexCount = (float)Vertices.Count; Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); // The cut face does not modify the extents of the object, so we only need to // loop through the original vertices to determine the bounds foreach(MeshVertex vertex in Vertices) { if (vertex.position.x < min.x) min.x = vertex.position.x; if (vertex.position.y < min.y) min.y = vertex.position.y; if (vertex.position.z < min.z) min.z = vertex.position.z; if (vertex.position.x > max.x) max.x = vertex.position.x; if (vertex.position.y > max.y) max.y = vertex.position.y; if (vertex.position.z > max.z) max.z = vertex.position.z; } this.Bounds = new Bounds((max + min) / 2f, max - min); } /// /// Converts the sliced mesh data into a mesh /// /// Returns the mesh object public Mesh ToMesh() { Mesh mesh = new Mesh(); var layout = new[] { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2), }; mesh.SetIndexBufferParams(triangleCount, IndexFormat.UInt32); mesh.SetVertexBufferParams(vertexCount, layout); mesh.SetVertexBufferData(Vertices, 0, 0, Vertices.Count); mesh.SetVertexBufferData(CutVertices, 0, Vertices.Count, CutVertices.Count); mesh.subMeshCount = Triangles.Length; int indexStart = 0; for(int i = 0; i < Triangles.Length; i++) { var subMeshIndexBuffer = Triangles[i]; mesh.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, subMeshIndexBuffer.Count); mesh.SetSubMesh(i, new SubMeshDescriptor(indexStart, subMeshIndexBuffer.Count)); indexStart += subMeshIndexBuffer.Count; } mesh.RecalculateBounds(); return mesh; } }