rj-action-library/External/Triangle.NET/Triangle/IO/TriangleReader.cs

757 lines
25 KiB
C#

// -----------------------------------------------------------------------
// <copyright file="TriangleReader.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using TriangleNet.Geometry;
/// <summary>
/// Helper methods for reading Triangle file formats.
/// </summary>
public 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;
}
/// <summary>
/// Read vertex information of the given line.
/// </summary>
/// <param name="data">The input geometry.</param>
/// <param name="index">The current vertex index.</param>
/// <param name="line">The current line.</param>
/// <param name="attributes">Number of point attributes</param>
/// <param name="marks">Number of point markers (0 or 1)</param>
private void ReadVertex(List<Vertex> 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
/// <summary>
/// Reads geometry information from .node or .poly files.
/// </summary>
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);
}
}
/// <summary>
/// Reads a mesh from .node, .poly or .ele files.
/// </summary>
public void Read(string filename, out Polygon geometry, out List<ITriangle> triangles)
{
triangles = null;
Read(filename, out geometry);
string path = Path.ChangeExtension(filename, ".ele");
if (File.Exists(path) && geometry != null)
{
triangles = ReadEleFile(path);
}
}
/// <summary>
/// Reads geometry information from .node or .poly files.
/// </summary>
public IPolygon Read(string filename)
{
Polygon geometry = null;
Read(filename, out geometry);
return geometry;
}
#endregion
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public Polygon ReadNodeFile(string nodefilename)
{
return ReadNodeFile(nodefilename, false);
}
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <param name="readElements"></param>
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;
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public Polygon ReadPolyFile(string polyfilename)
{
return ReadPolyFile(polyfilename, false, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <remarks>Will NOT read associated .area by default.</remarks>
public Polygon ReadPolyFile(string polyfilename, bool readElements)
{
return ReadPolyFile(polyfilename, readElements, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <param name="readElements">If true, look for an associated .area file.</param>
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;
}
/// <summary>
/// Read elements from an .ele file.
/// </summary>
/// <param name="elefilename">The file name.</param>
/// <returns>A list of triangles.</returns>
public List<ITriangle> ReadEleFile(string elefilename)
{
return ReadEleFile(elefilename, false);
}
/// <summary>
/// Read the elements from an .ele file.
/// </summary>
/// <param name="elefilename"></param>
/// <param name="data"></param>
/// <param name="readArea"></param>
private List<ITriangle> ReadEleFile(string elefilename, bool readArea)
{
int intriangles = 0, attributes = 0;
List<ITriangle> 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<ITriangle>(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;
}
/// <summary>
/// Read the area constraints from an .area file.
/// </summary>
/// <param name="areafilename"></param>
/// <param name="intriangles"></param>
/// <param name="data"></param>
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;
}
/// <summary>
/// Read an .edge file.
/// </summary>
/// <param name="edgeFile">The file name.</param>
/// <param name="invertices">The number of input vertices (read from a .node or .poly file).</param>
/// <returns>A List of edges.</returns>
public List<Edge> ReadEdgeFile(string edgeFile, int invertices)
{
// Read poly file
List<Edge> 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<Edge>(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;
}
}
}