DGtal  1.2.0
Surface mesh data structure for representing manifold or non-manifold polygonal surfaces in R3
Since
1.1

Part of Shapes package

This part of the manual describes how to represent and manipulate generic polygonal surfaces embedded in $$\mathbb{R}^3$$. The class SurfaceMesh proposes an index-based data structure that encodes all topological relations between vertices, edges and faces, even if the mesh presents some non manifold places (like 3 triangles tied along the same edge). Input/output operations to and from OBJ files are provided through classes SurfaceMeshReader and SurfaceMeshWriter. Creation of classical surface 3D shapes (sphere, torus, Schwarz lantern) with groundtruth geometry is provided in SurfaceMeshHelper.

The following programs are related to this documentation:

testSurfaceMesh.cpp, exampleSurfaceMesh.cpp

The useful includes are:

#include "DGtal/shapes/SurfaceMesh.h" // main class
#include "DGtal/shapes/SurfaceMeshHelper.h" // creation/conversion
#include "DGtal/io/readers/SurfaceMeshWriter.h" // output to OBJ file

# Creating a surface mesh

A surface mesh (class SurfaceMesh) is a template class parameterized by the types chosen for 3D points and 3D vectors. A common choice is PointVector< double, 3 > for both, or equivalently Z3i::RealPoint and Z3i::RealVector. Although the topological part of the class does not impose a 3D embedding, the class SurfaceMesh imposes it since its target is 3D geometry processing. Indeed some geometric operations like computing normals from positions or i/o to OBJ format have meaning only in 3D.

First, write the following typedefs:

// The following typedefs are useful
typedef SurfaceMesh< RealPoint, RealVector > SurfMesh;
typedef SurfaceMeshHelper< RealPoint, RealVector > Helper;
typedef SurfMesh::Vertices Vertices;

Then, there are several ways for creating a surface mesh (see exampleSurfaceMesh.cpp for several examples):

std::vector< RealPoint > positions =
{ { 0, 0, 5 }, { 1, 1, 3 }, { -1, 1, 3 }, { -1, -1, 3 }, { 1, -1, 3 } };
std::vector< Vertices > faces =
{ { 0, 1, 2 }, { 0, 2, 3 }, { 0, 3, 4 }, { 0, 4, 1 }, { 4, 3, 2, 1 } };
auto pyramid_mesh = SurfMesh( positions.cbegin(), positions.cend(),
faces.cbegin(), faces.cend() );
SurfMesh smesh;
std::string S = examplesPath + "samples/spot.obj";
std::ifstream input( S.c_str() );
input.close();
trace.info() << "Read " << ( ok_read ? "OK" : "ERROR" )
<< " mesh=" << smesh << std::endl;
std::ostream & info()
Trace trace
Definition: Common.h:154
static bool readOBJ(std::istream &input, SurfaceMesh &smesh)
auto torus_mesh = Helper::makeTorus
( 2.5, 0.5, RealPoint { 0.0, 0.0, 0.0 }, 40, 40, 0, Helper::NormalsType::NO_NORMALS );
Z2i::RealPoint RealPoint
Creating surface meshes from OBJ file, by specifying vertex/face information or predefined shapes

# Topological relations within a surface mesh

All topological relations are precomputed as static arrays in SurfaceMesh class (which is thus not adapted to dynamic topological updates). You may access the number of cells with SurfaceMesh::nbVertices, SurfaceMesh::nbEdges, SurfaceMesh::nbFaces. Note that edge indices corresponds to pairs of vertices (i,j) with i<j.

You may ask for each vertex v:

You may ask for each face f:

You may create an edge index with SurfaceMesh::makeEdge. If the two vertices (i,j) do not form an edge, then the returned index is SurfaceMesh::nbEdges. Note that calling makeEdge(i,j) or makeEdge(j,i) returns always the same index, whether valid or invalid.

You may ask for each edge e:

• its two incident vertices with SurfaceMesh::incidentVertices, as pair (i,j) with (i<j).
• its range of bordering faces with SurfaceMesh::edgeFaces (they can be incident clockwise or counterclockwise)
• its range of left bordering faces with SurfaceMesh::edgeLeftFaces (a face to its left, being defined ccw, means that the face is some (..., i, j, ... ))
• its range of right bordering faces with SurfaceMesh::edgeRightFaces (a face to its left, being defined ccw, means that the face is some (..., j, i, ... ))

