![]() |
Visualizing Scalar Fields ElVis is a visualization system created for the accurate and interactive visualization of scalar fields produced by high-order spectral/hp finite element simulations. Previous Versions |
| QT_QMAKE_EXECUTABLE | On many Linux systems, and if QTDIR is set, Qt will automatically be found by CMake. If it is not, set this variable to the full path to the qmake executable. |
| OptiX_INSTALL_DIR | The root path to your OptiX installation. Once this variable is set, running configure again will automatically set the corresponding OptiX variables (OptiX_INCLUDE, optix_LIBRARY, and optixu_LIBRARY). |
| CUDA_SAMPLE_DIR | Within the CUDA root installation directory there is a samples directory. |
| CUDA_TOOLKIT_ROOT_DIR | This is the directory that houses your nvcc executable. |
| GL_LIBRARY | The second path in this variable (separated by ;) should be equal to the path in the OPENGL_glu_LIBRARY variable. If it instead contains "NOT-FOUND", then set it to the OPENGL_glu_LIBRARY path. |

| ELVIS_ENABLE_DATA_CONVERTER | Enables the ElVis Data Conversion module, which converts existing high-order data into the Nektar++ data format. Requires Nektar++ 3.1 or greater. This module is only required to convert models to the Nektar++ format, and does not need to be built if runtime extensions are used. |
| ELVIS_ENABLE_FUNCTION_PROJECTION_MODULE | A reference implementation of a data conversion extension. This module projects a function onto the Nektar++ modified basis. |
| ELVIS_ENABLE_JACOBI_EXTENSION | Enables a reference extension which uses Jacobi polynomials as the basis function for each element type. |
| ELVIS_ENABLE_NEKTAR++_EXTENSION | Enables the Nektar++ extension. Requires Nektar++ 3.1 or greater. |
| ELVIS_ENABLE_NEKTAR_EXTENSION | Not yet implemented. |
| ELVIS_ENABLE_PRINTF | The presence of print statments in device code, even when they are not executed, increases compile and run time of the OptiX and Cuda kernels. This option allows all ELVIS_PRINTF statements to be turned off globally. |
| ELVIS_ENABLE_ProjectX_EXTENSION | Reference implementation based on ProjectX. This extension requires the ELVIS_USE_DOUBLE_PRECISION flag be set. |
| ELVIS_USE_DOUBLE_PRECISION | Selects whether to use float or double for floating point computations. |






| Click and Drag Left Mouse Button | Rotate around current look at point |
| Click and Drag Middle Mouse Button | Pan |
| Click and Drag Right Mouse Button | Zoom in and out |
| Mouse Wheel | Zoom in and out |


