Reading unstructured grid information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As we saw for the structured grid, we need to first set up an instance
of the unstructured mesh reading class and read the mesh itself.
Afterwards, we have access to the mesh information via getters just as
with the structured grid. The information is now in an unstructured grid
format and thus different from the structured grid information.
Additional information on the vertex connectivity is available as well
and the examples shown below indicate how to read an unstructured grid
with all of its information. But first, we have to set up the
unstructured grid as mentioned above.

.. code:: cpp

   #include <filesystem>
   #include "meshReaderLib/meshReader.hpp"

   int main() {
     // provide a relative or absolute path to where the CGNS file is located that should be read
     auto unstructuredMeshFile = std::filesystem::path("path/to/unstructured/mesh.cgns");

     // create an instance of the unstructured mesh reading class
     ReadUnstructuredMesh unstructuredMesh(unstructuredMeshFile);

     // read all unstructured grid properties at once
     unstructuredMesh.readMesh();

     // perform any other function calls here to get grid properties, discussed below in detail
     // ...

     return 0;
   }

Reading coordinate (vertex) information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The unstructured grid exposes the ``getCoordinates()`` function which
provides the ``x`` and ``y`` coordinates for each vertex in the
unstructured grid. Since the unstructured grid is allowed to be split
into several zones/blocks, we need to loop over the number of blocks
first, just as we did with our structured grid as well. A complete
example of accessing the ``x`` and ``y`` coordinates for an unstructured
grid is shown below.

.. code:: cpp

   // get the coordinates from the unstructured mesh
   auto coordinates = unstructuredMesh.getCoordinates();

   // get the number of grid blocks (could be a multi-block unstructured grid)
   auto numberOfBlocks = coordinates.size();

   // loop over all blocks
   for (unsigned block = 0; block < numberOfBlocks; ++block) {
     // get the number of vertices
     auto numberOfVertices = coordinates[block].size();

     // loop over all vertices
     for (unsigned vertex = 0; vertex < numberOfVertices; ++vertex) {
       const auto &x = coordinates[block][vertex][COORDINATE::X];
       const auto &y = coordinates[block][vertex][COORDINATE::Y];

       // process results and use x and y as required
       // ...

     }
   }

Reading element connectivity table
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As alluded to above, unstructured grids require additional element
connectivity information, which specifies how vertices are connected
into a cell/element. The unstructured grid class has the
``getInternalCells()`` function available for this, which exposes, for
each cell/element the vertex ID that makes up the current cell. We can
then get the location for each vertex in the cell by looking up the
information in the coordinate array that we read in the previous code
example.

.. code:: cpp

   // get the internal elements from the unstructured mesh
   auto interalElements = unstructuredMesh.getInternalCells();

   // get the number of grid blocks (could be a multi-block unstructured grid)
   auto numberOfBlocks = interalElements.size();

   // loop over all blocks
   for (unsigned block = 0; block < numberOfBlocks; ++block) {
     // get the number of internal elements in current block
     auto numberOfVertices = interalElements[block].size();

     // loop over all elements
     for (unsigned element = 0; element < numberOfVertices; ++element) {
       // get number of vertices making up current element
       auto numberOfVertices = interalElements[block][element].size();

       if (numberOfVertices == 3) {
         auto v0 = interalElements[block][element][0];
         auto v1 = interalElements[block][element][1];
         auto v2 = interalElements[block][element][2];
       
         std::cout << "triangle with vertices: " << v0 << ", " << v1 << ", " << v2 << std::endl;

         std::cout << "triangle has vertices at: ";

         std::cout << "(" << coordinates[block][v0][COORDINATE::X] << ", ";
         std::cout << coordinates[block][v0][COORDINATE::Y] << "), ";

         std::cout << "(" << coordinates[block][v1][COORDINATE::X] << ", ";
         std::cout << coordinates[block][v1][COORDINATE::Y] << "), ";

         std::cout << "(" << coordinates[block][v2][COORDINATE::X] << ", ";
         std::cout << coordinates[block][v2][COORDINATE::Y] << "), ";  
       }

       // process results and use x and y as required
       // ...

     }
   }

