// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                            avtM3DFileFormat.C                           //
// ************************************************************************* //

#include <avtM3DFileFormat.h>

#include <string>

#include <vtkFloatArray.h>
#include <vtkRectilinearGrid.h>
#include <vtkUnstructuredGrid.h>
#include <vtkWedge.h>
#include <vtkTriangle.h>
#include <vtkVertex.h>
#include <vtkMath.h>

#include <avtDatabaseMetaData.h>

#include <Expression.h>

#include <InvalidVariableException.h>
#include <InvalidFilesException.h>
#include <UnexpectedValueException.h>
#include <DebugStream.h>

#include <visit-hdf5.h>

using namespace std;

static bool doPlanes = false;

// ****************************************************************************
//  Method: avtM3DFileFormat constructor
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************

avtM3DFileFormat::avtM3DFileFormat(const char *filename)
    : avtMTMDFileFormat(filename)
{
    m_fileID = -1;
    m_filename = filename;
    m_XPClassStr = "Time_Mesh+Time_Node_Data+Time_Cell_Data";
    m_FullString = "Full";
    m_Plane3DString = "Planes3D";
    m_Plane2DString = "Planes2D";

    LoadFile();
}


// ****************************************************************************
//  Method: avtM3DFileFormat destructor
//
//  Programmer: Dave Pugmire
//  Creation:   Fri Aug 8 12:32:18 EDT 2008
//
// ****************************************************************************

avtM3DFileFormat::~avtM3DFileFormat()
{
    for ( size_t i = 0; i < m_cellInfo.size(); i++ )
        delete m_cellInfo[i];
    for ( size_t i = 0; i < m_scalarVarNames.size(); i++ )
        delete m_scalarVarNames[i];
    for ( size_t i = 0; i < m_vectorVarNames.size(); i++ )
        delete m_vectorVarNames[i];
    for ( size_t i = 0; i < m_tensorVarNames.size(); i++ )
        delete m_tensorVarNames[i];
    for ( size_t i = 0; i < m_scalarVars.size(); i++ )
        delete m_scalarVars[i];
    for ( size_t i = 0; i < m_vectorVars.size(); i++ )
        delete m_vectorVars[i];
    for ( size_t i = 0; i < m_tensorVars.size(); i++ )
        delete m_tensorVars[i];
}


// ****************************************************************************
//  Method: avtEMSTDFileFormat::GetNTimesteps
//
//  Purpose:
//      Tells the rest of the code how many timesteps there are in this file.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************

int
avtM3DFileFormat::GetNTimesteps(void)
{
    return (int)m_timeSteps.size();
}


// ****************************************************************************
//  Method: avtM3DFileFormat::FreeUpResources
//
//  Purpose:
//      When VisIt is done focusing on a particular timestep, it asks that
//      timestep to free up any resources (memory, file descriptors) that
//      it has associated with it.  This method is the mechanism for doing
//      that.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************

void
avtM3DFileFormat::FreeUpResources(void)
{
    H5Fclose( m_fileID );
    for ( size_t i = 0; i < m_planeXforms.size(); i++ )
        if ( m_planeXforms[i] )
            m_planeXforms[i]->Delete();
}


// ****************************************************************************
//  Method: avtM3DFileFormat::PopulateDatabaseMetaData
//
//  Purpose:
//      This database meta-data object is like a table of contents for the
//      file.  By populating it, you are telling the rest of VisIt what
//      information it can request from you.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
//  Modifications:
//    Dave Pugmire, Fri Aug 8 15:22:11 EDT 2008
//    Memory problem caused by CellInfo, VarInfo not being allocated on the heap.
//
//    Dave Pugmire, Tue Jul 21 16:23:42 EDT 2009
//    Turn off 2D and 3D planes for now.
//
// ****************************************************************************

