/** * Generate a mesh from the provided hull made of triangles */ private static Mesh CreateFrom(List <Triangle> hull) { int count = hull.Count; if (count <= 0) { return(null); } Mesh newMesh = new Mesh(); Vector3[] newVertices = new Vector3[count * 3]; Vector2[] newUvs = new Vector2[count * 3]; int[] newIndices = new int[count * 3]; int addedCount = 0; // fill our mesh arrays for (int i = 0; i < count; i++) { Triangle newTri = hull[i]; int i0 = addedCount + 0; int i1 = addedCount + 1; int i2 = addedCount + 2; newVertices[i0] = newTri.positionA; newVertices[i1] = newTri.positionB; newVertices[i2] = newTri.positionC; newUvs[i0] = newTri.uvA; newUvs[i1] = newTri.uvB; newUvs[i2] = newTri.uvC; // note -> this case could be optimized by having the order // returned properly from the intersector // -> note -> this is no longer required since triangles are added // as clockwise from the intersector /*if (newTri.IsCW()) { * newIndices[i0] = i0; * newIndices[i1] = i1; * newIndices[i2] = i2; * } * else { * newIndices[i0] = i0; * newIndices[i1] = i2; * newIndices[i2] = i1; * }*/ newIndices[i0] = i0; newIndices[i1] = i1; newIndices[i2] = i2; addedCount += 3; } // fill the mesh structure newMesh.vertices = newVertices; newMesh.uv = newUvs; newMesh.triangles = newIndices; // consider computing this array externally instead newMesh.RecalculateNormals(); return(newMesh); }
/** * Slice the gameobject mesh (if any) using the Plane, which will generate * a maximum of 2 other Meshes. * This function will recalculate new UV coordinates to ensure textures are applied * properly. * Returns null if no intersection has been found or the GameObject does not contain * a valid mesh to cut. */ public static SlicedHull Slice(Mesh sharedMesh, Plane pl, bool genCrossSection = true) { if (sharedMesh == null) { return(null); } Vector3[] ve = sharedMesh.vertices; Vector2[] uv = sharedMesh.uv; int[] indices = sharedMesh.triangles; int indicesCount = indices.Length; // we reuse this object for all intersection tests IntersectionResult result = new IntersectionResult(); // all our buffers, as Triangles List <Triangle> upperHull = new List <Triangle>(); List <Triangle> lowerHull = new List <Triangle>(); List <Vector3> crossHull = new List <Vector3>(); // loop through all the mesh vertices, generating upper and lower hulls // and all intersection points for (int index = 0; index < indicesCount; index += 3) { int i0 = indices[index + 0]; int i1 = indices[index + 1]; int i2 = indices[index + 2]; Triangle newTri = new Triangle(ve[i0], ve[i1], ve[i2], uv[i0], uv[i1], uv[i2]); // slice this particular triangle with the provided // plane if (newTri.Split(pl, result)) { int upperHullCount = result.upperHullCount; int lowerHullCount = result.lowerHullCount; int interHullCount = result.intersectionPointCount; for (int i = 0; i < upperHullCount; i++) { upperHull.Add(result.upperHull[i]); } for (int i = 0; i < lowerHullCount; i++) { lowerHull.Add(result.lowerHull[i]); } for (int i = 0; i < interHullCount; i++) { crossHull.Add(result.intersectionPoints[i]); } } else { SideOfPlane side = pl.SideOf(ve[i0]); if (side == SideOfPlane.UP || side == SideOfPlane.ON) { upperHull.Add(newTri); } else { lowerHull.Add(newTri); } } } // start creating our hulls Mesh finalUpperHull = CreateFrom(upperHull); Mesh finalLowerHull = CreateFrom(lowerHull); // we need to generate the cross section if set // NOTE -> This uses a MonotoneChain algorithm which will only work // on cross sections which are Convex if (genCrossSection) { Mesh[] crossSections = CreateFrom(crossHull, pl.normal); if (crossSections != null) { return(new SlicedHull(finalUpperHull, finalLowerHull, crossSections[0], crossSections[1])); } } return(new SlicedHull(finalUpperHull, finalLowerHull)); }
/** * O(n log n) Convex Hull Algorithm. * Accepts a list of vertices as Vector3 and triangulates them according to a projection * plane defined as planeNormal. Algorithm will output vertices, indices and UV coordinates * as arrays */ public static bool MonotoneChain(List <Vector3> vertices, Vector3 normal, out List <Triangle> tri, TextureRegion texRegion) { int count = vertices.Count; // we cannot triangulate less than 3 points. Use minimum of 3 points if (count < 3) { tri = null; return(false); } // first, we map from 3D points into a 2D plane represented by the provided normal Vector3 u = Vector3.Normalize(Vector3.Cross(normal, Vector3.up)); if (Vector3.zero == u) { u = Vector3.Normalize(Vector3.Cross(normal, Vector3.forward)); } Vector3 v = Vector3.Cross(u, normal); // generate an array of mapped values Mapped2D[] mapped = new Mapped2D[count]; // these values will be used to generate new UV coordinates later on float maxDivX = float.MinValue; float maxDivY = float.MinValue; float minDivX = float.MaxValue; float minDivY = float.MaxValue; // map the 3D vertices into the 2D mapped values for (int i = 0; i < count; i++) { Vector3 vertToAdd = vertices[i]; Mapped2D newMappedValue = new Mapped2D(vertToAdd, u, v); Vector2 mapVal = newMappedValue.mappedValue; // grab our maximal values so we can map UV's in a proper range maxDivX = Mathf.Max(maxDivX, mapVal.x); maxDivY = Mathf.Max(maxDivY, mapVal.y); minDivX = Mathf.Min(minDivX, mapVal.x); minDivY = Mathf.Min(minDivY, mapVal.y); mapped[i] = newMappedValue; } // sort our newly generated array values Array.Sort <Mapped2D>(mapped, (a, b) => { Vector2 x = a.mappedValue; Vector2 p = b.mappedValue; return((x.x < p.x || (x.x == p.x && x.y < p.y)) ? -1 : 1); }); // our final hull mappings will end up in herew Mapped2D[] hulls = new Mapped2D[count + 1]; int k = 0; // build the lower hull of the chain for (int i = 0; i < count; i++) { while (k >= 2) { Vector2 mA = hulls[k - 2].mappedValue; Vector2 mB = hulls[k - 1].mappedValue; Vector2 mC = mapped[i].mappedValue; if (Intersector.TriArea2D(mA.x, mA.y, mB.x, mB.y, mC.x, mC.y) > 0.0f) { break; } k--; } hulls[k++] = mapped[i]; } // build the upper hull of the chain for (int i = count - 2, t = k + 1; i >= 0; i--) { while (k >= t) { Vector2 mA = hulls[k - 2].mappedValue; Vector2 mB = hulls[k - 1].mappedValue; Vector2 mC = mapped[i].mappedValue; if (Intersector.TriArea2D(mA.x, mA.y, mB.x, mB.y, mC.x, mC.y) > 0.0f) { break; } k--; } hulls[k++] = mapped[i]; } // finally we can build our mesh, generate all the variables // and fill them up int vertCount = k - 1; int triCount = (vertCount - 2) * 3; // this should not happen, but here just in case if (vertCount < 3) { tri = null; return(false); } // ensure List does not dynamically grow, performing copy ops each time! tri = new List <Triangle>(triCount / 3); float width = maxDivX - minDivX; float height = maxDivY - minDivY; int indexCount = 1; // generate both the vertices and uv's in this loop for (int i = 0; i < triCount; i += 3) { // the Vertices in our triangle Mapped2D posA = hulls[0]; Mapped2D posB = hulls[indexCount]; Mapped2D posC = hulls[indexCount + 1]; // generate UV Maps Vector2 uvA = posA.mappedValue; Vector2 uvB = posB.mappedValue; Vector2 uvC = posC.mappedValue; uvA.x = (uvA.x - minDivX) / width; uvA.y = (uvA.y - minDivY) / height; uvB.x = (uvB.x - minDivX) / width; uvB.y = (uvB.y - minDivY) / height; uvC.x = (uvC.x - minDivX) / width; uvC.y = (uvC.y - minDivY) / height; Triangle newTriangle = new Triangle(posA.originalValue, posB.originalValue, posC.originalValue); // ensure our UV coordinates are mapped into the requested TextureRegion newTriangle.SetUV(texRegion.Map(uvA), texRegion.Map(uvB), texRegion.Map(uvC)); // the normals is the same for all vertices since the final mesh is completly flat newTriangle.SetNormal(normal, normal, normal); newTriangle.ComputeTangents(); tri.Add(newTriangle); indexCount++; } return(true); }