Houdini Engine 2.0
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Volumes

Querying

Volumetric data can be thought of as 3D grids in space. In Houdini, volumes can represent two main types of objects: either fields in a fluid simulation, or the surface of an object (SDF). For example, in a single fluid simulation, there are many fields, eg. density, velocity, heat, temperature, fuel. These fields all come together to become a fluid. Volumes may contain two types of data, ints or floats, and it may be scalars (tupleSize == 1) or vectors (tupleSize > 1).

In Houdini, volumes may be represented in two different ways, as Houdini native volumes, or as VDBs. Houdini native volumes are dense whereas VDBs are sparse. The volume data representation you will be extracting / setting will be sparse, as the dense data is a special case of the sparse data. Therefore, instead of retrieving the volume data as one massive array, it is given out in smaller subarrays called tiles. Each tile refers to a cube subset of the volume. Each volume might have a different resolution for the size of the tile. At the time of writing, this should either be 8 or 16 (ie. 8x8x8 and 16x16x16).

To extract volumetric data through HAPI, we begin at the point of the process where we are extracting parts from geos. A part is a volume part if HAPI_PartInfo::type is equal to HAPI_PARTTYPE_VOLUME and that means more information can be obtained about the volume though the HAPI_GetVolumeInfo() call.

A HAPI_VolumeInfo struct is returned. Note that the HAPI_VolumeInfo::minX, HAPI_VolumeInfo::minY, HAPI_VolumeInfo::minZ, HAPI_VolumeInfo::xLength, HAPI_VolumeInfo::yLength, and HAPI_VolumeInfo::zLength fields do NOT refer to the spatial extents of the volume - notice they are integers and not floats. Instead, they refer to the range of valid "indices" into the volume. The quotes are around the term indices because to index into volume data, a 3-tuple of indices in each of x,y, and z are required. It is important to notice that the 3-tuple of integers may be negative, as each of HAPI_VolumeInfo::minX, HAPI_VolumeInfo::minY, and HAPI_VolumeInfo::minZ could be negative.

Here's an example of getting the volume info:

// Load the library from file.
HAPI_AssetLibraryId library_id = -1;
HAPI_Result result =
hapiTestSession, "HAPI_Test_Volumes_VDBFog.otl",
false, &library_id );
assert( result == HAPI_RESULT_SUCCESS );
assert( library_id >= 0 );
// Instantiate the asset.
HAPI_AssetId asset_id = -1;
hapiTestSession, "Object/HAPI_Test_Volumes_VDBFog",
true, &asset_id );
assert( result == HAPI_RESULT_SUCCESS );
// There's only one object, so just reference it directly.
const HAPI_ObjectId object_id = 0;
// Get the part info. We know we only have on geo and one part so ids 0-0.
// The part info should claim it is a volume.
HAPI_PartInfo part_info;
result = HAPI_GetPartInfo(
hapiTestSession, asset_id, object_id, 0, 0, &part_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( part_info.type == HAPI_PARTTYPE_VOLUME );
// Get the volume info.
HAPI_VolumeInfo volume_info;
hapiTestSession, asset_id, object_id, 0, 0, &volume_info );
assert( result == HAPI_RESULT_SUCCESS );
// Check the information in the volume info against expected values.
UT_String volume_name = HAPItestGetString( volume_info.nameSH );
assert( volume_name == "density" );
assert( volume_info.type == HAPI_VOLUMETYPE_VDB );
assert( volume_info.xLength == 30 );
assert( volume_info.yLength == 9 );
assert( volume_info.zLength == 29 );
assert( volume_info.minX == -15 );
assert( volume_info.minY == -4 );
assert( volume_info.minZ == -14 );
assert( volume_info.tupleSize == 1 );
assert( volume_info.storage == HAPI_STORAGETYPE_FLOAT );
assert( volume_info.tileSize == 8 );
assert( volume_info.hasTaper == false );
assert( approx( volume_info.xTaper, 1.0f ) );
assert( approx( volume_info.yTaper, 1.0f ) );

Positioning

