/** * Write a set of meshes to an ASCII string in STL format. */ public static string WriteString(IList <Mesh> meshes, bool convertToRightHandedCoordinates = true) { StringBuilder sb = new StringBuilder(); string name = meshes.Count == 1 ? meshes[0].name : "Composite Mesh"; sb.AppendLine(string.Format("solid {0}", name)); foreach (Mesh mesh in meshes) { Vector3[] v = mesh.vertices; Vector3[] n = mesh.normals; int[] t = mesh.triangles; if (convertToRightHandedCoordinates) { for (int i = 0, c = v.Length; i < c; i++) { v[i] = Stl.ToCoordinateSpace(v[i], CoordinateSpace.Right); n[i] = Stl.ToCoordinateSpace(n[i], CoordinateSpace.Right); } System.Array.Reverse(t); } int triLen = t.Length; for (int i = 0; i < triLen; i += 3) { int a = t[i]; int b = t[i + 1]; int c = t[i + 2]; Vector3 nrm = AvgNrm(n[a], n[b], n[c]); sb.AppendLine(string.Format("facet normal {0} {1} {2}", nrm.x, nrm.y, nrm.z)); sb.AppendLine("outer loop"); sb.AppendLine(string.Format("\tvertex {0} {1} {2}", v[a].x, v[a].y, v[a].z)); sb.AppendLine(string.Format("\tvertex {0} {1} {2}", v[b].x, v[b].y, v[b].z)); sb.AppendLine(string.Format("\tvertex {0} {1} {2}", v[c].x, v[c].y, v[c].z)); sb.AppendLine("endloop"); sb.AppendLine("endfacet"); } } sb.AppendLine(string.Format("endsolid {0}", name)); return(sb.ToString()); }
static StlMesh[] ImportHardNormals(IEnumerable <Facet> faces, CoordinateSpace modelCoordinateSpace, UpAxis modelUpAxis, IndexFormat indexFormat) { var facets = faces as Facet[] ?? faces.ToArray(); int faceCount = facets.Length, f = 0; int maxFacetsPerMesh = indexFormat == IndexFormat.UInt32 ? MaxFacetsPerMesh32 : MaxFacetsPerMesh16; int maxVertexCount = maxFacetsPerMesh * 3; StlMesh[] meshes = new StlMesh[faceCount / maxFacetsPerMesh + 1]; for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { int len = System.Math.Min(maxVertexCount, (faceCount - f) * 3); StlVector3[] v = new StlVector3[len]; StlVector3[] n = new StlVector3[len]; int[] t = new int[len]; for (int it = 0; it < len; it += 3) { v[it] = facets[f].a; v[it + 1] = facets[f].b; v[it + 2] = facets[f].c; n[it] = facets[f].normal; n[it + 1] = facets[f].normal; n[it + 2] = facets[f].normal; t[it] = it + 0; t[it + 1] = it + 1; t[it + 2] = it + 2; f++; } if (modelCoordinateSpace == CoordinateSpace.Right) { for (int i = 0; i < len; i += 3) { v[i + 0] = Stl.ToCoordinateSpace(v[i + 0], CoordinateSpace.Left); v[i + 1] = Stl.ToCoordinateSpace(v[i + 1], CoordinateSpace.Left); v[i + 2] = Stl.ToCoordinateSpace(v[i + 2], CoordinateSpace.Left); n[i + 0] = Stl.ToCoordinateSpace(n[i + 0], CoordinateSpace.Left); n[i + 1] = Stl.ToCoordinateSpace(n[i + 1], CoordinateSpace.Left); n[i + 2] = Stl.ToCoordinateSpace(n[i + 2], CoordinateSpace.Left); var a = t[i + 2]; t[i + 2] = t[i]; t[i] = a; } } meshes[meshIndex] = new StlMesh { vertices = v, normals = n, triangles = t, indexFormat = indexFormat }; } return(meshes); }
static StlMesh[] ImportSmoothNormals(IEnumerable <Facet> faces, CoordinateSpace modelCoordinateSpace, UpAxis modelUpAxis, IndexFormat indexFormat) { var facets = faces as Facet[] ?? faces.ToArray(); int maxVertexCount = indexFormat == IndexFormat.UInt32 ? MaxFacetsPerMesh32 * 3 : MaxFacetsPerMesh16 * 3; int triangleCount = facets.Length * 3; //Dictionary<StlVector3, StlVector3> smoothNormals = new Dictionary<StlVector3, StlVector3>(triangleCount / 2); //// In case meshes are split, we need to calculate smooth normals first //foreach(var face in faces) //{ // var x = (StlVector3) face.a; // var y = (StlVector3) face.b; // var z = (StlVector3) face.c; // var normal = face.normal; // if(smoothNormals.ContainsKey(x)) // smoothNormals[x] += normal; // else // smoothNormals.Add(x, normal); // if(smoothNormals.ContainsKey(y)) // smoothNormals[y] += normal; // else // smoothNormals.Add(y, normal); // if(smoothNormals.ContainsKey(z)) // smoothNormals[z] += normal; // else // smoothNormals.Add(z, normal); //} int numProcs = Environment.ProcessorCount; int concurrencyLevel = numProcs * 2; ConcurrentDictionary <StlVector3, StlVector3> smoothNormals = new ConcurrentDictionary <StlVector3, StlVector3>(concurrencyLevel, triangleCount / 2); // In case meshes are split, we need to calculate smooth normals first Parallel.ForEach(faces, (face) => { var x = face.a; var y = face.b; var z = face.c; var normal = face.normal; if (smoothNormals.ContainsKey(x)) { smoothNormals[x] += normal; } else { smoothNormals.TryAdd(x, normal); } if (smoothNormals.ContainsKey(y)) { smoothNormals[y] += normal; } else { smoothNormals.TryAdd(y, normal); } if (smoothNormals.ContainsKey(z)) { smoothNormals[z] += normal; } else { smoothNormals.TryAdd(z, normal); } }); List <StlMesh> meshes = new List <StlMesh>(); List <StlVector3> pos = new List <StlVector3>(Math.Min(maxVertexCount, triangleCount)); List <StlVector3> nrm = new List <StlVector3>(Math.Min(maxVertexCount, triangleCount)); List <int> tri = new List <int>(triangleCount); Dictionary <StlVector3, int> map = new Dictionary <StlVector3, int>(); int vertex = 0; StlVector3[] points = new StlVector3[3]; foreach (var face in facets) { if (vertex + 3 > maxVertexCount) { var mesh = new StlMesh { vertices = pos.ToArray(), normals = nrm.ToArray(), indexFormat = indexFormat }; if (modelCoordinateSpace == CoordinateSpace.Right) { tri.Reverse(); } mesh.triangles = tri.ToArray(); meshes.Add(mesh); vertex = 0; pos.Clear(); nrm.Clear(); tri.Clear(); map.Clear(); } points[0] = face.a; points[1] = face.b; points[2] = face.c; for (int i = 0; i < 3; i++) { int index = -1; var hash = points[i]; if (!map.TryGetValue(hash, out index)) { if (modelCoordinateSpace == CoordinateSpace.Right) { pos.Add(Stl.ToCoordinateSpace(points[i], CoordinateSpace.Left)); nrm.Add(Stl.ToCoordinateSpace(smoothNormals[hash].normalized, CoordinateSpace.Left)); } else { pos.Add(points[i]); nrm.Add(smoothNormals[hash].normalized); } tri.Add(vertex); map.Add(hash, vertex++); } else { tri.Add(index); } } } if (vertex > 0) { var mesh = new StlMesh { vertices = pos.ToArray(), normals = nrm.ToArray(), indexFormat = indexFormat }; if (modelCoordinateSpace == CoordinateSpace.Right) { tri.Reverse(); } mesh.triangles = tri.ToArray(); meshes.Add(mesh); vertex = 0; pos.Clear(); nrm.Clear(); tri.Clear(); map.Clear(); } return(meshes.ToArray()); }
static Mesh[] ImportSmoothNormals(IEnumerable<Facet> faces, CoordinateSpace modelCoordinateSpace, UpAxis modelUpAxis, IndexFormat indexFormat) { var facets = faces as Facet[] ?? faces.ToArray(); int maxVertexCount = indexFormat == IndexFormat.UInt32 ? MaxFacetsPerMesh32 * 3 : MaxFacetsPerMesh16 * 3; int triangleCount = facets.Length * 3; Dictionary<StlVector3, Vector3> smoothNormals = new Dictionary<StlVector3, Vector3>(triangleCount / 2); // In case meshes are split, we need to calculate smooth normals first foreach(var face in faces) { var x = (StlVector3) face.a; var y = (StlVector3) face.b; var z = (StlVector3) face.c; var normal = face.normal; if(smoothNormals.ContainsKey(x)) smoothNormals[x] += normal; else smoothNormals.Add(x, normal); if(smoothNormals.ContainsKey(y)) smoothNormals[y] += normal; else smoothNormals.Add(y, normal); if(smoothNormals.ContainsKey(z)) smoothNormals[z] += normal; else smoothNormals.Add(z, normal); } List<Mesh> meshes = new List<Mesh>(); List<Vector3> pos = new List<Vector3>(Math.Min(maxVertexCount, triangleCount)); List<Vector3> nrm = new List<Vector3>(Math.Min(maxVertexCount, triangleCount)); List<int> tri = new List<int>(triangleCount); Dictionary<StlVector3, int> map = new Dictionary<StlVector3, int>(); int vertex = 0; Vector3[] points = new Vector3[3]; foreach(var face in facets) { if(vertex + 3 > maxVertexCount) { var mesh = new Mesh { vertices = pos.ToArray(), normals = nrm.ToArray(), indexFormat = indexFormat }; if(modelCoordinateSpace == CoordinateSpace.Right) tri.Reverse(); mesh.triangles = tri.ToArray(); meshes.Add(mesh); vertex = 0; pos.Clear(); nrm.Clear(); tri.Clear(); map.Clear(); } points[0] = face.a; points[1] = face.b; points[2] = face.c; for(int i = 0; i < 3; i++) { int index = -1; var hash = (StlVector3) points[i]; if(!map.TryGetValue(hash, out index)) { if(modelCoordinateSpace == CoordinateSpace.Right) { pos.Add(Stl.ToCoordinateSpace(points[i], CoordinateSpace.Left)); nrm.Add(Stl.ToCoordinateSpace(smoothNormals[hash].normalized, CoordinateSpace.Left)); } else { pos.Add(points[i]); nrm.Add(smoothNormals[hash].normalized); } tri.Add(vertex); map.Add(hash, vertex++); } else { tri.Add(index); } } } if(vertex > 0) { var mesh = new Mesh { vertices = pos.ToArray(), normals = nrm.ToArray(), indexFormat = indexFormat }; if(modelCoordinateSpace == CoordinateSpace.Right) tri.Reverse(); mesh.triangles = tri.ToArray(); meshes.Add(mesh); vertex = 0; pos.Clear(); nrm.Clear(); tri.Clear(); map.Clear(); } return meshes.ToArray(); }
static Mesh[] ImportHardNormals(IEnumerable <Facet> faces, CoordinateSpace modelCoordinateSpace, UpAxis modelUpAxis, IndexFormat indexFormat) { var facets = faces as Facet[] ?? faces.ToArray(); int faceCount = facets.Length, f = 0; int maxFacetsPerMesh = indexFormat == IndexFormat.UInt32 ? MaxFacetsPerMesh32 : MaxFacetsPerMesh16; int maxVertexCount = maxFacetsPerMesh * 3; Mesh[] meshes = new Mesh[faceCount / maxFacetsPerMesh + 1]; for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { int len = System.Math.Min(maxVertexCount, (faceCount - f) * 3); Vector3[] v = new Vector3[len]; Vector3[] n = new Vector3[len]; int[] indices = new int[len * 3]; int[] t = new int[len]; for (int it = 0; it < len; it += 3) { v[it] = facets[f].a; v[it + 1] = facets[f].b; v[it + 2] = facets[f].c; n[it] = facets[f].normal; n[it + 1] = facets[f].normal; n[it + 2] = facets[f].normal; t[it] = it + 0; t[it + 1] = it + 1; t[it + 2] = it + 2; f++; } if (modelCoordinateSpace == CoordinateSpace.Right) { for (int i = 0; i < len; i += 3) { v[i + 0] = Stl.ToCoordinateSpace(v[i + 0], CoordinateSpace.Left); v[i + 1] = Stl.ToCoordinateSpace(v[i + 1], CoordinateSpace.Left); v[i + 2] = Stl.ToCoordinateSpace(v[i + 2], CoordinateSpace.Left); n[i + 0] = Stl.ToCoordinateSpace(n[i + 0], CoordinateSpace.Left); n[i + 1] = Stl.ToCoordinateSpace(n[i + 1], CoordinateSpace.Left); n[i + 2] = Stl.ToCoordinateSpace(n[i + 2], CoordinateSpace.Left); var a = t[i + 2]; t[i + 2] = t[i]; t[i] = a; } } // auto detect mesh winding via calculating the mesh's volume float volume = 0; for (int it = 0; it < len; it += 3) { Vector3 v1 = v[it]; Vector3 v2 = v[it + 1]; Vector3 v3 = v[it + 2]; Vector3 cross = Vector3.Cross(v1, v2); volume -= Vector3.Dot(cross, v3); } if (volume < 0) { for (int it = 0; it < len; it += 3) { indices[it * 3] = it; indices[it * 3 + 1] = it + 1; indices[it * 3 + 2] = it + 2; } } else { for (int it = 0; it < len; it += 3) { indices[it * 3] = it; indices[it * 3 + 1] = it + 2; indices[it * 3 + 2] = it + 1; } } Mesh mesh = new Mesh() { vertices = v, normals = n, triangles = t, indexFormat = indexFormat }; mesh.SetIndices(indices, MeshTopology.Triangles, 0); } return(meshes); }
/** * Write a collection of mesh assets to an STL file. * No transformations are performed on meshes in this method. * Eg, if you want to export a set of a meshes in a transform * hierarchy the meshes should be transformed prior to this call. * * string path - Where to write the file. * IList<Mesh> meshes - The mesh assets to write. * FileType type - How to format the file (in ASCII or binary). */ public static bool WriteFile(string path, IList <Mesh> meshes, FileType type = FileType.Ascii, bool convertToRightHandedCoordinates = true) { try { switch (type) { case FileType.Binary: { // http://paulbourke.net/dataformats/stl/ // http://www.fabbers.com/tech/STL_Format using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create), new ASCIIEncoding())) { // 80 byte header writer.Write(new byte[80]); uint totalTriangleCount = (uint)(meshes.Sum(x => x.triangles.Length) / 3); // unsigned long facet count (4 bytes) writer.Write(totalTriangleCount); foreach (Mesh mesh in meshes) { Vector3[] v = mesh.vertices; Vector3[] n = mesh.normals; if (convertToRightHandedCoordinates) { for (int i = 0, c = v.Length; i < c; i++) { v[i] = Stl.ToCoordinateSpace(v[i], CoordinateSpace.Right); n[i] = Stl.ToCoordinateSpace(n[i], CoordinateSpace.Right); } } int[] t = mesh.triangles; int triangleCount = t.Length; if (convertToRightHandedCoordinates) { System.Array.Reverse(t); } for (int i = 0; i < triangleCount; i += 3) { int a = t[i], b = t[i + 1], c = t[i + 2]; Vector3 avg = AvgNrm(n[a], n[b], n[c]); writer.Write(avg.x); writer.Write(avg.y); writer.Write(avg.z); writer.Write(v[a].x); writer.Write(v[a].y); writer.Write(v[a].z); writer.Write(v[b].x); writer.Write(v[b].y); writer.Write(v[b].z); writer.Write(v[c].x); writer.Write(v[c].y); writer.Write(v[c].z); // specification says attribute byte count should be set to 0. writer.Write((ushort)0); } } } } break; default: string model = WriteString(meshes); File.WriteAllText(path, model); break; } } catch (System.Exception e) { UnityEngine.Debug.LogError(e.ToString()); return(false); } return(true); }