//Add a vertex to the mesh and return its position in the array //If we want hard edges, set shareVertices to false. Otherwise we will get a smooth surface public int AddVertexAndReturnIndex(MyMeshVertex v, bool shareVertices) { int vertexPosInList = -1; if (shareVertices) { for (int i = 0; i < vertices.Count; i++) { //Here we have to compare both position and normal or we can't get hard edges in combination with soft edges MyVector3 thisPos = vertices[i]; MyVector3 thisNormal = normals[i]; if (thisPos.Equals(v.position) && thisNormal.Equals(v.normal)) { vertexPosInList = i; return(vertexPosInList); } } } //If we got here it means the vertex is not in the list, so add it as the last vertex vertices.Add(v.position); normals.Add(v.normal); vertexPosInList = vertices.Count - 1; return(vertexPosInList); }
//Help method to build a triangle and add it to a mesh //v1-v2-v3 should be sorted clock-wise //v1-v2 should be the cut edge (if we have a cut edge), and we know this triangle has a cut edge if newEdges != null private static void AddTriangleToMesh(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, HalfEdgeData3 mesh, HashSet <HalfEdge3> newEdges) { //Create three new vertices HalfEdgeVertex3 half_v1 = new HalfEdgeVertex3(v1.position, v1.normal); HalfEdgeVertex3 half_v2 = new HalfEdgeVertex3(v2.position, v2.normal); HalfEdgeVertex3 half_v3 = new HalfEdgeVertex3(v3.position, v3.normal); //Create three new half-edges that points TO these vertices HalfEdge3 e_to_v1 = new HalfEdge3(half_v1); HalfEdge3 e_to_v2 = new HalfEdge3(half_v2); HalfEdge3 e_to_v3 = new HalfEdge3(half_v3); //Create the face (which is a triangle) which needs a reference to one of the edges HalfEdgeFace3 f = new HalfEdgeFace3(e_to_v1); //Connect the data: //Connect the edges clock-wise e_to_v1.nextEdge = e_to_v2; e_to_v2.nextEdge = e_to_v3; e_to_v3.nextEdge = e_to_v1; e_to_v1.prevEdge = e_to_v3; e_to_v2.prevEdge = e_to_v1; e_to_v3.prevEdge = e_to_v2; //Each vertex needs a reference to an edge going FROM that vertex half_v1.edge = e_to_v2; half_v2.edge = e_to_v3; half_v3.edge = e_to_v1; //Each edge needs a reference to the face e_to_v1.face = f; e_to_v2.face = f; e_to_v3.face = f; //Each edge needs an opposite edge //This is slow process but we need it to be able to split meshes which are not connected //You could do this afterwards when all triangles have been generate, but Im not sure which is the fastest... //Save the data mesh.verts.Add(half_v1); mesh.verts.Add(half_v2); mesh.verts.Add(half_v3); mesh.edges.Add(e_to_v1); mesh.edges.Add(e_to_v2); mesh.edges.Add(e_to_v3); mesh.faces.Add(f); //Save the new edge if (newEdges != null) { //We know the knew edge goes from v1 to v2, so we should save the half-edge that points to v2 newEdges.Add(e_to_v2); } }
//Convert to Unity mesh (if we know we have stored triangles in the data structure) //shareVertices means that we want a smooth surface where some vertices are shared between triangles public Mesh ConvertToUnityMesh(string name, bool shareVertices, bool generateNormals) { MyMesh myMesh = new MyMesh(); //Loop through each triangle foreach (HalfEdgeFace3 f in faces) { //These should have been stored clock-wise HalfEdgeVertex3 v1 = f.edge.v; HalfEdgeVertex3 v2 = f.edge.nextEdge.v; HalfEdgeVertex3 v3 = f.edge.nextEdge.nextEdge.v; //Standardize MyMeshVertex my_v1 = new MyMeshVertex(v1.position, v1.normal); MyMeshVertex my_v2 = new MyMeshVertex(v2.position, v2.normal); MyMeshVertex my_v3 = new MyMeshVertex(v3.position, v3.normal); myMesh.AddTriangle(my_v1, my_v2, my_v3, shareVertices: true); } Mesh unityMesh = myMesh.ConvertToUnityMesh(name); return(unityMesh); }
//Add a triangle (oriented clock-wise) to the mesh public void AddTriangle(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, MeshStyle meshStyle) { int index1 = AddVertexAndReturnIndex(v1, meshStyle); int index2 = AddVertexAndReturnIndex(v2, meshStyle); int index3 = AddVertexAndReturnIndex(v3, meshStyle); AddTrianglePositions(index1, index2, index3); }
//Add a triangle (oriented clock-wise) to the mesh //If we want hard edges, set shareVertices to false. Otherwise we will get a smooth surface public void AddTriangle(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, bool shareVertices) { int index1 = AddVertexAndReturnIndex(v1, shareVertices); int index2 = AddVertexAndReturnIndex(v2, shareVertices); int index3 = AddVertexAndReturnIndex(v3, shareVertices); AddTrianglePositions(index1, index2, index3); }
// // Add a triangle to this mesh // //We dont have a normal so we have to calculate it, so make sure v1-v2-v3 is clock-wise public HalfEdgeFace3 AddTriangle(Vector3 p1, Vector3 p2, Vector3 p3, bool findOppositeEdge = false) { Vector3 normal = Vector3.Normalize(Vector3.Cross(p3 - p2, p1 - p2)); MyMeshVertex v1 = new MyMeshVertex(p1, normal); MyMeshVertex v2 = new MyMeshVertex(p2, normal); MyMeshVertex v3 = new MyMeshVertex(p3, normal); HalfEdgeFace3 f = AddTriangle(v1, v2, v3); return(f); }
//Add a vertex to the mesh and return its position in the array //If we want only hard edges, set shareVertices to false. Otherwise we will get a smooth surface //If we want combination of smooth surface and hard edges, set shareVertices and hasHardEdges to true public int AddVertexAndReturnIndex(MyMeshVertex v, MeshStyle meshStyle) { int vertexPosInList = -1; if (meshStyle == MeshStyle.SoftEdges || meshStyle == MeshStyle.HardAndSoftEdges) { for (int i = 0; i < vertices.Count; i++) { Vector3 thisPos = vertices[i]; if (thisPos.Equals(v.position)) { //Here we have to compare both position and normal or we can't get hard edges in combination with soft edges Vector3 thisNormal = normals[i]; if (meshStyle == MeshStyle.HardAndSoftEdges && thisNormal.Equals(v.normal)) { vertexPosInList = i; return(vertexPosInList); } //Sometimes we dont have a normal to compare if (meshStyle == MeshStyle.SoftEdges) { vertexPosInList = i; return(vertexPosInList); } } } } //If we got here it means the vertex is not in the list, so add it as the last vertex vertices.Add(v.position); normals.Add(v.normal); vertexPosInList = vertices.Count - 1; return(vertexPosInList); }
//Convert from MyMesh (which is face-vertex data structure) to half-edge data structure //In the half-edge data structure, each edge has an opposite edge, which may have to be connected public HalfEdgeData3(MyMesh mesh, ConnectOppositeEdges connectOppositeEdges) : this() { //Loop through all triangles in the mesh List <int> triangles = mesh.triangles; List <Vector3> vertices = mesh.vertices; List <Vector3> normals = mesh.normals; for (int i = 0; i < triangles.Count; i += 3) { int index1 = triangles[i + 0]; int index2 = triangles[i + 1]; int index3 = triangles[i + 2]; Vector3 p1 = vertices[index1]; Vector3 p2 = vertices[index2]; Vector3 p3 = vertices[index3]; Vector3 n1 = normals[index1]; Vector3 n2 = normals[index2]; Vector3 n3 = normals[index3]; MyMeshVertex v1 = new MyMeshVertex(p1, n1); MyMeshVertex v2 = new MyMeshVertex(p2, n2); MyMeshVertex v3 = new MyMeshVertex(p3, n3); AddTriangle(v1, v2, v3); } //The fast method is only working if there are no floating point precision issues //So all vertices at the same position are actually at the same position if (connectOppositeEdges == ConnectOppositeEdges.Fast) { ConnectAllEdgesFast(); } else if (connectOppositeEdges == ConnectOppositeEdges.Slow) { ConnectAllEdgesSlow(); } }
//We have just the faces (which we know are triangles) public static MyMesh ConvertToMyMesh(string meshName, HashSet <HalfEdgeFace3> faces, MyMesh.MeshStyle meshStyle) { MyMesh myMesh = new MyMesh(meshName); //Loop through each triangle foreach (HalfEdgeFace3 f in faces) { //These should have been stored clock-wise HalfEdgeVertex3 v1 = f.edge.v; HalfEdgeVertex3 v2 = f.edge.nextEdge.v; HalfEdgeVertex3 v3 = f.edge.nextEdge.nextEdge.v; //Standardize MyMeshVertex my_v1 = new MyMeshVertex(v1.position, v1.normal); MyMeshVertex my_v2 = new MyMeshVertex(v2.position, v2.normal); MyMeshVertex my_v3 = new MyMeshVertex(v3.position, v3.normal); myMesh.AddTriangle(my_v1, my_v2, my_v3, meshStyle); } return(myMesh); }
//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); }
//Fill the hole (or holes) in the mesh private static HashSet <Hole> FillHoles(HashSet <HalfEdge3> holeEdgesI, HashSet <HalfEdge3> holeEdgesO, OrientedPlane3 orientedCutPlane, Transform meshTrans, MyVector3 planeNormal) { if (holeEdgesI == null || holeEdgesI.Count == 0) { Debug.Log("This mesh has no hole"); return(null); } //Find all separate holes HashSet <List <HalfEdge3> > allHoles = IdentifySeparateHoles(holeEdgesI); if (allHoles.Count == 0) { Debug.LogWarning("Couldn't identify any holes even though we have hole edges"); return(null); } //Debug //foreach (List<HalfEdge3> hole in allHoles) //{ // foreach (HalfEdge3 e in hole) // { // Debug.DrawLine(meshTrans.TransformPoint(e.v.position.ToVector3()), Vector3.zero, Color.white, 5f); // } //} //Fill the hole with a mesh HashSet <Hole> holeMeshes = new HashSet <Hole>(); foreach (List <HalfEdge3> hole in allHoles) { HalfEdgeData3 holeMeshI = new HalfEdgeData3(); HalfEdgeData3 holeMeshO = new HalfEdgeData3(); //Transform vertices to local position of the cut plane to make it easier to triangulate with Ear Clipping //Ear CLipping wants vertices in 2d List <MyVector2> sortedEdges_2D = new List <MyVector2>(); Transform planeTrans = orientedCutPlane.planeTrans; foreach (HalfEdge3 e in hole) { MyVector3 pMeshSpace = e.v.position; //Mesh space to Global space Vector3 pGlobalSpace = meshTrans.TransformPoint(pMeshSpace.ToVector3()); //Global space to Plane space Vector3 pPlaneSpace = planeTrans.InverseTransformPoint(pGlobalSpace); //Y is normal direction so should be 0 MyVector2 p2D = new MyVector2(pPlaneSpace.x, pPlaneSpace.z); sortedEdges_2D.Add(p2D); } //Triangulate with Ear Clipping HashSet <Triangle2> triangles = _EarClipping.Triangulate(sortedEdges_2D, null, optimizeTriangles: false); //Debug.Log($"Number of triangles from Ear Clipping: {triangles.Count}"); //Transform vertices to mesh space and half-edge data structure foreach (Triangle2 t in triangles) { //3d space Vector3 p1 = new Vector3(t.p1.x, 0f, t.p1.y); Vector3 p2 = new Vector3(t.p2.x, 0f, t.p2.y); Vector3 p3 = new Vector3(t.p3.x, 0f, t.p3.y); //Plane space to Global space Vector3 p1Global = planeTrans.TransformPoint(p1); Vector3 p2Global = planeTrans.TransformPoint(p2); Vector3 p3Global = planeTrans.TransformPoint(p3); //Global space to Mesh space Vector3 p1Mesh = meshTrans.InverseTransformPoint(p1Global); Vector3 p2Mesh = meshTrans.InverseTransformPoint(p2Global); Vector3 p3Mesh = meshTrans.InverseTransformPoint(p3Global); //For inside mesh MyMeshVertex v1_I = new MyMeshVertex(p1Mesh.ToMyVector3(), planeNormal); MyMeshVertex v2_I = new MyMeshVertex(p2Mesh.ToMyVector3(), planeNormal); MyMeshVertex v3_I = new MyMeshVertex(p3Mesh.ToMyVector3(), planeNormal); //For inside mesh MyMeshVertex v1_O = new MyMeshVertex(p1Mesh.ToMyVector3(), -planeNormal); MyMeshVertex v2_O = new MyMeshVertex(p2Mesh.ToMyVector3(), -planeNormal); MyMeshVertex v3_O = new MyMeshVertex(p3Mesh.ToMyVector3(), -planeNormal); //Now we can finally add this triangle to the half-edge data structure AddTriangleToMesh(v1_I, v2_I, v3_I, holeMeshI, null); AddTriangleToMesh(v1_O, v3_O, v2_O, holeMeshO, null); } //We also need an edge belonging to the mesh (not hole mesh) to easier merge mesh with hole //The hole edges were generated for the Inside mesh HalfEdge3 holeEdgeI = hole[0]; //But we also need an edge for the Outside mesh bool foundCorrespondingEdge = false; MyVector3 eGoingTo = holeEdgeI.v.position; MyVector3 eGoingFrom = holeEdgeI.prevEdge.v.position; foreach (HalfEdge3 holeEdgeO in holeEdgesO) { MyVector3 eOppsiteGoingTo = holeEdgeO.v.position; MyVector3 eOppsiteGoingFrom = holeEdgeO.prevEdge.v.position; if (eOppsiteGoingTo.Equals(eGoingFrom) && eOppsiteGoingFrom.Equals(eGoingTo)) { Hole newHoleMesh = new Hole(holeMeshI, holeMeshO, holeEdgeI, holeEdgeO); holeMeshes.Add(newHoleMesh); foundCorrespondingEdge = true; break; } } if (!foundCorrespondingEdge) { Debug.Log("Couldnt find opposite edge in hole, so no hole was added"); } } return(holeMeshes); }
//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); }
//v1-v2-v3 should be clock-wise which is Unity standard public HalfEdgeFace3 AddTriangle(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, bool findOppositeEdge = false) { //Create three new vertices HalfEdgeVertex3 half_v1 = new HalfEdgeVertex3(v1.position, v1.normal); HalfEdgeVertex3 half_v2 = new HalfEdgeVertex3(v2.position, v2.normal); HalfEdgeVertex3 half_v3 = new HalfEdgeVertex3(v3.position, v3.normal); //Create three new half-edges that points TO these vertices HalfEdge3 e_to_v1 = new HalfEdge3(half_v1); HalfEdge3 e_to_v2 = new HalfEdge3(half_v2); HalfEdge3 e_to_v3 = new HalfEdge3(half_v3); //Create the face (which is a triangle) which needs a reference to one of the edges HalfEdgeFace3 f = new HalfEdgeFace3(e_to_v1); //Connect the data: //Connect the edges clock-wise e_to_v1.nextEdge = e_to_v2; e_to_v2.nextEdge = e_to_v3; e_to_v3.nextEdge = e_to_v1; e_to_v1.prevEdge = e_to_v3; e_to_v2.prevEdge = e_to_v1; e_to_v3.prevEdge = e_to_v2; //Each vertex needs a reference to an edge going FROM that vertex half_v1.edge = e_to_v2; half_v2.edge = e_to_v3; half_v3.edge = e_to_v1; //Each edge needs a reference to the face e_to_v1.face = f; e_to_v2.face = f; e_to_v3.face = f; //Each edge needs an opposite edge //This is slow process //You could do this afterwards when all triangles have been generate //Doing it in this method takes 2.7 seconds for the bunny //Doing it afterwards takes 0.1 seconds by using the fast method and 1.6 seconds for the slow method //The reason is that we keep searching the list for an opposite which doesnt exist yet, so we get more searches even though //the list is shorter as we build up the mesh //But you could maybe do it here if you just add a new triangle? if (findOppositeEdge) { TryFindOppositeEdge(e_to_v1); TryFindOppositeEdge(e_to_v2); TryFindOppositeEdge(e_to_v3); } //Save the data this.verts.Add(half_v1); this.verts.Add(half_v2); this.verts.Add(half_v3); this.edges.Add(e_to_v1); this.edges.Add(e_to_v2); this.edges.Add(e_to_v3); this.faces.Add(f); return(f); }