//Initialize by making 2 triangles by using three points, so its a flat triangle with a face on each side //We could use the ideas from Quickhull to make the start triangle as big as possible //Then find a point which is the furthest away as possible from these triangles //Add that point and you have a tetrahedron (triangular pyramid) public static void BuildFirstTetrahedron(HashSet <Vector3> points, HalfEdgeData3 convexHull) { //Of all points, find the two points that are furthes away from each other Edge3 eFurthestApart = FindEdgeFurthestApart(points); //Remove the two points we found points.Remove(eFurthestApart.p1); points.Remove(eFurthestApart.p2); //Find a point which is the furthest away from this edge //TODO: Is this point also on the AABB? So we don't have to search all remaining points... Vector3 pointFurthestAway = FindPointFurthestFromEdge(eFurthestApart, points); //Remove the point points.Remove(pointFurthestAway); //Display the triangle //Debug.DrawLine(eFurthestApart.p1.ToVector3(), eFurthestApart.p2.ToVector3(), Color.white, 1f); //Debug.DrawLine(eFurthestApart.p1.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f); //Debug.DrawLine(eFurthestApart.p2.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f); //Now we can build two triangles //It doesnt matter how we build these triangles as long as they are opposite //But the normal matters, so make sure it is calculated so the triangles are ordered clock-wise while the normal is pointing out Vector3 p1 = eFurthestApart.p1; Vector3 p2 = eFurthestApart.p2; Vector3 p3 = pointFurthestAway; convexHull.AddTriangle(p1, p2, p3); convexHull.AddTriangle(p1, p3, p2); //Debug.Log(convexHull.faces.Count); /* * foreach (HalfEdgeFace3 f in convexHull.faces) * { * TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red); * } */ //Find the point which is furthest away from the triangle (this point cant be co-planar) List <HalfEdgeFace3> triangles = new List <HalfEdgeFace3>(convexHull.faces); //Just pick one of the triangles HalfEdgeFace3 triangle = triangles[0]; //Build a plane Plane3 plane = new Plane3(triangle.edge.v.position, triangle.edge.v.normal); //Find the point furthest away from the plane Vector3 p4 = FindPointFurthestAwayFromPlane(points, plane); //Remove the point points.Remove(p4); //Debug.DrawLine(p1.ToVector3(), p4.ToVector3(), Color.green, 1f); //Debug.DrawLine(p2.ToVector3(), p4.ToVector3(), Color.green, 1f); //Debug.DrawLine(p3.ToVector3(), p4.ToVector3(), Color.green, 1f); //Now we have to remove one of the triangles == the triangle the point is outside of HalfEdgeFace3 triangleToRemove = triangles[0]; HalfEdgeFace3 triangleToKeep = triangles[1]; //This means the point is inside the triangle-plane, so we have to switch //We used triangle #0 to generate the plane if (_Geometry.GetSignedDistanceFromPointToPlane(p4, plane) < 0f) { triangleToRemove = triangles[1]; triangleToKeep = triangles[0]; } //Delete the triangle convexHull.DeleteFace(triangleToRemove); //Build three new triangles //The triangle we keep is ordered clock-wise: Vector3 p1_opposite = triangleToKeep.edge.v.position; Vector3 p2_opposite = triangleToKeep.edge.nextEdge.v.position; Vector3 p3_opposite = triangleToKeep.edge.nextEdge.nextEdge.v.position; //But we are looking at it from the back-side, //so we add those vertices counter-clock-wise to make the new triangles clock-wise convexHull.AddTriangle(p1_opposite, p3_opposite, p4); convexHull.AddTriangle(p3_opposite, p2_opposite, p4); convexHull.AddTriangle(p2_opposite, p1_opposite, p4); //Make sure all opposite edges are connected convexHull.ConnectAllEdgesSlow(); //Debug.Log(convexHull.faces.Count); //Display what weve got so far //foreach (HalfEdgeFace3 f in convexHull.faces) //{ // TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red); //} /* * //Now we might as well remove all the points that are within the tetrahedron because they are not on the hull * //But this is slow if we have many points and none of them are inside * HashSet<MyVector3> pointsToRemove = new HashSet<MyVector3>(); * * foreach (MyVector3 p in points) * { * bool isWithinConvexHull = _Intersections.PointWithinConvexHull(p, convexHull); * * if (isWithinConvexHull) * { * pointsToRemove.Add(p); * } * } * * Debug.Log($"Removed {pointsToRemove.Count} points because they were within the tetrahedron"); * * foreach (MyVector3 p in pointsToRemove) * { * points.Remove(p); * } */ }
//Should return null if the mesh couldn't be cut because it doesn't intersect with the plane //Otherwise it should return two new meshes //meshTrans is needed so we can transform the cut plane to the mesh's local space public static List <Mesh> CutMesh(Transform meshTrans, OrientedPlane3 orientedCutPlaneGlobal) { //Validate the input data if (meshTrans == null) { Debug.Log("There's transform to cut"); return(null); } Mesh mesh = meshTrans.GetComponent <MeshFilter>().mesh; if (mesh == null) { Debug.Log("There's no mesh to cut"); return(null); } //The plane with just a normal Plane3 cutPlaneGlobal = orientedCutPlaneGlobal.Plane3; //First check if the AABB of the mesh is intersecting with the plane //Otherwise we can't cut the mesh, so its a waste of time //To get the AABB in world space we need to use the mesh renderer MeshRenderer mr = meshTrans.GetComponent <MeshRenderer>(); if (mr != null) { AABB3 aabb = new AABB3(mr.bounds); //The corners of this box HashSet <MyVector3> corners = aabb.GetCorners(); if (corners != null && corners.Count > 1) { //The points are in world space so use the plane in world space if (ArePointsOnOneSideOfPlane(new List <MyVector3>(corners), cutPlaneGlobal)) { Debug.Log("This mesh can't be cut because its AABB doesnt intersect with the plane"); return(null); } } } //The two meshes we might end up with after the cut //One is in front of the plane and another is in back of the plane HalfEdgeData3 newMeshO = new HalfEdgeData3(); HalfEdgeData3 newMeshI = new HalfEdgeData3(); //The data belonging to the original mesh Vector3[] vertices = mesh.vertices; int[] triangles = mesh.triangles; Vector3[] normals = mesh.normals; //Save the new edges we add when cutting triangles that intersects with the plane //Need to be edges so we can later connect them with each other to fill the hole //And to remove small triangles HashSet <HalfEdge3> newEdgesO = new HashSet <HalfEdge3>(); HashSet <HalfEdge3> newEdgesI = new HashSet <HalfEdge3>(); //Transform the plane from global space to local space of the mesh MyVector3 planePosLocal = meshTrans.InverseTransformPoint(cutPlaneGlobal.pos.ToVector3()).ToMyVector3(); MyVector3 planeNormalLocal = meshTrans.InverseTransformDirection(cutPlaneGlobal.normal.ToVector3()).ToMyVector3(); Plane3 cutPlane = new Plane3(planePosLocal, planeNormalLocal); //Loop through all triangles in the original mesh for (int i = 0; i < triangles.Length; i += 3) { //Get the triangle data we need int triangleIndex1 = triangles[i + 0]; int triangleIndex2 = triangles[i + 1]; int triangleIndex3 = triangles[i + 2]; //Positions Vector3 p1_unity = vertices[triangleIndex1]; Vector3 p2_unity = vertices[triangleIndex2]; Vector3 p3_unity = vertices[triangleIndex3]; MyVector3 p1 = p1_unity.ToMyVector3(); MyVector3 p2 = p2_unity.ToMyVector3(); MyVector3 p3 = p3_unity.ToMyVector3(); //Normals MyVector3 n1 = normals[triangleIndex1].ToMyVector3(); MyVector3 n2 = normals[triangleIndex2].ToMyVector3(); MyVector3 n3 = normals[triangleIndex3].ToMyVector3(); //To make it easier to send data to methods MyMeshVertex v1 = new MyMeshVertex(p1, n1); MyMeshVertex v2 = new MyMeshVertex(p2, n2); MyMeshVertex v3 = new MyMeshVertex(p3, n3); //First check on which side of the plane these vertices are //If they are all on one side we dont have to cut the triangle bool is_p1_front = _Geometry.IsPointOutsidePlane(v1.position, cutPlane); bool is_p2_front = _Geometry.IsPointOutsidePlane(v2.position, cutPlane); bool is_p3_front = _Geometry.IsPointOutsidePlane(v3.position, cutPlane); //Build triangles belonging to respective mesh //All are outside the plane if (is_p1_front && is_p2_front && is_p3_front) { AddTriangleToMesh(v1, v2, v3, newMeshO, newEdges: null); } //All are inside the plane else if (!is_p1_front && !is_p2_front && !is_p3_front) { AddTriangleToMesh(v1, v2, v3, newMeshI, newEdges: null); } //The vertices are on different sides of the plane, so we need to cut the triangle into 3 new triangles else { //We get 6 cases where each vertex is on its own in front or in the back of the plane //p1 is outside if (is_p1_front && !is_p2_front && !is_p3_front) { CutTriangleOneOutside(v1, v2, v3, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //p1 is inside else if (!is_p1_front && is_p2_front && is_p3_front) { CutTriangleTwoOutside(v2, v3, v1, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //p2 is outside else if (!is_p1_front && is_p2_front && !is_p3_front) { CutTriangleOneOutside(v2, v3, v1, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //p2 is inside else if (is_p1_front && !is_p2_front && is_p3_front) { CutTriangleTwoOutside(v3, v1, v2, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //p3 is outside else if (!is_p1_front && !is_p2_front && is_p3_front) { CutTriangleOneOutside(v3, v1, v2, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //p3 is inside else if (is_p1_front && is_p2_front && !is_p3_front) { CutTriangleTwoOutside(v1, v2, v3, newMeshO, newMeshI, newEdgesI, newEdgesO, cutPlane); } //Something is strange if we end up here... else { Debug.Log("No case was gound where we split triangle into 3 new triangles"); } } } //Generate the new meshes only needed the old mesh intersected with the plane if (newMeshO.verts.Count == 0 || newMeshI.verts.Count == 0) { return(null); } //Find opposite edges to each edge //This is a slow process, so should be done only if the mesh is intersecting with the plane newMeshO.ConnectAllEdgesSlow(); newMeshI.ConnectAllEdgesSlow(); //Display all edges which have no opposite DebugHalfEdge.DisplayEdgesWithNoOpposite(newMeshO.edges, meshTrans, Color.white); DebugHalfEdge.DisplayEdgesWithNoOpposite(newMeshI.edges, meshTrans, Color.white); //Remove small triangles at the seam where we did the cut because they will cause shading issues if the surface is smooth //RemoveSmallTriangles(F_Mesh, newEdges); //Split each mesh into separate meshes if the original mesh is not connected, meaning it has islands HashSet <HalfEdgeData3> newMeshesO = SeparateMeshIslands(newMeshO); HashSet <HalfEdgeData3> newMeshesI = SeparateMeshIslands(newMeshI); //Fill the holes in the mesh HashSet <Hole> allHoles = FillHoles(newEdgesI, newEdgesO, orientedCutPlaneGlobal, meshTrans, planeNormalLocal); //Connect the holes with respective mesh AddHolesToMeshes(newMeshesO, newMeshesI, allHoles); //Finally generate standardized Unity meshes List <Mesh> cuttedUnityMeshes = new List <Mesh>(); foreach (HalfEdgeData3 meshData in newMeshesO) { MyMesh myMesh = meshData.ConvertToMyMesh("Outside mesh", MyMesh.MeshStyle.HardAndSoftEdges); Mesh unityMesh = myMesh.ConvertToUnityMesh(generateNormals: false); cuttedUnityMeshes.Add(unityMesh); } foreach (HalfEdgeData3 meshData in newMeshesI) { MyMesh myMesh = meshData.ConvertToMyMesh("Inside mesh", MyMesh.MeshStyle.HardAndSoftEdges); Mesh unityMesh = myMesh.ConvertToUnityMesh(generateNormals: false); cuttedUnityMeshes.Add(unityMesh); } return(cuttedUnityMeshes); }