// Every face should be a triangle! So call Triangulate first public void SplitInLeftAndRightMesh(HalfEdgeMesh left, HalfEdgeMesh right) { // Have to re-index everything :( Dictionary <Vector3, short> leftVertDict = new Dictionary <Vector3, short>(); Dictionary <Vector3, short> rightVertDict = new Dictionary <Vector3, short>(); // First add the vertices, and get their indices short lVertIdx = 0; short rVertIdx = 0; foreach (HEVertex v in vertices) { HEVertex newVL = new HEVertex { v = v.v, heIndex = v.heIndex }; HEVertex newVR = new HEVertex { v = v.v, heIndex = v.heIndex }; if (v.config == PlaneConfig.Left) { newVL.config = PlaneConfig.Left; leftVertDict.Add(v.v, lVertIdx); left.vertices.Add(newVL); lVertIdx++; } else if (v.config == PlaneConfig.Right) { newVR.config = PlaneConfig.Right; rightVertDict.Add(v.v, rVertIdx); right.vertices.Add(newVR); rVertIdx++; } else { newVR.config = PlaneConfig.On; newVL.config = PlaneConfig.On; leftVertDict.Add(v.v, lVertIdx); left.vertices.Add(newVL); lVertIdx++; rightVertDict.Add(v.v, rVertIdx); right.vertices.Add(newVR); rVertIdx++; } } // The dicts should map old idx -> new idx... Dictionary <short, short> leftEdgeIdxMap = new Dictionary <short, short>(); Dictionary <short, short> rightEdgeIdxMap = new Dictionary <short, short>(); Dictionary <short, short> leftFaceIdxMap = new Dictionary <short, short>(); Dictionary <short, short> rightFaceIdxMap = new Dictionary <short, short>(); int lEdgeIdx = 0; int lFaceIdx = 0; int rEdgeIdx = 0; int rFaceIdx = 0; // Build the idx maps foreach (HalfEdge h in halfEdges) { PlaneConfig pConfig = GetFaceConfig(faces[h.faceIndex]); if (pConfig == PlaneConfig.Left) { leftEdgeIdxMap.Add(h.index, (short)lEdgeIdx); left.halfEdges.Add(h.Copy()); lEdgeIdx++; if (!leftFaceIdxMap.ContainsKey(h.faceIndex)) { left.faces.Add(faces[h.faceIndex].Copy()); left.faces[lFaceIdx].heIndex = (short)(lEdgeIdx - 1); leftFaceIdxMap.Add(h.faceIndex, (short)lFaceIdx); lFaceIdx++; } } else if (pConfig == PlaneConfig.Right) { rightEdgeIdxMap.Add(h.index, (short)rEdgeIdx); right.halfEdges.Add(h.Copy()); rEdgeIdx++; if (!rightFaceIdxMap.ContainsKey(h.faceIndex)) { right.faces.Add(faces[h.faceIndex].Copy()); right.faces[rFaceIdx].heIndex = (short)(rEdgeIdx - 1); rightFaceIdxMap.Add(h.faceIndex, (short)rFaceIdx); rFaceIdx++; } } else // On, not possible for a face? { // ... } } // Update all the indices and stuff // Edge case is if both vertices are ON the plane, then they don't have an opposite edge // I think I will leave it and deal with it when triangulating the cap foreach (KeyValuePair <short, short> kvp in leftEdgeIdxMap) { // The edge left.halfEdges[kvp.Value].faceIndex = leftFaceIdxMap[halfEdges[kvp.Key].faceIndex]; left.halfEdges[kvp.Value].nextIndex = leftEdgeIdxMap[halfEdges[kvp.Key].nextIndex]; left.halfEdges[kvp.Value].verIndex = leftVertDict[vertices[halfEdges[kvp.Key].verIndex].v]; left.halfEdges[kvp.Value].index = kvp.Value; // face done in loop above // Update vertices edge index, doesn't matter if we overwrite left.vertices[left.halfEdges[kvp.Value].verIndex].heIndex = kvp.Value; } foreach (KeyValuePair <short, short> kvp in rightEdgeIdxMap) { // The edge right.halfEdges[kvp.Value].faceIndex = rightFaceIdxMap[halfEdges[kvp.Key].faceIndex]; right.halfEdges[kvp.Value].nextIndex = rightEdgeIdxMap[halfEdges[kvp.Key].nextIndex]; right.halfEdges[kvp.Value].verIndex = rightVertDict[vertices[halfEdges[kvp.Key].verIndex].v]; right.halfEdges[kvp.Value].index = kvp.Value; // face done in loop above // Update vertices edge index, doesn't matter if we overwrite right.vertices[right.halfEdges[kvp.Value].verIndex].heIndex = kvp.Value; } }
public void CutWithPlane(Plane plane) { float sTime = Time.realtimeSinceStartup; GameObject child = gameObject.transform.GetChild(0).gameObject; meshFilter = child.GetComponent <MeshFilter>(); mesh = meshFilter.mesh; InitHalfEdgeMesh(); bool[] visited = new bool[heMesh.halfEdges.Count]; List <HalfEdge> newHalfEdges = new List <HalfEdge>(); int s = heMesh.halfEdges.Count; int rightCount = 0; int leftCount = 0; Vector3 newVertCoord = Vector3.zero; int onCount = 0; /* * Dictionary<Vector3, Vector2> uvMap = new Dictionary<Vector3, Vector2>(); * List<Vector2> uvs = new List<Vector2>(); * mesh.GetUVs(0, uvs); * for (int i = 0; i < mesh.vertices.Length; i++) * { * if (!uvMap.ContainsKey(mesh.vertices[i])) { * uvMap.Add(mesh.vertices[i], uvs[i]); * } * }*/ for (int i = 0; i < s; i++) { if (visited[i]) { continue; } // mark this and the opposite as visited HalfEdge he = heMesh.halfEdges[i]; HalfEdge heOpp = heMesh.halfEdges[he.oppositeIndex]; visited[he.index] = true; visited[heOpp.index] = true; // Check if intersection with plane Vector3 v0 = heMesh.vertices[he.verIndex].v; Vector3 v1 = heMesh.vertices[heOpp.verIndex].v; // First Set vertex plane config Vector3 c0 = v0 - plane.point; Vector3 c1 = v1 - plane.point; float c0_dot = Vector3.Dot(c0, plane.normal); float c1_dot = Vector3.Dot(c1, plane.normal); float eps = 0.0001f; if (Mathf.Abs(c0_dot) < eps) { heMesh.vertices[he.verIndex].config = PlaneConfig.On; } else if (c0_dot <= -eps) { heMesh.vertices[he.verIndex].config = PlaneConfig.Right; rightCount++; } else if (c0_dot >= eps) { heMesh.vertices[he.verIndex].config = PlaneConfig.Left; leftCount++; } if (Mathf.Abs(c1_dot) < eps) { heMesh.vertices[heOpp.verIndex].config = PlaneConfig.On; } else if (c1_dot <= -eps) { rightCount++; heMesh.vertices[heOpp.verIndex].config = PlaneConfig.Right; } else if (c1_dot >= eps) { leftCount++; heMesh.vertices[heOpp.verIndex].config = PlaneConfig.Left; } float t; Vector3 iPoint = Plane.LinePlaneIntersect(plane, v0, v1, out t); if (t > 1.0f - eps || t < 0.0f + eps) { // No intersection on line segment OR parallel with plane continue; } // Debug.Log(t); // add new intersection vertex to half-edge structure HEVertex iVert = new HEVertex(); iVert.v = iPoint; onCount += 1; newVertCoord += iPoint; iVert.heIndex = (short)heMesh.halfEdges.Count; iVert.config = PlaneConfig.On; heMesh.vertices.Add(iVert); HalfEdge[] newHEs = HalfEdge.CreateFromTwo(he, heOpp, (short)heMesh.halfEdges.Count, (short)(heMesh.vertices.Count - 1)); heMesh.halfEdges.Add(newHEs[0]); heMesh.halfEdges.Add(newHEs[1]); iPoints.Add(iPoint); } if (leftCount == 0 || rightCount == 0) { // Do nothing!!!! return; } heMesh.Triangulate(); HalfEdgeMesh rightMesh = new HalfEdgeMesh(); HalfEdgeMesh leftMesh = new HalfEdgeMesh(); heMesh.SplitInLeftAndRightMesh(leftMesh, rightMesh); GameObject copy = Instantiate(objectToCopy); GameObject childCopy = copy.transform.GetChild(0).gameObject; SliceableWithPlane copyDebug = copy.GetComponent <SliceableWithPlane>(); newVertCoord /= (float)onCount; rightMesh.CapClipPlane(plane.normal, newVertCoord); copyDebug.heMesh = rightMesh; copyDebug.meshFilter = childCopy.GetComponent <MeshFilter>(); copyDebug.meshFilter.mesh = rightMesh.GetMesh(); // MeshFilter mfCpy = copy.GetComponent<MeshFilter>(); leftMesh.CapClipPlane(-plane.normal, newVertCoord); meshFilter.mesh = leftMesh.GetMesh(); /* * List<Vector2> newUVs = new List<Vector2>(); * foreach (Vector3 v in meshFilter.mesh.vertices) * { * if (!uvMap.ContainsKey(v)) * { * newUVs.Add(Vector2.zero); * } else * { * newUVs.Add(uvMap[v]); * } * } * meshFilter.mesh.SetUVs(0, newUVs); */ copyDebug.cutPlane = cutPlane; //heMesh.CreateStructureFromMesh(mesh); //copyDebug.heMesh.CreateStructureFromMesh(copyDebug.meshFilter.mesh); child.GetComponent <MeshCollider>().sharedMesh = null; child.GetComponent <MeshCollider>().sharedMesh = meshFilter.mesh; childCopy.GetComponent <MeshCollider>().sharedMesh = null; childCopy.GetComponent <MeshCollider>().sharedMesh = copyDebug.meshFilter.mesh; // child.GetComponent<MeshCollider>().gameObject.SetActive(true); float eTime = Time.realtimeSinceStartup - sTime; Debug.Log("Time taken = " + eTime); }
public void CreateStructureFromMesh(Mesh mesh) { faces.Clear(); vertices.Clear(); halfEdges.Clear(); int[] meshTriangles = mesh.triangles; Vector3[] meshVertices = mesh.vertices; Vector3[] meshNormals = mesh.normals; // One face per triangle. Add normals for (int i = 0; i < meshTriangles.Length; i += 3) { HEFace f = new HEFace(); // Take the mean of the triangle normals Vector3 n = meshNormals[meshTriangles[i]] + meshNormals[meshTriangles[i + 1]] + meshNormals[meshTriangles[i + 2]]; n = n / 3.0f; f.normal = n.normalized; faces.Add(f); } // Simplify mesh vertices i.e. remove duplicates. // There are duplicate vertices since they have different normals // Use a dictionary to store the vertices and their new indices Dictionary <Vector3, int> d = new Dictionary <Vector3, int>(); List <Vector3> newVerts = new List <Vector3>(); List <int> newTris = new List <int>(); int newIdx = 0; for (int i = 0; i < meshVertices.Length; i++) { if (!d.ContainsKey(meshVertices[i])) { d[meshVertices[i]] = newIdx; newVerts.Add(meshVertices[i]); newIdx++; } } // Update the triangle indices to the new vertex indices. for (int i = 0; i < meshTriangles.Length; i++) { newTris.Add(d[meshVertices[meshTriangles[i]]]); } // Set to updated values meshVertices = newVerts.ToArray(); meshTriangles = newTris.ToArray(); // Init the vertices with vertex data, but not half-edge for (int i = 0; i < meshVertices.Length; i++) { HEVertex vert = new HEVertex { v = meshVertices[i] }; vertices.Add(vert); } // Dictionary for opposite lookup. Build in first loop over triangles, used in the second. // key is v0v1 Dictionary <string, List <short> > edgeMap = new Dictionary <string, List <short> >(); // Start with creating the halfedges and filling in everything except oppositite for (int i = 0; i < meshTriangles.Length; i += 3) { // Stored 3-by-3 indices. e.g. 0,1,2 forms the first triangle. short i0 = (short)i; short i1 = (short)(i + 1); short i2 = (short)(i + 2); // Triangles are in clockwise order in Unity HalfEdge h0 = new HalfEdge(); HalfEdge h1 = new HalfEdge(); HalfEdge h2 = new HalfEdge(); h0.index = i0; h1.index = i1; h2.index = i2; h0.faceIndex = (short)(i0 / 3); h1.faceIndex = (short)(i0 / 3); h2.faceIndex = (short)(i0 / 3); h0.verIndex = (short)meshTriangles[i]; h1.verIndex = (short)meshTriangles[i + 1]; h2.verIndex = (short)meshTriangles[i + 2]; // Set the vertices halfedge index. It doesn't matter if we overwrite previously set values. vertices[meshTriangles[i]].heIndex = i0; vertices[meshTriangles[i + 1]].heIndex = i1; vertices[meshTriangles[i + 2]].heIndex = i2; // next entries. Unity wants clockwise order of vertices h0.nextIndex = h1.index; h1.nextIndex = h2.index; h2.nextIndex = h0.index; // Add them! halfEdges.Add(h0); halfEdges.Add(h1); halfEdges.Add(h2); // set face HalfEdge index. i0 is a multiple of 3 faces[i0 / 3].heIndex = i0; // Fill edgemap, three times :) string sum; // This check is required so the ordering is consistent if (h0.verIndex >= h1.verIndex) { sum = h0.verIndex.ToString() + h1.verIndex.ToString(); } else { sum = h1.verIndex.ToString() + h0.verIndex.ToString(); } if (!edgeMap.ContainsKey(sum)) { List <short> l = new List <short>(); // add the face index l.Add(i0); edgeMap.Add(sum, l); } else { edgeMap[sum].Add(i0); } if (h1.verIndex >= h2.verIndex) { sum = h1.verIndex.ToString() + h2.verIndex.ToString(); } else { sum = h2.verIndex.ToString() + h1.verIndex.ToString(); } if (!edgeMap.ContainsKey(sum)) { List <short> l = new List <short>(); // add the face index l.Add(i1); edgeMap.Add(sum, l); } else { edgeMap[sum].Add(i1); } if (h0.verIndex >= h2.verIndex) { sum = h0.verIndex.ToString() + h2.verIndex.ToString(); } else { sum = h2.verIndex.ToString() + h0.verIndex.ToString(); } if (!edgeMap.ContainsKey(sum)) { List <short> l = new List <short>(); // add the face index l.Add(i2); edgeMap.Add(sum, l); } else { edgeMap[sum].Add(i2); } } // Fill the opposite entries for (int i = 0; i < meshTriangles.Length; i += 3) { // each edge is shared between exactly two faces // Stored 3-by-3 indices. e.g. 0,1,2 forms the first triangle. short i0 = (short)i; short i1 = (short)(i + 1); short i2 = (short)(i + 2); HalfEdge h0 = halfEdges[i0]; HalfEdge h1 = halfEdges[i1]; HalfEdge h2 = halfEdges[i2]; // First edge // Use our edgeMap to find opposites string sum; if (h0.verIndex >= h1.verIndex) { sum = h0.verIndex.ToString() + h1.verIndex.ToString(); } else { sum = h1.verIndex.ToString() + h0.verIndex.ToString(); } List <short> l = edgeMap[sum]; short h0Idx = l[0]; short h1Idx = l[1]; halfEdges[h0Idx].oppositeIndex = h1Idx; halfEdges[h1Idx].oppositeIndex = h0Idx; // Second edge if (h1.verIndex >= h2.verIndex) { sum = h1.verIndex.ToString() + h2.verIndex.ToString(); } else { sum = h2.verIndex.ToString() + h1.verIndex.ToString(); } l = edgeMap[sum]; h0Idx = l[0]; h1Idx = l[1]; halfEdges[h0Idx].oppositeIndex = h1Idx; halfEdges[h1Idx].oppositeIndex = h0Idx; // Third edge if (h0.verIndex >= h2.verIndex) { sum = h0.verIndex.ToString() + h2.verIndex.ToString(); } else { sum = h2.verIndex.ToString() + h0.verIndex.ToString(); } l = edgeMap[sum]; h0Idx = l[0]; h1Idx = l[1]; halfEdges[h0Idx].oppositeIndex = h1Idx; halfEdges[h1Idx].oppositeIndex = h0Idx; } }
public void CapClipPlane(Vector3 planeNormal, Vector3 newVertCoord) { // VERY IMPORTANT: Changes the half-edge structure but it is not a complete structure! // No oppositeIndices are added. Can be made complete by generating the mesh and then turn the mesh // into a half-edge structure. Major hack and major performance loss. // Idea: // Cap the clip by generating a new vertex at the center of the clipping area. // Then take all faces with two ON vertices and create a new face :- ) // Then do MeshFromHalfedge -> and back again for a good half-edge structure.... // Calculate new vertex pos Vector3 centerVert = new Vector3(0.0f, 0.0f, 0.0f); foreach (HEVertex v in vertices) { if (v.config == PlaneConfig.On) { centerVert += v.v; } } centerVert = centerVert / (float)vertices.Count; HEVertex newVert = new HEVertex { v = newVertCoord, config = PlaneConfig.On }; vertices.Add(newVert); short verIdx = (short)(vertices.Count - 1); // Get all faces with two ON vertices Dictionary <short, List <short> > onFaces = GetTwoOnFaces(); foreach (KeyValuePair <short, List <short> > kvp in onFaces) { // Make new face HalfEdge h0 = new HalfEdge(); HalfEdge h1 = new HalfEdge(); HalfEdge h2 = new HalfEdge(); short heIdx = (short)(halfEdges.Count); h0.index = heIdx; h1.index = (short)(heIdx + 1); h2.index = (short)(heIdx + 2); h0.nextIndex = h1.index; h1.nextIndex = h2.index; h2.nextIndex = h0.index; h0.verIndex = kvp.Value[0]; h1.verIndex = verIdx; h2.verIndex = kvp.Value[1]; // check correct winding order Vector3 v1 = (vertices[h1.verIndex].v - vertices[h0.verIndex].v).normalized; Vector3 v2 = (vertices[h2.verIndex].v - vertices[h0.verIndex].v).normalized; Vector3 c = Vector3.Cross(v1, v2).normalized; Vector3 diff = c - planeNormal; float diff_m = diff.magnitude; // Debug.Log("Cross = " + (Vector3.Cross(v1.normalized, v2.normalized) - planeNormal.normalized).magnitude + ". Normal = " + planeNormal + ". " + (Vector3.Cross(v1.normalized, v2.normalized))); if (diff_m >= 0.01f) { h0.nextIndex = h2.index; h1.nextIndex = h0.index; h2.nextIndex = h1.index; } HEFace newFace = new HEFace { heIndex = h0.index, normal = planeNormal }; short faceIdx = (short)faces.Count; h0.faceIndex = faceIdx; h1.faceIndex = faceIdx; h2.faceIndex = faceIdx; halfEdges.Add(h0); halfEdges.Add(h1); halfEdges.Add(h2); faces.Add(newFace); } }
/** * Splits a vertex in two along a plane. Returns true if the vertex can be split, false otherwise. Does not create new border * edges inside the tear in order to prevent non-manifold vertices emerging, so it is only suitable for realtime cloth tearing. * \param vertex the vertex to split. * \param splitPlane plane to split the vertex at. * \param newVertex the newly created vertex after the split operation has been performed. * \param vertices new mesh vertices list after the split operation. * \param removedEdges list of edge ids removed after splitting the vertex. * \param addedEdges list of edge ids added after splitting the vertex. * \param oldAndNewEdges a dictionary relating old and new edge ids. */ public bool SplitVertex(HEVertex vertex, Plane splitPlane, out HEVertex newVertex, out Vector3[] vertices, out HashSet<int> removedEdges, out HashSet<int> addedEdges, out Dictionary<int,int> oldAndNewEdges) { // initialize return values: removedEdges = new HashSet<int>(); addedEdges = new HashSet<int>(); oldAndNewEdges = new Dictionary<int,int>(); newVertex = null; // initialize face lists for each side of the split plane. List<HEFace> side1Faces = new List<HEFace>(); List<HEFace> side2Faces = new List<HEFace>(); HashSet<int> side2Edges = new HashSet<int>(); // Get a copy of mesh vertices: vertices = input.vertices; // classify adjacent faces depending on which side of the cut plane they reside in: foreach(HEFace face in GetNeighbourFacesEnumerator(vertex)){ int v1 = heEdges[face.edges[0]].startVertex; int v2 = heEdges[face.edges[1]].startVertex; int v3 = heEdges[face.edges[2]].startVertex; //Skip this face if it doesnt contain the splitted vertex. if (v1 != vertex.index && v2 != vertex.index && v3 != vertex.index) continue; Vector3 faceCenter = (vertices[heVertices[v1].physicalIDs[0]] + vertices[heVertices[v2].physicalIDs[0]] + vertices[heVertices[v3].physicalIDs[0]]) / 3.0f; if (splitPlane.GetSide(faceCenter)){ side1Faces.Add(face); }else{ side2Faces.Add(face); foreach(int e in face.edges) side2Edges.Add(e); } } // If the vertex cant be split, return false. if (side1Faces.Count == 0 || side2Faces.Count == 0) return false; // create new mesh vertex and triangle buffers: vertices = new Vector3[input.vertexCount+1]; Array.Copy(input.vertices,vertices,input.vertexCount); int[] triangles = input.triangles; // create a new vertex: newVertex = new HEVertex(input.vertexCount); newVertex.index = heVertices.Count; heVertices.Add(newVertex); // add the new vertex to the mesh vertices buffer: vertices[input.vertexCount] = vertices[vertex.physicalIDs[0]]; // Copy uvs, colors and other mesh info. Vector2[] uv = null; Vector2[] uv2 = null; Vector2[] uv3 = null; Vector2[] uv4 = null; Color32[] colors = null; if (input.uv.Length > 0){ uv = new Vector2[input.uv.Length+1]; Array.Copy(input.uv,uv,input.uv.Length); uv[input.uv.Length] = uv[vertex.physicalIDs[0]]; //TODO: could cause copying uvs from the other side of the cut... } if (input.uv2.Length > 0){ uv2 = new Vector2[input.uv2.Length+1]; Array.Copy(input.uv2,uv2,input.uv2.Length); uv2[input.uv2.Length] = uv2[vertex.physicalIDs[0]]; } if (input.uv3.Length > 0){ uv3 = new Vector2[input.uv3.Length+1]; Array.Copy(input.uv3,uv3,input.uv3.Length); uv3[input.uv3.Length] = uv3[vertex.physicalIDs[0]]; } if (input.uv4.Length > 0){ uv4 = new Vector2[input.uv4.Length+1]; Array.Copy(input.uv4,uv4,input.uv4.Length); uv4[input.uv4.Length] = uv4[vertex.physicalIDs[0]]; } if (input.colors32.Length > 0){ colors = new Color32[input.colors32.Length+1]; Array.Copy(input.colors32,colors,input.colors32.Length); colors[input.colors32.Length] = colors[vertex.physicalIDs[0]]; } // rearrange edges at side 1: foreach(HEFace face in side1Faces){ // find half edges that start or end at the split vertex: HEEdge edgeIn = heEdges[Array.Find<int>(face.edges,e => heEdges[e].endVertex == vertex.index)]; HEEdge edgeOut = heEdges[Array.Find<int>(face.edges,e => heEdges[e].startVertex == vertex.index)]; int oldInID = ObiUtils.Pair(edgeIn.startVertex,edgeIn.endVertex); int oldOutID = ObiUtils.Pair(edgeOut.startVertex,edgeOut.endVertex); if (ShouldRemoveEdge(edgeIn,side2Edges.Contains(edgeIn.pair))) removedEdges.Add(oldInID); if (ShouldRemoveEdge(edgeOut,side2Edges.Contains(edgeOut.pair))) removedEdges.Add(oldOutID); // stitch half edges to new vertex edgeIn.endVertex = newVertex.index; edgeOut.startVertex = newVertex.index; newVertex.halfEdgeIndex = edgeOut.index; int newInID = ObiUtils.Pair(edgeIn.startVertex,edgeIn.endVertex); int newOutID = ObiUtils.Pair(edgeOut.startVertex,edgeOut.endVertex); addedEdges.Add(newInID); addedEdges.Add(newOutID); if (!oldAndNewEdges.ContainsKey(newInID)) oldAndNewEdges.Add(newInID,oldInID); if (!oldAndNewEdges.ContainsKey(newOutID)) oldAndNewEdges.Add(newOutID,oldOutID); // update mesh triangle buffer to point at new vertex where needed: if (triangles[face.index*3] == vertex.physicalIDs[0]) triangles[face.index*3] = newVertex.physicalIDs[0]; if (triangles[face.index*3+1] == vertex.physicalIDs[0]) triangles[face.index*3+1] = newVertex.physicalIDs[0]; if (triangles[face.index*3+2] == vertex.physicalIDs[0]) triangles[face.index*3+2] = newVertex.physicalIDs[0]; } // update input mesh: input.vertices = vertices; input.triangles = triangles; if (uv != null) input.uv = uv; if (uv2 != null) input.uv2 = uv2; if (uv3 != null) input.uv3 = uv3; if (uv4 != null) input.uv4 = uv4; if (colors != null) input.colors32 = colors; _closed = false; _modified = true; return true; }
public IEnumerable<HEVertex> GetNeighbourVerticesEnumerator(HEVertex vertex) { HEEdge startEdge = heEdges[vertex.halfEdgeIndex]; HEEdge edge = startEdge; do{ edge = heEdges[edge.pair]; yield return heVertices[edge.startVertex]; edge = heEdges[edge.nextEdgeIndex]; } while (edge != startEdge); }
public IEnumerable<HEFace> GetNeighbourFacesEnumerator(HEVertex vertex) { HEEdge startEdge = heEdges[vertex.halfEdgeIndex]; HEEdge edge = startEdge; do{ edge = heEdges[edge.pair]; if (edge.faceIndex > -1) yield return heFaces[edge.faceIndex]; edge = heEdges[edge.nextEdgeIndex]; } while (edge != startEdge); }
/** * Analyzes the input mesh and populates the half-edge structure. Can be called as many times you want (for examples if the original mesh is modified). */ public IEnumerator Generate() { if (input != null){ heFaces.Clear(); heVertices.Clear(); heEdges.Clear(); _area = 0; _modified = false; Dictionary<Vector3, HEVertex> vertexBuffer = new Dictionary<Vector3, HEVertex>(); Dictionary<KeyValuePair<int,int>,HEEdge> edgeBuffer = new Dictionary<KeyValuePair<int,int>,HEEdge>(); // Get copies of vertex and triangle buffers: Vector3[] vertices = input.vertices; int[] triangles = input.triangles; // first, create vertices: for(int i = 0; i < vertices.Length; i++){ //if the vertex already exists, add physical vertex index to it. HEVertex vertex; if (vertexBuffer.TryGetValue(vertices[i], out vertex)){ vertex.physicalIDs.Add(i); }else{ vertex = new HEVertex(i); } vertexBuffer[vertices[i]] = vertex; if (i % 200 == 0) yield return new CoroutineJob.ProgressInfo("Half-edge: analyzing vertices...",i/(float)vertices.Length); } // assign unique indices to vertices: int index = 0; foreach(KeyValuePair<Vector3,HEVertex> pair in vertexBuffer){ ((HEVertex)pair.Value).index = index; heVertices.Add(pair.Value); if (index % 200 == 0) yield return new CoroutineJob.ProgressInfo("Half-edge: assigning indices...",index/(float)vertices.Length); index++; } // build half edge structure: for(int i = 0; i<triangles.Length;i+=3){ Vector3 pos1 = vertices[triangles[i]]; Vector3 pos2 = vertices[triangles[i+1]]; Vector3 pos3 = vertices[triangles[i+2]]; HEVertex v1 = vertexBuffer[pos1]; HEVertex v2 = vertexBuffer[pos2]; HEVertex v3 = vertexBuffer[pos3]; // create half edges: HEEdge e1 = new HEEdge(); e1.index = heEdges.Count; e1.indexOnFace = 0; e1.faceIndex = heFaces.Count; e1.endVertex = v1.index; e1.startVertex = v2.index; HEEdge e2 = new HEEdge(); e2.index = heEdges.Count+1; e2.indexOnFace = 1; e2.faceIndex = heFaces.Count; e2.endVertex = v2.index; e2.startVertex = v3.index; HEEdge e3 = new HEEdge(); e3.index = heEdges.Count+2; e3.indexOnFace = 2; e3.faceIndex = heFaces.Count; e3.endVertex = v3.index; e3.startVertex = v1.index; // link half edges together: e1.nextEdgeIndex = e3.index; e2.nextEdgeIndex = e1.index; e3.nextEdgeIndex = e2.index; // vertex outgoing half edge indices: v1.halfEdgeIndex = e3.index; v2.halfEdgeIndex = e1.index; v3.halfEdgeIndex = e2.index; // add edges: heEdges.Add(e1); heEdges.Add(e2); heEdges.Add(e3); // populate and add face: HEFace face = new HEFace(); face.edges[0] = e1.index; face.edges[1] = e2.index; face.edges[2] = e3.index; face.area = ObiUtils.TriangleArea(pos1,pos2,pos3); _area += face.area; _volume += Vector3.Dot(Vector3.Cross(pos1,pos2),pos3)/6f; face.index = heFaces.Count; heFaces.Add(face); try{ edgeBuffer.Add(new KeyValuePair<int,int>(v1.index,v2.index),e1); edgeBuffer.Add(new KeyValuePair<int,int>(v2.index,v3.index),e2); edgeBuffer.Add(new KeyValuePair<int,int>(v3.index,v1.index),e3); }catch{ Debug.LogError("Your mesh is non manifold, and thus cannot be processed by Obi: more than 1 edge joining the same pair of vertices."); heFaces.Clear(); heVertices.Clear(); heEdges.Clear(); _area = 0; yield break; } if (i % 500 == 0) yield return new CoroutineJob.ProgressInfo("Half-edge: generating edges and faces...",i/(float)triangles.Length); } List<HEEdge> borderEdges = new List<HEEdge>(); //edges belonging to a mesh border. // stitch half edge pairs together: index = 0; foreach(KeyValuePair<KeyValuePair<int,int>,HEEdge> pair in edgeBuffer){ KeyValuePair<int,int> edgeKey = new KeyValuePair<int,int>(pair.Key.Value,pair.Key.Key); HEEdge edge = null; if (edgeBuffer.TryGetValue(edgeKey, out edge)){ ((HEEdge)pair.Value).pair = edge.index; }else{ //create border edge: HEEdge e = new HEEdge(); e.index = heEdges.Count; e.endVertex = ((HEEdge)pair.Value).startVertex; e.startVertex = ((HEEdge)pair.Value).endVertex; heVertices[e.startVertex].halfEdgeIndex = e.index; e.pair = ((HEEdge)pair.Value).index; ((HEEdge)pair.Value).pair = e.index; heEdges.Add(e); borderEdges.Add(e); } if (index % 1000 == 0) yield return new CoroutineJob.ProgressInfo("Half-edge: stitching half-edges...",index/(float)edgeBuffer.Count); index++; } _closed = borderEdges.Count == 0; // link together border edges: foreach(HEEdge edge in borderEdges){ edge.nextEdgeIndex = heVertices[edge.endVertex].halfEdgeIndex; } }else{ Debug.LogWarning("Tried to generate adjacency info for an empty mesh."); } }
public bool AreLinked(HEVertex v1, HEVertex v2) { HEEdge startEdge = heEdges[v1.halfEdgeIndex]; HEEdge edge = startEdge; do{ edge = heEdges[edge.pair]; if (edge.startVertex == v2.index) return true; edge = heEdges[edge.nextEdgeIndex]; } while (edge != startEdge); return false; }