void
avtM3DFileFormat::PopulateDatabaseMetaData( avtDatabaseMetaData *md, int timeState )
{
    avtMeshType mt = AVT_UNSTRUCTURED_MESH;
    int nblocks = m_nCellSets;
    int block_origin = 0;
    int spatial_dimension = 3;
    int topological_dimension = 3;
    double *extents = NULL;
    char str[512];

    // The 3D wedge mesh.
    if (doPlanes)
        sprintf( str, "%s/mesh", m_FullString.c_str() );
    else {
        //sprintf( str, "mesh", m_FullString.c_str() );
        strcpy( str, "mesh"); ///TODO: check on fix
    }
    string meshname = str;
    AddMeshToMetaData( md, meshname, mt, extents, nblocks, block_origin,
                       spatial_dimension, topological_dimension );
    m_meshes.push_back( meshname );

    if (doPlanes)
    {
        //The 3D planes.
        for ( int i = 0; i < m_nPlanes; i++ )
        {
            meshname = GetPlaneName( m_Plane3DString, i );
            topological_dimension = 2;
            AddMeshToMetaData( md, meshname, mt, extents, nblocks, block_origin,
                               spatial_dimension, topological_dimension );
            m_meshesPlane3D.push_back( meshname );
        }
        
        //The 2D planes.
        for ( int i = 0; i < m_nPlanes; i++ )
        {
            meshname = GetPlaneName( m_Plane2DString, i );
            spatial_dimension = 3;
            topological_dimension = 2;
            AddMeshToMetaData( md, meshname, mt, extents, nblocks, block_origin,
                               spatial_dimension, topological_dimension );
            m_meshesPlane2D.push_back( meshname );
        }
    }

    // Add all the variables.

    //Scalar vars.
    for ( size_t i = 0; i < m_scalarVarNames.size(); i++ )
    {
        string varname = m_scalarVarNames[i]->varName;
        
        //Add for each full mesh.
        for ( size_t m = 0; m < m_meshes.size(); m++ )
        {
            if (doPlanes)
                sprintf( str, "%s/%s", m_FullString.c_str(), varname.c_str() );
            else
                sprintf( str, "%s", varname.c_str() );
            string meshvarname = str;
            
            avtCentering cent = AVT_NODECENT;
            AddScalarVarToMetaData( md, meshvarname, m_meshes[m], cent );
            m_scalarVars.push_back( new VarInfo( meshvarname, m_scalarVarNames[i]->dataID,
                                                 m_scalarVarNames[i]->varDim ) );
        }

        if (doPlanes)
        {
            //Add for each 3D plane.
            for ( int m = 0; m < (int)m_meshesPlane3D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane3D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                avtCentering cent = AVT_NODECENT;
                AddScalarVarToMetaData( md, meshvarname, m_meshesPlane3D[m], cent );
                m_scalarVars.push_back( new VarInfo( meshvarname, m_scalarVarNames[i]->dataID,
                                                     m_scalarVarNames[i]->varDim, m ) );
            }
            
            //Add for each 2D plane.
            for ( int m = 0; m < (int)m_meshesPlane2D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane2D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                avtCentering cent = AVT_NODECENT;
                AddScalarVarToMetaData( md, meshvarname, m_meshesPlane2D[m], cent );
                m_scalarVars.push_back( new VarInfo( meshvarname, m_scalarVarNames[i]->dataID,
                                                     m_scalarVarNames[i]->varDim, m ) );
            }
        }
    }

    //Vector vars.
    for ( size_t i = 0; i < m_vectorVarNames.size(); i++ )
    {
        string varname = m_vectorVarNames[i]->varName;
                
        //Add for each full mesh.
        for ( size_t m = 0; m < m_meshes.size(); m++ )
        {
            if (doPlanes)
                sprintf( str, "%s/%s", m_FullString.c_str(), varname.c_str() );
            else
                sprintf( str, "%s", varname.c_str() );
            string meshvarname = str;
            
            avtCentering cent = AVT_NODECENT;
            int vector_dim = m_vectorVarNames[i]->varDim;
            AddVectorVarToMetaData( md, meshvarname, m_meshes[m], cent, vector_dim );
            m_vectorVars.push_back( new VarInfo( meshvarname, m_vectorVarNames[i]->dataID,
                                                 m_vectorVarNames[i]->varDim ) );
        }

        if (doPlanes)
        {
            //Add for each 3D plane.
            for ( int m = 0; m < (int)m_meshesPlane3D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane3D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                avtCentering cent = AVT_NODECENT;
                int vector_dim = m_vectorVarNames[i]->varDim;
                AddVectorVarToMetaData( md, meshvarname, m_meshesPlane3D[m], cent, vector_dim );
                m_vectorVars.push_back( new VarInfo( meshvarname, m_vectorVarNames[i]->dataID,
                                                     m_vectorVarNames[i]->varDim, m ) );
            }
            
            //Add for each 2D plane.
            for ( int m = 0; m < (int) m_meshesPlane2D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane2D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                avtCentering cent = AVT_NODECENT;
                int vector_dim = m_vectorVarNames[i]->varDim;
                AddVectorVarToMetaData( md, meshvarname, m_meshesPlane2D[m], cent, vector_dim );
                m_vectorVars.push_back( new VarInfo( meshvarname, m_vectorVarNames[i]->dataID,
                                                     m_vectorVarNames[i]->varDim, m ) );
            }
        }
    }

    //Tensor vars.
    for ( size_t i = 0; i < m_tensorVarNames.size(); i++ )
    {
        string varname = m_tensorVarNames[i]->varName;
        
        //Add for each full mesh.
        for ( size_t m = 0; m < m_meshes.size(); m++ )
        {
            if (doPlanes)
                sprintf( str, "%s/%s", m_FullString.c_str(), varname.c_str() );
            else
                sprintf( str, "%s", varname.c_str() );
            
            string meshvarname = str;
            
            int tensor_dim = m_tensorVarNames[i]->varDim;
            avtCentering cent = AVT_NODECENT;
            AddTensorVarToMetaData( md, varname, m_meshes[m], cent, tensor_dim );
            m_tensorVars.push_back( new VarInfo( meshvarname, m_tensorVarNames[i]->dataID,
                                                 m_tensorVarNames[i]->varDim ) );
        }

        if (doPlanes)
        {
            //Add for each 3D plane.
            for ( int m = 0; m < (int)m_meshesPlane3D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane3D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                int tensor_dim = m_tensorVarNames[i]->varDim;
                avtCentering cent = AVT_NODECENT;
                AddTensorVarToMetaData( md, varname, m_meshesPlane3D[m], cent, tensor_dim );
                m_tensorVars.push_back( new VarInfo( meshvarname, m_tensorVarNames[i]->dataID,
                                                     m_tensorVarNames[i]->varDim, m ) );
            }
            
            //Add for each 2D plane.
            for ( int m = 0; m < (int)m_meshesPlane2D.size(); m++ )
            {
                sprintf( str, "%s/%s", m_meshesPlane2D[m].c_str(), varname.c_str() );
                string meshvarname = str;
                
                int tensor_dim = m_tensorVarNames[i]->varDim;
                avtCentering cent = AVT_NODECENT;
                AddTensorVarToMetaData( md, varname, m_meshesPlane2D[m], cent, tensor_dim );
                m_tensorVars.push_back( new VarInfo( meshvarname, m_tensorVarNames[i]->dataID,
                                                     m_tensorVarNames[i]->varDim, m ) );
            }
        }
    }
}


