Practical Delaunay Meshing Examples

I/O: Bridging your Software with Fade2D

Fade2D offers powerful triangulation functionalities, essential in fields like land surveying, mining, and road construction. This article provides an overview of the I/O operations engineers use in practice to bridge their software with the Fade2D library. We will discuss the individual I/O methods through a C++ example, that you can also find in the download package under examples_real/importExportLoadSave.

Generating a Delaunay Triangulation and Zones

The function createRandomTriangulation() generates a random terrain triangulation with two zones, which we will use in various contexts throughout this example. However, as its purpose is solely to provide sample data for this example, we will not further discuss it. The triangulation comprises 5313 triangles, with Zone1 containing 675 triangles and Zone2 containing 217 triangles. See the image below.

Random Terrain Delaunay Triangulation with Two Zones
Random Terrain: 5313 triangles, Zone 1, red: 675 triangles, Zone 2, green: 217 triangles

Importing Triangles from your Data Structures

In practice, you often have triangles stored in your own data structures in memory that you want to import into Fade2D. The following function importFromPointTriples() demonstrates how to achieve this.

void importFromPointTriples(std::vector<Zone2*>& vZones)
  cout<<"* importFromPointTriples()"<<endl;
  // * 1 *   Setup
  vector<Triangle2*> vYourTriangles;
  for(Zone2* pZone:vZones) pZone->getTriangles(vYourTriangles);
  vector<Point2> vYourCorners;
  for(Triangle2* pT:vYourTriangles)
  cout<<"  Setup, vYourCorners: "<<vYourCorners.size()<<" points (= "<<vYourCorners.size()/3<<" triangles)"<<endl;

  // * 2 *   Import from vYourCorners to Fade_2D
  Fade_2D dt2;
  Zone2* pZone=dt2.importTriangles_robust(vYourCorners);
  cout<<"  imported pZone:\t"<<pZone->getNumberOfTriangles()<<endl<<endl;
  cout<<"  imported dt2:\t"<<dt2.numberOfTriangles()<<" triangles (with convex fill triangles)"<<endl;
  • Step 1, Setup: Let’s assume that vZones, provided as an argument of the function, is part of your own data structures, and that you want to import the triangles of these zones into a new triangulation dt2. You access your triangles and store the 3 corner points of each triangle in vYourCorners.
  • Step 2, Import: Create a new Fade_2D object dt2 and invoke importTriangles_robust() with vYourCorners as parameter for this call. This command returns one Zone2* pZone containing all imported triangles.
Zone imported from a vector of point triples, 892 triangles

The imported Zone2* pZone contains only the triangles that correspond to vYourCorners.

Triangulation imported from a vector of point triples

The underlying triangulation dt2 includes additional fill triangles that complete the convex hull. Fade added these because a Delaunay triangulation is always convex.

Note: The importTriangles_robust() method, which we use, automatically corrects wrong orientations and overlapping triangles, if any, which may result in a few extra triangles being generated in pZone.

Warning: Avoid using the similar importTriangles() method unless you’re working with extremely large datasets and are confident that they are 100% error-free. While this method is faster due to fewer checks, feeding corrupt data to it can cause unexpected behavior. Therefore, we strongly recommend to use the newer importTriangles_robust() method.

Importing Triangulations from .PLY and saving to .PLY

PLY, or Polygon File Format, is a popular file format for storing 3D geometry. Writing a triangulation or zone to a .PLY file is demonstrated by the writePly() function below. Support for both ASCII and binary file formats exists. However, for precise coordinate storage, we advise using the binary .PLY format. Fade always writes coordinates with 64-bit precision.

void writePly(Fade_2D& dt,vector<Zone2*>& vZones)
  // Write the whole triangulation to a *.ply file
  const char* all_fn("triangulation.ply");
  dt.writePly(all_fn,false); // ...use bASCII=false for binary format

  // Write only one zone to a *.ply file
  const char* zone_fn("zone.ply");

  cout<<"* writePly()"<<endl;
  cout<<"  triangulation: "<<dt.numberOfTriangles()<<" triangles"<<endl;
  cout<<"  zone: "<<vZones[0]->getNumberOfTriangles()<<" triangles"<<endl;
  cout<<"  Have created "<<all_fn<<", "<<zone_fn<<endl<<endl;

The following code snippet calls importTrianglesFromPly() to import triangles from a .PLY file. Fade detects the exact type of the PLY file (32/64-bit, binary/ASCII) automatically. Internally, this method uses the importTriangles_robust() function described above, thereby automatically correcting any errors that may be present in the input data.

