2.2.3. The abstract mesh interface

In MeshViz Interface, the data structure used to store the mesh representation is not defined by the MeshViz Interface API as it is in most classical APIs such as 3DdataMaster.

For instance, in 3DdataMaster, a mesh can be created like in the following sample code:

float*x = new float[nx];
float*y = new float[ny];
float*z = new float[nz];
// Copy internal geometry into x, y and z arrays
for (int i = 0; i < nx ; i++)
  {
  x[i] = ...
    y[i] = ...
    z[i] = ...
  }
// Do the same for dataset
...
// Now pass the data to the mesh
PoCartesianGrid3D *po_mesh = new PoCartesianGrid3D;
po_mesh->setGeometry(nx, ny, nz, x, y, z);
po_mesh->addValuesSet(dataIndex,dataset);

Where the data and the geometry defining the mesh are copied into intermediate data structures which have a predefined type and organization.

With MeshViz Interface, the mesh passed to the extraction or data mapping classes is a reference to an object in the application which implements one of the MeshViz Interface mesh abstract interfaces.

A MeshViz Interface mesh contains a topology interface derived from MiTopology MiTopology and a geometry interface derived from MiGeometry MiGeometry .

MiTopology MiTopology derived classes return the Nth cell of the mesh as a class derived from the MiCell MiCell interface :

const MiVolumeCell& 
cell = mesh.getTopology().getCell(n);

Similarly, MiGeometry MiGeometry derived classes return the Nth node (3D or 2D point) of the mesh.

MbVec3d node = mesh.getGeometry().getCoord(m);

MiCell MiCell derived classes must implement several methods to get the cell information, number of nodes, node indices, etc.

Scalar and vector sets are abstract interfaces of type MiDataSet MiDataSet which return the Nth value of the set as a double or a vector.

So instead of allocating and filling a set of arrays with the values read from the internal data structure like in legacy APIs, the application has to implement these abstract interfaces in its existing classes as in the following code snippet:

Class declaration (partial):

class TetraMeshBuilder : public MiVolumeMeshUnstructured
{
public:
 TetraMeshBuilder(size_t ni, size_t nj, size_t nk);

 // Methods from MiVolumeMeshUnstructured
   virtual const MiVolumeTopologyExplicitI& getTopology() const
   { return m_topology; };

 // Methods from MiMeshUnstructured
 virtual const MiGeometryI& getGeometry() const
   { return m_geometry; };

 // Utility methods to manage data sets
 virtual const MiScalardSetI* getScalarSet(size_t setId) const
   { return &m_scalarSet; };
 virtual size_t getNumScalarSets() const {return 1;};

protected:

 // The mesh geometry: a simple list of points implementing 
 // the MiMeshGeometry interface
 Coordinates m_geometry;

 // The mesh topology: since this is a set of tetrahedron, 
 // we know that each cell will contain four indices.
 // Thus no need to store more than that list to describe the entire topology.
   Topology m_topology;

 // Scalar set
   ScalarSet m_scalarSet;

   class Coordinates : public MiGeometryI
   {
   public:
     Coordinates() {};

     // Methods from MiGeometryI
     virtual MbVec3<double> getCoord(size_t i) const
       { return m_coords[i].getMbVec3DValue(); };
     virtual size_t getSize() const { return m_coords.size(); };
     virtual MbVec3<double> getMin() const { return m_min.getMbVec3DValue();};
     virtual MbVec3<double> getMax() const { return m_max.getMbVec3DValue();};
     virtual size_t getTimeStamp() const { return 1;};

     // Utility methods to manage the point list
     void reserve(size_t numPoints) {m_coords.reserve(numPoints); };
     void addPoint(Point &pt) {m_coords.push_back(pt);};
     void setMinMax(Point &min,Point &max) { m_min = min; m_max = max; };

   protected:
     std::vector<Point> m_coords;
     // The Point class contains three values representing X, Y and Z
       Point m_min;
       Point m_max;
   };

 class Topology : public MiVolumeTopologyExplicitI
   {
   public:
     Topology() {};

     // Methods from MiVolumeTopologyExplicitI
     virtual const MiVolumeCell* getCell(size_t i) const
       { return &(m_cells[i]); };
     virtual size_t getNumCells() const { return m_cells.size(); };
     virtual size_t getBeginNodeId() const { return 0; };
     virtual size_t getEndNodeId() const { return m_numCoords; };
     virtual size_t getTimeStamp() const { return 1; };

     // Utility methods to manage the list of cells
     void reserve(size_t numIndices, size_t numCoords)
       {
       m_cells.reserve(numIndices);
         m_numCoords = numCoords;
       };
     void addTetraIndices(size_t ind0,size_t ind1,size_t ind2,size_t ind3)
       {
       Cell cell(ind0,ind1,ind2,ind3);
         m_cells.push_back(cell);
       };