The HAPI_VolumeInfo::minX, HAPI_VolumeInfo::minY, HAPI_VolumeInfo::minZ, HAPI_VolumeInfo::xLength, HAPI_VolumeInfo::yLength, and HAPI_VolumeInfo::zLength fields tell us how to index into the overall volume, and the transform field tells us how to go from volume object space into world space, but we need to know how to map a voxel at a particular (i,j,k) index into object space. This mapping from (i,j,k) index into object space is given by the following pseudo-code:

indexToObject( int i, int j, int k, HAPI_VolumeInfo vol ):
vec3 pos = 2.0 * ( ( i, j, k ) - ( 0.5, 0.5, 0.5 ) )
if ( vol.hasTaper ):
taper( pos, vol );
pos = vol.transform.scale * pos
pos = rotate( pos, vol.transform.rotationEuler, vol.transform.rotationOrder )
pos = pos + vol.transform.position
return pos

The taper function pseudo-code is given as follows:

taper( vec3 pos, HAPI_VolumeInfo vol ):
zscale = ( 1 - pos.z ) * 0.5;
taperx = 1 + ( vol.xTaper - 1 ) * zscale;
tapery = 1 + ( vol.yTaper - 1 ) * zscale;
pos.x = pos.x * taperx;
pos.y = pos.y * tapery;
return pos;

Note that the situation of tapering is relatively rare in practice.

Volume Output

By Tile

Knowing the size of each tile from the HAPI_VolumeInfo, we can retrieve each tile as a HAPI_VolumeTileInfo with the following functions:

The HAPI_VolumeTileInfo::minX, HAPI_VolumeTileInfo::minY, and HAPI_VolumeTileInfo::minZ fields denote the tile's position within the overall volume. Note that a tile may not have values at all of its voxels. This can happen in two cases:

  1. when the tile straddles either the boundary for the entire volume,
  2. or one of many boundaries within a VDB where there is sparse data.

In the first case, the invalid voxels will not be written to (ie. the input buffer at those locations will be left as is). Whether a voxel index is within the volume or not can be determined by checking if the index is in the range given by HAPI_VolumeInfo::minX, HAPI_VolumeInfo::minY, HAPI_VolumeInfo::minZ, HAPI_VolumeInfo::xLength, HAPI_VolumeInfo::yLength, and HAPI_VolumeInfo::zLength.

Finally, the actual values in each tile can be retrieved with:

Given an index into the tile (i,j,k), and the component index c (0 for x, 1 for y, 2 for z), the value of the component c in the voxel is given by:

values[ k*tileSize*tileSize*tupleSize + j*tileSize*tupleSize + i*tupleSize + c ]

Here's an example that goes through all the tiles in a VDB color volume and adds up all the voxel values:

// Load the library from file.
HAPI_AssetLibraryId library_id = -1;
HAPI_Result result =
hapiTestSession, "HAPI_Test_Volumes_VDBFogColor.otl",
false, &library_id );
assert( result == HAPI_RESULT_SUCCESS );
assert( library_id >= 0 );
// Instantiate the asset.
HAPI_AssetId asset_id = -1;
hapiTestSession, "Object/HAPI_Test_Volumes_VDBFogColor",
true, &asset_id );
assert( result == HAPI_RESULT_SUCCESS );
// There's only one object, so just reference it directly.
const HAPI_ObjectId object_id = 0;
// Get the part info for the second part which should be the color volume.
const HAPI_PartId part_id = 1;
// Get the volume info.
HAPI_VolumeInfo volume_info;
hapiTestSession, asset_id, object_id, 0, part_id, &volume_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( volume_info.tupleSize == 3 );
assert( volume_info.hasTaper == false );
// Get the first volume tile.
HAPI_VolumeTileInfo volume_tile_info;
hapiTestSession, asset_id, object_id, 0, part_id, &volume_tile_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( volume_tile_info.isValid );
// Allocate tile data buffer.
const int tile_value_count =
volume_info.tileSize *
volume_info.tileSize *
volume_info.tileSize *
volume_info.tupleSize;
float * tile_values = new float[ tile_value_count ];
// Get volume tiles until we've gotten all the volume tiles.
// Will re-use the first volume tile's info.
float full_volume_value_sum = 0.0f;
while ( volume_tile_info.isValid )
{
// Reset the data. These values should all be overwritten by
// the fill_value of -8.8 below.
for ( int i = 0; i < tile_value_count; ++i )
tile_values[ i ] = -5.8f;
// Get the color data.
hapiTestSession,
asset_id, object_id, 0, part_id, -8.8f, &volume_tile_info,
tile_values, tile_value_count );
assert( result == HAPI_RESULT_SUCCESS );
// Verify tile data.
const int color_tile_tuple_count =
tile_value_count / volume_info.tupleSize;
for ( int i = 0; i < color_tile_tuple_count; ++i )
{
// The volume voxel values are either the correct color constant
// or 1.0/1.0/1.0 if the voxel is outside the dense areas of the
// volume.
const int voxel_index = i * volume_info.tupleSize;
assert(
approx( tile_values[ voxel_index + 0 ], 0.8f ) ||
approx( tile_values[ voxel_index + 0 ], 1.0f ) );
assert(
approx( tile_values[ voxel_index + 1 ], 0.5f ) ||
approx( tile_values[ voxel_index + 1 ], 1.0f ) );
assert(
approx( tile_values[ voxel_index + 2 ], 0.2f ) ||
approx( tile_values[ voxel_index + 2 ], 1.0f ) );
}
// Add up all values for a sum check at the end.
for ( int i = 0; i < tile_value_count; ++i )
full_volume_value_sum += tile_values[ i ];
// Get the next color tile.
hapiTestSession,
asset_id, object_id, 0, part_id,
&volume_tile_info );
assert( result == HAPI_RESULT_SUCCESS );
}
// Check the value sum against expected.
assert( approx( full_volume_value_sum, 56388.0f ) );
// Cleanup.
delete [] tile_values;