void readPly()
	Fade_2D dt1;
	const char *all_fn("triangulation.ply");
	cout << "* readPly()" << endl;
	cout << "  Triangulation: " << dt1.numberOfTriangles() << " triangles, read from "<<all_fn<<endl;

	Fade_2D dt2;
	const char *zone_fn("zone.ply");
	Zone2 *pZone2 = dt2.importTrianglesFromPly(zone_fn);
	cout << "  pZone2: " << pZone2->getNumberOfTriangles() << " triangles, read from "<<zone_fn<<endl;

Note: The .PLY file type is not supported on very old platforms that do not properly support C++11 (up to and including VS2012).

Saving and Loading in Fade2D’s Native Format

Fade2D’s native file format for triangulations offers the key benefit of supporting zones. The below saveNative() function demonstrates writing of:

  • A triangulation along with two zones
  • The triangles of two zones
  • An individual zone
void saveNative(Fade_2D& dt,vector<Zone2*>& vSaveZones)
  // Save the triangulation along with the zones
  const char* all_fn("triangulation_native.fade");

  // Alternatively we can save only the two zones
  const char* zones_fn("zones_native.fade");

  // Or save an individual zone
  const char* zone_fn("one_zone_native.fade");

Reading the data written by the above function is demonstrated in void loadNative() below. If zones are present, the resulting vector of zones will contain the zones in the same order as they were written. If the given data is non-convex, the underlying Delaunay triangulation includes additional fill triangles to complete their convex hull.

void loadNative()
	Fade_2D dt1;
	vector<Zone2*> vZones1;
	// Load the triangulation with the zones
	const char* all_fn("triangulation_native.fade");

	// Alternatively, we could only be interested in loading zones. Thereby,
	// the triangulation may contain additional triangles if required to
	// complete the convex hull of the zones.
	const char* zones_fn("zones_native.fade");
	Fade_2D dt2;
	vector<Zone2*> vZones2;

Writing Wavefront .OBJ

The Wavefront .OBJ file format enjoys broad support across various applications for 3D geometry, and Fade2D supports writing Fade_2D and Zone2 objects to ASCII .OBJ files.

void writeObj(Fade_2D& dt,vector<Zone2*>& vZones)
	const char* all_fn("triangulation.obj");

	const char* zone_fn("zone.obj");

Writing and Reading Points and Line Segments

The writeElements() function below extracts the vertices and edges from a Fade_2D object and writes them to binary files. While there are also ASCII versions of the used commands available, for the best possible accuracy, we recommend using the binary formats.

void writeElements(Fade_2D& dt)
	// Fetch the triangles
	vector<Triangle2*> vAllT;

	// Fetch the vertices
	vector<Point2*> vVertices;

	// Create directed edges
	vector<Edge2> vDirectedEdges;
	vector<Segment2> vSegments;
	for(Edge2& e:vDirectedEdges) vSegments.emplace_back(Segment2(*e.getSrc(),*e.getTrg()));

	cout<<"* writeElements()"<<endl;
	cout<<"  vAllT:\t"<<vAllT.size()<<endl;
	cout<<"  vVertices:\t"<<vVertices.size()<<endl;
	cout<<"  vSegments:\t"<<vSegments.size()<<endl;

	const char* points_fn("points.bin");
	const char* segments_fn("segments.bin");

	// Write the unique points

	// Write the segments

	cout<<"  Have created "<<points_fn<<" and "<<segments_fn<<endl;

The readElements() function reads the points and segments from the binary files that we have previously written. Additionally, it’s worth mentioning that there are also ASCII point readers named readXY() for 2D and readXYZ() for 3D taking whitespace-separated coordinates.

void readElements()
	// Reads individual elements that have been written before using writePointsBIN() and writeSegmentsBIN()

	const char* points_fn("points.bin");
	std::vector<Point2> vPoints;
	readPointsBIN(points_fn,vPoints,true); // Use bWithHeader=true for files that have been written with writePointsBIN()

	const char* segments_fn("segments.bin");
	std::vector<Segment2> vSegments;

The FadeExport Struct

The FadeExport struct is extensively described in a separate article. Therefore, it is only mentioned here briefly. FadeExport is designed primarily for exporting data in memory, facilitating the integration of data from a Fade_2D object into your own data structures. For more information about the FadeExport struct, please refer to the detailed description available in the Exporting a Triangulation article. However, for self-containment, FadeExport is briefly utilized below:

void demoFadeExport(Fade_2D& dt)
  FadeExport fadeExport; // Simple struct, see "FadeExport.h"
  bool bCustomIndices(false); // To retrieve custom indices also
  bool bClear(false); // Clear memory in dt to save memory

  cout<<"* demoFadeExport()"<<endl;
  cout<<"  numPoints: "<<fadeExport.numPoints<<endl;
  cout<<"  numTriangles: "<<fadeExport.numTriangles<<endl;

Leave a Reply

Your email address will not be published. Required fields are marked *