HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Storing and Manipulating Physical Fields

Table Of Contents

Introduction

Systems in continuum mechanics, for example fluids and gas systems, can be described in terms of physical fields. A field assigns a physical quantity to each point in space, such as a scalar, a vector, or a second-order tensor. In theory, these fields are defined on all points of a continuous space, and the physical quantity varies smoothly on this space. An important example of a vector field (represented by SIM_VectorField) is the velocity field, which assigns a velocity to each point in a fluid at some given time.

The SIM_*Field classes represent discretized fields; physical quantities are stored only for a finite number of sample points. Each sample point belongs to a voxel in a voxel array. Much of the physics code pretends that the discretized field is a continuous field; it asks for quantities at positions that do not coincide with any sample point. The SIM_*Field classes provide interpolation methods for this purpose. The accuracy of these interpolated methods depends on the resolution of the grid and the smoothness properties of the original physical field.

There are different options for choosing the positions of the sample point relative to each voxel (see the SIM_FieldSample enum in SIM_RawField.h). For example, when using SIM_RawField to represent a MAC grid, which is commonly used in fluids, you will want to specify that the sample points must lie on the centers of voxel faces instead of the voxel centers.

Common Interface Elements

The SIM_*Field classes share lots of common interface. This section explains how you perform basic operations, using SIM_ScalarField as an example. If you're considering to write your own gas solver, then you want to use the GAS_SubSolver interface, which simplifies some of the work involved in creating and manipulating SIM_*Field objects. Consult the section Building DOP Microsolvers from GAS_SubSolver for more details.

A new field is created and attached to an object like this:

SIM_Object* object;
const char* field_name;
...
SIM_ScalarField* field =
SIM_DATA_CREATE(*object, field_name, SIM_ScalarField, 0);

At some other point in the code, same field may be retrieved from an object:

SIM_ScalarField* field =
SIM_DATA_GET(*object, field_name, SIM_ScalarField, 0);

When the field doesn't need to be modified, then SIM_DATA_GETCONST should be used instead of SIM_DATA_GET.

The methods setDivisions, setSize, and setCenter can be called to initialize the dimensions and the resolution of the discrete field. Alternatively, you may want to align your new field to an existing field using the matchField method.

Each of SIM_ScalarField, SIM_VectorField, and SIM_MatrixField consists of one or more SIM_RawField objects. In turn, SIM_RawField holds a UT_VoxelArray. Initializing and modifying the values in the field is done through these SIM_RawField objects.

The following code shows how elements of a scalar field may be initialized using elements from another scalar field that is aligned with it. It simply makes the destination field contain the squared values of the source field:

SIM_RawField* destination;
...
UT_VoxelArrayIteratorF vit;
UT_VoxelWOProbeF w; // WO = write only
w.setArray(destination->getField()->field(), 0, 0);
r.setArray(source->getField()->field(), 0, 0);
fpreal value_r = 0;
vit.setArray(destination->getField()->field());
vit.setCompressOnExit(true);
vit.setPartialRange(info.job(), info.numJobs());
for (vit.rewind(); !vit.atEnd(); vit.advance())
{
if (w.setIndex(vit))
{
w.setIndex(vit);
r.setIndex(vit);
}
else
{
w.advanceX();
r.advanceX();
}
// At this voxel, make w the squared value of r
value_r = r.getValue();
w.setValue(value_r * value_r);
}
destination->pubHandleModification();

Getting values from a scalar field in a random-access fashion is trivial: to get an interpolated value of the field at a given position of type UT_Vector3, you can simply call getValue(position).

Different Flavors

SIM_ScalarField stores a single scalar for each grid point. It is used in fluid and gas simulations to represent divergence fields (which describe the net rate of flow through each voxel's boundary). Beside its value, you can ask a SIM_ScalarField for its partial derivatives at a position using the getGradient method.

SIM_VectorField stores a 3-dimensional vector for each grid point. SIM_VectorField is used to represent the velocity of points in a fluid or gas. This class contains three SIM_RawField objects; the getField(i) will give you the SIM_RawField for each axis i = 0, 1, 2. A SIM_VectorField that represents a velocity field can be used to advect another field for a given time step. Each of SIM_ScalarField, SIM_VectorField, and SIM_MatrixField has an advect method for this purpose.

SIM_MatrixField stores a 3x3 matrix for each grid point. They can be used to represent second-rank tensors, such as strain tensors. SIM_MatrixField has a separate SIM_RawField for each matrix location, which can be accessed by calling getField(i, j), where i and j may be 0, 1, or 2.

SIM_IndexField is a somewhat nonphysical version of a field; it stores integers. Instead of a SIM_RawField it holds a SIM_RawIndexField. SIM_IndexField can be used to assign indices to voxels in another field.

Examples

The gas sub solver implementation SIM/SIM_GasAdd.C is an example that works with SIM_ScalarField, SIM_VectorField, and SIM_MatrixField. It shows how to add each of these field types together in a pointwise fashion.