/// <summary> /// Thickens each mesh edge in the plane of the mesh surface. /// </summary> /// <param name="offset">Distance to offset edges in plane of adjacent faces</param> /// <param name="boundaries">If true, attempt to ribbon boundary edges</param> /// <returns>The ribbon mesh</returns> public Mesh Ribbon(float offset, Boolean boundaries, float smooth) { Mesh ribbon = Duplicate(); var orig_faces = ribbon.Faces.ToArray(); List <List <Halfedge> > incidentEdges = ribbon.Vertices.Select(v => v.Halfedges).ToList(); // create new "vertex" faces List <List <Vertex> > all_new_vertices = new List <List <Vertex> >(); for (int k = 0; k < Vertices.Count; k++) { Vertex v = ribbon.Vertices[k]; List <Vertex> new_vertices = new List <Vertex>(); List <Halfedge> halfedges = incidentEdges[k]; Boolean boundary = halfedges[0].Next.Pair != halfedges[halfedges.Count - 1]; // if the edge loop around this vertex is open, close it with 'temporary edges' if (boundaries && boundary) { Halfedge a, b; a = halfedges[0].Next; b = halfedges[halfedges.Count - 1]; if (a.Pair == null) { a.Pair = new Halfedge(a.Prev.Vertex) { Pair = a }; } if (b.Pair == null) { b.Pair = new Halfedge(b.Prev.Vertex) { Pair = b }; } a.Pair.Next = b.Pair; b.Pair.Prev = a.Pair; a.Pair.Prev = a.Pair.Prev ?? a; // temporary - to allow access to a.Pair's start/end vertices halfedges.Add(a.Pair); } foreach (Halfedge edge in halfedges) { if (halfedges.Count < 2) { continue; } Vector3f normal = edge.Face != null ? edge.Face.Normal : Vertices[k].Normal; Halfedge edge2 = edge.Next; Vector3f o1 = Vector3f.CrossProduct(normal, edge.Vector); Vector3f o2 = Vector3f.CrossProduct(normal, edge2.Vector); o1.Unitize(); o2.Unitize(); o1 *= offset; o2 *= offset; if (edge.Face == null) { // boundary condition: create two new vertices in the plane defined by the vertex normal Vertex v1 = new Vertex(v.Position + (edge.Vector * (1 / edge.Vector.Length) * -offset) + o1); Vertex v2 = new Vertex(v.Position + (edge2.Vector * (1 / edge2.Vector.Length) * offset) + o2); ribbon.Vertices.Add(v2); ribbon.Vertices.Add(v1); new_vertices.Add(v2); new_vertices.Add(v1); Halfedge c = new Halfedge(v2, edge2, edge, null); edge.Next = c; edge2.Prev = c; } else { // internal condition: offset each edge in the plane of the shared face and create a new vertex where they intersect eachother Line l1 = new Line(edge.Vertex.Position + o1, edge.Prev.Vertex.Position + o1); Line l2 = new Line(edge2.Vertex.Position + o2, edge2.Prev.Vertex.Position + o2); double a, b; Rhino.Geometry.Intersect.Intersection.LineLine(l1, l2, out a, out b); Point3d new_point = l1.PointAt(a); Vertex new_vertex = new Vertex(new Point3f((float)new_point.X, (float)new_point.Y, (float)new_point.Z)); ribbon.Vertices.Add(new_vertex); new_vertices.Add(new_vertex); } } if ((!boundaries && boundary) == false) // only draw boundary node-faces in 'boundaries' mode { ribbon.Faces.Add(new_vertices); } all_new_vertices.Add(new_vertices); } // change edges to reference new vertices for (int k = 0; k < Vertices.Count; k++) { Vertex v = ribbon.Vertices[k]; if (all_new_vertices[k].Count < 1) { continue; } int c = 0; foreach (Halfedge edge in incidentEdges[k]) { if (!ribbon.Halfedges.SetVertex(edge, all_new_vertices[k][c++])) { edge.Vertex = all_new_vertices[k][c]; } } //v.Halfedge = null; // unlink from halfedge as no longer in use (culled later) // note: new vertices don't link to any halfedges in the mesh until later } // cull old vertices ribbon.Vertices.RemoveRange(0, Vertices.Count); // use existing edges to create 'ribbon' faces MeshHalfedgeList temp = new MeshHalfedgeList(); for (int i = 0; i < Halfedges.Count; i++) { temp.Add(ribbon.Halfedges[i]); } List <Halfedge> items = temp.GetUnique(); foreach (Halfedge halfedge in items) { if (halfedge.Pair != null) { // insert extra vertices close to the new 'vertex' vertices to preserve shape when subdividing if (smooth > 0.0) { if (smooth > 0.5) { smooth = 0.5f; } Vertex[] new_vertices = new Vertex[] { new Vertex(halfedge.Vertex.Position + (-smooth * halfedge.Vector)), new Vertex(halfedge.Prev.Vertex.Position + (smooth * halfedge.Vector)), new Vertex(halfedge.Pair.Vertex.Position + (-smooth * halfedge.Pair.Vector)), new Vertex(halfedge.Pair.Prev.Vertex.Position + (smooth * halfedge.Pair.Vector)) }; ribbon.Vertices.AddRange(new_vertices); Vertex[] new_vertices1 = new Vertex[] { halfedge.Vertex, new_vertices[0], new_vertices[3], halfedge.Pair.Prev.Vertex }; Vertex[] new_vertices2 = new Vertex[] { new_vertices[1], halfedge.Prev.Vertex, halfedge.Pair.Vertex, new_vertices[2] }; ribbon.Faces.Add(new_vertices); ribbon.Faces.Add(new_vertices1); ribbon.Faces.Add(new_vertices2); } else { Vertex[] new_vertices = new Vertex[] { halfedge.Vertex, halfedge.Prev.Vertex, halfedge.Pair.Vertex, halfedge.Pair.Prev.Vertex }; ribbon.Faces.Add(new_vertices); } } } // remove original faces, leaving just the ribbon //var orig_faces = Enumerable.Range(0, Faces.Count).Select(i => ribbon.Faces[i]); foreach (Face item in orig_faces) { ribbon.Faces.Remove(item); } // search and link pairs ribbon.Halfedges.MatchPairs(); return(ribbon); }
public Mesh() { Halfedges = new MeshHalfedgeList(this); Vertices = new MeshVertexList(this); Faces = new MeshFaceList(this); }