// ----------------------------------------------------------------------- // // Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- namespace UnityEngine.U2D.Animation.TriangleNet .IO { using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Animation.TriangleNet.Geometry; /// /// Helper methods for reading Triangle file formats. /// internal class TriangleReader { static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; int startIndex = 0; #region Helper methods private bool TryReadLine(StreamReader reader, out string[] token) { token = null; if (reader.EndOfStream) { return false; } string line = reader.ReadLine().Trim(); while (IsStringNullOrWhiteSpace(line) || line.StartsWith("#")) { if (reader.EndOfStream) { return false; } line = reader.ReadLine().Trim(); } token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); return true; } /// /// Read vertex information of the given line. /// /// The input geometry. /// The current vertex index. /// The current line. /// Number of point attributes /// Number of point markers (0 or 1) private void ReadVertex(List data, int index, string[] line, int attributes, int marks) { double x = double.Parse(line[1], nfi); double y = double.Parse(line[2], nfi); var v = new Vertex(x, y); // Read a vertex marker. if (marks > 0 && line.Length > 3 + attributes) { v.Label = int.Parse(line[3 + attributes]); } if (attributes > 0) { #if USE_ATTRIBS var attribs = new double[attributes]; // Read the vertex attributes. for (int j = 0; j < attributes; j++) { if (line.Length > 3 + j) { attribs[j] = double.Parse(line[3 + j], nfi); } } v.attributes = attribs; #endif } data.Add(v); } #endregion #region Main I/O methods /// /// Reads geometry information from .node or .poly files. /// public void Read(string filename, out Polygon polygon) { polygon = null; string path = Path.ChangeExtension(filename, ".poly"); if (File.Exists(path)) { polygon = ReadPolyFile(path); } else { path = Path.ChangeExtension(filename, ".node"); polygon = ReadNodeFile(path); } } /// /// Reads a mesh from .node, .poly or .ele files. /// public void Read(string filename, out Polygon geometry, out List triangles) { triangles = null; Read(filename, out geometry); string path = Path.ChangeExtension(filename, ".ele"); if (File.Exists(path) && geometry != null) { triangles = ReadEleFile(path); } } /// /// Reads geometry information from .node or .poly files. /// public IPolygon Read(string filename) { Polygon geometry = null; Read(filename, out geometry); return geometry; } #endregion /// /// Read the vertices from a file, which may be a .node or .poly file. /// /// /// Will NOT read associated .ele by default. public Polygon ReadNodeFile(string nodefilename) { return ReadNodeFile(nodefilename, false); } /// /// Read the vertices from a file, which may be a .node or .poly file. /// /// /// public Polygon ReadNodeFile(string nodefilename, bool readElements) { Polygon data; startIndex = 0; string[] line; int invertices = 0, attributes = 0, nodemarkers = 0; using (var reader = new StreamReader(nodefilename)) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file."); } // Read number of vertices, number of dimensions, number of vertex // attributes, and number of boundary markers. invertices = int.Parse(line[0]); if (invertices < 3) { throw new Exception("Input must have at least three input vertices."); } if (line.Length > 1) { if (int.Parse(line[1]) != 2) { throw new Exception("Triangle only works with two-dimensional meshes."); } } if (line.Length > 2) { attributes = int.Parse(line[2]); } if (line.Length > 3) { nodemarkers = int.Parse(line[3]); } data = new Polygon(invertices); // Read the vertices. if (invertices > 0) { for (int i = 0; i < invertices; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (vertices)."); } if (line.Length < 3) { throw new Exception("Invalid vertex."); } if (i == 0) { startIndex = int.Parse(line[0], nfi); } ReadVertex(data.Points, i, line, attributes, nodemarkers); } } } if (readElements) { // Read area file string elefile = Path.ChangeExtension(nodefilename, ".ele"); if (File.Exists(elefile)) { ReadEleFile(elefile, true); } } return data; } /// /// Read the vertices and segments from a .poly file. /// /// /// Will NOT read associated .ele by default. public Polygon ReadPolyFile(string polyfilename) { return ReadPolyFile(polyfilename, false, false); } /// /// Read the vertices and segments from a .poly file. /// /// /// If true, look for an associated .ele file. /// Will NOT read associated .area by default. public Polygon ReadPolyFile(string polyfilename, bool readElements) { return ReadPolyFile(polyfilename, readElements, false); } /// /// Read the vertices and segments from a .poly file. /// /// /// If true, look for an associated .ele file. /// If true, look for an associated .area file. public Polygon ReadPolyFile(string polyfilename, bool readElements, bool readArea) { // Read poly file Polygon data; startIndex = 0; string[] line; int invertices = 0, attributes = 0, nodemarkers = 0; using (var reader = new StreamReader(polyfilename)) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file."); } // Read number of vertices, number of dimensions, number of vertex // attributes, and number of boundary markers. invertices = int.Parse(line[0]); if (line.Length > 1) { if (int.Parse(line[1]) != 2) { throw new Exception("Triangle only works with two-dimensional meshes."); } } if (line.Length > 2) { attributes = int.Parse(line[2]); } if (line.Length > 3) { nodemarkers = int.Parse(line[3]); } // Read the vertices. if (invertices > 0) { data = new Polygon(invertices); for (int i = 0; i < invertices; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (vertices)."); } if (line.Length < 3) { throw new Exception("Invalid vertex."); } if (i == 0) { // Set the start index! startIndex = int.Parse(line[0], nfi); } ReadVertex(data.Points, i, line, attributes, nodemarkers); } } else { // If the .poly file claims there are zero vertices, that means that // the vertices should be read from a separate .node file. data = ReadNodeFile(Path.ChangeExtension(polyfilename, ".node")); invertices = data.Points.Count; } var points = data.Points; if (points.Count == 0) { throw new Exception("No nodes available."); } // Read the segments from a .poly file. // Read number of segments and number of boundary markers. if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (segments)."); } int insegments = int.Parse(line[0]); int segmentmarkers = 0; if (line.Length > 1) { segmentmarkers = int.Parse(line[1]); } int end1, end2, mark; // Read and insert the segments. for (int i = 0; i < insegments; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (segments)."); } if (line.Length < 3) { throw new Exception("Segment has no endpoints."); } // TODO: startIndex ok? end1 = int.Parse(line[1]) - startIndex; end2 = int.Parse(line[2]) - startIndex; mark = 0; if (segmentmarkers > 0 && line.Length > 3) { mark = int.Parse(line[3]); } if ((end1 < 0) || (end1 >= invertices)) { if (Log.Verbose) { Log.Instance.Warning("Invalid first endpoint of segment.", "MeshReader.ReadPolyfile()"); } } else if ((end2 < 0) || (end2 >= invertices)) { if (Log.Verbose) { Log.Instance.Warning("Invalid second endpoint of segment.", "MeshReader.ReadPolyfile()"); } } else { data.Add(new Segment(points[end1], points[end2], mark)); } } // Read holes from a .poly file. // Read the holes. if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (holes)."); } int holes = int.Parse(line[0]); if (holes > 0) { for (int i = 0; i < holes; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (holes)."); } if (line.Length < 3) { throw new Exception("Invalid hole."); } data.Holes.Add(new Point(double.Parse(line[1], nfi), double.Parse(line[2], nfi))); } } // Read area constraints (optional). if (TryReadLine(reader, out line)) { int id, regions = int.Parse(line[0]); if (regions > 0) { for (int i = 0; i < regions; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (region)."); } if (line.Length < 4) { throw new Exception("Invalid region attributes."); } if (!int.TryParse(line[3], out id)) { id = i; } double area = 0.0; if (line.Length > 4) { double.TryParse(line[4], NumberStyles.Number, nfi, out area); } // Triangle's .poly file format allows region definitions with // either 4 or 5 parameters, and different interpretations for // them depending on the number of parameters. // // See http://www.cs.cmu.edu/~quake/triangle.poly.html // // The .NET version will interpret the fourth parameter always // as an integer region id and the optional fifth parameter as // an area constraint. data.Regions.Add(new RegionPointer( double.Parse(line[1], nfi), // Region x double.Parse(line[2], nfi), // Region y id, area)); } } } } // Read ele file if (readElements) { string elefile = Path.ChangeExtension(polyfilename, ".ele"); if (File.Exists(elefile)) { ReadEleFile(elefile, readArea); } } return data; } /// /// Read elements from an .ele file. /// /// The file name. /// A list of triangles. public List ReadEleFile(string elefilename) { return ReadEleFile(elefilename, false); } /// /// Read the elements from an .ele file. /// /// /// /// private List ReadEleFile(string elefilename, bool readArea) { int intriangles = 0, attributes = 0; List triangles; using (var reader = new StreamReader(elefilename)) { // Read number of elements and number of attributes. string[] line; bool validRegion = false; if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (elements)."); } intriangles = int.Parse(line[0]); // We irgnore index 1 (number of nodes per triangle) attributes = 0; if (line.Length > 2) { attributes = int.Parse(line[2]); validRegion = true; } if (attributes > 1) { Log.Instance.Warning("Triangle attributes not supported.", "FileReader.Read"); } triangles = new List(intriangles); InputTriangle tri; // Read triangles. for (int i = 0; i < intriangles; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (elements)."); } if (line.Length < 4) { throw new Exception("Triangle has no nodes."); } // TODO: startIndex ok? tri = new InputTriangle( int.Parse(line[1]) - startIndex, int.Parse(line[2]) - startIndex, int.Parse(line[3]) - startIndex); // Read triangle region if (attributes > 0 && validRegion) { int region = 0; validRegion = int.TryParse(line[4], out region); tri.label = region; } triangles.Add(tri); } } // Read area file if (readArea) { string areafile = Path.ChangeExtension(elefilename, ".area"); if (File.Exists(areafile)) { ReadAreaFile(areafile, intriangles); } } return triangles; } /// /// Read the area constraints from an .area file. /// /// /// /// private double[] ReadAreaFile(string areafilename, int intriangles) { double[] data = null; using (var reader = new StreamReader(areafilename)) { string[] line; if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (area)."); } if (int.Parse(line[0]) != intriangles) { Log.Instance.Warning("Number of area constraints doesn't match number of triangles.", "ReadAreaFile()"); return null; } data = new double[intriangles]; // Read area constraints. for (int i = 0; i < intriangles; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (area)."); } if (line.Length != 2) { throw new Exception("Triangle has no nodes."); } data[i] = double.Parse(line[1], nfi); } } return data; } /// /// Read an .edge file. /// /// The file name. /// The number of input vertices (read from a .node or .poly file). /// A List of edges. public List ReadEdgeFile(string edgeFile, int invertices) { // Read poly file List data = null; startIndex = 0; string[] line; using (var reader = new StreamReader(edgeFile)) { // Read the edges from a .edge file. // Read number of segments and number of boundary markers. if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (segments)."); } int inedges = int.Parse(line[0]); int edgemarkers = 0; if (line.Length > 1) { edgemarkers = int.Parse(line[1]); } if (inedges > 0) { data = new List(inedges); } int end1, end2, mark; // Read and insert the segments. for (int i = 0; i < inedges; i++) { if (!TryReadLine(reader, out line)) { throw new Exception("Can't read input file (segments)."); } if (line.Length < 3) { throw new Exception("Segment has no endpoints."); } // TODO: startIndex ok? end1 = int.Parse(line[1]) - startIndex; end2 = int.Parse(line[2]) - startIndex; mark = 0; if (edgemarkers > 0 && line.Length > 3) { mark = int.Parse(line[3]); } if ((end1 < 0) || (end1 >= invertices)) { if (Log.Verbose) { Log.Instance.Warning("Invalid first endpoint of segment.", "MeshReader.ReadPolyfile()"); } } else if ((end2 < 0) || (end2 >= invertices)) { if (Log.Verbose) { Log.Instance.Warning("Invalid second endpoint of segment.", "MeshReader.ReadPolyfile()"); } } else { data.Add(new Edge(end1, end2, mark)); } } } return data; } bool IsStringNullOrWhiteSpace(string value) { if (value != null) { for (int i = 0; i < value.Length; i++) { if (!char.IsWhiteSpace(value[i])) { return false; } } } return true; } } }