// ****************************************************************************
//  Method: avtM3DFileFormat::GetMesh
//
//  Purpose:
//      Gets the mesh associated with this file.  The mesh is returned as a
//      derived type of vtkDataSet (ie vtkRectilinearGrid, vtkStructuredGrid,
//      vtkUnstructuredGrid, etc).
//
//  Arguments:
//      timestate   The index of the timestate.  If GetNTimesteps returned
//                  'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain      The index of the domain.  If there are NDomains, this
//                  value is guaranteed to be between 0 and NDomains-1,
//                  regardless of block origin.
//      meshname    The name of the mesh of interest.  This can be ignored if
//                  there is only one mesh.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************

vtkDataSet *
avtM3DFileFormat::GetMesh( int timestate, int domain, const char *nm )
{
    string meshname = nm;

    debug5 << "Reading in mesh " << meshname << " [domain, timestate] = " << domain << " " << timestate << endl;

    // Look for a full mesh first.
    for ( size_t m = 0; m < m_meshes.size(); m++ )
    {
        if ( meshname != m_meshes[m] )
            continue;

        vtkUnstructuredGrid *grid = vtkUnstructuredGrid::New();
        
        // Read in points.
        hid_t dataID = m_coordIDs[ timestate ];
        hid_t spaceID = H5Dget_space( dataID );
        hsize_t dims[10];
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        
        vtkPoints *pts = vtkPoints::New();
        pts->SetNumberOfPoints( dims[0] );
        H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, pts->GetVoidPointer(0) );

        //Read in connectivity.
        dataID = m_cellInfo[domain]->id;
        spaceID = H5Dget_space( dataID );
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        if ( dims[1] != 6 )
            EXCEPTION2( UnexpectedValueException, "Expecting a wedge!", "Connectivity" );
                
        vtkWedge *wedge = vtkWedge::New();
        
        int *conn = new int[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_INT, H5S_ALL, spaceID, H5P_DEFAULT, conn );
        int *ptr = conn;
        for ( size_t i = 0; i < dims[0]; i++ )
        {
            for ( int j = 0; j < 6; j++ )
            {
                wedge->GetPointIds()->SetId( j, *ptr );
                ptr++;
            }
            grid->InsertNextCell( wedge->GetCellType(), wedge->GetPointIds() );
        }
        delete [] conn;
        
        grid->InsertNextCell( wedge->GetCellType(), wedge->GetPointIds() );
        grid->SetPoints( pts );
        
        wedge->Delete();
        pts->Delete();
        return grid;
    }

    // Try the 3D planes.
    for ( int m = 0; m <(int) m_meshesPlane3D.size(); m++ )
    {
        if ( meshname != m_meshesPlane3D[m] )
            continue;

        vtkUnstructuredGrid *grid = vtkUnstructuredGrid::New();
        
        // Read in points.
        hid_t dataID = m_coordIDs[ timestate ];
        hid_t spaceID = H5Dget_space( dataID );
        hsize_t dims[10];
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        
        float *vals = new float[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, vals );


        //Read in connectivity.
        dataID = m_cellInfo[domain]->id;
        spaceID = H5Dget_space( dataID );
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        if ( dims[1] != 6 )
            EXCEPTION2( UnexpectedValueException, "Expecting a wedge!", "Connectivity" );
        int *conn = new int[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_INT, H5S_ALL, spaceID, H5P_DEFAULT, conn );
        
        int nPts = m_nNodes / m_nPlanes;
        vtkPoints *pts = vtkPoints::New();
        pts->SetNumberOfPoints( nPts );
        
        int nTris = m_cellInfo[domain]->numCells / m_nPlanes;
        
        // Calc point offset based on how many planes deep we are.
        int offset = m * nPts*3;

        memcpy( pts->GetVoidPointer(0), (void*)&vals[offset], nPts*3*sizeof(float) );
        grid->SetPoints( pts );
        delete [] vals;
        
        // Connectivity is the same for all planes, so no offset is needed.
        vtkTriangle *tri = vtkTriangle::New();
        for ( int i = 0; i < nTris; i++ )
        {
            tri->GetPointIds()->SetId( 0, conn[(i*6) + 0] );
            tri->GetPointIds()->SetId( 1, conn[(i*6) + 1] );
            tri->GetPointIds()->SetId( 2, conn[(i*6) + 2] );
            
            grid->InsertNextCell( tri->GetCellType(), tri->GetPointIds() );
        }
        tri->Delete();
        delete [] conn;
        
        return grid;
    }

    // Try the 2D planes.
    for ( int m = 0; m < (int)m_meshesPlane2D.size(); m++ )
    {
        if ( meshname != m_meshesPlane2D[m] )
            continue;

        vtkUnstructuredGrid *grid = vtkUnstructuredGrid::New();
        
        // Read in points.
        hid_t dataID = m_coordIDs[ timestate ];
        hid_t spaceID = H5Dget_space( dataID );
        hsize_t dims[10];
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        
        float *vals = new float[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, vals );

        //Read in connectivity.
        dataID = m_cellInfo[domain]->id;
        spaceID = H5Dget_space( dataID );
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
        if ( dims[1] != 6 )
            EXCEPTION2( UnexpectedValueException, "Expecting a wedge!", "Connectivity" );
        int *conn = new int[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_INT, H5S_ALL, spaceID, H5P_DEFAULT, conn );
        
        int nPts = m_nNodes / m_nPlanes;
        vtkPoints *pts = vtkPoints::New();
        pts->SetNumberOfPoints( nPts );
        
        int nTris = m_cellInfo[domain]->numCells / m_nPlanes;
        
        // Calc point offset based on how many planes deep we are.
        int offset = m * nPts*3;

        // Transform the points to the plane.
        for ( int i = 0; i < nPts; i++ )
        {
            float inPt[4] = {vals[offset +i*3 + 0],vals[offset +i*3 + 1],vals[offset +i*3 + 2], 1.0};
            float outPt[4];
            m_planeXforms[m]->MultiplyPoint( inPt, outPt );
            // Put it in the XY plane. Y *should* be zero.
            vals[offset+i*3 +0] = outPt[0];
            vals[offset+i*3 +1] = outPt[2];
            vals[offset+i*3 +2] = 0.0;
        }
        
        memcpy( pts->GetVoidPointer(0), (void*)&vals[offset], nPts*3*sizeof(float) );
        grid->SetPoints( pts );
        delete [] vals;
        
        // Connectivity is the same for all planes, so no offset is needed.
        vtkTriangle *tri = vtkTriangle::New();
        for ( int i = 0; i < nTris; i++ )
        {
            tri->GetPointIds()->SetId( 0, conn[(i*6) + 0] );
            tri->GetPointIds()->SetId( 1, conn[(i*6) + 1] );
            tri->GetPointIds()->SetId( 2, conn[(i*6) + 2] );
            
            grid->InsertNextCell( tri->GetCellType(), tri->GetPointIds() );
        }
        tri->Delete();
        delete [] conn;
        
        return grid;
    }

    EXCEPTION2( UnexpectedValueException, "the name of a mesh", "" );
    return NULL;
}


