//3d private static MyVector3 GetIntersectionCoordinate(Plane3 plane, Ray3 ray) { float denominator = MyVector3.Dot(-plane.normal, ray.dir); MyVector3 vecBetween = plane.pos - ray.origin; float t = MyVector3.Dot(vecBetween, -plane.normal) / denominator; MyVector3 intersectionPoint = ray.origin + ray.dir * t; return(intersectionPoint); }
//Relations of a point to a plane //3d //Outside means in the planes normal direction public static bool IsPointOutsidePlane(Plane3 plane, MyVector3 pointPos) { float distance = GetSignedDistanceFromPointToPlane(plane, pointPos); //To avoid floating point precision issues we can add a small value float epsilon = MathUtility.EPSILON; if (distance > 0f + epsilon) { return(true); } else { return(false); } }
//Is a list of points on one side of a plane? public static bool ArePointsOnOneSideOfPlane(List <MyVector3> points, Plane3 plane) { //First check the first point bool isInFront = _Geometry.IsPointOutsidePlane(plane, points[0]); for (int i = 1; i < points.Count; i++) { bool isOtherOutside = _Geometry.IsPointOutsidePlane(plane, points[i]); //We have found a point which is not at the same side of the plane as the first point if (isInFront != isOtherOutside) { return(false); } } return(true); }
//3d public static OutsideOnInside IsPoint_Outside_On_Inside_Plane(Plane3 plane, MyVector3 pointPos) { float distance = GetSignedDistanceFromPointToPlane(plane, pointPos); //To avoid floating point precision issues we can add a small value float epsilon = MathUtility.EPSILON; if (distance > 0f + epsilon) { return(OutsideOnInside.Outside); } else if (distance < 0f - epsilon) { return(OutsideOnInside.Inside); } else { return(OutsideOnInside.On); } }
//Find a visible triangle from a point private static HalfEdgeFace3 FindVisibleTriangleFromPoint(Vector3 p, HashSet <HalfEdgeFace3> triangles) { HalfEdgeFace3 visibleTriangle = null; foreach (HalfEdgeFace3 triangle in triangles) { //A triangle is visible from a point the point is outside of a plane formed with the triangles position and normal Plane3 plane = new Plane3(triangle.edge.v.position, triangle.edge.v.normal); bool isPointOutsidePlane = _Geometry.IsPointOutsidePlane(p, plane); //We have found a triangle which is visible from the point and should be removed if (isPointOutsidePlane) { visibleTriangle = triangle; break; } } return(visibleTriangle); }
//Cut a triangle where two vertices are inside and the other vertex is outside //Make sure they are sorted clockwise: O1-O2-I1 //F means that this vertex is outside the plane private static void CutTriangleTwoOutside(MyMeshVertex O1, MyMeshVertex O2, MyMeshVertex I1, HalfEdgeData3 newMeshO, HalfEdgeData3 newMeshI, HashSet <HalfEdge3> newEdgesI, HashSet <HalfEdge3> newEdgesO, Plane3 cutPlane) { //Cut the triangle by using edge-plane intersection //Triangles in Unity are ordered clockwise, so form edges that intersects with the plane: Edge3 e_O2I1 = new Edge3(O2.position, I1.position); //Edge3 e_F1F2 = new Edge3(F1, F2); //Not needed because never intersects with the plane Edge3 e_I1O1 = new Edge3(I1.position, O1.position); //The positions of the intersection vertices MyVector3 pos_O2I1 = _Intersections.GetLinePlaneIntersectionPoint(cutPlane, e_O2I1); MyVector3 pos_I1O1 = _Intersections.GetLinePlaneIntersectionPoint(cutPlane, e_I1O1); //The normals of the intersection vertices float percentageBetween_O2I1 = MyVector3.Distance(O2.position, pos_O2I1) / MyVector3.Distance(O2.position, I1.position); float percentageBetween_I1O1 = MyVector3.Distance(I1.position, pos_I1O1) / MyVector3.Distance(I1.position, O1.position); MyVector3 normal_O2I1 = _Interpolation.Lerp(O2.normal, I1.normal, percentageBetween_O2I1); MyVector3 normal_I1O1 = _Interpolation.Lerp(I1.normal, O1.normal, percentageBetween_I1O1); //MyVector3 normal_F2B1 = Vector3.Slerp(F2.normal.ToVector3(), B1.normal.ToVector3(), percentageBetween_F2B1).ToMyVector3(); //MyVector3 normal_B1F1 = Vector3.Slerp(B1.normal.ToVector3(), F1.normal.ToVector3(), percentageBetween_B1F1).ToMyVector3(); normal_O2I1 = MyVector3.Normalize(normal_O2I1); normal_I1O1 = MyVector3.Normalize(normal_I1O1); //The intersection vertices MyMeshVertex v_O2I1 = new MyMeshVertex(pos_O2I1, normal_O2I1); MyMeshVertex v_I1O1 = new MyMeshVertex(pos_I1O1, normal_I1O1); //Form 3 new triangles //Outside AddTriangleToMesh(v_O2I1, v_I1O1, O2, newMeshO, newEdgesO); AddTriangleToMesh(O2, v_I1O1, O1, newMeshO, null); //Inside AddTriangleToMesh(v_I1O1, v_O2I1, I1, newMeshI, newEdgesI); }
//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(cutPlane, v1.position); bool is_p2_front = _Geometry.IsPointOutsidePlane(cutPlane, v2.position); bool is_p3_front = _Geometry.IsPointOutsidePlane(cutPlane, v3.position); //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.ConnectAllEdges(); newMeshI.ConnectAllEdges(); //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) { Mesh unityMesh = meshData.ConvertToUnityMesh("Outside mesh", shareVertices: true, generateNormals: false); cuttedUnityMeshes.Add(unityMesh); } foreach (HalfEdgeData3 meshData in newMeshesI) { Mesh unityMesh = meshData.ConvertToUnityMesh("Inside mesh", shareVertices: true, generateNormals: false); cuttedUnityMeshes.Add(unityMesh); } return(cuttedUnityMeshes); }
//Given points and a plane, find the point furthest away from the plane private static Vector3 FindPointFurthestAwayFromPlane(HashSet <Vector3> points, Plane3 plane) { //Cant init by picking the first point in a list because it might be co-planar Vector3 bestPoint = default; float bestDistance = -Mathf.Infinity; foreach (Vector3 p in points) { float distance = _Geometry.GetSignedDistanceFromPointToPlane(p, plane); //Make sure the point is not co-planar float epsilon = MathUtility.EPSILON; //If distance is around 0 if (distance > -epsilon && distance < epsilon) { continue; } //Make sure distance is positive if (distance < 0f) { distance *= -1f; } if (distance > bestDistance) { bestDistance = distance; bestPoint = p; } } return(bestPoint); }
//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); * } */ }
//Find all visible triangles from a point //Also find edges on the border between invisible and visible triangles public static void FindVisibleTrianglesAndBorderEdgesFromPoint(Vector3 p, HalfEdgeData3 convexHull, out HashSet <HalfEdgeFace3> visibleTriangles, out HashSet <HalfEdge3> borderEdges) { //Flood-fill from the visible triangle to find all other visible triangles //When you cross an edge from a visible triangle to an invisible triangle, //save the edge because thhose edge should be used to build triangles with the point //These edges should belong to the triangle which is not visible borderEdges = new HashSet <HalfEdge3>(); //Store all visible triangles here so we can't visit triangles multiple times visibleTriangles = new HashSet <HalfEdgeFace3>(); //Start the flood-fill by finding a triangle which is visible from the point //A triangle is visible if the point is outside the plane formed at the triangles //Another sources is using the signed volume of a tetrahedron formed by the triangle and the point HalfEdgeFace3 visibleTriangle = FindVisibleTriangleFromPoint(p, convexHull.faces); //If we didn't find a visible triangle, we have some kind of edge case and should move on for now if (visibleTriangle == null) { Debug.LogWarning("Couldn't find a visible triangle so will ignore the point"); return; } //The queue which we will use when flood-filling Queue <HalfEdgeFace3> trianglesToFloodFrom = new Queue <HalfEdgeFace3>(); //Add the first triangle to init the flood-fill trianglesToFloodFrom.Enqueue(visibleTriangle); List <HalfEdge3> edgesToCross = new List <HalfEdge3>(); int safety = 0; while (true) { //We have visited all visible triangles if (trianglesToFloodFrom.Count == 0) { break; } HalfEdgeFace3 triangleToFloodFrom = trianglesToFloodFrom.Dequeue(); //This triangle is always visible and should be deleted visibleTriangles.Add(triangleToFloodFrom); //Investigate bordering triangles edgesToCross.Clear(); edgesToCross.Add(triangleToFloodFrom.edge); edgesToCross.Add(triangleToFloodFrom.edge.nextEdge); edgesToCross.Add(triangleToFloodFrom.edge.nextEdge.nextEdge); //Jump from this triangle to a bordering triangle foreach (HalfEdge3 edgeToCross in edgesToCross) { HalfEdge3 oppositeEdge = edgeToCross.oppositeEdge; if (oppositeEdge == null) { Debug.LogWarning("Found an opposite edge which is null"); break; } HalfEdgeFace3 oppositeTriangle = oppositeEdge.face; //Have we visited this triangle before (only test visible triangles)? if (trianglesToFloodFrom.Contains(oppositeTriangle) || visibleTriangles.Contains(oppositeTriangle)) { continue; } //Check if this triangle is visible //A triangle is visible from a point the point is outside of a plane formed with the triangles position and normal Plane3 plane = new Plane3(oppositeTriangle.edge.v.position, oppositeTriangle.edge.v.normal); bool isPointOutsidePlane = _Geometry.IsPointOutsidePlane(p, plane); //This triangle is visible so save it so we can flood from it if (isPointOutsidePlane) { trianglesToFloodFrom.Enqueue(oppositeTriangle); } //This triangle is invisible. Since we only flood from visible triangles, //it means we crossed from a visible triangle to an invisible triangle, so save the crossing edge else { borderEdges.Add(oppositeEdge); } } safety += 1; if (safety > 50000) { Debug.Log("Stuck in infinite loop when flood-filling visible triangles"); break; } } }
//Remove flat tetrahedrons (a vertex in a triangle) private static bool RemoveFlatTetrahedrons(HalfEdgeData3 meshData, Normalizer3 normalizer = null) { HashSet <HalfEdgeVertex3> vertices = meshData.verts; bool foundFlatTetrahedron = false; foreach (HalfEdgeVertex3 vertex in vertices) { HashSet <HalfEdge3> edgesGoingToVertex = vertex.GetEdgesPointingToVertex(meshData); if (edgesGoingToVertex.Count == 3) { //Find the vertices of the triangle covering this vertex clock-wise HalfEdgeVertex3 v1 = vertex.edge.v; HalfEdgeVertex3 v2 = vertex.edge.prevEdge.oppositeEdge.v; HalfEdgeVertex3 v3 = vertex.edge.oppositeEdge.nextEdge.v; //Build a plane MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(v3.position - v2.position, v1.position - v2.position)); Plane3 plane = new Plane3(v1.position, normal); //Find the distance from the vertex to the plane float distance = _Geometry.GetSignedDistanceFromPointToPlane(vertex.position, plane); distance = Mathf.Abs(distance); if (distance < FLAT_TETRAHEDRON_DISTANCE) { //Debug.Log("Found flat tetrahedron"); Vector3 p1 = normalizer.UnNormalize(v1.position).ToVector3(); Vector3 p2 = normalizer.UnNormalize(v2.position).ToVector3(); Vector3 p3 = normalizer.UnNormalize(v3.position).ToVector3(); TestAlgorithmsHelpMethods.DebugDrawTriangle(p1, p2, p3, normal.ToVector3(), Color.blue, Color.red); foundFlatTetrahedron = true; //Save the opposite edges HashSet <HalfEdge3> oppositeEdges = new HashSet <HalfEdge3>(); oppositeEdges.Add(v1.edge.oppositeEdge); oppositeEdges.Add(v2.edge.oppositeEdge); oppositeEdges.Add(v3.edge.oppositeEdge); //Remove the three triangles foreach (HalfEdge3 e in edgesGoingToVertex) { meshData.DeleteFace(e.face); } //Add the new triangle (could maybe connect it ourselves) HalfEdgeFace3 newTriangle = meshData.AddTriangle(v1.position, v2.position, v3.position, findOppositeEdge: false); meshData.TryFindOppositeEdge(newTriangle.edge, oppositeEdges); meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge, oppositeEdges); meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge.nextEdge, oppositeEdges); break; } } } return(foundFlatTetrahedron); }
// // Point-plane relations // //https://gamedevelopment.tutsplus.com/tutorials/understanding-sutherland-hodgman-clipping-for-physics-engines--gamedev-11917 //Notice that the plane normal doesnt have to be normalized //The signed distance from a point to a plane //- Positive distance denotes that the point p is on the front side of the plane (in the direction of the plane normal) //- Negative means it's on the back side //3d public static float GetSignedDistanceFromPointToPlane(Plane3 plane, MyVector3 pointPos) { float distance = MyVector3.Dot(plane.normal, pointPos - plane.pos); return(distance); }