//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); }
//HashSet<HalfEdgeFace3> public HashSet <HalfEdgeFace3> UnNormalize(HashSet <HalfEdgeFace3> data) { foreach (HalfEdgeFace3 f in data) { //TODO: This will generate a new list for each face, so maybe better to put the code from the method here List <HalfEdge3> edges = f.GetEdges(); if (edges == null) { continue; } foreach (HalfEdge3 e in edges) { HalfEdgeVertex3 v = e.v; v.position = UnNormalize(v.position); } } return(data); }
//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); }
public HalfEdge3(HalfEdgeVertex3 v) { this.v = v; }
//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); }
// // Contract an edge if we know we are dealing only with triangles // //Returns all edge pointing to the new vertex public HashSet <HalfEdge3> ContractTriangleHalfEdge(HalfEdge3 e, Vector3 mergePos, System.Diagnostics.Stopwatch timer = null) { //Step 1. Get all edges pointing to the vertices we will merge //And edge is going TO a vertex, so this edge goes from v1 to v2 HalfEdgeVertex3 v1 = e.prevEdge.v; HalfEdgeVertex3 v2 = e.v; //timer.Start(); //It's better to get these before we remove triangles because then we will get a messed up half-edge system? HashSet <HalfEdge3> edgesGoingToVertex_v1 = v1.GetEdgesPointingToVertex(this); HashSet <HalfEdge3> edgesGoingToVertex_v2 = v2.GetEdgesPointingToVertex(this); //timer.Stop(); //Step 2. Remove the triangles, which will create a hole, //and the edges on the opposite sides of the hole are connected RemoveTriangleAndConnectOppositeSides(e); //We might also have an opposite triangle, so we may have to delete a total of two triangles if (e.oppositeEdge != null) { RemoveTriangleAndConnectOppositeSides(e.oppositeEdge); } //Step 3. Move the vertices to the merge position //Some of these edges belong to the triangles we removed, but it doesnt matter because this operation is fast //We can at the same time find the edges pointing to the new vertex HashSet <HalfEdge3> edgesPointingToVertex = new HashSet <HalfEdge3>(); if (edgesGoingToVertex_v1 != null) { foreach (HalfEdge3 edgeToV in edgesGoingToVertex_v1) { //This edge belonged to one of the faces we removed if (edgeToV.face == null) { continue; } edgeToV.v.position = mergePos; edgesPointingToVertex.Add(edgeToV); } } if (edgesGoingToVertex_v2 != null) { foreach (HalfEdge3 edgeToV in edgesGoingToVertex_v2) { //This edge belonged to one of the faces we removed if (edgeToV.face == null) { continue; } edgeToV.v.position = mergePos; edgesPointingToVertex.Add(edgeToV); } } return(edgesPointingToVertex); }
//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); }