   protected:
     std::vector<Cell> m_cells;
       size_t m_numCoords;
   };

The Cell class implements the MiVolumeCell MiVolumeCell interface. In this example, they are all tetrahedrons so the number of facets is always 4, number of points is 4 and number of edges is 6. The other implementation methods are not shown here:

class Cell : public MiVolumeCell
   {
   public:

     virtual size_t getNumFacets() const {return 4; }
     virtual size_t getNumEdges() const {return 6; }
     virtual size_t getNumNodes() const {return 4; };
     ...
   }

Usage of the extraction class:

TetraMeshBuilder mesh(32,10,10);

// Get an instance of an isosurf extractor according to the mesh type
MiSkinExtractUnstructured* skinExtract =
    MiSkinExtractUnstructured::getNewInstance(mesh);

const MiSurfaceMeshUnstructured& skin = skinExtract->extractSkin();

In this code, the mesh is implemented in the TetraMeshBuilder class. It is a set of tetrahedron splitting a cube created for the purpose of the example. It is derived from a MiVolumeMeshUnstructured MiVolumeMeshUnstructured which represents a generic unstructured mesh. The mesh is passed to the MiSkinExtractUnstructured::getNewInstance() method which returns an abstract interface to a MiSkinExtractUnstructured MiSkinExtractUnstructured object. Calling extractSkin() on this interface returns a MiSurfaceMeshUnstructured MiSurfaceMeshUnstructured abstract interface which can be parsed to get the extracted polygons.

So, from a volume mesh implementing an abstract interface, the extraction mechanism produces an interface to a surface mesh.

The main advantages of this architecture are:

  • The real data set contained in the mesh is never copied or duplicated by MeshViz Interface reducing the memory overhead of the extraction mechanism.

  • Any type of storage can be used internally (byte, float, double, or even parametric data...) since values are read through methods of abstract interfaces returning double.

  • Any type of cell topology can be used in the mesh: tetrahedron, hexahedron, etc.

The mesh organizations available are:

When selecting which mesh type to implement in your application, you will have to choose among these three major types depending on your data organization. Altough the unstructured mesh is the most general mesh type and can apply to any case, it is the type which requires the most complex algorithms when extracting features. It also requires that you implement more methods since each cell of an unstructured mesh must be described precisely whereas structured meshes have implicit cells.

[Tip]

Note: Some of the available interfaces do not contain any additional methods compared to their parent class (for instance MiMesh MiMesh ). They are provided for convenience to classify the different types of geometry/topology pairs representing a type of mesh.

There are three main types of structured meshes. They all share the same topology defined by the MiTopologyIjk MiTopologyIjk interface representing a 2D or 3D matrix of quadrangles or hexahedron cells. However, they differ in their geometry.

  • Regular: The geometry is given by a bounding box defined by two points representing the corners of a cube. The 3D space is implicitly split into the number of cells contained in the topology. They implement the MiGeometryRegular interface which contains the getMin() and getMax() methods to return the corners of the bounding box.

  • Rectilinear: The geometry is defined by a set of values for X, a set of values for Y and, for volumes only, a set of values for Z, splitting the cube into irregular quadrangles or parallelepipeds. The number of values on each axis is defined by the number of cells along this axis from the topology interface. They implement the MiSurfaceGeometryRectilinear or MiVolumeGeometryRectilinear interface containing the getX(), getY() and getZ() (for volumes only) methods.

  • Curvilinear: All the values of the matrix of points are given as a 3D point. The cells are generic hexahedra which can all have different shapes. They implement the MiGeometryIj MiGeometryIj or MiGeometryIjk interface containing the getCoord(i,j,k) method.

As described above, structured meshes contain only hexahedron cells and thus do not require implementing any specific cell interface. All extraction algorithms are able to figure out the exact description of each cell by looking at the point list. This is not the same for unstructured meshes where cells can have any kind of shape. In this case, the interface derived from MiTopologyI MiTopologyI contains a list of line (MiLineCell MiLineCell ), surface (MiSurfaceCell MiSurfaceCell ) or volume (MiVolumeCell MiVolumeCell ) cells.

They are the most complex interfaces to implement since they must be able to return advanced topological information required by extraction algorithms. Even though some of the MiCell MiCell methods are simple descriptions of the cell’s topology (number of edges, list of facets and list of points) and are thus easy to implement, some other methods are more complex and require further explanation. Some extraction algorithms, such as isosurface computation, stream lines and point probing, need to know specific information about the cell that depends on the cell’s shape.

For volume cells, these methods are:

  • getIsosurfTopology: Gets the list of topological polygons defining the part of the isosurface topology in this cell.

  • getWeight: Gets the weights of a point defined by its iso parametric coordinates.