// ****************************************************************************
//  Method: avtM3DFileFormat::GetVar
//
//  Purpose:
//      Gets a scalar variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      timestate  The index of the timestate.  If GetNTimesteps returned
//                 'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************

vtkDataArray *
avtM3DFileFormat::GetVar( int timestate, int domain, const char *nm )
{
    char values[512];
    sprintf( values, "/time_node_data[%d]/node_data[%d]/values", timestate, domain );

    string varname = nm;
    for ( size_t i = 0; i < m_scalarVars.size(); i++ )
    {
        if ( varname != m_scalarVars[i]->varName )
            continue;
        
        hid_t dataID = m_scalarVars[i]->dataID;
        H5Dopen( dataID, values );

        hid_t spaceID = H5Dget_space( dataID );
        hsize_t dims[10];
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
                        
        float *vals = new float[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, vals );
        
        vtkFloatArray *var = vtkFloatArray::New();
        int nScalars = dims[0], offset = 0;

        if ( m_scalarVars[i]->planeIdx != -1 )
        {
            nScalars = m_nNodes / m_nPlanes;
            offset = m_scalarVars[i]->planeIdx * nScalars;
        }

        var->SetNumberOfTuples( nScalars );
        float *entry = &vals[offset];

        for ( int j = 0; j < nScalars; j++ )
        {
            var->SetTuple1( j, vals[j] );
            ++entry;
        }
                        
        delete [] vals;
        return var;
    }

    EXCEPTION1( InvalidVariableException, varname );
    return NULL;
}


