using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Plankton
{
Provides access to the vertices and PlanktonVertex related functionality of a Mesh.
public class PlanktonVertexList : IEnumerable<PlanktonVertex>
{
private readonly PlanktonMesh _mesh;
private List<PlanktonVertex> _list;
Initializes a new instance of the PlanktonVertexList class. Should be called from the mesh constructor.
internal PlanktonVertexList(PlanktonMesh owner)
{
this._list = new List<PlanktonVertex>();
this._mesh = owner;
}
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.
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.
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.
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.
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.
Indices of the newly created vertices.
public int[] AddVertices(IEnumerable<PlanktonXYZ> vertices)
{
return vertices.Select(v => this.Add(v)).ToArray();
}
Returns the PlanktonVertex at the given index.
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.
on success, 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.
on success, 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
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.
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<int> 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.
An enumerable of halfedge indices incident to the specified vertex. Ordered clockwise around the vertex. The returned enumerable will start with the specified halfedge.
[Obsolete("GetHalfedgesCirculator(int,int) is deprecated, please use" +
"Halfedges.GetVertexCirculator(int) instead.")]
public IEnumerable<int> 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.
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.
The opposing halfedge for each returned by GetHalfedges(int). 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).
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.
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 halfedge for a vertex.
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.
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.
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.
if the specified vertex is on a boundary; otherwise, . Also returns 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 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 successor of index around its vertex, or -1 on failure.
The invariant 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 new halfedge which starts at the existing vertex.
After the split, the second 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 successor of halfedgeIndex 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 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
public IEnumerator<PlanktonVertex> GetEnumerator()
{
return this._list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}