  • getIsoParametricCoord: Gets the iso parametric coordinates of a point. If the point is inside the cell its parametric coordinates must be between 0 and 1.

See the section called “Properties of shape functions” for more details on iso parametric coordinates and weights.

Obviously, the internal extraction algorithms depend on the implementation of such methods. So a set of utility classes are provided for the most common shapes: tetrahedron, wedge, pyramid and hexahedron providing a static implementation of these methods. For instance, the MxHexahedronCellExtract MxHexahedronCellExtract class contains the implementation of these methods for an hexahedron. So when implementing an hexahedron cell in your own cell code, you can just call the MxHexahedronCellExtract::getIsosurfTopology in the getIsosurfTopology method of your cell implementation.

If you don’t do that, your application may work as long as you don’t use one of the extraction algorithms that requires them such as isosurface, stream lines or point probe. These methods are not pure virtual and their default implementation (from MiCell MiCell ) throws an exception to show you that the extraction method used requires a valid implementation.

For other shapes, such as a generic polyhedron, an implementation may not be possible. Thus, these cells must be converted to a set of basic cells before calling the extract algorithm. See Section 2.2.7, “Support of polygonal surface meshes and polyhedral volume meshes” for more details on converting a polyhedral mesh.

Notice also that when using these utility classes, the cells must follow some strict orientation rules relative to the parametric coordinate space. See the documentation of these classes for a detailed description of the required topologies.

See the MeshViz Implementation library for valid examples on how to implement a generic cell.

Data sets are values attached to a cell or to point nodes that must be analyzed. In some fields of application, they are also called fields, properties or attributes. They can be scalar or vector sets.

Any number of data sets can be analyzed for each mesh using any type of internal representation (float, double, char, bytes,…) or even implicit data computed on the fly. This is possible thanks to the MiDataSet MiDataSet interface. This template interface is a very powerful solution for defining data sets associated with a mesh. The template definition is used to define the dimension of the associated data: Scalar sets are one-dimension data, vector sets are tri-dimension data.

Data sets are not stored in the mesh. This is because a data set is associated with a mesh only during a feature extraction operation. Thus, several extracts can be done on the same mesh for different data sets.

Data can be given per cell or per node as specified by the getBinding() method. When the binding is defined per cell, the index of the data set matches the index of the cells. When the binding is defined per node, the index of the data set matches the index of the nodes.

Data sets type and organization must be consistent with the mesh type they are used with. Unstructured meshes support MiDataSetI MiDataSetI data sets. Structured meshes support MiDataSetIj MiDataSetIj and MiDataSetIjk MiDataSetIjk data sets. For unstructured IJK meshes, when the binding is per node, data are unstructured (derived from MiDataSetI MiDataSetI ) but when the binding is per cell, since the cells are structured, data sets derive from MiDataSetIjk MiDataSetIjk .

Several specialized types are provided for convenience such as MiScalardSetI that can store any scalar field for temperature, pressure, etc and MiVec3dSetI for storing 3D vector fields.

Similar to geometry interfaces, a MiDataSet MiDataSet interface completely hides the storage type used by the application. This is done by returning double for scalar fields and MbVec3d for vector fields. This cast operation allows you to store internally data sets as shorts, bytes, double or floats or to compute them on the fly.

Most interfaces have a getTimeStamp() method. This method can be used to notify the MeshViz extractors that the content of the object implementing the interface has changed. Thus, MeshViz extractors are informed that they must be recomputed. Any value can be used to represent the time stamp (not only time). A simple integer value incremented each time the values are changed is mandatory. Furthermore, this integer must be unique across objects implementing the same interface. This allows the MeshViz extractors to identify these objects. Using the utility class MxTimeStamp MxTimeStamp ensures an application to respect this rules. Note that this static class is not thread safe.

Often a mesh contains cells having undefined values. You may also want to limit your study to a specific set of cells given by their index range or by their position in space. Two mechanisms are provided for doing that: Cell Filters and Dead Cells.

A Cell Filter is an user class implementing the MiCellFilter MiCellFilter interface passed to the Mesh Extraction objects. It contains a method called for each cell returning true for accepted cells and false for rejected cells.

Like for cells, the Cell Filter can have a structured or an unstructured interface. Structured interfaces derives from MiCellFilterIj MiCellFilterIj or MiCellFilterIjk MiCellFilterIjk . They contain a method acceptCell(i,j,k) indexed exactly like cells. Similarly, for an unstructured mesh, the MiCellFilterI MiCellFilterI ::acceptCell method of the class MiCellFilterI MiCellFilterI has only one parameter representing the cell index.

The following source code shows a simple filter accepting cells indexed in the range 101 to 199 for an unstructured mesh.

class MyCellFilter : public MiCellFilterI
{
virtual bool acceptCell(int cellIndex) const
  {
  return (cellIndex > 100 && cellIndex < 200);
  }
virtual size_t getTimeStamp() const { return 0; }
};

Obviously, since you have access to your own internal data structure, you may get the real cell and do more complex filtering operations. Cell filtering is well adapted to dynamic filters that can change on the fly. For instance, when one wants to temporary isolate a group of cells to have a simplified view of part of a mesh.