// ****************************************************************************
//  Method: avtM3DFileFormat::GetVectorVar
//
//  Purpose:
//      Gets a vector variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      timestate  The index of the timestate.  If GetNTimesteps returned
//                 'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
//  Modifications:
//    Dave Pugmire, Fri Aug 8 15:22:11 EDT 2008
//    Memory problem caused by CellInfo, VarInfo not being allocated on the heap.
//
// ****************************************************************************

vtkDataArray *
avtM3DFileFormat::GetVectorVar( int timestate, int domain, const char *nm )
{
    char values[512];
    sprintf( values, "/time_node_data[%d]/node_data[%d]/values", timestate, domain );

    string varname = nm;
    for ( size_t i = 0; i < m_vectorVars.size(); i++ )
    {
        if ( varname != m_vectorVars[i]->varName )
            continue;

        hid_t dataID = m_vectorVars[i]->dataID;
        H5Dopen( dataID, values );

        hid_t spaceID = H5Dget_space( dataID );
        hsize_t dims[10];
        H5Sget_simple_extent_dims( spaceID, dims, NULL );
                        
        float *vals = new float[dims[0]*dims[1]];
        H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, vals );
        
        vtkFloatArray *var = vtkFloatArray::New();
        int nVecs = dims[0], offset = 0;

        if ( m_vectorVars[i]->planeIdx != -1 )
        {
            nVecs = m_nNodes / m_nPlanes;
            offset = m_vectorVars[i]->planeIdx * (nVecs * dims[1]);
        }
        
        var->SetNumberOfComponents( dims[1] );
        var->SetNumberOfTuples( nVecs );
        float *entry = &vals[offset];

        for ( int j = 0; j < nVecs; j++ )
        {
            var->SetTuple3( j, entry[0], entry[1], entry[2] );
            entry += dims[1];
        }
        
        delete [] vals;
        return var;
    }

    EXCEPTION1( InvalidVariableException, varname );
    return NULL;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::NormalizeH5Type
