using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Plankton { /// /// Provides access to the vertices and related functionality of a Mesh. /// public class PlanktonVertexList : IEnumerable { private readonly PlanktonMesh _mesh; private List _list; /// /// Initializes a new instance of the class. /// Should be called from the mesh constructor. /// /// The to which this list of vertices belongs. internal PlanktonVertexList(PlanktonMesh owner) { this._list = new List(); this._mesh = owner; } /// /// Gets the number of vertices. /// public int Count { get { return this._list.Count; } } #region methods #region vertex access #region adding /// /// Adds a new vertex to the end of the Vertex list. /// /// Vertex to add. /// The index of the newly added vertex. internal int Add(PlanktonVertex vertex) { if (vertex == null) return -1; this._list.Add(vertex); return this.Count - 1; } /// /// Adds a new vertex to the end of the Vertex list. /// /// Vertex to add. /// The index of the newly added vertex. internal int Add(PlanktonXYZ vertex) { this._list.Add(new PlanktonVertex(vertex.X,vertex.Y,vertex.Z)); return this.Count - 1; } /// /// Adds a new vertex to the end of the Vertex list. /// /// X component of new vertex coordinate. /// Y component of new vertex coordinate. /// Z component of new vertex coordinate. /// The index of the newly added vertex. public int Add(double x, double y, double z) { return this.Add(new PlanktonVertex(x, y, z)); } /// /// Adds a new vertex to the end of the Vertex list. /// /// X component of new vertex coordinate. /// Y component of new vertex coordinate. /// Z component of new vertex coordinate. /// The index of the newly added vertex. public int Add(float x, float y, float z) { return this.Add(new PlanktonVertex(x, y, z)); } #endregion /// /// Adds a series of new vertices to the end of the vertex list. /// /// A list, an array or any enumerable set of . /// Indices of the newly created vertices. public int[] AddVertices(IEnumerable vertices) { return vertices.Select(v => this.Add(v)).ToArray(); } /// /// Returns the at the given index. /// /// /// Index of vertex to get. /// Must be larger than or equal to zero and smaller than the Vertex Count of the mesh. /// /// The vertex at the given index. public PlanktonVertex this[int index] { get { return this._list[index]; } internal set { this._list[index] = value; } } /// /// Sets or adds a vertex to the Vertex List. /// If [index] is less than [Count], the existing vertex at [index] will be modified. /// If [index] equals [Count], a new vertex is appended to the end of the vertex list. /// If [index] is larger than [Count], the function will return false. /// /// Index of vertex to set. /// X component of vertex location. /// Y component of vertex location. /// Z component of vertex location. /// true on success, false on failure. public bool SetVertex(int vertexIndex, float x, float y, float z) { if (vertexIndex >= 0 && vertexIndex < _list.Count) { var v = this._list[vertexIndex]; v.X = x; v.Y = y; v.Z = z; } else if (vertexIndex == _list.Count) { this.Add(x, y, z); } else { return false; } return true; } /// /// Sets or adds a vertex to the Vertex List. /// If [index] is less than [Count], the existing vertex at [index] will be modified. /// If [index] equals [Count], a new vertex is appended to the end of the vertex list. /// If [index] is larger than [Count], the function will return false. /// /// Index of vertex to set. /// X component of vertex location. /// Y component of vertex location. /// Z component of vertex location. /// true on success, false on failure. public bool SetVertex(int vertexIndex, double x, double y, double z) { if (vertexIndex >= 0 && vertexIndex < _list.Count) { var v = this._list[vertexIndex]; v.X = (float)x; v.Y = (float)y; v.Z = (float)z; } else if (vertexIndex == _list.Count) { this.Add(x, y, z); } else { return false; } return true; } #endregion /// /// Helper method to remove dead vertices from the list, re-index and compact. /// internal int CompactHelper() { int marker = 0; // Location where the current vertex should be moved to // Run through all the vertices for (int iter = 0; iter < _list.Count; iter++) { // If vertex is alive, check if we need to shuffle it down the list if (!_list[iter].IsUnused) { if (marker < iter) { // Room to shuffle. Copy current vertex to marked slot. _list[marker] = _list[iter]; // Update all halfedges which start here int first = _list[marker].OutgoingHalfedge; foreach (int h in _mesh.Halfedges.GetVertexCirculator(first)) { _mesh.Halfedges[h].StartVertex = marker; } } marker++; // That spot's filled. Advance the marker. } } // Trim list down to new size if (marker < _list.Count) { _list.RemoveRange(marker, _list.Count - marker); } return _list.Count - marker; } /// /// Removes all vertices that are currently not used by the Halfedge list. /// /// The number of unused vertices that were removed. public int CullUnused() { return this.CompactHelper(); } #region traversals /// /// Traverses the halfedge indices which originate from a vertex. /// /// A vertex index. /// An enumerable of halfedge indices incident to the specified vertex. /// Ordered clockwise around the vertex. [Obsolete("GetHalfedgesCirculator(int) is deprecated, please use" + "Halfedges.GetVertexCirculator(int) instead.")] public IEnumerable GetHalfedgesCirculator(int v) { int he_first = this[v].OutgoingHalfedge; if (he_first < 0) yield break; // vertex has no connectivity, exit int he_current = he_first; var hs = _mesh.Halfedges; do { yield return he_current; he_current = hs[hs.GetPairHalfedge(he_current)].NextHalfedge; } while (he_current != he_first); } /// /// Traverses the halfedge indices which originate from a vertex. /// /// A vertex index. /// A halfedge index. Halfedge must start at the specified vertex. /// An enumerable of halfedge indices incident to the specified vertex. /// Ordered clockwise around the vertex. /// The returned enumerable will start with the specified halfedge. /// /// The specified halfedge does not originate from the specified vertex. /// [Obsolete("GetHalfedgesCirculator(int,int) is deprecated, please use" + "Halfedges.GetVertexCirculator(int) instead.")] public IEnumerable GetHalfedgesCirculator(int v, int first) { if (_mesh.Halfedges[first].StartVertex != v) throw new ArgumentOutOfRangeException("Halfedge does not start at vertex."); // TODO: The code below is the same as above. // Can we refactor (without extra, unnecessary iterators)? int h = first; var hs = _mesh.Halfedges; do { yield return h; h = hs[hs.GetPairHalfedge(h)].NextHalfedge; } while (h != first); } #endregion #region adjacency queries /// /// Gets the halfedges which originate from a vertex. /// /// A vertex index. /// The indices of halfedges incident to a particular vertex. /// Ordered clockwise around the vertex. public int[] GetHalfedges(int v) { return _mesh.Halfedges.GetVertexCirculator(this[v].OutgoingHalfedge).ToArray(); } /// /// Gets the halfedges which end at a vertex. /// /// A vertex index. /// The opposing halfedge for each returned by . /// Ordered clockwise around the vertex. public int[] GetIncomingHalfedges(int v) { return _mesh.Halfedges.GetVertexCirculator(this[v].OutgoingHalfedge) .Select(h => _mesh.Halfedges.GetPairHalfedge(h)).ToArray(); } /// /// Gets vertex neighbours (a.k.a. 1-ring). /// /// A vertex index. /// An array of vertex indices incident to the specified vertex. /// Ordered clockwise around the vertex. public int[] GetVertexNeighbours(int v) { var hs = _mesh.Halfedges; return _mesh.Halfedges.GetVertexCirculator(this[v].OutgoingHalfedge) .Select(h => hs[hs.GetPairHalfedge(h)].StartVertex).ToArray(); } /// /// Gets faces incident to a vertex. /// /// A vertex index. /// An array of face indices incident to the specified vertex. /// Ordered clockwise around the vertex public int[] GetVertexFaces(int v) { return _mesh.Halfedges.GetVertexCirculator(this[v].OutgoingHalfedge) .Select(h => _mesh.Halfedges[h].AdjacentFace).ToArray(); } /// /// Gets the first incoming halfedge for a vertex. /// /// A vertex index. /// The index of the halfedge paired with the specified vertex's . public int GetIncomingHalfedge(int v) { return _mesh.Halfedges.GetPairHalfedge(this[v].OutgoingHalfedge); } #endregion /// /// Gets the number of naked edges incident to this vertex. /// /// A vertex index. /// The number of incident halfedges which lie on a boundary. public int NakedEdgeCount(int v) { int nakedCount = 0; var hs = _mesh.Halfedges; foreach (int i in _mesh.Halfedges.GetVertexCirculator(this[v].OutgoingHalfedge)) { if (hs[i].AdjacentFace == -1 || hs[hs.GetPairHalfedge(i)].AdjacentFace == -1) nakedCount++; } return nakedCount; } /// /// Gets the number of edges incident to this vertex. /// /// A vertex index. /// The number of incident edges. public int GetValence(int v) { int h = this[v].OutgoingHalfedge; return _mesh.Halfedges.GetVertexCirculator(h).Count(); } /// /// A vertex is on a boundary if its outgoing halfedge has no adjacent face. /// /// The index of a vertex. /// true if the specified vertex is on a boundary; otherwise, false. /// Also returns true if the vertex is unused (i.e. no outgoing halfedge). public bool IsBoundary(int index) { int h = this[index].OutgoingHalfedge; return (h < -1 || _mesh.Halfedges[h].AdjacentFace == -1); } /// /// Gets the normal vector at a vertex. /// /// The index of a vertex. /// The area weighted vertex normal. public PlanktonXYZ GetNormal(int index) { PlanktonXYZ vertex = this[index].ToXYZ(); PlanktonXYZ normal = new PlanktonXYZ(); var ring = this.GetVertexNeighbours(index); int n = ring.Length; for (int i = 0; i < n-1; i++) { normal += PlanktonXYZ.CrossProduct( this[ring[i]].ToXYZ() - vertex, this[ring[i+1]].ToXYZ() - vertex); } if (this.IsBoundary(index) == false) { normal += PlanktonXYZ.CrossProduct( this[n-1].ToXYZ() - vertex, this[0].ToXYZ() - vertex); } return normal * (-1.0f / normal.Length); // return unit vector } /// /// Gets the normal vectors for all vertices in the mesh. /// /// The area weighted vertex normals of all vertices in the mesh. /// /// This will be accurate at the time of calling but will quickly /// become outdated if you start fiddling with the mesh. /// public PlanktonXYZ[] GetNormals() { return Enumerable.Range(0, this.Count).Select(i => this.GetNormal(i)).ToArray(); } /// /// Gets the positions of all vertices. /// /// The positions of all vertices in the mesh. public PlanktonXYZ[] GetPositions() { return Enumerable.Range(0, this.Count).Select(i => this[i].ToXYZ()).ToArray(); } #region Euler operators /// /// Merges two vertices by collapsing the pair of halfedges between them. /// /// /// The index of a halfedge between the two vertices to be merged. /// The starting vertex of this halfedge will be retained. /// The successor of around its vertex, or -1 on failure. /// The invariant mesh.Vertices.MergeVertices(mesh.Vertices.SplitVertex(a, b)) will return a, /// leaving the mesh unchanged. public int MergeVertices(int halfedge) { return _mesh.Halfedges.CollapseEdge(halfedge); } /// /// Splits the vertex into two, joined by a new pair of halfedges. /// /// The index of a halfedge which starts at the vertex to split. /// The index of a second halfedge which starts at the vertex to split. /// The new halfedge which starts at the existing vertex. /// After the split, the halfedge will be starting at the newly added vertex. public int SplitVertex(int first, int second) { var hs = _mesh.Halfedges; // Check that both halfedges start at the same vertex int v_old = hs[first].StartVertex; if (v_old != hs[second].StartVertex) { return -1; } // TODO: return ArgumentException instead? // Create a copy of the existing vertex (user can move it afterwards if needs be) int v_new = this.Add(this[v_old].ToXYZ()); // copy vertex by converting to XYZ and back // Go around outgoing halfedges, from 'second' to just before 'first' // Set start vertex to new vertex bool reset_v_old = false; foreach (int h in hs.GetVertexCirculator(second)) { if (h == first) { break; } hs[h].StartVertex = v_new; // If new vertex has no outgoing yet and current he is naked... if (this[v_new].OutgoingHalfedge == -1 && hs[h].AdjacentFace == -1) this[v_new].OutgoingHalfedge = h; // Also check whether existing vert's he is now incident to new one if (h == this[v_old].OutgoingHalfedge) { reset_v_old = true; } } // If no naked halfedges, just use 'second' if (this[v_new].OutgoingHalfedge == -1) { this[v_new].OutgoingHalfedge = second; } // Add the new pair of halfedges from old vertex to new int h_new = hs.AddPair(v_old, v_new, hs[second].AdjacentFace); int h_new_pair = hs.GetPairHalfedge(h_new); hs[h_new_pair].AdjacentFace = hs[first].AdjacentFace; // Link new pair into mesh hs.MakeConsecutive(hs[first].PrevHalfedge, h_new_pair); hs.MakeConsecutive(h_new_pair, first); hs.MakeConsecutive(hs[second].PrevHalfedge, h_new); hs.MakeConsecutive(h_new, second); // Re-set existing vertex's outgoing halfedge, if necessary if (reset_v_old) { this[v_old].OutgoingHalfedge = h_new; foreach (int h in hs.GetVertexCirculator(h_new)) { if (hs[h].AdjacentFace == -1) { this[v_old].OutgoingHalfedge = h; } } } // return the new vertex which starts at the existing vertex return h_new; } /// /// Erases a vertex and all incident halfedges by merging its incident faces. /// /// The index of a halfedge which starts at the vertex to erase. /// The retained face will be the one adjacent to this halfedge. /// The successor of around its original face. public int EraseCenterVertex(int halfedgeIndex) { int vertexIndex = _mesh.Halfedges[halfedgeIndex].StartVertex; // Check that the vertex is completely surrounded by faces if (this.IsBoundary(vertexIndex)) throw new ArgumentException("Center vertex must not be on a boundary"); // Get outgoing halfedges around vertex, starting with specified halfedge int[] vertexHalfedges = _mesh.Halfedges.GetVertexCirculator(halfedgeIndex).ToArray(); // Check for 2-valent vertices in the 1-ring (no antennas) int v; foreach (int h in vertexHalfedges) { v = _mesh.Halfedges.EndVertex(h); if (this.GetHalfedges(v).Length < 3) throw new ArgumentException("Vertex in 1-ring is 2-valent"); } // Store face to keep and set its first halfedge int faceIndex = _mesh.Halfedges[halfedgeIndex].AdjacentFace; int firstHalfedge = _mesh.Halfedges[halfedgeIndex].NextHalfedge; _mesh.Faces[faceIndex].FirstHalfedge = firstHalfedge; // Remove incident halfedges and mark faces for deletion (except first face) _mesh.Halfedges.RemovePairHelper(vertexHalfedges[0]); for (int i = 1; i < vertexHalfedges.Length; i++) { _mesh.Faces[_mesh.Halfedges[vertexHalfedges[i]].AdjacentFace] = PlanktonFace.Unset; _mesh.Halfedges.RemovePairHelper(vertexHalfedges[i]); } // Set adjacent face for all halfedges in hole foreach (int h in _mesh.Halfedges.GetFaceCirculator(firstHalfedge)) { _mesh.Halfedges[h].AdjacentFace = faceIndex; } // Mark center vertex for deletion this[vertexIndex] = PlanktonVertex.Unset; return _mesh.Faces[faceIndex].FirstHalfedge; } #endregion /// /// Truncates a vertex by creating a face with vertices on each of the outgoing halfedges. /// /// The index of a vertex. /// The index of the newly created face. public int TruncateVertex(int v) { var hs = this.GetHalfedges(v); // set h_new and move original vertex int h_new = hs[0]; // circulate outgoing halfedges (clockwise, skip first) for (int i = 1; i < hs.Length; i++) { // split vertex int h_tmp = this.SplitVertex(hs[i], h_new); h_new = h_tmp; // tidy-up if 'vs' is removed } // split face to create new truncated face int splitH = this._mesh.Faces.SplitFace(hs[0], h_new); return this._mesh.Halfedges[this._mesh.Halfedges.GetPairHalfedge(splitH)].AdjacentFace; } #endregion #region IEnumerable implementation /// /// Gets an enumerator that yields all faces in this collection. /// /// An enumerator. public IEnumerator GetEnumerator() { return this._list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion } }