/// <summary> /// If two mesh points have the same (X, Y, Z) coords, then they get combined into one. /// </summary> public static Mesh JoinVertices(Mesh mesh) { Dictionary<Vector3, int> pointIxMap = new Dictionary<Vector3, int>(); List<Vector3> points = new List<Vector3>(); List<Vector3> norms = new List<Vector3>(); for (int i = 0; i < mesh.points.Length; i++) { if (!pointIxMap.ContainsKey(mesh.points[i])) { points.Add(mesh.points[i]); norms.Add(mesh.normals[i]); pointIxMap.Add(mesh.points[i], points.Count-1); } } Logger.info("mesh has " + mesh.points.Length + " verts, "+ "joined " + (mesh.points.Length - pointIxMap.Count) + " dupes"); List<Mesh.Triangle> tris = new List<Mesh.Triangle>(); for (int i = 0; i < mesh.triangles.Length; i++) { var oldTri = mesh.triangles[i]; var tri = new Mesh.Triangle( pointIxMap[mesh.points[oldTri.vertexA]], pointIxMap[mesh.points[oldTri.vertexB]], pointIxMap[mesh.points[oldTri.vertexC]]); if (tri.vertexA != tri.vertexB && tri.vertexA != tri.vertexC && tri.vertexB != tri.vertexC) { tris.Add(tri); } } return new Mesh(points.ToArray(), norms.ToArray(), tris.ToArray()); }
public void Parse(string filename) { var verts = new List<Vector3>(); var tris = new List<Mesh.Triangle>(); var norms = new List<Vector3>(); //deduplicate the verts... var vertIxs = new Dictionary<Vector3, int>(); // parse the file with a simple state machine const int INIT = 1, SOLID = 2, FACET = 3, LOOP = 4, ENDFACET=5; var triIxs = new List<int>(); var norm = new Vector3(); int state = INIT; StreamReader reader = new StreamReader(filename); String line; for (int lineNum = 1; (line = reader.ReadLine()) != null; lineNum++) { string[] parts = line.Trim().ToLower().Split(new char[0], StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) { // skip empty lines continue; } if (parts[0] == "solid") { Assert(state == INIT, filename, lineNum); Logger.info("reading stl solid " + String.Join(" ", parts, 1, parts.Length - 1)); state = SOLID; } else if (parts[0] == "facet") { Assert(state == SOLID, filename, lineNum); Assert(parts[1] == "normal", filename, lineNum); norm = ReadVector(parts, 2, filename, lineNum); state = FACET; } else if (parts[0] == "outer") { Assert(state == FACET, filename, lineNum); Assert(parts[1] == "loop", filename, lineNum); triIxs.Clear(); state = LOOP; } else if (parts[0] == "vertex") { Assert(state == LOOP, filename, lineNum); var vertex = ReadVector(parts, 1, filename, lineNum); if (vertIxs.ContainsKey(vertex)) { int vertIx = vertIxs[vertex]; triIxs.Add(vertIx); norms[vertIx] += norm; } else { int vertIx = verts.Count; vertIxs.Add(vertex, vertIx); triIxs.Add(vertIx); verts.Add(vertex); norms.Add(norm); } } else if (parts[0] == "endloop") { Assert(state == LOOP, filename, lineNum); //every face must be a triangle Assert(triIxs.Count == 3, filename, lineNum); var tri = new Mesh.Triangle(triIxs[0], triIxs[1], triIxs[2]); tri.normal = norm; tris.Add(tri); state = ENDFACET; } else if (parts[0] == "endfacet") { Assert(state == ENDFACET, filename, lineNum); state = SOLID; } else if (parts[0] == "endsolid") { Assert(state == SOLID, filename, lineNum); state = INIT; } } // get all the averaged vertex normals and set them to unit length for(int i = 0; i < norms.Count; i++){ float len = norms[i].Length; if (len == 0) { norms[i] = new Vector3(1, 0, 0); } else { norms[i] /= len; } } // finally, return a mesh mesh = new Mesh(verts.ToArray(), norms.ToArray(), tris.ToArray()); }
public static Mesh MirrorAndCombine(Mesh mesh, Vector3 axis) { /* if the current mesh has n points, we'll have up to 2n-1 points in the mirrored and combined one */ var points = new List<Vector3>(); points.AddRange(mesh.points); /* find the point or points we're going to mirror around--the ones closest in the 'axis' direction * (in other words, find the mirror plane) */ var minDot = mesh.points.Min(point => Vector3.Dot(point, axis)); /* if points are this close or closer to the mirror plane, they will not be duplicated--they will simply be shared by additional triangles */ var epsilon = 0.002f; /* create a dictionary mapping point indices in the original mesh to the corresponding mirrored points */ var pointIndexMap = new Dictionary<int, int>(); for (int i = 0; i < mesh.points.Length; i++) { var distance = Vector3.Dot(mesh.points[i], axis) - minDot; if (distance < epsilon) { /* point lies on mirror plane */ pointIndexMap.Add(i, i); } else { /* point is not on mirror plane; create its mirror image as a new point */ pointIndexMap.Add(i, points.Count); var mirrorPoint = mesh.points[i] - 2 * distance * axis; points.Add(mirrorPoint); } } /* normals correspond to points, but may need to be mirrored. * if a point is on the mirror plane, make sure the corresponding normal is in the mirror plane */ var normals = new Vector3[points.Count]; for (int i = 0; i < mesh.points.Length; i++) { if (pointIndexMap[i] == i) { normals[i] = mesh.normals[i] - Vector3.Dot(mesh.normals[i], axis) * axis; normals[i].Normalize(); } else { normals[i] = mesh.normals[i]; normals[pointIndexMap[i]] = mesh.normals[i] - 2 * Vector3.Dot(mesh.normals[i], axis) * axis; } } /* triangles are simply duplicated--each triangle gets a mirror image */ var triangles = new Mesh.Triangle[mesh.triangles.Length * 2]; for (int i = 0; i < mesh.triangles.Length; i++) { triangles[i] = mesh.triangles[i]; } for (int i = 0; i < mesh.triangles.Length; i++) { var triangle = mesh.triangles[i]; triangles[mesh.triangles.Length + i] = new Mesh.Triangle( pointIndexMap[triangle.vertexA], pointIndexMap[triangle.vertexB], pointIndexMap[triangle.vertexC] ); } return new Mesh(points.ToArray(), normals, triangles); }
public static void Split(Mesh input, IVolume volume, out Mesh output, out bool[] trisInside) { /** * * for triangle in input * if triangle is entirely inside or outside * add to corresponding mesh * else split triangle into 4 pieces. * three go in one mesh, one goes into the other * * Splitting a triangle, Before: * * * vA * / \ * boundary --- /---\ --- * / \ * vB1 *-------* vB2 * * * After: * * * vA * vMid1 / \ vMid2 * boundary --- *---* --- * / \ / \ * vB1 *---*---* vB2 * vMidB * **/ int np = input.points.Length, nt = input.triangles.Length; List<Vector3> points = input.points.ToList(); List<Vector3> norms = input.normals.ToList(); List<Mesh.Triangle> tris = new List<Mesh.Triangle>(); List<bool> trisIn = new List<bool>(); for(int i = 0; i < nt; i++){ var tri = input.triangles[i]; Vector3 vA = input.points[tri.vertexA]; Vector3 vB = input.points[tri.vertexB]; Vector3 vC = input.points[tri.vertexC]; int ncontains = (volume.Contains(vA) ? 1 : 0) + (volume.Contains(vB) ? 1 : 0) + (volume.Contains(vC) ? 1 : 0); if (ncontains==3) { tris.Add(tri); trisIn.Add(true); } else if (ncontains==0){ tris.Add(tri); trisIn.Add(false); } else { // see comment above for explanation Debug.Assert(ncontains == 1 || ncontains == 2); bool containsA = (ncontains==1); int ixA, ixB1, ixB2; if (volume.Contains(vA) == containsA) { ixA = tri.vertexA; ixB1 = tri.vertexB; ixB2 = tri.vertexC; } else if (volume.Contains(vB) == containsA) { ixA = tri.vertexB; ixB1 = tri.vertexA; ixB2 = tri.vertexC; } else { Debug.Assert(volume.Contains(vC) == containsA); ixA = tri.vertexC; ixB1 = tri.vertexA; ixB2 = tri.vertexB; } Vector3 vAO = input.points[ixA]; Vector3 vB1 = input.points[ixB1]; Vector3 vB2 = input.points[ixB2]; Vector3 vMid1 = FindBoundary(vAO, vB1, volume); Vector3 vMid2 = FindBoundary(vAO, vB2, volume); Vector3 vMidB = input.points[ixB1]*0.5f + input.points[ixB2]*0.5f; points.Add(vMid1); points.Add(vMid2); points.Add(vMidB); float b1 = (vMid1 - vAO).Length / ((vB1 - vAO).Length + float.Epsilon); float b2 = (vMid2 - vAO).Length / ((vB2 - vAO).Length + float.Epsilon); Debug.Assert(0 <= b1 && b1 <= 1 && 0 <= b2 && b2 <= 1); Vector3 nMid1 = input.normals[ixA] * (1 - b1) + input.normals[ixB1] * b1; Vector3 nMid2 = input.normals[ixA] * (1 - b2) + input.normals[ixB2] * b2; Vector3 nMidB = input.normals[ixB1] * 0.5f + input.normals[ixB2] * 0.5f; nMid1.Normalize(); nMid2.Normalize(); nMidB.Normalize(); norms.Add(nMid1); norms.Add(nMid2); norms.Add(nMidB); //norms.Add(input.normals[ixA]); norms.Add(input.normals[ixA]); norms.Add(input.normals[ixA]); var tri1 = new Mesh.Triangle(ixA, points.Count - 3, points.Count - 2); var tri2 = new Mesh.Triangle(ixB1, points.Count - 3, points.Count - 1); var tri3 = new Mesh.Triangle(points.Count - 3, points.Count - 2, points.Count - 1); var tri4 = new Mesh.Triangle(ixB2, points.Count - 2, points.Count - 1); tri1.normal = tri2.normal = tri3.normal = tri4.normal = tri.normal; tris.Add(tri1); tris.Add(tri2); tris.Add(tri3); tris.Add(tri4); trisIn.Add(containsA); trisIn.Add(!containsA); trisIn.Add(!containsA); trisIn.Add(!containsA); } } // done Logger.info("split mesh, started with " + nt + " tris, added " + (tris.Count-nt)); Debug.Assert(points.Count==norms.Count); output = new Mesh(points.ToArray(), norms.ToArray(), tris.ToArray()); trisInside = trisIn.ToArray(); }
public void Parse(string filename) { /* load file */ XmlDocument doc = new XmlDocument(); doc.Load(filename); /* parse file */ var points = new List<Vector3>(); var normals = new List<Vector3>(); var triangles = new List<Mesh.Triangle>(); //TODO: disgusting hack. //XmlNodeList xmlSurfaces; = doc.FirstChild.SelectNodes( //"//GeometricRepresentationSet/Representation/AssociatedXML/Rep"); XmlNode rep; if (doc["XMLRepresentation"] != null && doc["XMLRepresentation"]["Root"] != null && doc["XMLRepresentation"]["Root"]["Rep"] != null) { rep = doc["XMLRepresentation"]["Root"]["Rep"]; } else if (doc["Model_3dxml"] != null && doc["Model_3dxml"]["GeometricRepresentationSet"] != null && doc["Model_3dxml"]["GeometricRepresentationSet"]["Representation"] != null && doc["Model_3dxml"]["GeometricRepresentationSet"]["Representation"]["AssociatedXML"] != null) { rep = doc["Model_3dxml"]["GeometricRepresentationSet"]["Representation"]["AssociatedXML"]; } else { throw new ArgumentException("cannot parse " + filename); } foreach (XmlNode node in rep.ChildNodes) { if (node["VertexBuffer"] == null || node["VertexBuffer"]["Positions"] == null || node["VertexBuffer"]["Normals"] == null || node["Faces"]["Face"] == null) { Logger.warn("skipping rep..."); continue; } //read verts var units = 0.001f; /* units in mm */ var newPoints = Parse3DXMLVectors(node["VertexBuffer"]["Positions"].InnerText); for (int i = 0; i < newPoints.Length; i++) newPoints[i] = newPoints[i] * units; var newNormals = Parse3DXMLVectors(node["VertexBuffer"]["Normals"].InnerText); for (int i = 0; i < newNormals.Length; i++) newNormals[i] = -newNormals[i]; int offset = points.Count; points.AddRange(newPoints); normals.AddRange(newNormals); //read triangles XmlAttributeCollection faceAttrs = node["Faces"]["Face"].Attributes; Logger.info("parsing face " + faceAttrs["id"].Value); if (faceAttrs["triangles"] != null) { int[] vertIxs = Parse3DXMLInts(faceAttrs["triangles"].Value); var triangleIxs = new HashSet<int>(vertIxs); /*for (int i = 0; i < newPoints.Length - 2; i++) { if (triangleIxs.Contains(i)) { triangles.Add(new MeshSprite.Triangle() { VertexA = offset + i, VertexB = offset + i + 1, VertexC = offset + i + 2 }); } }*/ for (int i = 0; i < vertIxs.Length; i += 3) { var tri = new Mesh.Triangle( offset + vertIxs[i], offset + vertIxs[i + 1], offset + vertIxs[i + 2] ); tri.normal = newNormals[vertIxs[i]]; triangles.Add(tri); } } else if (faceAttrs["strips"] != null) { String[] strips = faceAttrs["strips"].Value.Split(','); foreach (String strip in strips) { int[] vertIxs = Parse3DXMLInts(strip); for (int i = 2; i < vertIxs.Length; i++) { var tri = new Mesh.Triangle( offset + vertIxs[i - ((i % 2) == 0 ? 1 : 2)], offset + vertIxs[i - ((i % 2) == 0 ? 2 : 1)], offset + vertIxs[i] ); tri.normal = newNormals[vertIxs[i]]; triangles.Add(tri); } } } else { throw new ArgumentException("Found an unsupported 3dxml mesh surface.\n" + "Clean up your mesh in MeshLab. Triangles only."); } } /* create mesh sprite */ mesh = new Mesh(points.ToArray(), normals.ToArray(), triangles.ToArray()); }