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
}
}