public void CannotSplitFaceBadArguments() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create one quadrangular face pMesh.Faces.AddFace(0, 1, 2, 3); // First halfedge is a boundary Assert.AreEqual(-1, pMesh.Faces.SplitFace(1, 4)); // Second halfedge is a boundary Assert.AreEqual(-1, pMesh.Faces.SplitFace(4, 1)); // Same halfedge used for both arguments Assert.AreEqual(-1, pMesh.Faces.SplitFace(0, 0)); // Second halfedge is successor to first Assert.AreEqual(-1, pMesh.Faces.SplitFace(0, 2)); // Second halfedge is predecessor to first Assert.AreEqual(-1, pMesh.Faces.SplitFace(0, 6)); }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object can be used to retrieve data from input parameters and /// to store data in output parameters.</param> protected override void SolveInstance(IGH_DataAccess DA) { PlanktonMesh mesh = null; DA.GetData(0, ref mesh); DA.SetData(0, mesh.Dual()); }
public void CanSplitFace() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create one quadrangular face pMesh.Faces.AddFace(0, 1, 2, 3); // Split face into two triangles int new_he = pMesh.Faces.SplitFace(0, 4); // Returned halfedge should be adjacent to old face (#0) Assert.AreEqual(0, pMesh.Halfedges[new_he].AdjacentFace); // Traverse from returned halfedge to new face int new_he_pair = pMesh.Halfedges.GetPairHalfedge(new_he); int new_face = pMesh.Halfedges[new_he_pair].AdjacentFace; Assert.AreEqual(1, new_face); // Check that both faces are now triangular Assert.AreEqual(3, pMesh.Faces.GetFaceVertices(0).Length); Assert.AreEqual(3, pMesh.Faces.GetFaceVertices(1).Length); // Check the halfedges of each face Assert.AreEqual(new int[] { 8, 0, 2 }, pMesh.Faces.GetHalfedges(0)); Assert.AreEqual(new int[] { 9, 4, 6 }, pMesh.Faces.GetHalfedges(1)); }
public void CanSplitMergeInvariant() { // TODO: draw figure here... PlanktonMesh pMesh = new PlanktonMesh(); // Create 3x3 grid of vertices pMesh.Vertices.Add(0, 2, 0); // 0 pMesh.Vertices.Add(0, 1, 0); // 1 pMesh.Vertices.Add(0, 0, 0); // 2 pMesh.Vertices.Add(1, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Vertices.Add(1, 0, 0); // 5 pMesh.Vertices.Add(2, 2, 0); // 6 pMesh.Vertices.Add(2, 1, 0); // 7 pMesh.Vertices.Add(2, 0, 0); // 8 pMesh.Faces.AddFace(2, 4, 1); pMesh.Faces.AddFace(7, 4, 8); pMesh.Faces.AddFace(0, 1, 4, 3); pMesh.Faces.AddFace(3, 4, 7, 6); int start_he = 8; Assert.AreEqual(start_he, pMesh.Halfedges.FindHalfedge(4, 8)); Assert.AreEqual(2, pMesh.Halfedges.FindHalfedge(4, 1)); // Split face into two triangles int new_he = pMesh.Vertices.SplitVertex(start_he, 2); // Merge them back again int old_he = pMesh.Vertices.MergeVertices(new_he); // We should be back where we started... Assert.AreEqual(start_he, old_he); }
public static Mesh PlanktonToUnity(this PlanktonMesh plankton) { int vCount = plankton.Vertices.Count; var vertices = new Vector3[vCount]; for (int i = 0; i < vCount; i++) { PlanktonVertex v = plankton.Vertices[i]; vertices[i] = new Vector3(v.X, v.Y, v.Z); } int fCount = plankton.Faces.Count; var triangles = new int[fCount * 3]; int[] faceVertices = new int[3]; for (int i = 0; i < fCount; i++) { if (!plankton.Faces[i].IsUnused) { plankton.Faces.GetFaceVerticesNonAlloc(i, faceVertices); triangles[i * 3] = faceVertices[0]; triangles[i * 3 + 1] = faceVertices[1]; triangles[i * 3 + 2] = faceVertices[2]; } } var mesh = new Mesh(); mesh.vertices = vertices; mesh.uv = plankton.Vertices.Select(v => v.data.UV).ToArray(); mesh.normals = plankton.Vertices.Select(v => v.data.Normal).ToArray(); mesh.triangles = triangles; mesh.RecalculateTangents(); return(mesh); }
public void CanDualCube() { // Create a simple cube PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(-0.5, -0.5, 0.5); pMesh.Vertices.Add(-0.5, -0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, 0.5); pMesh.Faces.AddFace(3, 2, 1, 0); pMesh.Faces.AddFace(1, 5, 4, 0); pMesh.Faces.AddFace(2, 6, 5, 1); pMesh.Faces.AddFace(7, 6, 2, 3); pMesh.Faces.AddFace(4, 7, 3, 0); pMesh.Faces.AddFace(5, 6, 7, 4); var dual = pMesh.Dual(); Assert.AreEqual(new int[] { 2, 3, 0, 1 }, dual.Vertices.GetVertexFaces(0)); }
public static Point3d MidPt(PlanktonMesh P, int E) { Point3d Pos1 = P.Vertices[P.Halfedges[2 * E].StartVertex].ToPoint3d(); Point3d Pos2 = P.Vertices[P.Halfedges[2 * E + 1].StartVertex].ToPoint3d(); return((Pos1 + Pos2) * 0.5); }
public void CanTraverseUnusedVertex() { // Getting halfedges for unused vertex should return empty PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(0, 0, 0); Assert.IsEmpty(pMesh.Vertices.GetHalfedges(0)); }
//--------------------------------------------------Calculate vertex normals methods------------------------------------------// /*The vertex normals are calculated as the weighted average of the adjacent face normals */ //FACE NORMAL /* calculate the face normal (not normalised) as average of cross products of edge pairs (n-gon) */ public Vector3d calcFaceNormal(PlanktonMesh pMesh, int faceIndex) { Vector3d[] edgesCCW = new Vector3d[pMesh.Vertices.Count]; int[] faceHalfedges = pMesh.Faces.GetHalfedges(faceIndex); for (int i = 0; i < faceHalfedges.Length; i++) { int startVertex = pMesh.Halfedges[faceHalfedges[i]].StartVertex; Point3d start = new Point3d(pMesh.Vertices[startVertex].X, pMesh.Vertices[startVertex].Y, pMesh.Vertices[startVertex].Z); int nextHalfedge = pMesh.Halfedges[faceHalfedges[i]].NextHalfedge; int endVertex = pMesh.Halfedges[nextHalfedge].StartVertex; Point3d end = new Point3d(pMesh.Vertices[endVertex].X, pMesh.Vertices[endVertex].Y, pMesh.Vertices[endVertex].Z); edgesCCW[i] = new Vector3d(end - start); } //shift edgesCCW Vector3d[] shift_edgesCCW = new Vector3d[edgesCCW.Length]; for (int j = 0; j < edgesCCW.Length; j++) { shift_edgesCCW[j] = edgesCCW[(j + 1) % edgesCCW.Length]; } //normal vector Vector3d normal = new Vector3d(0, 0, 0); for (int k = 0; k < edgesCCW.Length; k++) { normal += Vector3d.CrossProduct(edgesCCW[k], shift_edgesCCW[k]); } normal = normal / edgesCCW.Length; return(normal); }
public void CanGetNormal() { PlanktonMesh pMesh = new PlanktonMesh(); // Create 3x3 grid of vertices pMesh.Vertices.Add(0, 2, 0); // 0 pMesh.Vertices.Add(0, 1, 0); // 1 pMesh.Vertices.Add(0, 0, 0); // 2 pMesh.Vertices.Add(1, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Vertices.Add(1, 0, 0); // 5 pMesh.Vertices.Add(2, 2, 0); // 6 pMesh.Vertices.Add(2, 1, 0); // 7 pMesh.Vertices.Add(2, 0, 0); // 8 // Create four quadrangular faces pMesh.Faces.AddFace(0, 1, 4, 3); pMesh.Faces.AddFace(3, 4, 7, 6); pMesh.Faces.AddFace(1, 2, 5, 4); pMesh.Faces.AddFace(4, 5, 8, 7); Assert.AreEqual(new PlanktonXYZ(0, 0, 1), pMesh.Vertices.GetNormal(0)); // corner Assert.AreEqual(new PlanktonXYZ(0, 0, 1), pMesh.Vertices.GetNormal(7)); // edge Assert.AreEqual(new PlanktonXYZ(0, 0, 1), pMesh.Vertices.GetNormal(4)); // centre }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { //Global variables PlanktonMesh pMesh = new PlanktonMesh(); DA.GetData(0, ref pMesh); Matrix mV = null; DA.GetData(1, ref mV); //--------------------------------------------------------------- //Detect number of peaks/troughs of each mode List <String> peaksPerMode = new List <String>(); List <String> troughsPerMode = new List <String>(); for (int j = 0; j < mV.ColumnCount; j++) { peaksPerMode.Add(calcNumberOfPeaks(pMesh, mV, j, true)); troughsPerMode.Add(calcNumberOfPeaks(pMesh, mV, j, false)); } //--------------------------------------------------------------- //Output DA.SetDataList(0, peaksPerMode); DA.SetDataList(1, troughsPerMode); }
//HALFEDGE INDEX FROM VERTEX INDICES /* return halfedge index from given i and j vertex indices. -1 if halfedge doesn't exist */ public int findHalfedgeIndex(PlanktonMesh pMesh, int i, int j) { //find halfedge from vertex i to j if it exists int halfedge_ij = pMesh.Halfedges.FindHalfedge(i, j); return halfedge_ij; }
//COTANGENT LAPLACIAN /* calculate the cotangent Laplacian of the input mesh */ public Matrix calcCotLaplacian(PlanktonMesh pMesh, List<double> cotEdgeWeights) { int n = pMesh.Vertices.Count; Matrix mL = new Matrix(n, n); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i == j) { mL[i, j] = calcCotVertexWeight(pMesh, i, cotEdgeWeights); } else { int edgeIndex = findHalfedgeIndex(pMesh, i, j); if (edgeIndex != -1) { mL[i, j] = -1 * cotEdgeWeights[edgeIndex]; } } } } return mL; }
public void CanTruncateMesh() { // Create a simple cube PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(-0.5, -0.5, 0.5); pMesh.Vertices.Add(-0.5, -0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, 0.5); pMesh.Faces.AddFace(3, 2, 1, 0); pMesh.Faces.AddFace(1, 5, 4, 0); pMesh.Faces.AddFace(2, 6, 5, 1); pMesh.Faces.AddFace(7, 6, 2, 3); pMesh.Faces.AddFace(4, 7, 3, 0); pMesh.Faces.AddFace(5, 6, 7, 4); Assert.AreEqual(6, pMesh.Faces.Count); var tMesh = pMesh.TruncateVertices(); Assert.AreEqual(pMesh.Faces.Count + pMesh.Vertices.Count, tMesh.Faces.Count); // TODO: geometrical check }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { //------------------INPUT--------------------// PlanktonMesh pMesh = new PlanktonMesh(); DA.GetData(0, ref pMesh); Plane pln = new Plane(); DA.GetData(1, ref pln); //------------------CALCULATE--------------------// PMeshExt pMeshE = new PMeshExt(pMesh); if (!pMeshE.isMeshTriangulated()) { this.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "The mesh has to be triangulated"); } Point3d[] verticesXYZ = pMeshE.convertVerticesToXYZ(); Vector3d[] vertexAreas = pMeshE.calcVertexVoronoiAreas(pln); //------------------OUTPUT--------------------// DA.SetDataList(0, verticesXYZ); DA.SetDataList(1, vertexAreas); }
public void CanCompact() { PlanktonMesh pMesh = new PlanktonMesh(); // Create vertices in 3x2 grid pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 pMesh.Vertices.Add(2, 0, 0); // 4 pMesh.Vertices.Add(2, 1, 0); // 5 // Create two quadrangular faces pMesh.Faces.AddFace(0, 1, 2, 3); pMesh.Faces.AddFace(1, 4, 5, 2); // Remove the first face and compact pMesh.Faces.RemoveFace(0); pMesh.Vertices.CullUnused(); pMesh.Compact(); // Check some things about the compacted mesh Assert.AreEqual(4, pMesh.Vertices.Count); Assert.AreEqual(1, pMesh.Faces.Count); Assert.AreEqual(8, pMesh.Halfedges.Count); Assert.AreEqual(new int[] { 0, 2, 3, 1 }, pMesh.Faces.GetFaceVertices(0)); }
public void CanCalculateVolume() { // Create a simple cube PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(-0.5, -0.5, 0.5); pMesh.Vertices.Add(-0.5, -0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, 0.5); pMesh.Faces.AddFace(3, 2, 1, 0); pMesh.Faces.AddFace(1, 5, 4, 0); pMesh.Faces.AddFace(2, 6, 5, 1); pMesh.Faces.AddFace(7, 6, 2, 3); pMesh.Faces.AddFace(4, 7, 3, 0); pMesh.Faces.AddFace(5, 6, 7, 4); // Calculate volume Assert.AreEqual(1, pMesh.Volume(), 1E-9); }
public void CannotCollapseNonManifoldEdge() { PlanktonMesh pMesh = new PlanktonMesh(); // Create vertices in 3x2 grid pMesh.Vertices.Add(-1, 0, 0); // 0 pMesh.Vertices.Add(0, -1, 0); // 1 pMesh.Vertices.Add(1, 0, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 pMesh.Vertices.Add(-1, -2, 0); // 4 pMesh.Vertices.Add(0, -3, 0); // 5 pMesh.Vertices.Add(1, -2, 0); // 6 // Create several triangular faces pMesh.Faces.AddFace(0, 1, 3); pMesh.Faces.AddFace(1, 2, 3); pMesh.Faces.AddFace(0, 4, 1); pMesh.Faces.AddFace(4, 5, 1); // And one quad face pMesh.Faces.AddFace(1, 5, 6, 2); pMesh.Faces.Stellate(0); pMesh.Faces.Stellate(1); Assert.AreEqual(9, pMesh.Faces.Count); Assert.AreEqual(-1, pMesh.Halfedges.CollapseEdge(2)); Assert.AreEqual(-1, pMesh.Halfedges.CollapseEdge(6)); }
public void CanCollapseSameFace(int h) { // 3-------2 // | f1 | Tries to collapse the halfedge... // | | * 0 - from vertex 1 to vertex 4 // | 4 | * 1 - from vertex 4 to vertex 1 // | / \ | * 2 - from vertex 4 to vertex 0 // |/ f0 \| * 3 - from vertex 0 to vertex 4 // 0-------1 PlanktonMesh mesh = new PlanktonMesh(); mesh.Vertices.Add(0, 0, 0); // 0 mesh.Vertices.Add(100, 0, 0); // 1 mesh.Vertices.Add(100, 100, 0); // 2 mesh.Vertices.Add(0, 100, 0); // 3 mesh.Vertices.Add(50, 50, 0); // 4 mesh.Faces.AddFace(1, 4, 0); mesh.Faces.AddFace(new int[] { 1, 2, 3, 0, 4 }); mesh.Halfedges.CollapseEdge(h); Assert.IsTrue(mesh.Faces[0].IsUnused, "face 0 should be unset"); Assert.AreEqual(4, mesh.Faces.GetFaceVertices(1).Length, "face 1 should have 4 vertices"); }
public void CanCollapseValenceThreeVertex() { PlanktonMesh pMesh = new PlanktonMesh(); // Create mesh with one triangular face pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(2, 0, 0); // 1 pMesh.Vertices.Add(1, 1.4, 0); // 2 pMesh.Faces.AddFace(0, 1, 2); // create vertex at center and get a halfedge pointing *towards* it int v = pMesh.Faces.Stellate(0); int h = pMesh.Vertices.GetIncomingHalfedge(v); // count faces before collapse Assert.AreEqual(3, pMesh.Faces.Count); // attempt to collapse one of the internal edges Assert.GreaterOrEqual(0, pMesh.Halfedges.CollapseEdge(h)); // there should be 6 unused halfedges now... Assert.AreEqual(6, pMesh.Halfedges.Where(q => q.IsUnused).Count()); // compact and count faces again pMesh.Compact(); Assert.AreEqual(1, pMesh.Faces.Count); }
//RMS public double calcRMS(PlanktonMesh pMesh, Matrix mV, List <int> eigsIndices, List <double> weights, double scale, List <Vector3d> displDir, List <double> distanceSignal) { List <double> nodalValues = nodalLinearCombination(weights, mV, eigsIndices); List <Vector3d> vertexDisplacements = mapToDisplacements(nodalValues, displDir, scale); List <double> vertexDeviations = new List <double>(); for (int i = 0; i < pMesh.Vertices.Count; i++) { Point3d vPos = new Point3d(pMesh.Vertices[i].X, pMesh.Vertices[i].Y, pMesh.Vertices[i].Z); Point3d vPosNew = vPos + vertexDisplacements[i]; Point3d vPosSrf = vPos + displDir[i] * distanceSignal[i]; double deviation = vPosNew.DistanceTo(vPosSrf); vertexDeviations.Add(deviation); } double rms = 0.0; foreach (double dev in vertexDeviations) { rms += Math.Pow(dev, 2); } double n = (double)vertexDeviations.Count; rms /= n; rms = Math.Sqrt(rms); return(rms); }
//Find the two indexes in the warpIndexList which correspond to the two neighbour vertices in the warp direction int[] findWarpIndexes(PlanktonMesh pMesh, int vIndex, List <int> warpIndexList) { int[] wIndexes = new int[2]; //find center vertex in warp index list (every vertex has a warp direction) int cIndexW = warpIndexList.IndexOf(vIndex); //extract index+1 and index-1 (always works because only internal vertices are considered) int v0IndexW = warpIndexList[cIndexW - 1]; int v1IndexW = warpIndexList[cIndexW + 1]; //find index of these two in vNeighbours int[] vNeighbours = pMesh.Vertices.GetVertexNeighbours(vIndex); int v0IndexN = Array.IndexOf(vNeighbours, v0IndexW); int v1IndexN = Array.IndexOf(vNeighbours, v1IndexW); if (v0IndexN != -1 && v1IndexN != -1) { wIndexes[0] = v0IndexN; wIndexes[1] = v1IndexN; } else { this.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Not able to find warp direction within the neighbourhood of vertex " + vIndex); } return(wIndexes); }
//Convert polyline points to mesh indexes List <int> convertPolyToMeshIndexes(PlanktonMesh pMesh, Polyline pl) { List <int> plIndexes = new List <int>(); List <Point3d> verticesXYZ = extractMeshVerticesXYZ(pMesh); List <Point3d> polylinePts = new List <Point3d>(); for (int i = 0; i < pl.Count; i++) { polylinePts.Add(pl[i]); } foreach (Point3d pt in polylinePts) { int index = verticesXYZ.IndexOf(pt); if (index == -1) { this.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Was not able to find point P(" + pt.X + "," + pt.Y + "," + pt.Z + ") amongst the vertices in the mesh"); } else { plIndexes.Add(index); } } return(plIndexes); }
//Methods //Create bitmap frame as bounding box of vertices in mesh public Rectangle createFrame(PlanktonMesh pMesh) { //desired max frame dimension int dim = 300; List <double> xCoord = new List <double>(); List <double> yCoord = new List <double>(); foreach (PlanktonVertex pV in pMesh.Vertices) { xCoord.Add(pV.X); yCoord.Add(pV.Y); } double xRange = xCoord.Max() - xCoord.Min(); double yRange = yCoord.Max() - yCoord.Min(); //Compare and scale according to desired dimension double ratio = dim / xRange; if (yRange > xRange) { ratio = dim / yRange; } int w = (int)Math.Ceiling(xRange * ratio); int h = (int)Math.Ceiling(yRange * ratio); Rectangle rec = new Rectangle(0, 0, w, h); return(rec); }
PlanktonMesh PMeshFromPolylines(List <Polyline> faces) { var pMesh = new PlanktonMesh(); //add n-gon faces var verts = new List <Point3d>(); for (int i = 0; i < faces.Count; i++) { var currFace = new List <int>(); for (int j = 0; j < faces[i].Count - 1; j++) { var currPt = faces[i].PointAt(j); var id = verts.IndexOf(currPt); if (id < 0) { //push a vertex to list //push that index to current face pMesh.Vertices.Add(currPt.X, currPt.Y, currPt.Z); verts.Add(currPt); currFace.Add(pMesh.Vertices.Count - 1); } else { //push this index to current face currFace.Add(id); } } pMesh.Faces.AddFace(currFace); } return(pMesh); }
public void CanGetNormals() { PlanktonMesh pMesh = new PlanktonMesh(); // Create 3x3 grid of vertices pMesh.Vertices.Add(0, 2, 0); // 0 pMesh.Vertices.Add(0, 1, 0); // 1 pMesh.Vertices.Add(0, 0, 0); // 2 pMesh.Vertices.Add(1, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Vertices.Add(1, 0, 0); // 5 pMesh.Vertices.Add(2, 2, 0); // 6 pMesh.Vertices.Add(2, 1, 0); // 7 pMesh.Vertices.Add(2, 0, 0); // 8 // Create four quadrangular faces pMesh.Faces.AddFace(0, 1, 4, 3); pMesh.Faces.AddFace(3, 4, 7, 6); pMesh.Faces.AddFace(1, 2, 5, 4); pMesh.Faces.AddFace(4, 5, 8, 7); var expected = Enumerable.Repeat(new PlanktonXYZ(0, 0, 1), 9).ToArray(); Assert.AreEqual(expected, pMesh.Vertices.GetNormals()); }
public static Vector3d Normal(PlanktonMesh P, int V) { Point3d Vertex = P.Vertices[V].ToPoint3d(); Vector3d Norm = new Vector3d(); int[] OutEdges = P.Vertices.GetHalfedges(V); int[] Neighbours = P.Vertices.GetVertexNeighbours(V); Vector3d[] OutVectors = new Vector3d[Neighbours.Length]; int Valence = P.Vertices.GetValence(V); for (int j = 0; j < Valence; j++) { OutVectors[j] = P.Vertices[Neighbours[j]].ToPoint3d() - Vertex; } for (int j = 0; j < Valence; j++) { if (P.Halfedges[OutEdges[(j + 1) % Valence]].AdjacentFace != -1) { Norm += (Vector3d.CrossProduct(OutVectors[(j + 1) % Valence], OutVectors[j])); } } Norm.Unitize(); return(Norm); }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { //Global variables List <Point3d> list_pt_Vertices = new List <Point3d>(); DA.GetDataList(0, list_pt_Vertices); List <Polyline> list_pl_FacePolygons = new List <Polyline>(); //convert from curve to polyline since pManager only takes curves List <Curve> list_crv_FacePolygons = new List <Curve>(); DA.GetDataList(1, list_crv_FacePolygons); foreach (Curve crv in list_crv_FacePolygons) { Polyline pl; crv.TryGetPolyline(out pl); list_pl_FacePolygons.Add(pl); } //Create plankton mesh PlanktonMesh pMesh = createPlanktonMesh(list_pt_Vertices, list_pl_FacePolygons); DA.SetData(0, pMesh); }
/// <summary> /// Returns all the holes on the specified mesh, ignoring seams along the UV edge. /// </summary> public static IEnumerable <List <int> > AllBorderLoops(this PlanktonMesh mesh) { HashSet <int> seenEdges = new HashSet <int>(); List <int> startEdges = new List <int>(); foreach (var edge in AllBorderEdges(mesh.Halfedges)) { if (!seenEdges.Contains(edge)) { startEdges.Add(edge); foreach (var borderEdge in TraceBorderFromEdge(mesh, edge)) { seenEdges.Add(borderEdge); } } } foreach (var edge in startEdges) { foreach (var loop in CollapseSeamEdgeIntoLoops(mesh, TraceBorderFromEdge(mesh, edge).ToList())) { yield return(loop); } } }
public static PlanktonMesh UnityToPlankton(this Mesh mesh) { var plankton = new PlanktonMesh(); // Cache the mesh data. var vertices = mesh.vertices; var triangles = mesh.triangles; var uvs = mesh.uv; var normals = mesh.normals; for (int i = 0; i < vertices.Length; i++) { plankton.Vertices.Add( vertices[i].x, vertices[i].y, vertices[i].z, new PlanktonVertexData() { UV = uvs[i], Normal = normals[i] } ); } int[] triIndices = new int[3]; for (int i = 0; i < triangles.Length; i += 3) { triIndices[0] = triangles[i]; triIndices[1] = triangles[i + 1]; triIndices[2] = triangles[i + 2]; plankton.Faces.AddFace(triIndices); } return(plankton); }
public static HashSet <int> FindConnectedFaces(this PlanktonMesh mesh, int startFaceIndex, HashSet <int> blockedEdges) { HashSet <int> seenFaces = new HashSet <int>(); Queue <int> queued = new Queue <int>(); seenFaces.Add(startFaceIndex); queued.Enqueue(startFaceIndex); int[] foundEdges = new int[3]; while (queued.Count > 0) { int face = queued.Dequeue(); int edgeNum = mesh.Faces.GetHalfedgesNonAlloc(face, foundEdges); for (int i = 0; i < edgeNum; i++) { var pairEdge = mesh.Halfedges.GetPairHalfedge(foundEdges[i]); int pairFace = mesh.Halfedges[pairEdge].AdjacentFace; if (pairFace != -1 && !seenFaces.Contains(pairFace) && !blockedEdges.Contains(pairEdge)) { queued.Enqueue(pairFace); seenFaces.Add(pairFace); } } } return(seenFaces); }
/// <summary> /// Creates a Rhino mesh from a Plankton halfedge mesh. /// Uses the face-vertex information available in the halfedge data structure. /// </summary> /// <returns>A <see cref="Mesh"/> which represents the source mesh (as best it can).</returns> /// <param name="source">A Plankton mesh to convert from.</param> /// <remarks>Any faces with five sides or more will be triangulated.</remarks> public static Mesh ToRhinoMesh(this PlanktonMesh source) { // could add different options for triangulating ngons later Mesh rMesh = new Mesh(); foreach (PlanktonVertex v in source.Vertices) { rMesh.Vertices.Add(v.X, v.Y, v.Z); } for (int i = 0; i < source.Faces.Count; i++) { int[] fvs = source.Faces.GetFaceVertices(i); if (fvs.Length == 3) { rMesh.Faces.AddFace(fvs[0], fvs[1], fvs[2]); } else if (fvs.Length == 4) { rMesh.Faces.AddFace(fvs[0], fvs[1], fvs[2], fvs[3]); } else if (fvs.Length > 4) { // triangulate about face center (fan) var fc = source.Faces.GetFaceCenter(i); rMesh.Vertices.Add(fc.X, fc.Y, fc.Z); for (int j = 0; j < fvs.Length; j++) { rMesh.Faces.AddFace(fvs[j], fvs[(j + 1) % fvs.Length], rMesh.Vertices.Count - 1); } } } return(rMesh); }
public void CanCollapseBoundaryEdge() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create one quadrangular face pMesh.Faces.AddFace(0, 1, 2, 3); int h_clps = 0; int h_clps_prev = pMesh.Halfedges[h_clps].PrevHalfedge; // Confirm face's first halfedge is the one to be collapsed Assert.AreEqual(h_clps, pMesh.Faces[0].FirstHalfedge); // Collapse edge int h_rtn = pMesh.Halfedges.CollapseEdge(h_clps); // Edge collapse should return successor around start vertex Assert.AreEqual(7, h_rtn); // Check face's first halfedge was updated Assert.AreNotEqual(h_clps, pMesh.Faces[0].FirstHalfedge); // Check for closed loop (without collapsed halfedge) Assert.AreEqual(new int[] { 2, 4, 6 }, pMesh.Faces.GetHalfedges(0)); // Pair of predecessor to collapsed halfedge should now have its start vertex int h_clps_prev_pair = pMesh.Halfedges.GetPairHalfedge(h_clps_prev); Assert.AreEqual(0, pMesh.Halfedges[h_clps_prev_pair].StartVertex); }
public void CanFindHalfedgeUnusedVertices() { PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(0, 0, 0); pMesh.Vertices.Add(1, 1, 1); // Check for halfedge between v0 and v1 // In fact, both are unused so we shouldn't find one Assert.AreEqual(-1, pMesh.Halfedges.FindHalfedge(0, 1)); }
public void CanCullUnused() { // Create a mesh and add some vertices, but don't connect anything to them! PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(0, 0, 0); pMesh.Vertices.Add(1, 1, 1); // Cull unused vertices and check count pMesh.Vertices.CullUnused(); Assert.AreEqual(0, pMesh.Vertices.Count); }
public void CanAddFacesAroundNonManifoldVertex() { // a.k.a. the pizza slice test... PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(0.0, 0.0, 0.0); // 0 - center pMesh.Vertices.Add(-0.5, -0.2, 0.0); // 1 - left/bottom pMesh.Vertices.Add(-0.5, 0.2, 0.0); // 2 - left/top pMesh.Vertices.Add(-0.2, 0.5, 0.0); // 3 - top/left pMesh.Vertices.Add(0.2, 0.5, 0.0); // 4 - top/right pMesh.Vertices.Add(0.5, 0.2, 0.0); // 5 - right/top pMesh.Vertices.Add(0.5, -0.2, 0.0); // 6 - right/bottom // Add the first face pMesh.Faces.AddFace(2, 1, 0); // Check vertex #0 outgoing halfedge index Assert.AreEqual(3, pMesh.Vertices[0].OutgoingHalfedge); // Add a face which would create a non-manifold condition at vertex #1 pMesh.Faces.AddFace(0, 4, 3); //Assert.AreEqual(12, pMesh.Halfedges.Count); // Check that vertex #0 has the expected number of boundary edges Assert.AreEqual(4, pMesh.Vertices.NakedEdgeCount(0)); // Check vertex #0 outgoing halfedge index Assert.AreEqual(11, pMesh.Vertices[0].OutgoingHalfedge); // Add another face and check again pMesh.Faces.AddFace(6, 5, 0); Assert.AreEqual(6, pMesh.Vertices.NakedEdgeCount(0)); Assert.AreEqual(15, pMesh.Vertices[0].OutgoingHalfedge); Assert.AreEqual(6, pMesh.Vertices.GetHalfedges(0).Length); // Plug a gap - vertex #0 ->outgoing should move pMesh.Faces.AddFace(5, 4, 0); Assert.AreEqual(4, pMesh.Vertices.NakedEdgeCount(0)); Assert.IsTrue(pMesh.Halfedges[pMesh.Vertices[0].OutgoingHalfedge].AdjacentFace < 0); Assert.AreEqual(6, pMesh.Vertices.GetHalfedges(0).Length); // Plug another gap which should make vertex #0 manifold again int f = pMesh.Faces.AddFace(0, 3, 2); Assert.AreEqual(6, pMesh.Vertices.GetHalfedges(0).Length); Assert.AreEqual(2, pMesh.Vertices.NakedEdgeCount(0)); // Try adding a face which already exits f = pMesh.Faces.AddFace(0, 5, 4); Assert.AreEqual(-1, f, "Face not added."); }
public void CanCollapseAdjacentTriangles() { // TODO: draw figure here... PlanktonMesh pMesh = new PlanktonMesh(); // Create several vertices pMesh.Vertices.Add(0, 3, 0); // 0 pMesh.Vertices.Add(0, 2, 0); // 1 pMesh.Vertices.Add(0, 1, 0); // 2 pMesh.Vertices.Add(1, 3, 0); // 3 pMesh.Vertices.Add(1, 2, 0); // 4 pMesh.Vertices.Add(1, 1, 0); // 5 pMesh.Vertices.Add(1, 0, 0); // 6 pMesh.Vertices.Add(2, 2, 0); // 7 pMesh.Vertices.Add(2, 1, 0); // 8 // Create several faces pMesh.Faces.AddFace(0, 1, 4, 3); // 0 pMesh.Faces.AddFace(1, 2, 5, 4); // 1 pMesh.Faces.AddFace(3, 4, 7); // 2 pMesh.Faces.AddFace(4, 5, 7); // 3 pMesh.Faces.AddFace(7, 5, 6, 8); // 4 // Try to collapse edge between vertices #4 and #7 int h_clps = pMesh.Halfedges.FindHalfedge(4, 7); //int v_keep = pMesh.Halfedges[h_clps].StartVertex; int h_succ = pMesh.Halfedges.GetVertexCirculator(h_clps).ElementAt(1); Assert.AreEqual(h_succ, pMesh.Halfedges.CollapseEdge(h_clps)); // Successor to h (around h's start vertex) should now be adjacent to face #4 Assert.AreEqual(4, pMesh.Halfedges[h_succ].AdjacentFace); // Check new vertices of face #4 Assert.AreEqual(new int[] { 5, 6, 8, 4 }, pMesh.Faces.GetFaceVertices(4)); // Traverse around mesh boundary and count halfedges int count, he_first, he_current; count = 0; he_first = 1; he_current = he_first; do { count++; he_current = pMesh.Halfedges[he_current].NextHalfedge; } while (he_current != he_first); Assert.AreEqual(8, count); Assert.IsTrue(pMesh.Faces[2].IsUnused && pMesh.Faces[3].IsUnused); }
public void CanFindHalfedge() { // Create a mesh with a single quad face PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(0, 0, 0); pMesh.Vertices.Add(1, 0, 0); pMesh.Vertices.Add(1, 1, 0); pMesh.Vertices.Add(0, 1, 0); pMesh.Faces.AddFace(0, 1, 2, 3); // Try and find some halfedges... Assert.AreEqual(0, pMesh.Halfedges.FindHalfedge(0, 1)); Assert.AreEqual(2, pMesh.Halfedges.FindHalfedge(1, 2)); Assert.AreEqual(-1, pMesh.Halfedges.FindHalfedge(0, 2)); }
public void CannotMergeFacesAntenna() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 pMesh.Vertices.Add(0.5, 0.5, 0); // 4 // Create two quadrangular faces pMesh.Faces.AddFace(0, 1, 2, 4); pMesh.Faces.AddFace(2, 3, 0, 4); // Merge should fail (faces are joined by two edges) Assert.AreEqual(-1, pMesh.Faces.MergeFaces(4)); }
public void CanCompact() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create two triangular faces pMesh.Faces.AddFace(0, 1, 2); pMesh.Faces.AddFace(2, 3, 0); // Merge faces and compact (squashing face #0) pMesh.Faces.MergeFaces(4); pMesh.Faces.CompactHelper(); // Check some things about the compacted mesh Assert.AreEqual(1, pMesh.Faces.Count); Assert.AreEqual(new int[] { 0, 1, 2, 3 }, pMesh.Faces.GetFaceVertices(0)); }
public void CanMergeFaces() { PlanktonMesh pMesh = new PlanktonMesh(); // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create two triangular faces pMesh.Faces.AddFace(0, 1, 2); pMesh.Faces.AddFace(2, 3, 0); // Force merge to update outgoing halfedge of vertex #2 pMesh.Vertices[2].OutgoingHalfedge = 4; // Merge faces int h_rtn = pMesh.Faces.MergeFaces(4); // Check that the correct face was retained int f = pMesh.Halfedges[h_rtn].AdjacentFace; Assert.AreEqual(0, f); // Check face halfedges int[] fhs = pMesh.Faces.GetHalfedges(f); Assert.AreEqual(new int[] { 0, 2, 6, 8 }, fhs); foreach (int h in fhs) { Assert.AreEqual(f, pMesh.Halfedges[h].AdjacentFace); } // Check that outgoing halfedge of vertex #2 was updated correctly Assert.AreEqual(6, pMesh.Vertices[2].OutgoingHalfedge); Assert.AreEqual(f, pMesh.Halfedges[6].AdjacentFace); }
public void CanSplitVertex() { // Create a simple non-manifold vertex and split it to make it manifold. PlanktonMesh pMesh = new PlanktonMesh(); // Create 'X' of vertices pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(2, 0, 0); // 1 pMesh.Vertices.Add(2, 2, 0); // 2 pMesh.Vertices.Add(0, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Faces.AddFace(0, 4, 3); pMesh.Faces.AddFace(2, 4, 1); // Check we're using the correct halfedges to split vertex #4 Assert.AreEqual(8, pMesh.Halfedges.FindHalfedge(4, 1)); Assert.AreEqual(2, pMesh.Halfedges.FindHalfedge(4, 3)); Assert.AreEqual(7, pMesh.Vertices[4].OutgoingHalfedge); // Split vertex #4 (center) int h_new = pMesh.Vertices.SplitVertex(8, 2); // Check the new halfedge... Assert.AreEqual(12, h_new); Assert.AreEqual(h_new, pMesh.Halfedges.FindHalfedge(4, 5)); // Check old vertex Assert.AreEqual(1, pMesh.Vertices[4].OutgoingHalfedge); Assert.AreEqual(new int[] { 1, 12, 8 }, pMesh.Vertices.GetHalfedges(4)); // Check new vertex Assert.AreEqual(new int[] { 7, 13, 2 }, pMesh.Vertices.GetHalfedges(5)); }
public void CanCompact() { PlanktonMesh pMesh = new PlanktonMesh(); // Create 3x3 grid of vertices pMesh.Vertices.Add(0, 2, 0); // 0 pMesh.Vertices.Add(0, 1, 0); // 1 pMesh.Vertices.Add(0, 0, 0); // 2 pMesh.Vertices.Add(1, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Vertices.Add(1, 0, 0); // 5 pMesh.Vertices.Add(2, 2, 0); // 6 pMesh.Vertices.Add(2, 1, 0); // 7 pMesh.Vertices.Add(2, 0, 0); // 8 // Create four quadrangular faces pMesh.Faces.AddFace(0, 1, 4, 3); pMesh.Faces.AddFace(3, 4, 7, 6); pMesh.Faces.AddFace(1, 2, 5, 4); pMesh.Faces.AddFace(4, 5, 8, 7); int vertexCount = pMesh.Vertices.Count; // Collapse a couple of edges, thus removing two vertices (0 and 2) pMesh.Halfedges.CollapseEdge(1); pMesh.Halfedges.CollapseEdge(14); // Compact vertex list pMesh.Vertices.CompactHelper(); // Check new size of vertex list Assert.AreEqual(vertexCount - 2, pMesh.Vertices.Count); // Check we can still traverse from vertices correctly (5 used to be 7) Assert.AreEqual(new int[] { -1, 3, 1 }, pMesh.Vertices.GetVertexFaces(5)); }
public void CanEraseCenterVertex() { // TODO: draw figure here... PlanktonMesh pMesh = new PlanktonMesh(); // Create 3x3 grid of vertices pMesh.Vertices.Add(0, 2, 0); // 0 pMesh.Vertices.Add(0, 1, 0); // 1 pMesh.Vertices.Add(0, 0, 0); // 2 pMesh.Vertices.Add(1, 2, 0); // 3 pMesh.Vertices.Add(1, 1, 0); // 4 (center) pMesh.Vertices.Add(1, 0, 0); // 5 pMesh.Vertices.Add(2, 2, 0); // 6 pMesh.Vertices.Add(2, 1, 0); // 7 pMesh.Vertices.Add(2, 0, 0); // 8 // Create four quadrangular faces pMesh.Faces.AddFace(0, 1, 4, 3); pMesh.Faces.AddFace(3, 4, 7, 6); pMesh.Faces.AddFace(1, 2, 5, 4); pMesh.Faces.AddFace(4, 5, 8, 7); Assert.AreEqual(4, pMesh.Halfedges[4].StartVertex); Assert.AreEqual(0, pMesh.Halfedges[4].AdjacentFace); // Erase center vertex pMesh.Vertices.EraseCenterVertex(4); Assert.IsFalse(pMesh.Faces[0].IsUnused); int[] faceHalfedges = pMesh.Faces.GetHalfedges(0); int[] expected = new int[] { 6, 0, 14, 16, 20, 22, 10, 12 }; Assert.AreEqual(8, faceHalfedges.Length); Assert.AreEqual(expected, faceHalfedges); Assert.AreEqual(-1, pMesh.Vertices[4].OutgoingHalfedge); }
public void CanCollapseInternalEdge() { PlanktonMesh pMesh = new PlanktonMesh(); // Create a three-by-three grid of vertices pMesh.Vertices.Add(-0.5, -0.5, 0.0); // 0 pMesh.Vertices.Add(-0.5, 0.0, 0.0); // 1 pMesh.Vertices.Add(-0.5, 0.5, 0.0); // 2 pMesh.Vertices.Add(0.0, -0.5, 0.0); // 3 pMesh.Vertices.Add(0.0, 0.0, 0.0); // 4 pMesh.Vertices.Add(0.0, 0.5, 0.0); // 5 pMesh.Vertices.Add(0.5, -0.5, 0.0); // 6 pMesh.Vertices.Add(0.5, 0.0, 0.0); // 7 pMesh.Vertices.Add(0.5, 0.5, 0.0); // 8 // Create four quadrangular faces pMesh.Faces.AddFace(1, 4, 5, 2); pMesh.Faces.AddFace(0, 3, 4, 1); pMesh.Faces.AddFace(4, 7, 8, 5); pMesh.Faces.AddFace(3, 6, 7, 4); Assert.AreEqual(4, pMesh.Faces.Count); int h_clps = pMesh.Vertices[4].OutgoingHalfedge; int v_suc = pMesh.Vertices.GetHalfedges(4)[1]; int h_boundary = pMesh.Vertices[3].OutgoingHalfedge; // Collapse center vertex's outgoing halfedge int h_rtn = pMesh.Halfedges.CollapseEdge(h_clps); // Check that center vertex's outgoing halfedge has been updated Assert.AreEqual(h_boundary, pMesh.Vertices[4].OutgoingHalfedge); // Edge collapse should return successor around start vertex Assert.AreEqual(v_suc, h_rtn); // Check for closed loops (without collapsed halfedge) Assert.AreEqual(4, pMesh.Faces.GetHalfedges(0).Length); Assert.AreEqual(3, pMesh.Faces.GetHalfedges(1).Length); Assert.AreEqual(4, pMesh.Faces.GetHalfedges(2).Length); Assert.AreEqual(3, pMesh.Faces.GetHalfedges(3).Length); // Check no halfedges reference removed vertex (#7) for (int h = 0; h < pMesh.Halfedges.Count; h++) { if (h == h_clps || h == pMesh.Halfedges.GetPairHalfedge(h_clps)) continue; // Skip removed halfedges Assert.AreNotEqual(3, pMesh.Halfedges[h].StartVertex); } }
public void CanCollapseValenceThreeVertex() { // Create five faces and collapse diagonal edge // (halfedge {4->8} - valence three vertex at end) // // 0---3---6 // | | | // | | | // 1-- 4---7 // | |\ | // | | \| // 2---5---8 PlanktonMesh pMesh = new PlanktonMesh(); // Create mesh with one triangular face pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(2, 0, 0); // 1 pMesh.Vertices.Add(1, 1.4, 0); // 2 pMesh.Faces.AddFace(0, 1, 2); pMesh.Faces.Stellate(0); int h = pMesh.Vertices.GetIncomingHalfedge(3); Assert.AreEqual(3, pMesh.Faces.Count); Assert.GreaterOrEqual(0, pMesh.Halfedges.CollapseEdge(h)); pMesh.Compact(); Assert.AreEqual(1, pMesh.Faces.Count); }
public void CanFlipEdge() { // Create a triangulated grid and flip one of the edges. // // Before >>> After // // 2---5---8 2---5- // |\ | /| | /| // | \ | / | | / | // | \|/ | |/ |/ // 1---4---7 1---4- // | /|\ | | /|\ // | / | \ | // |/ | \| (etc.) // 0---3---6 // PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(-0.5, -0.5, 0.0); // 0 pMesh.Vertices.Add(-0.5, 0.0, 0.0); // 1 pMesh.Vertices.Add(-0.5, 0.5, 0.0); // 2 pMesh.Vertices.Add(0.0, -0.5, 0.0); // 3 pMesh.Vertices.Add(0.0, 0.0, 0.0); // 4 pMesh.Vertices.Add(0.0, 0.5, 0.0); // 5 pMesh.Vertices.Add(0.5, -0.5, 0.0); // 6 pMesh.Vertices.Add(0.5, 0.0, 0.0); // 7 pMesh.Vertices.Add(0.5, 0.5, 0.0); // 8 pMesh.Faces.AddFace(4, 1, 0); // 0 pMesh.Faces.AddFace(4, 0, 3); // 1 pMesh.Faces.AddFace(4, 3, 6); // 2 pMesh.Faces.AddFace(4, 6, 7); // 3 pMesh.Faces.AddFace(4, 7, 8); // 4 pMesh.Faces.AddFace(4, 8, 5); // 5 pMesh.Faces.AddFace(4, 5, 2); // 6 pMesh.Faces.AddFace(4, 2, 1); // 7 // Find the outgoing halfedge of Vertex #4 (center) int he = pMesh.Vertices[4].OutgoingHalfedge; Assert.AreEqual(29, he); Assert.IsTrue(pMesh.Halfedges.FlipEdge(he)); // Check vertices for each face Assert.AreEqual(new int[]{ 1, 5, 2 }, pMesh.Faces.GetFaceVertices(6)); Assert.AreEqual(new int[]{ 5, 1, 4 }, pMesh.Faces.GetFaceVertices(7)); // Check outgoing he of Vertex #4 has been updated he = pMesh.Vertices[4].OutgoingHalfedge; Assert.AreNotEqual(29, he, "Vertex #4 should not be linked to Halfedge #29 post-flip"); Assert.AreEqual(25, he); // Check adjacent face in each interior halfedge is correct foreach (int h in pMesh.Faces.GetHalfedges(0)) { Assert.AreEqual(0, pMesh.Halfedges[h].AdjacentFace); } foreach (int h in pMesh.Faces.GetHalfedges(1)) { Assert.AreEqual(1, pMesh.Halfedges[h].AdjacentFace); } // Check halfedges for each vertex if (pMesh.Vertices.GetHalfedges(4).Contains(29)) Assert.Fail("Vertex #4 should not be linked to Halfedge #29 post-flip"); if (pMesh.Vertices.GetHalfedges(2).Contains(28)) Assert.Fail("Vertex #2 should not be linked to Halfedge #28 post-flip"); Assert.Contains(29, pMesh.Vertices.GetHalfedges(5), "Vertex #5 should now be linked to Halfedge #29"); Assert.Contains(28, pMesh.Vertices.GetHalfedges(1), "Vertex #1 should now be linked to Halfedge #28"); }
public void CanCreateCubeFromFaceVertex() { // Create a simple cube PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Vertices.Add(-0.5, -0.5, 0.5); pMesh.Vertices.Add(-0.5, -0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, -0.5); pMesh.Vertices.Add(-0.5, 0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, 0.5); pMesh.Vertices.Add(0.5, -0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, -0.5); pMesh.Vertices.Add(0.5, 0.5, 0.5); pMesh.Faces.AddFace(3, 2, 1, 0); pMesh.Faces.AddFace(1, 5, 4, 0); pMesh.Faces.AddFace(2, 6, 5, 1); pMesh.Faces.AddFace(7, 6, 2, 3); pMesh.Faces.AddFace(4, 7, 3, 0); pMesh.Faces.AddFace(5, 6, 7, 4); Assert.AreEqual(24, pMesh.Halfedges.Count); // Check that half-edges have been linked up correctly // TODO: Add individual unit tests to verify the methods used below // Get all outgoing halfedges from vertex #0 and compare against expected int[] vertexZeroHalfedges = pMesh.Vertices.GetHalfedges(0); int[] vertexZeroHalfedgesExpected = new int[]{ 13, 5, 6 }; Assert.AreEqual(3, vertexZeroHalfedges.Length); foreach (int halfedge in vertexZeroHalfedgesExpected) { Assert.Contains(halfedge, vertexZeroHalfedges); } // Check that none of these edges are on a boundary (closed mesh) Assert.AreEqual(0, pMesh.Vertices.NakedEdgeCount(0)); // Get all halfedges from face #2 and compare against expected int[] faceTwoHalfedges = pMesh.Faces.GetHalfedges(2); int[] faceTwoHalfedgesExpected = new int[]{ 14, 16, 9, 3 }; Assert.AreEqual(4, faceTwoHalfedges.Length); foreach (int halfedge in faceTwoHalfedgesExpected) { Assert.Contains(halfedge, faceTwoHalfedges); } // Check that none of these edges are on a boundary (closed mesh) Assert.AreEqual(0, pMesh.Faces.NakedEdgeCount(2)); // Get all vertices from face #4 and compare against expected int[] faceFourVertices = pMesh.Faces.GetFaceVertices(4); int[] faceFourVerticesExpected = new int[]{ 4, 7, 3, 0 }; Assert.AreEqual(faceFourVerticesExpected, faceFourVertices); // Get all faces from vertex #1 and compare against expected int[] vertexOneFaces = pMesh.Vertices.GetVertexFaces(1); int[] vertexOneFacesExpected = new int[]{ 0, 1, 2 }; Assert.AreEqual(3, vertexOneFaces.Length); foreach (int face in vertexOneFacesExpected) { Assert.Contains(face, vertexOneFaces); } // Get all vertex neighbours from vertex #0 and compare against expected int[] vertexZeroNeighbours = pMesh.Vertices.GetVertexNeighbours(0); int[] vertexZeroNeighboursExpected = new int[]{ 4, 1, 3 }; Assert.AreEqual(3, vertexZeroNeighbours.Length); foreach (int vertex in vertexZeroNeighboursExpected) { Assert.Contains(vertex, vertexZeroNeighbours); } // Check that halfedges exist where they are expected to Assert.AreEqual(13, pMesh.Halfedges.FindHalfedge(0, 4)); // exists Assert.AreEqual(-1, pMesh.Halfedges.FindHalfedge(1, 3)); // doesn't exist }
public void CanSplitEdge() { PlanktonMesh pMesh = new PlanktonMesh(); var hs = pMesh.Halfedges; // Create one vertex for each corner of a square pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 // Create two triangular faces pMesh.Faces.AddFace(0, 1, 2); pMesh.Faces.AddFace(2, 3, 0); // Change outgoing of vert #2 so that we can check it updates pMesh.Vertices[2].OutgoingHalfedge = 4; // Split the diagonal edge int split_he = 5; // he from v #0 to #2 int new_he = hs.SplitEdge(split_he); // Returned halfedge should start at the new vertex Assert.AreEqual(4, hs[new_he].StartVertex); // Check that the 4 halfedges are all in the right places... // New ones are between new vertex and second vertex Assert.AreEqual(new_he, hs.FindHalfedge(4, 2)); Assert.AreEqual(hs.GetPairHalfedge(new_he), hs.FindHalfedge(2, 4)); // Existing ones are now between first vertex and new vertex Assert.AreEqual(split_he, hs.FindHalfedge(0, 4)); Assert.AreEqual(hs.GetPairHalfedge(split_he), hs.FindHalfedge(4, 0)); // New halfedges should have the same faces as the existing ones next to them Assert.AreEqual(hs[split_he].AdjacentFace, hs[new_he].AdjacentFace); Assert.AreEqual(hs[hs.GetPairHalfedge(split_he)].AdjacentFace, hs[hs.GetPairHalfedge(new_he)].AdjacentFace); // New vertex's outgoing should be returned halfedge Assert.AreEqual(new_he, pMesh.Vertices[4].OutgoingHalfedge); // New vertex should be 2-valent Assert.AreEqual(2, pMesh.Vertices.GetHalfedges(4).Length); // Check existing vertices... Assert.AreEqual(new int[] {9, 5, 0}, pMesh.Vertices.GetHalfedges(0)); Assert.AreEqual(new int[] {11, 6, 3}, pMesh.Vertices.GetHalfedges(2)); }
public void CannotTraverseUnusedHalfedge() { PlanktonMesh pMesh = new PlanktonMesh(); pMesh.Halfedges.Add(PlanktonHalfedge.Unset); pMesh.Halfedges.Add(PlanktonHalfedge.Unset); // You shouldn't be able to enumerate a circulator for either of these unset halfedges Assert.Throws<InvalidOperationException>(() => pMesh.Halfedges.GetFaceCirculator(0).ToArray()); Assert.Throws<InvalidOperationException>( delegate { foreach (int h in pMesh.Halfedges.GetVertexCirculator(1)) {} } ); }
public void CannotCollapseNonManifoldVertex() { PlanktonMesh pMesh = new PlanktonMesh(); // Create vertices in 3x2 grid pMesh.Vertices.Add(0, 0, 0); // 0 pMesh.Vertices.Add(1, 0, 0); // 1 pMesh.Vertices.Add(1, 1, 0); // 2 pMesh.Vertices.Add(0, 1, 0); // 3 pMesh.Vertices.Add(2, 0, 0); // 4 pMesh.Vertices.Add(2, 1, 0); // 5 // Create two quadrangular faces pMesh.Faces.AddFace(0, 1, 2, 3); pMesh.Faces.AddFace(1, 4, 5, 2); // Try to collapse edge between vertices #1 and #2 // (which would make vertex #1 non-manifold) int h = pMesh.Halfedges.FindHalfedge(1, 2); Assert.AreEqual(-1, pMesh.Halfedges.CollapseEdge(h)); // That's right, you can't! }