//
//  Purpose:
//     Convert HDF5 types to visit types if necessary.
//
//  Arguments:
//      type       Input type from file.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************
hid_t
avtM3DFileFormat::NormalizeH5Type( hid_t type )
{
    H5T_class_t tclass = H5Tget_class( type );
    size_t size = H5Tget_size( type );

    switch ( tclass )
    {
    case H5T_INTEGER:
        if ( size == 8 )
            return H5T_NATIVE_INT64;
        else if ( size == 4 )
            return H5T_NATIVE_INT32;
        else if ( size == 1 )
            return H5T_NATIVE_CHAR;
        break;
    case H5T_FLOAT:
        if ( size == 8 )
            return H5T_NATIVE_DOUBLE;
        else if ( size == 4 )
            return H5T_NATIVE_FLOAT;
    default:
        break;
    }
    return -1;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::ReadStringAttribute
//
//  Purpose:
//      Read a string attribute from an HDF5 file.
//
//  Arguments:
//      parentID   The id of the parent.
//      attr       Name of the attribute to be read.
//      value      The attribute value that was read.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************
bool
avtM3DFileFormat::ReadStringAttribute( hid_t parentID, const char *attr, string *value )
{
    hid_t attrID = H5Aopen_name( parentID, attr );
    if ( attrID <= 0 )
        return false;

    hid_t typeID = H5Aget_type( attrID );
    if ( typeID < 0 )
        return false;
    hsize_t nelem = H5Tget_size( typeID );
    if ( nelem <= 0 )
        return false;
    char *str = new char[nelem];
    H5Aread( attrID, typeID, str );
    *value = str;
    delete [] str;

    H5Tclose( typeID );
    H5Aclose( attrID );
    return true;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::ReadAttribute
//
//  Purpose:
//      Read an attribute from an HDF5 file.
//
//  Arguments:
//      parentID   The id of the parent.
//      attr       Name of the attribute to be read.
//      value      The attribute value that was read.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************
bool
avtM3DFileFormat::ReadAttribute( hid_t parentID, const char *attr, void *value )
{
    hid_t attrID = H5Aopen_name( parentID, attr );
    if ( attrID <= 0 )
        return false;

    hid_t attrType = H5Aget_type( attrID );
    if ( attrType < 0 )
        return false;

    hid_t spaceID = H5Aget_space( attrID );
    if ( spaceID < 0 )
        return false;

    hsize_t nelem = H5Sget_simple_extent_npoints( spaceID );
    if ( nelem <= 0 ) ///TODO: changed to <= from < 0 if 0 elements are possible this would be incorrect
        return false;

    hid_t typeID = NormalizeH5Type( attrType );
    if ( typeID < 0 )
        return false;

    if ( H5Aread ( attrID, typeID, value ) < 0 )
        return false;

    H5Sclose( spaceID );
    H5Tclose( attrType );
    H5Aclose( attrID );

    return true;
}


// ****************************************************************************
//  Method: avtM3DFileFormat::LoadFile
//
//  Purpose:
//      Open an m3d file and read in it's structure.
//
//  Arguments:
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
//  Modifications:
//    Dave Pugmire, Fri Aug 8 15:22:11 EDT 2008
//    Memory problem caused by CellInfo, VarInfo not being allocated on the heap.
//
//    Dave Pugmire, Tue Jul 21 16:23:42 EDT 2009
//    Turn off 2D and 3D planes for now.
//
//    Jeremy Meredith, Thu Jan  7 15:36:19 EST 2010
//    Close all open ids when returning an exception.  Added error detection.
//
// ****************************************************************************
void
avtM3DFileFormat::LoadFile()
{
    debug1 << "Attempting to open M3D file " << m_filename << endl;
    // Init HDF5.
    H5open();
    H5Eset_auto( NULL, NULL );

    m_fileID = H5Fopen( m_filename.c_str(), 0, 0 );
    if ( m_fileID < 0 )
        EXCEPTION1( InvalidFilesException, m_filename.c_str() );

    hid_t rootID = H5Gopen( m_fileID, "/" );
    if ( rootID < 0 )
    {
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "Root Group", "NOT FOUND" );
    }

    // Read XPClass name and check for validity.
    string xpClass;
    if ( ! ReadStringAttribute( rootID, "XP_CLASS", &xpClass ) || xpClass != m_XPClassStr)
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "XP_CLASS", "Not found or wrong type" );
    }

    // Read in step and time information.
    int numTimeSteps;
    if ( ! ReadAttribute( rootID, "nsteps", &numTimeSteps ) )
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "nsteps", "Not found or wrong type" );                
    }

    float *times = new float[numTimeSteps];
    if ( ! ReadAttribute( rootID, "time", times ) )
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "time", "Not found or wrong type" );                
    }
    for ( int i = 0; i < numTimeSteps; i++ )
        m_timeSteps.push_back( times[i] );
    delete [] times;

    //Read in planes information.
    hid_t groupid = H5Gopen( m_fileID, "/planes" );
    hid_t dataid = H5Dopen( groupid, "values" );
    hid_t dataspace = H5Dget_space( dataid );
    H5Sselect_all( dataspace );
    int planes[2];
    H5Dread( dataid, H5T_NATIVE_INT_g, dataspace, dataspace, H5P_DEFAULT, planes );
    m_nPlanes = planes[0];
    m_nPlanesPerProc = planes[1];
    H5Sclose( dataspace );
    H5Dclose( dataid );
    H5Gclose( groupid );

    if ( ! ReadAttribute( rootID, "nnodes", &m_nNodes ) )
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "nnodes", "Not found or wrong type" );                
    }
    
    if ( ! ReadAttribute( rootID, "ncell_sets", &m_nCellSets ) )
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "ncell_sets", "Not found or wrong type" );                
    }
    
    if ( ! ReadAttribute( rootID, "nnode_data", &m_nVars ) )
    {
        H5Gclose(rootID);
        H5Fclose(m_fileID);
        EXCEPTION2( UnexpectedValueException, "nnode_data", "Not found or wrong type" );                
    }
    
    //Load basic info on variables.
    for ( size_t t = 0; t < m_timeSteps.size(); t++ )
    {
        for ( int n = 0; n < m_nVars; n++ )
        {
            char field[512], values[512];
            sprintf( field, "/time_node_data[%ld]/node_data[%d]", t, n );
            sprintf( values, "%s/values", field );
            
            string labelStr;
            hid_t labelID = H5Gopen( m_fileID, field );
            if ( ! ReadStringAttribute( labelID, "labels", &labelStr ) )
                EXCEPTION2( UnexpectedValueException, "Variable Label", "Not found or wrong type" );
            
            string varName;
            varName.assign( labelStr.c_str(), labelStr.find("," ) );
            hid_t dataID = H5Dopen( m_fileID, values );
            //hid_t clss = H5Tget_class( H5Dget_type( dataID ) );
            hid_t spaceID = H5Dget_space( dataID );
            int numDims = H5Sget_simple_extent_ndims( spaceID );
            hsize_t dimSize[10];
            H5Sget_simple_extent_dims( spaceID, dimSize, NULL );
            
            int varDim = dimSize[numDims-1];
            debug1 << "Variable: " << varName << " of dimension " << varDim << endl;
            
            if ( varDim == 1 )
                m_scalarVarNames.push_back( new VarInfo( varName, dataID, varDim ) );
            else if ( varDim == 2 || varDim == 3 )
                m_vectorVarNames.push_back( new VarInfo( varName, dataID, varDim ) );
            else 
                m_tensorVarNames.push_back( new VarInfo( varName, dataID, varDim ) );
        }
    }
    
    //Load mesh info.
    for ( int n = 0; n < m_nCellSets; n++ )
    {
        char field[512];
        sprintf( field, "/cell_set[%d]/node_connect_list", n );
        hid_t connectID = H5Dopen( m_fileID, field );
        
        sprintf( field ,"/cell_set[%d]", n );
        hid_t groupid = H5Gopen( m_fileID, field );
        int nCells;
        ReadAttribute( groupid, "ncells", &nCells );
        
        m_cellInfo.push_back( new CellInfo( connectID, nCells ) );
    }
    
    for ( size_t t = 0; t < m_timeSteps.size(); t++ )
    {
        char values[512];
        sprintf( values, "/time_coordinates[%ld]/coordinates/values", t );
        hid_t coordID = H5Dopen( m_fileID, values );
        m_coordIDs.push_back( coordID );
    }

    // Finally, calculate the angular spacing of each plane.
    if (doPlanes)
        CalcPlaneAngularSpacing();

    debug1 << "SUCCESS in opening M3D file " << m_filename << endl;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::GetPlaneName