.. _reading-interface-connectivity-information-1:

Reading interface connectivity information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Similar to the structured grid, we may have several zones or blocks in
our grid. At the interface between two zones/blocks, we need to know
which faces of the unstructured cells are connected to each other so
that we can request information from neighboring cells on different
zones/blocks for the calculation of flow properties. An example to
obtain the matching vertex pairs that make up a face is shown below.

.. code:: cpp

   // get the interface connectivity from the unstructured mesh
   auto interfaceConnectivity = unstructuredMesh.getInterfaceConnectivity();

   // get the number of blocks in the mesh
   auto numberOfBlocks = interfaceConnectivity.size();

   // loop over all interfaces
   for (unsigned block = 0; block < numberOfBlocks; ++block) {
     // get the number of interfaces in current block
     auto numberOfInterfaces = interfaceConnectivity[block].size();

     // loop over all interfaces in current block
     for (unsigned interface = 0; interface < numberOfInterfaces; ++interface) {
       auto ownerBlock = interfaceConnectivity[block][interface].zones[0];
       auto neighbourBlock = interfaceConnectivity[block][interface].zones[1];  

       // get the number of vertices in interface
       auto numberOfFacesInInterface = interfaceConnectivity[block][interface].ownerIndex.size();

       // loop over faces in interface
       for (int face = 0; face < numberOfFacesInInterface; ++face) {
         // get the starting vertex of the owning face
         const auto &ownerFaceStart = interfaceConnectivity[block][interface].ownerIndex[face][0];
         
         // get the ending vertex of the owning face
         const auto &ownerFaceEnd = interfaceConnectivity[block][interface].ownerIndex[face][1];

         // get the starting vertex of the neighbour face
         const auto &neighbourFaceStart = interfaceConnectivity[block][interface].neighbourIndex[face][0];
         
         // get the ending vertex of the neighbour face
         const auto &neighbourFaceEnd = interfaceConnectivity[block][interface].neighbourIndex[face][1];
       }
     }
   }

.. _reading-boundary-condition-information-1:

Reading boundary condition information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Getting boundary conditions is also no different from reading boundary
conditions on a structured grid. The only difference here is that
instead of reading vertices that are on the boundary, we read vertex
pairs that make up the face of the boundary. The example below shows how
to extract the type of boundary, as well as the faces that are contained
within that boundary patch.

.. code:: cpp

   // get the boundary condition information
   auto boundaryConditions = unstructuredMesh.getBoundaryConditions();

   // get the number of blocks in the mesh
   auto numberOfBlocks = boundaryConditions.size();

   // loop over all blocks
   for (unsigned block = 0; block < numberOfBlocks; ++block) {
     // check how many boundaries are within each block
     auto numberOfBoundaries = boundaryConditions[block].size();

     // loop over all boundaries in current block
     for (unsigned boundary = 0; boundary < numberOfBoundaries; ++boundary) {
       // get the boundary type, can be of type BC::WALL, BC::SYMMETRY, BC::INLET, or BC::OUTLET
       auto bcType = boundaryConditions[block][boundary].boundaryType;

       // get the number of faces in boundary
       auto numberOfFaces = boundaryConditions[block][boundary].indices.size();

       // loop over all faces in boundary
       for (unsigned face = 0; face < numberOfFaces; ++face) {
         // get current starting location of boundary face
         const auto bcFaceStart = boundaryConditions[block][boundary].indices[face][0];

         // get current ending location of boundary face
         const auto bcFaceEnd = boundaryConditions[block][boundary].indices[face][1];

         // apply boundary condition based on specified type
         if (bcType == BC::WALL) {
           // process wall ...
         } else if (bcType == BC::SYMMETRY) {
           // process symmetry ...
         } else if (bcType == BC::INLET) {
           // process inlet ...
         } else if (bcType == BC::OUTLET) {
           // process outlet ...
         }
       }
     }
   }
