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));
 }
Example #4
0
        /**
         * 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));
        }
Example #5
0
        /**
         * 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);
        }
Example #6
0
        /**
         * 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));
        }