Hi Robert,
The SetHeightfieldData() function is a helper function to modify data on an exisiting heightfield.
There is no better way to create a new Heightfield than the way you described above, as Heightfields are just volumes in houdini (two volumes, “height” and “mask”, merged together with an added volume visualization node).
The way you do it on your second example is correct, though you might want to add a merge node between the height volume and the volume visualisation, and add a second volume called “mask”, as most heightfield SOPs will not create a mask volume if its not present, so not having it originally might cause you some trouble down the line.
To create a new heightfield node, you need to:
- Create a volume visualisation node (“sop/volumevisualisation”)
- set its “vismode” parameter to 2 (heightfield)
- set its “densityfield” parameter to “height”
- cook it
- Create a merge node, and connect it to the volume visualisation input
- For the “height” volume:
- Create an Input node
- Create a HAPI_VolumeInfo for it
zLength needs to be set to 1, the minX/Y/Z values to 0 (and the transform will need a non zero scale)
type HAPI_VOLUMETYPE_HOUDINI, storage HAPI_STORAGETYPE_FLOAT with a tuple and tile size of 1, no taper.
- Create a Part Info, with 1 primitive attribute, 1 face, type HAPI_PARTTYPE_VOLUME.
- You can the call HAPI_SetHeightFieldData, using “height” for the volume name.
- Commit the geo, cook it, then connect it to the merge node's first input.
- For the “mask” volume
- Repeat the process used for the height data
( just make sure to name the mask “mask” and connect it to the merge node's second input. Default value for mask is zero )
- Finally, you can cook the Volume visualisation node.
I'll update HAPI docs so the volume page has a proper example of how to create a new heightfield node.
Here's an updated version of your code with the added merge node and mask volume.
#include <HAPI/HAPI.h>
#include <iostream>
#include <string>
#include <vector>
#include <random>
#define ENSURE_SUCCESS( result ) \
if ( (result) != HAPI_RESULT_SUCCESS ) \
{ \
std::cout << "Failure at " << __FILE__ << ": " << __LINE__ << std::endl; \
std::cout << getLastError() << std::endl; \
exit( 1 ); \
}
static std::string getLastError();
static std::string getString(HAPI_StringHandle stringHandle);
int
main(int argc, char ** argv)
{
HAPI_CookOptions cookOptions = HAPI_CookOptions_Create();
HAPI_Session session;
HAPI_CreateInProcessSession(&session);
ENSURE_SUCCESS(HAPI_Initialize(&session, &cookOptions, true, -1, nullptr, nullptr, nullptr, nullptr, nullptr));
HAPI_NodeId volume_visualization_id = -1;
HAPI_NodeId merge_node_id = -1;
HAPI_NodeId height_input_id = -1;
HAPI_NodeId mask_input_id = -1;
HAPI_NodeId display_node_id = -1;
HAPI_NodeInfo volume_visualization_info = HAPI_NodeInfo_Create();
HAPI_ParmId vis_id = -1;
HAPI_ParmId density_id = -1;
HAPI_ParmInfo parm_info = HAPI_ParmInfo_Create();
// Create the Heightfield visualisation node, this will be our display node
// We need to set the visualisation mode to heightfield, and the density field to height.
ENSURE_SUCCESS(HAPI_CreateNode(&session, -1, "sop/volumevisualization", "Volvis", false, &volume_visualization_id));
ENSURE_SUCCESS(HAPI_SetParmIntValue(&session, volume_visualization_id, "vismode", 0, 2));
ENSURE_SUCCESS(HAPI_GetParmIdFromName(&session, volume_visualization_id, "densityfield", &density_id));
ENSURE_SUCCESS(HAPI_GetParmInfo(&session, volume_visualization_id, density_id, &parm_info));
ENSURE_SUCCESS(HAPI_SetParmStringValue(&session, volume_visualization_id, "height", density_id, 0));
ENSURE_SUCCESS(HAPI_CookNode(&session, volume_visualization_id, nullptr));
display_node_id = volume_visualization_id;
// Create a merge node
// This will be connected to the volvis node, and be used to merge the heightfields height and mask(s) volumes
ENSURE_SUCCESS(HAPI_CreateNode(&session, -1, "merge", "MergeNode", false, &merge_node_id));
ENSURE_SUCCESS(HAPI_ConnectNodeInput(&session, volume_visualization_id, 0, merge_node_id));
// Create the height volume
char * name = "height";
int start = 0;
HAPI_PartId part_id = 0;
HAPI_NodeId volume_node_id = -1;
ENSURE_SUCCESS(HAPI_CreateInputNode(&session, &volume_node_id, name));
ENSURE_SUCCESS(HAPI_CookNode(&session, volume_node_id, nullptr));
HAPI_GeoInfo volume_geo_info = HAPI_GeoInfo_Create();
ENSURE_SUCCESS(HAPI_GetDisplayGeoInfo(&session, volume_node_id, &volume_geo_info));
HAPI_Transform transform = HAPI_Transform_Create();
transform.scale[0] = 1000.0f;
transform.scale[1] = 1000.0f;
transform.scale[2] = 1.0f;
HAPI_VolumeInfo heightfield_volume_info = HAPI_VolumeInfo_Create();
heightfield_volume_info.xLength = 500;
heightfield_volume_info.yLength = 500;
heightfield_volume_info.zLength = 1;
heightfield_volume_info.minX = 0;
heightfield_volume_info.minY = 0;
heightfield_volume_info.minZ = 0;
heightfield_volume_info.transform = transform;
heightfield_volume_info.type = HAPI_VOLUMETYPE_HOUDINI;
heightfield_volume_info.storage = HAPI_STORAGETYPE_FLOAT;
heightfield_volume_info.tupleSize = 1;
heightfield_volume_info.tileSize = 1;
heightfield_volume_info.hasTaper = false;
heightfield_volume_info.xTaper = 0.0;
heightfield_volume_info.yTaper = 0.0;
const int totalsize = (heightfield_volume_info.xLength * heightfield_volume_info.yLength);
std::vector< float > heightfieldData(totalsize);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(1, 2);
for (int n = 0; n < totalsize; ++n)
{
heightfieldData[n] = (float)dis(gen);
}
HAPI_PartInfo part = HAPI_PartInfo_Create();
part.nameSH = 0;
part.id = part_id;
part.attributeCounts[HAPI_ATTROWNER_POINT] = 0;
part.attributeCounts[HAPI_ATTROWNER_PRIM] = 1;
part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0;
part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0;
part.pointCount = 0;
part.vertexCount = 0;
part.faceCount = 1;
part.type = HAPI_PARTTYPE_VOLUME;
ENSURE_SUCCESS(HAPI_SetPartInfo(&session, volume_geo_info.nodeId, part.id, &part));
ENSURE_SUCCESS(HAPI_SetVolumeInfo(&session, volume_geo_info.nodeId, part.id, &heightfield_volume_info));
ENSURE_SUCCESS(HAPI_SetHeightFieldData(&session, volume_geo_info.nodeId, part.id, heightfieldData.data(), start, totalsize, name));
ENSURE_SUCCESS(HAPI_CommitGeo(&session, volume_geo_info.nodeId));
ENSURE_SUCCESS(HAPI_CookNode(&session, volume_geo_info.nodeId, nullptr));
// Create the mask volume
// We can reuse the part and volume info created for the height volume
char * maskname = "mask";
HAPI_NodeId mask_volume_node_id = -1;
ENSURE_SUCCESS(HAPI_CreateInputNode(&session, &mask_volume_node_id, name));
ENSURE_SUCCESS(HAPI_CookNode(&session, mask_volume_node_id, nullptr));
HAPI_GeoInfo mask_volume_geo_info = HAPI_GeoInfo_Create();
ENSURE_SUCCESS(HAPI_GetDisplayGeoInfo(&session, mask_volume_node_id, &mask_volume_geo_info));
HAPI_VolumeInfo mask_volume_info = heightfield_volume_info;
heightfield_volume_info.transform = transform;
// fill the mask with zeros
std::vector< float > maskData(totalsize);
for (int n = 0; n < totalsize; ++n)
{
maskData[n] = 0.0f;
}
// we can reuse the height part info here
ENSURE_SUCCESS(HAPI_SetPartInfo(&session, mask_volume_geo_info.nodeId, part.id, &part));
ENSURE_SUCCESS(HAPI_SetVolumeInfo(&session, mask_volume_geo_info.nodeId, part.id, &heightfield_volume_info));
ENSURE_SUCCESS(HAPI_SetHeightFieldData(&session, mask_volume_geo_info.nodeId, part.id, maskData.data(), 0, totalsize, maskname));
ENSURE_SUCCESS(HAPI_CommitGeo(&session, mask_volume_geo_info.nodeId));
ENSURE_SUCCESS(HAPI_CookNode(&session, mask_volume_geo_info.nodeId, nullptr));
// Connect the height to the merge node
int next_input = 0;
ENSURE_SUCCESS(HAPI_ConnectNodeInput(&session, merge_node_id, next_input++, volume_geo_info.nodeId));
// And the mask
ENSURE_SUCCESS(HAPI_ConnectNodeInput(&session, merge_node_id, next_input++, mask_volume_geo_info.nodeId));
ENSURE_SUCCESS(HAPI_CookNode(&session, volume_visualization_id, nullptr));
ENSURE_SUCCESS(HAPI_SaveHIPFile(&session, "height_field.hip", false));
HAPI_Cleanup(&session);
return 0;
}
static std::string
getLastError()
{
int bufferLength;
HAPI_GetStatusStringBufLength(nullptr, HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS, &bufferLength);
char * buffer = new char[bufferLength];
HAPI_GetStatusString(nullptr, HAPI_STATUS_CALL_RESULT, buffer, bufferLength);
std::string result(buffer);
delete[] buffer;
return result;
}
static std::string
getString(HAPI_StringHandle stringHandle)
{
if (stringHandle == 0)
{
return "";
}
int bufferLength;
HAPI_GetStringBufLength(nullptr,
stringHandle,
&bufferLength);
char * buffer = new char[bufferLength];
HAPI_GetString(nullptr, stringHandle, buffer, bufferLength);
std::string result(buffer);
delete[] buffer;
return result;
}