/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> private static void QuadSplitGeometry <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { // create face vertices foreach (var f in mesh.Faces) { var v = mesh.AddVertex(); if (!f.IsUnused) { position.Set(v, f.Vertices.Mean(position.Get)); } } // create edge vertices foreach (var he in mesh.Edges) { var v = mesh.AddVertex(); if (!he.IsUnused) { var p = (position.Get(he.Start) + position.Get(he.End)) * 0.5; position.Set(v, p); } } }
/// <summary> /// /// </summary> /// <param name="mesh"></param> /// <returns></returns> public G CreateFromFaceTopology(HeMesh <HeMesh3d.Vertex, HeMesh3d.Halfedge, HeMesh3d.Face> mesh) { var graph = Create(mesh.Faces.Count, mesh.Halfedges.Count); graph.AppendFaceTopology(mesh); return(graph); }
/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="start"></param> /// <param name="setEdge"></param> public static void DetachFaceCycles <V, E, F>(HeMesh <V, E, F> mesh, F start, Action <E, E> setEdge) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { Impl <V, E, F> .DetachFaceCycles(mesh, start, setEdge); }
/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="start"></param> /// <param name="getPosition"></param> /// <param name="setUnrolledPosition"></param> /// <param name="getUnrollFactor"></param> public static void Unroll <V, E, F>(HeMesh <V, E, F> mesh, F start, Func <V, Vector3d> getPosition, Action <V, Vector3d> setUnrolledPosition, Func <E, double> getUnrollFactor = null) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { Impl <V, E, F> .Unroll(mesh, start, getPosition, setUnrolledPosition, getUnrollFactor); }
/// <summary> /// /// </summary> /// <param name="mesh"></param> /// <param name="start"></param> /// <param name="setEdge"></param> public static void DetachFaceCycles(HeMesh <V, E, F> mesh, F start, Action <E, E> setEdge) { var currTag = mesh.Halfedges.NextTag; // tag traversed edges during BFS foreach (var he in mesh.GetFacesBreadthFirst2(start.Yield())) { he.Edge.Tag = currTag; } var edges = mesh.Edges; var ne = edges.Count; // detach all untagged edges for (int i = 0; i < ne; i++) { var he0 = edges[i]; if (he0.IsUnused || he0.IsBoundary || he0.Tag == currTag) { continue; } var he1 = mesh.DetachEdgeImpl(he0); setEdge(he1, he0); } }
/// <summary> /// /// </summary> private static void CatmullClarkSmoothFixed <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var verts = mesh.Vertices; int ev0 = verts.Count - mesh.Edges.Count; // index of first edge vertex int fv0 = ev0 - mesh.Faces.Count; // index of first face vertex // set old vertices for (int i = 0; i < fv0; i++) { var v = verts[i]; if (v.IsUnused || v.IsBoundary) { continue; // skip boundary verts } var fsum = new Vector3d(); var esum = new Vector3d(); int n = 0; foreach (var he in v.OutgoingHalfedges) { fsum += position.Get(verts[he.Face.Index + fv0]); esum += position.Get(verts[(he.Index >> 1) + ev0]); n++; } var t = 1.0 / n; position.Set(v, (position.Get(v) * (n - 3) + fsum * t + 2 * esum * t) * t); } }
/// <summary> /// Returns all quad strips in the given mesh. /// </summary> public static IEnumerable <QuadStrip <V, E, F> > GetQuadStrips <V, E, F>(HeMesh <V, E, F> mesh, bool flip) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var faces = mesh.Faces; var result = new List <QuadStrip <V, E, F> >(); var stack = new Stack <E>(); int currTag = faces.NextTag; for (int i = 0; i < faces.Count; i++) { var f = faces[i]; if (f.IsUnused || f.Tag == currTag || !f.IsDegree(4)) { continue; // skip if unused, visited, or non-quads } stack.Push((flip) ? f.First.Next : f.First); foreach (var strip in GetQuadStrips <V, E, F>(stack, currTag)) { yield return(strip); } } }
/// <summary> /// Returns a quad strip that includes the given halfedge. /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="hedge"></param> /// <returns></returns> public static QuadStrip <V, E, F> GetQuadStrip <V, E, F>(HeMesh <V, E, F> mesh, E hedge) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { mesh.Halfedges.OwnsCheck(hedge); return(GetQuadStrip <V, E, F>(hedge, mesh.Faces.NextTag)); }
/// <summary> /// Applies a single iteration of Catmull-Clark subdivision to the given mesh. /// If using pre-allocated external buffers to store vertex attributes, the number of vertices after subdivision equals the sum of the number of vertices, edges, and faces in the initial mesh. /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> public static void QuadSplit <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { QuadSplitGeometry(mesh, position); QuadSplitTopology(mesh); }
/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> public static void Loop <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { // TODO implement throw new NotImplementedException(); }
/// <summary> /// Applies a single iteration of Catmull-Clark subdivision to the given mesh. /// If using external buffers to store vertex attributes, the number of vertices after subdivision equals the sum of the number of vertices, edges, and faces in the initial mesh. /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> /// <param name="boundaryType"></param> public static void CatmullClark <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position, SmoothBoundaryType boundaryType) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { // impl ref // http://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface // http://w3.impa.br/~lcruz/courses/cma/surfaces.html CatmullClarkGeometry(mesh, position, boundaryType); QuadSplitTopology(mesh); }
/* * /// <summary> * /// Applies a single iteration of Catmull-Clark subdivision to the given mesh. * /// http://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface * /// http://w3.impa.br/~lcruz/courses/cma/surfaces.html * /// </summary> * /// <typeparam name="V"></typeparam> * /// <typeparam name="E"></typeparam> * /// <typeparam name="F"></typeparam> * /// <param name="mesh"></param> * /// <param name="position"></param> * /// <param name="boundaryType"></param> * public static void CatmullClark<V, E, F>(HeMesh<V, E, F> mesh, Property<V, Vec3d> position, SmoothBoundaryType boundaryType, bool parallel) * where V : HeMeshBase<V, E, F>.Vertex * where E : HeMeshBase<V, E, F>.Halfedge * where F : HeMeshBase<V, E, F>.Face * { * CatmullClarkGeometry(mesh, position, boundaryType, parallel); * QuadSplitTopology(mesh); * } * * * /// <summary> * /// * /// </summary> * private static void CatmullClarkGeometry<V, E, F>(HeMesh<V, E, F> mesh, Property<V, Vec3d> position, SmoothBoundaryType boundaryType, bool parallel) * where V : HeMeshBase<V, E, F>.Vertex * where E : HeMeshBase<V, E, F>.Halfedge * where F : HeMeshBase<V, E, F>.Face * { * var verts = mesh.Vertices; * var edges = mesh.Edges; * var faces = mesh.Faces; * * int fv0 = verts.Count; // index of first face vertex * int ev0 = verts.Count + faces.Count; * * // add all new vertices * mesh.AddVertices(faces.Count); * mesh.AddVertices(edges.Count); * * // set attributes of face vertices * Action<Tuple<int, int>> setFaceVerts = range => * { * for (int i = range.Item1; i < range.Item2; i++) * { * var f = faces[i]; * * if (!f.IsUnused) * position.Set(verts[i + fv0], f.Vertices.Mean(position.Get)); * } * }; * * // set attributes of edge vertices * Action<Tuple<int, int>> setEdgeVerts = range => * { * for (int i = range.Item1; i < range.Item2; i++) * { * var he0 = edges[i]; * if (he0.IsUnused) continue; * * if (he0.IsBoundary) * { * position.Set(verts[i + ev0], he0.Lerp(position.Get, 0.5)); * continue; * } * * var he1 = he0.Twin; * var p0 = position.Get(he0.Start); * var p1 = position.Get(he1.Start); * var p2 = position.Get(verts[he0.Face.Index + fv0]); * var p3 = position.Get(verts[he1.Face.Index + fv0]); * position.Set(verts[i + ev0], (p0 + p1 + p2 + p3) * 0.25); * } * }; * * // set attributes of old vertices * //CatmullClarkSmooth(mesh, position, boundaryType); * Action<Tuple<int, int>> setOldVerts = range => * { * for (int i = range.Item1; i < range.Item2; i++) * { * var v = verts[i]; * if (v.IsUnused) continue; * * if (v.IsBoundary) * { * var he0 = v.FirstOut; * var he1 = he0.PrevInFace; * var p0 = position.Get(verts[(he0.Index >> 1) + ev0]); * var p1 = position.Get(verts[(he1.Index >> 1) + ev0]); * position.Set(v, position.Get(v) * 0.5 + (p0 + p1) * 0.25); * } * else * { * Vec3d fsum = new Vec3d(); * Vec3d esum = new Vec3d(); * int n = 0; * * foreach (var he in v.OutgoingHalfedges) * { * fsum += position.Get(verts[he.Face.Index + fv0]); * esum += position.Get(verts[(he.Index >> 1) + ev0]); * n++; * } * * double t = 1.0 / n; * position.Set(v, (position.Get(v) * (n - 3) + fsum * t + 2 * esum * t) * t); * } * } * }; * * * if (parallel) * { * Parallel.ForEach(Partitioner.Create(0, faces.Count), setFaceVerts); * Parallel.ForEach(Partitioner.Create(0, edges.Count), setEdgeVerts); * Parallel.ForEach(Partitioner.Create(0, verts.Count), setOldVerts); * } * else * { * setFaceVerts(Tuple.Create(0, faces.Count)); * setEdgeVerts(Tuple.Create(0, edges.Count)); * setOldVerts(Tuple.Create(0, verts.Count)); * } * } */ #endregion #region Misc /// <summary> /// If using pre-allocated external buffers to store vertex attributes, the number of vertices after subdivision equals the sum of the number of vertices and faces in the initial mesh. /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> /// <param name="skipBoundary"></param> public static void Diagonalize <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position, bool skipBoundary) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var edges = mesh.Edges; var faces = mesh.Faces; int ne = edges.Count; int nf = faces.Count; // stellate faces for (int i = 0; i < nf; i++) { var f = faces[i]; if (f.IsUnused) { continue; } var v = mesh.AddVertex(); position.Set(v, f.GetBarycenter(position.Get)); mesh.PokeFaceImpl(f.First, v); } // merge faces if (skipBoundary) { for (int i = 0; i < ne; i++) { var he = edges[i]; if (he.IsUnused || he.IsBoundary) { continue; } mesh.MergeFaces(he); } } else { for (int i = 0; i < ne; i++) { var he = edges[i]; if (he.IsUnused) { continue; } mesh.MergeFaces(he); } } }
/// <summary> /// /// </summary> private static void CatmullClarkSmoothCornerFixed <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var verts = mesh.Vertices; int ev0 = verts.Count - mesh.Edges.Count; // index of first edge vertex int fv0 = ev0 - mesh.Faces.Count; // index of first face vertex // set old vertices for (int i = 0; i < fv0; i++) { var v = verts[i]; if (v.IsUnused) { continue; } if (v.IsBoundary) { var he0 = v.First; if (he0.IsAtDegree2) { continue; // skip corner verts } var he1 = he0.Previous; var p0 = position.Get(verts[(he0.Index >> 1) + ev0]); var p1 = position.Get(verts[(he1.Index >> 1) + ev0]); position.Set(v, position.Get(v) * 0.5 + (p0 + p1) * 0.25); } else { Vector3d fsum = new Vector3d(); Vector3d esum = new Vector3d(); int n = 0; foreach (var he in v.OutgoingHalfedges) { fsum += position.Get(verts[he.Face.Index + fv0]); esum += position.Get(verts[(he.Index >> 1) + ev0]); n++; } double t = 1.0 / n; position.Set(v, (position.Get(v) * (n - 3) + fsum * t + 2 * esum * t) * t); } } }
/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> /// <param name="position"></param> /// <param name="boundaryType"></param> private static void CatmullClarkGeometry <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position, SmoothBoundaryType boundaryType) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var verts = mesh.Vertices; int fv0 = verts.Count; // index of first face vertex // create face vertices foreach (var f in mesh.Faces) { var v = mesh.AddVertex(); if (!f.IsUnused) { position.Set(v, f.Vertices.Mean(position.Get)); } } // create edge vertices foreach (var he0 in mesh.Edges) { var v = mesh.AddVertex(); if (he0.IsUnused) { continue; } if (he0.IsBoundary) { var p = (position.Get(he0.Start) + position.Get(he0.End)) * 0.5; position.Set(v, p); continue; } var he1 = he0.Twin; var p0 = position.Get(he0.Start); var p1 = position.Get(he1.Start); var p2 = position.Get(verts[he0.Face.Index + fv0]); var p3 = position.Get(verts[he1.Face.Index + fv0]); position.Set(v, (p0 + p1 + p2 + p3) * 0.25); } // smooth old vertices CatmullClarkSmooth(mesh, position, boundaryType); }
/// <summary> /// /// </summary> /// <typeparam name="V"></typeparam> /// <typeparam name="E"></typeparam> /// <typeparam name="F"></typeparam> /// <param name="mesh"></param> private static void QuadSplitTopology <V, E, F>(HeMesh <V, E, F> mesh) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { var verts = mesh.Vertices; var hedges = mesh.Halfedges; var faces = mesh.Faces; int ne = hedges.Count >> 1; int nf = faces.Count; int ev0 = verts.Count - ne; // index of first edge vertex int fv0 = ev0 - nf; // index of first face vertex (also the number of vertices in the initial mesh) // split edges for (int i = 0; i < ne; i++) { var he = hedges[i << 1]; if (he.IsUnused) { continue; } var ev = verts[i + ev0]; mesh.SplitEdgeImpl(he, ev); } // split faces for (int i = 0; i < nf; i++) { var f = faces[i]; if (f.IsUnused) { continue; } var he = f.First; if (he.Start.Index >= fv0) { he = he.Previous; // ensure halfedge starts from an old vertex } mesh.QuadSplitFace(he, verts[i + fv0]); } }
/// <summary> /// /// </summary> private static void CatmullClarkSmooth <V, E, F>(HeMesh <V, E, F> mesh, Property <V, Vector3d> position, SmoothBoundaryType boundaryType) where V : HeMesh <V, E, F> .Vertex where E : HeMesh <V, E, F> .Halfedge where F : HeMesh <V, E, F> .Face { switch (boundaryType) { case SmoothBoundaryType.Fixed: CatmullClarkSmoothFixed(mesh, position); return; case SmoothBoundaryType.CornerFixed: CatmullClarkSmoothCornerFixed(mesh, position); return; case SmoothBoundaryType.Free: CatmullClarkSmoothFree(mesh, position); return; } throw new NotSupportedException(); }
/// <summary> /// /// </summary> /// <param name="mesh"></param> /// <param name="start"></param> /// <param name="getPosition"></param> /// <param name="setUnrolledPosition"></param> /// <param name="getUnrollFactor"></param> public static void Unroll(HeMesh <V, E, F> mesh, F start, Func <V, Vector3d> getPosition, Action <V, Vector3d> setUnrolledPosition, Func <E, double> getUnrollFactor = null) { const string errorMessage = "Face cycle detected. The given mesh cannot be unrolled."; start.UnusedCheck(); mesh.Faces.OwnsCheck(start); // stack of edges with corresponding transforms var queue = new Queue <(E, Orient3d)>(); var tag = mesh.Faces.NextTag; // current unroll transform var unroll = Orient3d.Identity; // enqueue twin of each halfedge in the first face foreach (var he in start.Halfedges) { var v = he.Start; setUnrolledPosition(v, getPosition(v)); TryEnqueue(he.Twin); } // breadth-first walk and transform while (queue.Count > 0) { (var he0, var m0) = queue.Dequeue(); var m1 = getUnrollFactor == null? GetHalfedgeTransform(he0, getPosition) : GetHalfedgeTransform(he0, getPosition, getUnrollFactor(he0)); unroll = m0.Apply(ref m1); var he1 = he0.Next; // enqueue first hedge TryEnqueue(he1.Twin); he1 = he1.Next; // transform and enqueue remaining hedges do { var v = he1.Start; setUnrolledPosition(v, unroll.Apply(getPosition(v))); TryEnqueue(he1.Twin); he1 = he1.Next; } while (he1 != he0); } void TryEnqueue(E hedge) { var f = hedge.Face; if (f == null) { return; } if (f.Tag == tag) { throw new ArgumentException(errorMessage); } queue.Enqueue((hedge, unroll)); f.Tag = tag; } }
/// <summary> /// /// </summary> /// <param name="mesh"></param> public void AppendFaceTopology(HeMesh <HeMesh3d.Vertex, HeMesh3d.Halfedge, HeMesh3d.Face> mesh) { AppendFaceTopology(mesh, Set, Set); }