By Voxel

You can also get the values of individual voxels using either HAPI_GetVolumeVoxelFloatData() or HAPI_GetVolumeVoxelIntData(). Here's an example of adding up all voxels in a VDB color volume:

// Load the library from file.
HAPI_AssetLibraryId library_id = -1;
HAPI_Result result =
hapiTestSession, "HAPI_Test_Volumes_VDBFogColor.otl",
false, &library_id );
assert( result == HAPI_RESULT_SUCCESS );
assert( library_id >= 0 );
// Instantiate the asset.
HAPI_AssetId asset_id = -1;
hapiTestSession, "Object/HAPI_Test_Volumes_VDBFogColor",
true, &asset_id );
assert( result == HAPI_RESULT_SUCCESS );
// There's only one object, so just reference it directly.
const HAPI_ObjectId object_id = 0;
// Get the part info for the second part which should be the color volume.
const HAPI_PartId part_id = 1;
// Get the volume info.
HAPI_VolumeInfo volume_info;
hapiTestSession, asset_id, object_id, 0, part_id, &volume_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( volume_info.tupleSize == 3 );
assert( volume_info.hasTaper == false );
// Get the values of all the voxels in the volume.
float full_volume_value_sum = 0.0f;
{
const HAPI_VolumeInfo & vi = volume_info;
for ( int x = vi.minX; x < vi.minX + vi.xLength; ++x )
for ( int y = vi.minY; y < vi.minY + vi.yLength; ++y )
for ( int z = vi.minZ; z < vi.minZ + vi.zLength; ++z )
{
float voxel_value[ 3 ]; // vi.tupleSize == 3
hapiTestSession,
asset_id, object_id, 0, part_id,
x, y, z, (float *) voxel_value, vi.tupleSize );
// The volume voxel values are either the correct color
// constant or 1.0/1.0/1.0 if the voxel is outside the
// dense areas of the volume.
assert(
approx( voxel_value[ 0 ], 0.8f ) ||
approx( voxel_value[ 0 ], 1.0f ) );
assert(
approx( voxel_value[ 1 ], 0.5f ) ||
approx( voxel_value[ 1 ], 1.0f ) );
assert(
approx( voxel_value[ 2 ], 0.2f ) ||
approx( voxel_value[ 2 ], 1.0f ) );
for ( int i = 0; i < vi.tupleSize; ++i )
full_volume_value_sum += voxel_value[ i ];
}
}
// Check the value sum against expected.
assert( approx( full_volume_value_sum, 37785.0f ) );

Volume Marshalling

