Chapter 8. Customized Volume Drawing

Drawing a volume can be as simple as calling voGeometryActions::draw(). This method renders polygons, called voIndexedFaceSets, each of which is a slice of a volume. The slices are often planar, but they also can be arbitrary surfaces. Each of these slices, clipped to volume and brick boundaries, can then be drawn.

Applications that choose to use unconventional array types or provide user-defined per-vertex data have to implement their own draw method.

This chapter provides a procedure for implementing a custom draw method in the following sections:

Customized Volume Drawing Procedure

Use the following procedure to create a custom draw method. For each frame, your draw method must perform the following tasks.

  1. Polygonize a volume using voGeometryActions::sample().

    This method slices a volume's geometry with a plane to create a set of slices, as shown in Figure 8-1.

    Figure 8-1. Polygonization of a Single Tetrahedron.

    Figure 8-1 Polygonization of a Single Tetrahedron.

    sample() returns the vertices of all the polygon slices. The orientation of the planes can be specified using one of the following flags:

    • VIEWPORT_ALIGNED

    • AXIS_ALIGNED

    • SPHERICAL

    voSamplingPlaneSet describes the set of planes and can be used to iterate through them to draw the volume.

    If you want the sampling surfaces not to be planar, you can implement your own, application-specific voSamplingSurfaceSet. For more information, see “General Clipping”.

  2. Once the sampling surfaces are clipped to volume's geometry, the resulting polygons need to be further clipped to brick boundaries to facilitate state sorting. You clip polygons to brick boundaries using voGeometryActions::clip().

    For more information about this method, see “clip()”.

  3. Sort the bricks from back to front using voGeometryActions::voSortAction.

    Given a brick set, voSortAction produces a sorted array of indices that can be accessed in sorted order. For example, the following code will visit each brick in visibility sorted order:

    voSortAction aSortAction(aVolume->getCurrentBrickSet(), modelMatrix, projMatrix);
    
       for(brickNo=0;brickNo<BrickCount;brickNo++) {
         int brickSortedNo = aSortAction[brickNo];
        voBrick *aBrick =     
        aVolume->currentBrickSet->getBrick(brickSortedNo);
         ...
       }
    

  4. Process bricks individually in a visibility-sorted order. Load the current brick of texture using voAppearanceActions::textureLoad() or voAppearanceActions::textureBind().

  5. Draw all the polygons with texture using the vertices returned from voGeometryActions::clip().

  6. Repeat steps four and five with each successive brick until the volume is fully rendered.

    Some applications, may want to compute the area of a voIndexedFaceSet after it was projected onto the screen. This may be required for the sake of fill rate computation, for example. If you need to determine the area of a face in the set, use voGeometryActions:: findProjectedArea(). Please, note, that this is a rather heavy weight routine and should be used only sparingly.

clip()

voGeometryActions::cpolygonize() calls voGeometryActions::clip(), defined as follows, to clip a single polygon computed by voSampleAction() to brick boundaries, specified by its lower-left and upper-right vertices:

static int clip (voVertexData* vertexData,
    voInterleavedArrayType interleavedArrayFormat,
    float lowerLeft[3], float upperRight[3], voIndexedFaceSet* cSet);

The arguments specify the coordinates of the polygon's vertices and possibly other per-vertex attributes. The input format is described by voVertexData in “Drawing Brick Set Collections”. The clipped polygon ends up as a voIndexedFaceSet, which then can be drawn using voGeometryActions::draw().

General Clipping

An overloaded version of voAppearanceActions::clip() clips a polygon to a convex polyhedron determined by set of positive half-spaces:

static int clip(voVertexData* vertexData, voInterleavedArrayType
interleavedArrayFormat,float* planes,int planeCount, voIndexedFaceSet* cSet);

The convex polyhedron is specified by a set of positive half planes given by their equations {A, B, C, D]. The input array specifies the coordinates of the polygon's vertices and possibly other per-vertex attributes. The input format is described by voVertexData. The clipped polygon ends up as a voIndexedFaceSet.

This action allows applications to implement alternative ways of sampling and clipping. For example, applications may want to implement a box volumetric primitive by directly sampling a cube without tessellating it into tetrahedra. In this case, the default instance of voAppearanceActions::sample() can be replaced with a call to clip().