ElVis can be extended with two different types of extensions. In the following sections, we describe each type of extension and discuss their pros and cons.
A conversion extension is used to convert a finite element volume to the Nektar++ format used internally by ElVis. This type of extension is most appropriate for volumes that have the following properties:
All elements are represented by hexahedra, tetrahedra, prisms, or square-based pyramids.
The field is represented by a polynomial.
If the volume contains elements of different types than those listed above, a conversion exetension may still be created, but the elements must be converted into one of the four element types indicated above. If the field is not represented by a polynomial, then the converted volume will contain error. In this case, we recommend that a runtime extension be used.
The model conversion extension's relys on the Nektar++ extension for all runtime processing. Since the Nektar++ extension only currently supports hexahedra, model conversion extensions only support hexahedra as well. Additional functionality is currently being added to the Nektar++ extensions to support the remaining element types.
Runtime extensions give ElVis the ability to access a simulation's native data, rather than first converting it to the Nektar++ format. It does this through a collection of high-level OptiX and Cuda functions that must be implemented in your extension.
Runtime extensions are implemented as shared libraries that are loaded by ElVis at runtime. Each runtime extension consists of three components, described in further detail below.
The OptiX and Cuda interfaces are both written in Cuda. Cuda does not support linking of pre-compiled object files, so ElVis uses a different methods to provide customized behavior for each extension.
In this section, we will create a fully-functional runtime extension to illustrate the necessary steps and code. This section, we assume that there is an ElVis source distribution. We will refer to the location of the source distribution with the variable $ELVIS. Therefore, the source files are located at $ELVIS/src, the build is at $ELVIS/Build, and the ElVis distribution is at $ELVIS/build/dist. To simplilfy the initial extension development steps, we have included a runtime extension template at $ELVIS/src/ElVis/Extensions/RuntimeExtensionTemplate. This template will enable the creation of a simple runtime extension that can be loaded into ElVis (although it will have no functionality). The code that is located in this directory is not able to be compiled directly; rather, CMake must first be used to generate the runtime extension code, customized with your extension's name.
These steps generate the source code for the new runtime extension, which is located at $BUILD_BASE/$RUNTIME_EXTENSION_NAME (which we will refer to as $EXTENSION_BASE). The folders $BUILD_BASE/Build and $BUILD_BASE/CMakeFiles are not necessary and can be deleted, as well as the files $BUILD_BASE/CMakeCache.txt, $BUILD_BASE/cmake_install.cmake, and $BUILD_BASE/Makefile.
The C++ interface is responsible for loading high-order data from disk and tranferring it to the GPU for visualization. It is also responsible for handling user interaction and populating the ElVis gui. This is primarily accomplished through concrete subclasses of ElVis::Model. Each extension is required to have a model-specific version of this class available.
When an extension is initially loaded, ElVis looks for the following three methods, which must exist at the global namespace, have C linkage, and be visible outside the shared library. On Linux/OS X, these methods will be visible by default. On Windows, however, they are not, and must be explicitly exported (see ElVis/Extensions/JacobiExtension/Declspec.h for an example). These methods provide the initial interface to the extension.
const char* GetPluginName();
Each plugin must provide a name that is used in the ElVis gui to differentiate it from the other loaded plugins. There are no restrictions on the name.
const char* GetVolumeFileFilter();
Returns a file name filter for use by the Qt QFileDialog class to open volume. The format of the string is " (*.)". For example, "Jacobi Volumes (*.dat)".
ElVis::Model* LoadModel(const char* path);
Loads the model from the given location and returns a pointer to a new Model object representing it. ElVis will take control of this pointer and handle cleanup.
The runtime extension template provides default implementations of LoadModel and GetPluginName that are unlikely to need modification. The implementation of GetVolumeFileFilter, however, will need to be modified to correspond to the extension used by the model.
std::string GetVolumeFileFilter()
{
std::string result("SampleRuntimeExtension Models (*.exd)");
return result;
}The next step is to fill in the implementation of the model class (located in $EXTENSION_BASE/Model.h and $EXTENSION_BASE/Model.cpp). For this sample extension, we will build a model that can visualize a simple high-order system that consists of axis-aligned hexahedra.
The file format we will use is a binary format with the following structure:
sizeof(int) : Number of Vertices
sizeof(double) * 3 * Number of Vertices : Vertex Array
sizeof(int) : Number of faces
sizeof(int) * 4 * Number of faces : For each face, an index into the vertex array for each of the face's vertices
sizeof(int) : Number of hexahedra
sizeof(int) * Number of hexahedra : For each hex, an index into the face array for each of the hexahedra's faces.
sizeof(int) : Number of coefficients
sizeof(double) * Number of coefficients : Coefficient array.
sizeof(int) * 3 * Number of hexahedra : Modes
In this example, there is a single scalar field associated with the volume. This example uses a modal basis. This is not a restriction imposed by ElVis and is simply the example used. Volumes using a nodal basis can be used as well.
Loading a volume from disk is performd in the LoadModel method. The default behavior of this method is to call the SampleRuntimeExtensionModel constructor that takes a single parameter (the location of the volume on disk). So the first step is to populate this constructor with the appropriate code to perform this task.
SampleRuntimeExtensionModel::SampleRuntimeExtensionModel(const std::string& path) :
Model(),
m_numVertices(0),
m_vertices(0),
m_numFaces(0),
m_faces(0),
m_numHexes(0),
m_hexes(0),
m_numberCoeffs(0),
m_coefficients(0),
m_coefficientMappings(0),
m_modes(0)
{
// Load the model from disk, and populate all of the internal data. The constructor
// is not responsible for populating any GPU constructs. That will be handled by
// some of the class' virtual functions.
FILE* inFile = fopen(path.c_str(), "rb");
if( !inFile )
{
// ElVis should never pass an invalid file name to the constructor, but, if it
// does, ElVis will handle the exception and abort the model load procedure.
throw std::runtime_error(std::string("File ") + path + " does not exist.");
}
// Read all vertices into a vertex array.
fread(&m_numVertices, sizeof(int), 1, inFile);
m_vertices = new double[m_numVertices*3];
fread(m_vertices, sizeof(double)*3, m_numVertices, inFile);
// Faces are represented by the verteices that make up the face.
// Each face is therefore represented by the indices into the
// vertex array.
fread(&m_numFaces, sizeof(int), 1, inFile);
m_faces = new Face[m_numFaces];
for(int i = 0; i < m_numFaces; ++i)
{
fread(&(m_faces[i].VertexIndex[0]), sizeof(int), 4, inFile);
}
// Each hex is represented by the 6 faces.
fread(&m_numHexes, sizeof(int), 1, inFile);
m_hexes = new Hex[m_numHexes];
for(int i = 0; i < m_numHexes; ++i)
{
fread(&(m_hexes[i].FaceIndex[0]), sizeof(int), 6, inFile);
}
// Coefficients are stored in a global coefficient array, rather than with each
// element. The GPU implementation often works better this way.
fread(&m_numberCoeffs, sizeof(int), 1, inFile);
m_coefficients = new double[m_numberCoeffs];
fread(m_coefficients, sizeof(double), m_numberCoeffs, inFile);
// Since each element can have a different number of coefficients,
// we use this array to indicate where in the coefficient buffer each
// element's coefficients begin.
m_coefficientMappings = new int[m_numHexes];
fread(m_coefficientMappings, sizeof(int), m_numHexes, inFile);
// Each element can have a different number of modes. We store the
// modes in this array, indexed by element number.
m_modes = new int[m_numHexes*3];
fread(m_modes, sizeof(int)*3, m_numHexes, inFile);
fclose(inFile);
}
Several of the Model class' virtual functions are reporting functions that inform ElVis about the model.
DoGetNumFields int SampleRuntimeExtensionModel::DoGetNumFields() const
{
return 1;
}
DoGetFieldInfo FieldInfo SampleRuntimeExtensionModel::DoGetFieldInfo(unsigned int index) const
{
if( index > 0 )
{
throw std::runtime_error("Invalid field index.");
}
FieldInfo result;
result.Name = "Density";
result.Id = 0;
result.Shortcut = "Ctrl+D";
return result;
}
DoGetNumberOfElementsunsigned int SampleRuntimeExtensionModel::DoGetNumberOfElements() const
{
return m_numHexes;
}
DoCalculateExtents. void SampleRuntimeExtensionModel::DoCalculateExtents(WorldPoint& min, WorldPoint& max)
{
min = WorldPoint(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
max = WorldPoint(-std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max());
// Calculating extents this way is only valid for linear geometry.
for(int i = 0; i < m_numVertices; ++i)
{
WorldPoint p(m_vertices[i*3], m_vertices[i*3+1], m_vertices[i*3+2]);
min = CalcMin(p, min);
max = CalcMax(p, max);
}
}
For some volumes, the behavior of the field around some surface is of interest, and the surface coincides with element faces. This often occurs at the boundary between the elements and some geometry of interest. For 3D volumes, ElVis supports the collection of element faces into groups called Boundary Surfaces. Boundary surfaces can be rendered using color maps and contours.
DoGetNumberOfBoundarySurfaces.int SampleRuntimeExtensionModel::DoGetNumberOfBoundarySurfaces() const
{
return 1;
}
DoGetBoundarySurface. The OptiX interface can be found in ElVis/Core/ElVisOptiX.cu. Each of the methods in the interface section must be implemented by the extension in a filed called ExtensionOptiXInterface.cu.
The Cuda interface can be found in ElVis/Core/ElVisCuda.cu. Each of the methods in the interface section must be implemented by the extension in a filed called ExtensionCudaInterface.cu.