You can marshal in volume data into an asset created with HAPI_CreateInputAsset() similar to geometry in Marshalling Geometry Into Houdini. Volumes are just another primitive type that can be stored in a part of an Input Asset.

Once you have an Input Asset, you have to set the parameters of the volume part with a HAPI_SetPartInfo() call. For volumes, HAPI_PartInfo::type has to be set to HAPI_PARTTYPE_VOLUME. You can also set the attribute counts if you wish to add attributes to your volume but everything else in the HAPI_PartInfo can be left to the default value returned by HAPI_PartInfo_Init().

Next, set the volume info with HAPI_SetVolumeInfo(). It is up to you to make sure the prorties you set in your HAPI_VolumeInfo are valid. Some validation will be done on HAPI too so make sure you check the HAPI_Result when setting the volume info.

You are now ready to input tile data. You do this with HAPI_SetVolumeTileFloatData() and HAPI_SetVolumeTileIntData().

Finally, when you're done inputting data, you have to commit the volume by calling HAPI_CommitGeo().

Here's an example that takes a VDB volume and reproduces it inside an input asset:

// Load the library from file.
HAPI_AssetLibraryId library_id = -1;
HAPI_Result result =
hapiTestSession, "HAPI_Test_Volumes_VDBFogColor.otl",
false, &library_id );
assert( result == HAPI_RESULT_SUCCESS );
assert( library_id >= 0 );
// Instantiate the asset.
HAPI_AssetId asset_id = -1;
hapiTestSession, "Object/HAPI_Test_Volumes_VDBFogColor",
true, &asset_id );
assert( result == HAPI_RESULT_SUCCESS );
// There's only one object, so just reference it directly.
const HAPI_ObjectId object_id = 0;
// Get the part info for the second part which should be the color volume.
const HAPI_PartId part_id = 1;
// Get the volume info.
HAPI_VolumeInfo volume_info;
hapiTestSession, asset_id, object_id, 0, part_id, &volume_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( volume_info.tupleSize == 3 );
assert( volume_info.hasTaper == false );
// Create input asset to receive volume.
HAPI_AssetId input_asset_id;
hapiTestSession, &input_asset_id, "Input_Volume" );
assert( result == HAPI_RESULT_SUCCESS );
// Set the volume info.
hapiTestSession, input_asset_id, 0, 0, &volume_info );
assert( result == HAPI_RESULT_SUCCESS );
// Get the first volume tile.
HAPI_VolumeTileInfo volume_tile_info;
hapiTestSession, asset_id, object_id, 0, part_id, &volume_tile_info );
assert( result == HAPI_RESULT_SUCCESS );
assert( volume_tile_info.isValid );
// Allocate tile data buffer.
const int tile_value_count =
volume_info.tileSize *
volume_info.tileSize *
volume_info.tileSize *
volume_info.tupleSize;
float * tile_values = new float[ tile_value_count ];
// Get volume tiles until we've gotten all the volume tiles.
// Will re-use the first volume tile's info.
// There should be the same number of tiles in both the density and
// color volumes.
while ( volume_tile_info.isValid )
{
// Reset the data. These values should all be overwritten by
// the fill_value of -8.8 below.
for ( int i = 0; i < tile_value_count; ++i )
tile_values[ i ] = -5.8f;
// Get the color data.
hapiTestSession,
asset_id, object_id, 0, part_id, -8.8f, &volume_tile_info,
tile_values, tile_value_count );
assert( result == HAPI_RESULT_SUCCESS );
// Set the color data on the input volume.
hapiTestSession,
input_asset_id, 0, 0, &volume_tile_info,
tile_values, tile_value_count );
assert( result == HAPI_RESULT_SUCCESS );
// Get the next color tile.
hapiTestSession,
asset_id, object_id, 0, part_id,
&volume_tile_info );
assert( result == HAPI_RESULT_SUCCESS );
}
// Commit the volume inputs.
result = HAPI_CommitGeo( hapiTestSession, input_asset_id, 0, 0 );
assert( result == HAPI_RESULT_SUCCESS );
// Cook the asset.
result = HAPI_CookAsset( hapiTestSession, input_asset_id, NULL );
assert( result == HAPI_RESULT_SUCCESS );
// Cleanup.
delete [] tile_values;