public static GameObject[] SliceInstantiate(this GameObject obj, Vector3 position, Vector3 direction, TextureRegion cuttingRegion, Material crossSectionMaterial = null) { EzySlice.Plane cuttingPlane = new EzySlice.Plane(); Matrix4x4 mat = obj.transform.worldToLocalMatrix; Matrix4x4 transpose = mat.transpose; Matrix4x4 inv = transpose.inverse; Vector3 refUp = inv.MultiplyVector(direction).normalized; Vector3 refPt = obj.transform.InverseTransformPoint(position); cuttingPlane.Compute(refPt, refUp); return(SliceInstantiate(obj, cuttingPlane, cuttingRegion, crossSectionMaterial)); }
/** * 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 r = Mathf.Abs(normal.x) > Mathf.Abs(normal.y) ? new Vector3(0, 1, 0) : new Vector3(1, 0, 0); Vector3 v = Vector3.Normalize(Vector3.Cross(r, normal)); Vector3 u = Vector3.Cross(normal, v); // 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 = 0.0f; float maxDivY = 0.0f; // 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, Mathf.Abs(mapVal.x)); maxDivY = Mathf.Max(maxDivY, Mathf.Abs(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 here 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 maxDiv = Mathf.Max(maxDivX, maxDivY); 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 / maxDiv) * 0.5f; uvA.y = (uvA.y / maxDiv) * 0.5f; uvB.x = (uvB.x / maxDiv) * 0.5f; uvB.y = (uvB.y / maxDiv) * 0.5f; uvC.x = (uvC.x / maxDiv) * 0.5f; uvC.y = (uvC.y / maxDiv) * 0.5f; 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); }
public static SlicedHull Slice(this GameObject obj, Plane pl, TextureRegion textureRegion, Material crossSectionMaterial = null) { return(Slicer.Slice(obj, pl, textureRegion, crossSectionMaterial)); }
/** * Helper function to accept a gameobject which will transform the plane * approprietly before the slice occurs * See -> Slice(Mesh, Plane) for more info */ public static SlicedHull Slice(GameObject obj, Plane pl, TextureRegion crossRegion, Material crossMaterial) { SkinnedMeshRenderer smr = obj.GetComponent <SkinnedMeshRenderer>(); MeshRenderer mr = obj.GetComponent <MeshRenderer>(); MeshFilter mf = obj.GetComponent <MeshFilter>(); // cannot continue without a proper filter if (mr == null && smr == null) { Debug.LogWarning("EzySlice::Slice -> Provided GameObject must have a MeshFilter Component or Skinned Mesh Renderer Component."); return(null); } // cannot continue without a proper renderer //if (filter.re == null) { // Debug.LogWarning("EzySlice::Slice -> Provided GameObject must have a MeshRenderer Component."); // return null; //} Material[] materials; Mesh mesh; if (mr != null) { materials = mr.sharedMaterials; mesh = mf.mesh; } else { materials = smr.sharedMaterials; mesh = smr.sharedMesh; } // cannot slice a mesh that doesn't exist if (mesh == null) { Debug.LogWarning("EzySlice::Slice -> Provided GameObject must have a Mesh that is not NULL."); return(null); } int submeshCount = mesh.subMeshCount; // to make things straightforward, exit without slicing if the materials and mesh // array don't match. This shouldn't happen anyway if (materials.Length != submeshCount) { Debug.LogWarning("EzySlice::Slice -> Provided Material array must match the length of submeshes."); return(null); } // we need to find the index of the material for the cross section. // default to the end of the array int crossIndex = materials.Length; // for cases where the sliced material is null, we will append the cross section to the end // of the submesh array, this is because the application may want to set/change the material // after slicing has occured, so we don't assume anything if (crossMaterial != null) { for (int i = 0; i < crossIndex; i++) { if (materials[i] == crossMaterial) { crossIndex = i; break; } } } return(Slice(mesh, pl, crossRegion, crossIndex)); }
/** * Generate Two Meshes (an upper and lower) cross section from a set of intersection * points and a plane normal. Intersection Points do not have to be in order. */ private static List <Triangle> CreateFrom(List <Vector3> intPoints, Vector3 planeNormal, TextureRegion region) { List <Triangle> tris; if (Triangulator.MonotoneChain(intPoints, planeNormal, out tris, region)) { return(tris); } return(null); }
/** * 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, TextureRegion region, int crossIndex) { if (sharedMesh == null) { return(null); } Vector3[] verts = sharedMesh.vertices; Vector2[] uv = sharedMesh.uv; Vector3[] norm = sharedMesh.normals; Vector4[] tan = sharedMesh.tangents; int submeshCount = sharedMesh.subMeshCount; // each submesh will be sliced and placed in its own array structure SlicedSubmesh[] slices = new SlicedSubmesh[submeshCount]; // the cross section hull is common across all submeshes List <Vector3> crossHull = new List <Vector3>(); // we reuse this object for all intersection tests IntersectionResult result = new IntersectionResult(); // see if we would like to split the mesh using uv, normals and tangents bool genUV = verts.Length == uv.Length; bool genNorm = verts.Length == norm.Length; bool genTan = verts.Length == tan.Length; // iterate over all the submeshes individually. vertices and indices // are all shared within the submesh for (int submesh = 0; submesh < submeshCount; submesh++) { int[] indices = sharedMesh.GetTriangles(submesh); int indicesCount = indices.Length; SlicedSubmesh mesh = new SlicedSubmesh(); // 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(verts[i0], verts[i1], verts[i2]); // generate UV if available if (genUV) { newTri.SetUV(uv[i0], uv[i1], uv[i2]); } // generate normals if available if (genNorm) { newTri.SetNormal(norm[i0], norm[i1], norm[i2]); } // generate tangents if available if (genTan) { newTri.SetTangent(tan[i0], tan[i1], tan[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++) { mesh.upperHull.Add(result.upperHull[i]); } for (int i = 0; i < lowerHullCount; i++) { mesh.lowerHull.Add(result.lowerHull[i]); } for (int i = 0; i < interHullCount; i++) { crossHull.Add(result.intersectionPoints[i]); } } else { SideOfPlane side = pl.SideOf(verts[i0]); if (side == SideOfPlane.UP || side == SideOfPlane.ON) { mesh.upperHull.Add(newTri); } else { mesh.lowerHull.Add(newTri); } } } // register into the index slices[submesh] = mesh; } // check if slicing actually occured for (int i = 0; i < slices.Length; i++) { // check if at least one of the submeshes was sliced. If so, stop checking // because we need to go through the generation step if (slices[i] != null && slices[i].isValid) { return(CreateFrom(slices, CreateFrom(crossHull, pl.normal, region), crossIndex)); } } // no slicing occured, just return null to signify return(null); }
public static GameObject[] SliceInstantiate(this GameObject obj, Vector3 position, Vector3 direction, TextureRegion cuttingRegion, bool displayUpperHull, bool displayLowerHull, Material crossSectionMaterial = null) { EzySlice.Plane cuttingPlane = new EzySlice.Plane(); Vector3 refUp = obj.transform.InverseTransformDirection(direction); Vector3 refPt = obj.transform.InverseTransformPoint(position); cuttingPlane.Compute(refPt, refUp); return(SliceInstantiate(obj, cuttingPlane, cuttingRegion, displayUpperHull, displayLowerHull, crossSectionMaterial)); }
public static SlicedHull Slice(this GameObject obj, Vector3 position, Vector3 direction, TextureRegion textureRegion, Material crossSectionMaterial = null) { Plane cuttingPlane = new Plane(); Vector3 refUp = obj.transform.InverseTransformDirection(direction); Vector3 refPt = obj.transform.InverseTransformPoint(position); cuttingPlane.Compute(refPt, refUp); return(Slice(obj, cuttingPlane, textureRegion, crossSectionMaterial)); }