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.
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) { vYourCorners.emplace_back(*pT->getCorner(0)); vYourCorners.emplace_back(*pT->getCorner(1)); vYourCorners.emplace_back(*pT->getCorner(2)); } 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 triangulationdt2
. You access your triangles and store the 3 corner points of each triangle invYourCorners
. - Step 2, Import: Create a new Fade_2D object
dt2
and invokeimportTriangles_robust()
withvYourCorners
as parameter for this call. This command returns oneZone2* pZone
containing all imported triangles.
Result:
The imported Zone2* pZone
contains only the triangles that correspond to vYourCorners
.
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 newerimportTriangles_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"); vZones[0]->writePly(zone_fn,false); 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"); dt1.importTrianglesFromPly(all_fn); 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"); dt.saveTriangulation(all_fn,vSaveZones); // Alternatively we can save only the two zones const char* zones_fn("zones_native.fade"); dt.saveZones(zones_fn,vSaveZones); // Or save an individual zone const char* zone_fn("one_zone_native.fade"); vSaveZones[0]->save(zone_fn); }
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"); dt1.load(all_fn,vZones1); // 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; dt2.load(zones_fn,vZones2); cout<<endl; }
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"); dt.writeObj(all_fn); const char* zone_fn("zone.obj"); vZones[0]->writeObj(zone_fn); }
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; dt.getTrianglePointers(vAllT); // Fetch the vertices vector<Point2*> vVertices; dt.getVertexPointers(vVertices); // Create directed edges vector<Edge2> vDirectedEdges; dt.getUndirectedEdges(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 writePointsBIN(points_fn,vVertices); // Write the segments writeSegmentsBIN(segments_fn,vSegments); 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; readSegmentsBIN(segments_fn,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 dt.exportTriangulation(fadeExport,bCustomIndices,bClear); cout<<"* demoFadeExport()"<<endl; cout<<" numPoints: "<<fadeExport.numPoints<<endl; cout<<" numTriangles: "<<fadeExport.numTriangles<<endl; }