using UnityEngine; /// /// Class which handles slicing a mesh into two pieces given the origin and normal of the slice plane. /// public static class MeshSlicer { /// /// Slices the mesh by the plane specified by `sliceNormal` and `sliceOrigin` /// The sliced mesh data is return via out parameters. /// /// /// The normal of the slice plane (points towards the top slice) /// The origin of the slice plane /// Scale factor to apply to UV coordinates /// Offset to apply to UV coordinates /// Out parameter returning fragment mesh data for slice above the plane /// Out parameter returning fragment mesh data for slice below the plane public static void Slice(FragmentData meshData, Vector3 sliceNormal, Vector3 sliceOrigin, Vector2 textureScale, Vector2 textureOffset, out FragmentData topSlice, out FragmentData bottomSlice) { topSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount); bottomSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount); // Keep track of what side of the cutting plane each vertex is on bool[] side = new bool[meshData.vertexCount]; // Go through and identify which vertices are above/below the split plane for (int i = 0; i < meshData.Vertices.Count; i++) { var vertex = meshData.Vertices[i]; side[i] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin); var slice = side[i] ? topSlice : bottomSlice; slice.AddMappedVertex(vertex, i); } int offset = meshData.Vertices.Count; for (int i = 0; i < meshData.CutVertices.Count; i++) { var vertex = meshData.CutVertices[i]; side[i + offset] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin); var slice = side[i + offset] ? topSlice : bottomSlice; slice.AddMappedVertex(vertex, i + offset); } SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.Default); SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.CutFace); // Fill in the cut plane for each mesh. // The slice normal points to the "above" mesh, so the face normal for the cut face // on the above mesh is opposite of the slice normal. Conversely, normal for the // cut face on the "below" mesh is in the direction of the slice normal FillCutFaces(topSlice, bottomSlice, -sliceNormal, textureScale, textureOffset); } /// /// Fills the cut faces for each sliced mesh. The `sliceNormal` is the normal for the plane and points /// in the direction of `topMeshData` /// /// Fragment mesh data for slice above the slice plane /// Fragment mesh data for slice above the slice plane /// Normal of the slice plane (points towards the top slice) /// Scale factor to apply to UV coordinates /// Offset to apply to UV coordinates private static void FillCutFaces(FragmentData topSlice, FragmentData bottomSlice, Vector3 sliceNormal, Vector2 textureScale, Vector2 textureOffset) { // Since the topSlice and bottomSlice both share the same cut face, we only need to calculate it // once. Then the same vertex/triangle data for the face will be used for both slices, except // with the normals reversed. // First need to weld the coincident vertices for the triangulation to work properly topSlice.WeldCutFaceVertices(); // Need at least 3 vertices to triangulate if (topSlice.CutVertices.Count < 3) return; // Triangulate the cut face var triangulator = new ConstrainedTriangulator(topSlice.CutVertices, topSlice.Constraints, sliceNormal); int[] triangles = triangulator.Triangulate(); // Update normal and UV for the cut face vertices for (int i = 0; i < topSlice.CutVertices.Count; i++) { var vertex = topSlice.CutVertices[i]; var point = triangulator.points[i]; // UV coordinates are based off of the 2D coordinates used for triangulation // During triangulation, coordinates are normalized to [0,1], so need to multiply // by normalization scale factor to get back to the appropritate scale Vector2 uv = new Vector2( (triangulator.normalizationScaleFactor * point.coords.x) * textureScale.x + textureOffset.x, (triangulator.normalizationScaleFactor * point.coords.y) * textureScale.y + textureOffset.y); // Update normals and UV coordinates for the cut vertices var topVertex = vertex; topVertex.normal = sliceNormal; topVertex.uv = uv; var bottomVertex = vertex; bottomVertex.normal = -sliceNormal; bottomVertex.uv = uv; topSlice.CutVertices[i] = topVertex; bottomSlice.CutVertices[i] = bottomVertex; } // Add the new triangles to the top/bottom slices int offsetTop = topSlice.Vertices.Count; int offsetBottom = bottomSlice.Vertices.Count; for (int i = 0; i < triangles.Length; i += 3) { topSlice.AddTriangle( offsetTop + triangles[i], offsetTop + triangles[i + 1], offsetTop + triangles[i + 2], SlicedMeshSubmesh.CutFace); bottomSlice.AddTriangle( offsetBottom + triangles[i], offsetBottom + triangles[i + 2], // Swap two vertices so triangles are wound CW offsetBottom + triangles[i + 1], SlicedMeshSubmesh.CutFace); } } /// /// Identifies triangles that are intersected by the slice plane and splits them in two /// /// /// Fragment mesh data for slice above the slice plane /// Fragment mesh data for slice above the slice plane /// The normal of the slice plane (points towards the top slice) /// The origin of the slice plane /// Array mapping each vertex to either the top/bottom slice /// Index of the sub mesh private static void SplitTriangles(FragmentData meshData, FragmentData topSlice, FragmentData bottomSlice, Vector3 sliceNormal, Vector3 sliceOrigin, bool[] side, SlicedMeshSubmesh subMesh) { int[] triangles = meshData.GetTriangles((int)subMesh); // Keep track of vertices that lie on the intersection plane int a, b, c; for (int i = 0; i < triangles.Length; i += 3) { // Get vertex indexes for this triangle a = triangles[i]; b = triangles[i + 1]; c = triangles[i + 2]; // Triangle is contained completely within mesh A if (side[a] && side[b] && side[c]) { topSlice.AddMappedTriangle(a, b, c, subMesh); } // Triangle is contained completely within mesh B else if (!side[a] && !side[b] && !side[c]) { bottomSlice.AddMappedTriangle(a, b, c, subMesh); } // Triangle is intersected by the slicing plane. Need to subdivide it else { // In these cases, two vertices of the triangle are above the cut plane and one vertex is below if (side[b] && side[c] && !side[a]) { SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); } else if (side[c] && side[a] && !side[b]) { SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); } else if (side[a] && side[b] && !side[c]) { SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); } // In these cases, two vertices of the triangle are below the cut plane and one vertex is above else if (!side[b] && !side[c] && side[a]) { SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); } else if (!side[c] && !side[a] && side[b]) { SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); } else if (!side[a] && !side[b] && side[c]) { SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); } } } } /// /// Splits triangle defined by the points (v1,v2,v3) /// /// Index of first vertex in triangle /// Index of second vertex in triangle< /// Index of third vertex in triangle< /// The normal of the slice plane (points towards the top slice) /// The origin of the slice plane /// Original mesh data /// Mesh data for top slice /// Mesh data for bottom slice /// Index of the submesh that the triangle belongs to /// Boolean indicating whether v3 is above or below the slice plane. private static void SplitTriangle(int v1_idx, int v2_idx, int v3_idx, Vector3 sliceNormal, Vector3 sliceOrigin, FragmentData meshData, FragmentData topSlice, FragmentData bottomSlice, SlicedMeshSubmesh subMesh, bool v3BelowCutPlane) { // - `v1`, `v2`, `v3` are the indexes of the triangle relative to the original mesh data // - `v1` and `v2` are on the the side of split plane that belongs to meshA // - `v3` is on the side of the split plane that belongs to meshB // - `vertices`, `normals`, `uv` are the original mesh data used for interpolation // // v3BelowCutPlane = true // ====================== // // v1 *_____________* v2 . // \ / /|\ cutNormal // \ / | // ----*-------*---------*-- // v13 \ / v23 cutOrigin // \ / // \ / // * v3 triangle normal out of screen // // v3BelowCutPlane = false // ======================= // // * v3 . // / \ /|\ cutNormal // v23 / \ v13 | // -----*-----*----------*-- // / \ cut origin // / \ // v2 *___________* v1 triangle normal out of screen // float s13; float s23; Vector3 v13; Vector3 v23; MeshVertex v1 = v1_idx < meshData.Vertices.Count ? meshData.Vertices[v1_idx] : meshData.CutVertices[v1_idx - meshData.Vertices.Count]; MeshVertex v2 = v2_idx < meshData.Vertices.Count ? meshData.Vertices[v2_idx] : meshData.CutVertices[v2_idx - meshData.Vertices.Count]; MeshVertex v3 = v3_idx < meshData.Vertices.Count ? meshData.Vertices[v3_idx] : meshData.CutVertices[v3_idx - meshData.Vertices.Count]; if (MathUtils.LinePlaneIntersection(v1.position, v3.position, sliceNormal, sliceOrigin, out v13, out s13) && MathUtils.LinePlaneIntersection(v2.position, v3.position, sliceNormal, sliceOrigin, out v23, out s23)) { // Interpolate normals and UV coordinates var norm13 = (v1.normal + s13 * (v3.normal - v1.normal)).normalized; var norm23 = (v2.normal + s23 * (v3.normal - v2.normal)).normalized; var uv13 = v1.uv + s13 * (v3.uv - v1.uv); var uv23 = v2.uv + s23 * (v3.uv - v2.uv); // Add vertices/normals/uv for the intersection points to each mesh topSlice.AddCutFaceVertex(v13, norm13, uv13); topSlice.AddCutFaceVertex(v23, norm23, uv23); bottomSlice.AddCutFaceVertex(v13, norm13, uv13); bottomSlice.AddCutFaceVertex(v23, norm23, uv23); // Indices for the intersection vertices (for the original mesh data) int index13_A = topSlice.Vertices.Count - 2; int index23_A = topSlice.Vertices.Count - 1; int index13_B = bottomSlice.Vertices.Count - 2; int index23_B = bottomSlice.Vertices.Count - 1; if (v3BelowCutPlane) { // Triangle slice above the cutting plane is a quad, so divide into two triangles topSlice.AddTriangle(index23_A, index13_A, topSlice.IndexMap[v2_idx], subMesh); topSlice.AddTriangle(index13_A, topSlice.IndexMap[v1_idx], topSlice.IndexMap[v2_idx], subMesh); // One triangle must be added to mesh 2 bottomSlice.AddTriangle(bottomSlice.IndexMap[v3_idx], index13_B, index23_B, subMesh); // When looking at the cut-face, the edges should wind counter-clockwise topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 2, topSlice.CutVertices.Count - 1)); bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 1, bottomSlice.CutVertices.Count - 2)); } else { // Triangle slice above the cutting plane is a simple triangle topSlice.AddTriangle(index13_A, index23_A, topSlice.IndexMap[v3_idx], subMesh); // Triangle slice below the cutting plane is a quad, so divide into two triangles bottomSlice.AddTriangle(bottomSlice.IndexMap[v1_idx], bottomSlice.IndexMap[v2_idx], index13_B, subMesh); bottomSlice.AddTriangle(bottomSlice.IndexMap[v2_idx], index23_B, index13_B, subMesh); // When looking at the cut-face, the edges should wind counter-clockwise topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 1, topSlice.CutVertices.Count - 2)); bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 2, bottomSlice.CutVertices.Count - 1)); } } } }