Similarly, for primitives that are convex polyhedra (for example, tetrahedra) it is possible to collapse sample() and clip() into a single routine. After all, “slicing” a tetrahedron with a plane followed by clipping the resulting polygon to a box, is equivalent to clipping the plane to the convex region determined by the ten positive half-planes defining the region of intersection between the tetrahedron and the box.

Other Applications

clip() is a general purpose utility and may be used by applications to accomplish general-purpose clipping to a clip box. (For more information about clip boxes, see “Clip Boxes”.)

For example, it is common in many diagnostic applications to display an arbitrarily oriented plane through the data set that may not necessarily coincide with the original acquisition direction (this task is referred to as Multi-Planar Reformation (MPR). For more information about MPR, see “Polygonizing Arbitrarily Oriented Cross Sections”.

Similarly, in seismic data interpretation it is desired to visualize data along an arbitrarily shaped surface (e.g., geological horizon). In both of these situations, the planes/polygons need to be clipped to the brick boundaries so that the resulting polygons can be texture mapped correctly.

Consider a situation where an MPR along a plane described by [A, B, C, D] is required. One can, apply the sample() in order to clip the plane to each individual tetrahedron and feed the results into clip() to clip them to each brick.

float planeEquation[4] = { A, B, C, D,};
float tetraVerts[4][3] = { ... };  // vertices of the tetrahedron
float brickLowerLeft[3]  = { 0.5, 0.5, 0.5 };
float brickUpperRight[3] = { 254.5, 254.5, 254.5 };
voVertexData vData(4,valuesPerVertex); // intermediate polygon verts

sample(tetraVerts,planeEquation,&polygonVerts);
clip(&polygonVerts,brickLowerLeft, brickUpperRight, aFaceSet);

Given a brick, one should clip to the clip box rather than the brick boundary to avoid seams:

voBrick *aBrick; 

aBrick->getClipBrickSizes(brickLowerLeft, brickUpperRight);


Note: For applications that deal exclusively with canonical volumes, where the geometry coincides with appearance, it is not necessary to clip to tetrahedra boundaries. For example, the polygons that comprise the geological horizon can be fed directly to clip().


Drawing Brick Set Collections

In a voBrickSetCollection, you want to draw the copy of the volume that has the least sampling artifacts.

voGeometryActions::findClosestAxisIndex() identifies the orientation (XY, XZ, or YZ) that minimizes sampling artifacts under some sampling modes. The result is generally used in conjunction with the voBrickSetCollection to select the most suitable copy of a volume for the current view:

voGeometryActions::findClosestAxisIndex(), defined as follows, returns a voPlaneOrientation.

static voPlaneOrientation findClosestAxisIndex(
    GLdouble modelMatrix[16], GLdouble projMatrix[16], 
    int samplingMode);

modelMatrix[] and projMatrix[] are the ModelView and Projection matrices that can be easily obtained from OpenGL state:

glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);

samplingMode can have several values:

  • voSamplingModeScope::VIEWPORT_ALIGNED, in which the planes are parallel to the viewport, that is, they change with every view.

  • voSamplingModeScope::AXIS_ALIGNED, in which the planes are aligned with the axis that is most parallel to the line of sight.

The return value is one of the following:

enum voPlaneOrientation{ 
    UNSPECIFIED,
    XY,
    XZ,
    YZ,  
};

The value is the axis that is most aligned parallel either to the line-of-sight vector or the viewport, depending on samplingMode. For proper 3D volumes (i.e., none of the brick dimensions is 1) the value of UNSPECIFIED is returned.

Example Use of findClosestAxisIndex()

Example 8-0 shows how to use findClosestAxisIndex().

Example 8-1. findClosestAxisIndex() Example


if( aVolume->interpolationType == voInterpolationTypeScope::_2D)
    aVolume->setCurrentBrickSet(
        findClosestAxisIndex(
            modelMatrix,projMatrix,
            voSamplingModeScope::AXIS_ALIGNED) );

draw()
// voGeometryActions::draw() can be applied to a voFaceSet to render it into the // frame buffer:  

void
draw(
    voIndexedFaceSet *aFaceSet,
    GLenum mode,  // GL_LINE or GL_FILL
    voInterleavedArrayType interleavedArrayFormat);

interleavedArrayFormat is one of the interleaved array types described for voVertexData in “Read Brick Data from Disk”.