All the preceding methods have global variants returning all incident faces, all incident vertices, etc: SurfaceMesh::allIncidentFaces, SurfaceMesh::allIncidentVertices, SurfaceMesh::allNeighborFaces, SurfaceMesh::allNeighborVertices, SurfaceMesh::allEdgeFaces, SurfaceMesh::allEdgeLeftFaces, SurfaceMesh::allEdgeRightFaces.

Since vertices/edges/faces are indices, visiting them is simply a loop from 0 (included) till SurfaceMesh::nbVertices / SurfaceMesh::nbEdges / SurfaceMesh::nbFaces (all excluded).

# A surface mesh is a graph

Class SurfaceMesh is a model of concepts::CUndirectedSimpleGraph (see also moduleGraphDefinitions). Hence you can for instance perform a breadth first traversal on its vertices.

#include "DGtal/shapes/SurfaceMesh.h"
...
typedef SurfaceMesh< RealPoint, RealVector > SurfMesh;
SurfMesh smesh;
BreadthFirstVisitor< SurfMesh > visitor( smesh, 0 );
std::vector<double> distances( smesh.nbVertices() );
double biggest_d = 0.0;
while ( ! visitor.finished() )
{
auto v = visitor.current().first; // current vertex
auto d = visitor.current().second; // current distance
biggest_d = (double) d;
distances[ v ] = biggest_d;
visitor.expand();
}

# Getting manifold, boundary and non-manifold parts

SurfaceMesh can compute range of edges that have the same topology.

Locating non manifold vertices (like pinched vertices) requires more work and is not implemented.

# Geometric positions and normals, and other information associated to cells

Vertex positions can be accessed and modified through methods SurfaceMesh::positions, or SurfaceMesh::position with a given vertex index.

You may associate normal vectors to the mesh as follows:

Normals are then accessed with SurfaceMesh::vertexNormals for vertices, SurfaceMesh::faceNormals for faces, or per element with SurfaceMesh::vertexNormal and SurfaceMesh::faceNormal.

More generally, you can transfer (by averaging) vector of values:

# Further geometric services

The following local geometric services are provided:

The following global geometric services are provided:

You may also perturbate the mesh positions with uniform or non uniform random noise: with SurfaceMesh::perturbateWithUniformRandomNoise and SurfaceMesh::perturbateWithAdaptiveUniformRandomNoise;

# Conversion and output to OBJ file format

You can convert a SurfaceMesh to a Mesh object simply by calling MeshHelpers::surfaceMesh2Mesh.

You can also output OBJ file (if available, with vertex normal information) using class SurfaceMeshWriter::writeOBJ, with some specialization allowing you to color faces. Edge lines and iso-lines can also be output as OBJ in same class.

The snippet below shows how to output the distances computed in A surface mesh is a graph as a surface colored per face with three isolines corresponding to relative distances 0.25, 0.5 and 0.75.

// Displaying faces colored by their distance to vertex 0.
auto face_distances = smesh.computeFaceValuesFromVertexValues( distances );
auto cmap = GradientColorMap< double >( 0.0, biggest_d, CMAP_JET );
std::vector<Color> face_colors( smesh.nbFaces() );
for ( SurfMesh::Face j = 0; j < smesh.nbFaces(); ++j )
face_colors[ j ] = cmap( face_distances[ j ] );
typedef SurfaceMeshWriter< RealPoint, RealVector > Writer;
Writer::writeOBJ( "spot-bft.obj", smesh, face_colors );
// Displaying three isolines.
Writer::writeIsoLinesOBJ( "spot-iso-0_25.obj", smesh,
face_distances, distances, distances.back() * 0.25, 0.2 );
Writer::writeIsoLinesOBJ( "spot-iso-0_5.obj", smesh,
face_distances, distances, distances.back() * 0.5, 0.2 );
Writer::writeIsoLinesOBJ( "spot-iso-0_75.obj", smesh,
face_distances, distances, distances.back() * 0.75, 0.2 );
TriMesh::Face Face
SurfaceMesh faces colored according to distance to bluest vertex and three isodistance lines