//
//  Purpose:
//      Open an m3d file and read in it's structure.
//
//  Arguments:
//  prefix:      Name prefix
//  p:           Which plane
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************
string
avtM3DFileFormat::GetPlaneName( const string &prefix, int p )
{
    char str[512];
    if ( m_planeAngleClash )
    {
        sprintf( str, "%s/plane_%g", prefix.c_str(), m_planeAngles[p] );
    }
    else
    {
        int ang = (int)(m_planeAngles[p]+0.5);
        sprintf( str, "%s/plane_%03d", prefix.c_str(), ang );
    }
    
    string name = str;
    return name;
}

static void MakeVec( float *p1, float *p2, float *v )
{
    v[0] = p1[0]-p2[0];
    v[1] = p1[1]-p2[1];
    v[2] = p1[2]-p2[2];
}

static float Determinant( float *D )
{
    return D[0] * D[3] - D[1]*D[2];
}

static void PlanePlaneIntersect( float *p0, float *p1, float *pt, float *axis )
{
    pt[0] = pt[1] = pt[2] = 0.0f;

    vtkMath::Cross( p0, p1, axis ); // Line dir is perp to both planes.
    vtkMath::Normalize( axis );
    //This *should* be the z axis.
    if ( fabs( fabs(axis[2]) -1 ) < 1e-7 )
        EXCEPTION2( UnexpectedValueException, "Expected a z Dir central axis.", "Center axis" );

    // We now have a 2x2 system, where {A,B,C,D} is the plane equation, and Z = 0.
    // A1 X + B1 Y = -D1
    // A2 X + B2 Y = -D2
    float D[4] = {p0[0],p0[1], p1[0], p1[1] };
    float Dx[4] = {-p0[3], p0[1], -p1[3],p1[1] };
    float Dy[4] = {p0[0], -p0[3], p1[0],-p1[3] };
    float DetD = Determinant( D );
    if ( fabs( DetD ) < 1e-10 )
        EXCEPTION2( UnexpectedValueException, "Plane-Plane Intersection", "Solution failed." );
    pt[0] = Determinant( Dx ) / DetD;
    pt[1] = Determinant( Dy ) / DetD;
    pt[2] = 0.0f;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::CalcPlaneAngularSpacing()
//
//  Purpose:
//      Calculate the angular spacing between the planes.
//
//  Arguments:
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Tue Sep 25 08:49:28 PDT 2007
//
// ****************************************************************************
void
avtM3DFileFormat::CalcPlaneAngularSpacing()
{
    // Find the normal of the first plane.
    hid_t dataID = m_coordIDs[0];
    hid_t spaceID = H5Dget_space( dataID );

    hsize_t dims[10];
    H5Sget_simple_extent_dims( spaceID, dims, NULL );

    float *vals = new float[dims[0]*dims[1]];
    H5Dread( dataID, H5T_NATIVE_FLOAT, H5S_ALL, spaceID, H5P_DEFAULT, vals );

    vector<float *> planes;
    int nPts = m_nNodes / m_nPlanes;

    // Calculate the plane equation of each plane.
    for ( int i = 0; i < m_nPlanes; i++ )
    {
        int offset = i * nPts*3;
        //Get three points to calculate a plane.
        float pts[3][3];
        for ( int j = 0; j < 3; j++ )
            for ( int k = 0; k < 3; k++ )
                pts[j][k] = vals[offset + (j*3 + k)];
    
        float vec1[3], vec2[3], *plane = new float[4];
        
        MakeVec( pts[0], pts[1], vec1 );
        MakeVec( pts[0], pts[2], vec2 );
        vtkMath::Normalize( vec1 );
        vtkMath::Normalize( vec2 );
        vtkMath::Cross( vec1, vec2, plane );
        vtkMath::Normalize( plane );

        // D in plane equation.
        plane[3] = pts[0][0]*plane[0] + pts[0][1]*plane[1] + pts[0][2]*plane[2];
        planes.push_back( plane );
    }

    // Now, determine the rotation axis for these planes.
    float *p0 = planes[0], *p1 = planes[1], pt[3], axis[3];
    PlanePlaneIntersect( p0, p1, pt, axis );

    // Plane 0 is 0 degrees, Identity xform.
    float n0[3] = {planes[0][0],planes[0][1],planes[0][2] };
    m_planeAngles.push_back( 0.0 );
    m_planeXforms.push_back( vtkTransform::New() );

    // For the rest, calc the angle between the first plane.
    for ( int i = 1; i < m_nPlanes; i++ )
    {
        float *plane = planes[i];
        float n[3] = {plane[0],plane[1],plane[2]};
        float ang = vtkMath::DegreesFromRadians(vtkMath::Dot(n0, n));

        m_planeAngles.push_back( ang );

        //Calc the rotation matrix to move the plane into 2D.
        vtkTransform *xform = vtkTransform::New();
        xform->Translate( -pt[0], -pt[1], -pt[2] );
        xform->RotateWXYZ( -ang, axis[0], axis[1], axis[2] );
        xform->Translate( pt[0], pt[1], pt[2] );
        m_planeXforms.push_back( xform );
    }

    //See if we clash if we cast these to ints....
    m_planeAngleClash = false;
    for ( size_t i = 1; i < m_planeAngles.size(); i++ )
    {
        int a0 = int(m_planeAngles[i-1] + 0.5);
        int a1 = int(m_planeAngles[i] + 0.5);
        if ( a0 == a1 )
        {
            m_planeAngleClash = true;
            break;
        }
    }

    for ( size_t i = 0; i < planes.size(); i++ )
        delete planes[i];

    delete [] vals;
}

// ****************************************************************************
//  Method: avtM3DFileFormat::GetTimes()
//
//  Purpose:
//      Return the simulation time for each timestep.
//
//  Arguments:
//
//  Programmer: David Camp
//  Creation:   Thu Jan 28 09:13:21 PST 2016
//
// ****************************************************************************
void
avtM3DFileFormat::GetTimes(vector<double> &outTimes)
{
    outTimes = m_timeSteps;
}

