Houdini Engine 7.0
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Instancing

Table of Contents

Packed Primitives

One way to handle instancing in Houdini and Houdini Engine is using packed primitives. Packed primitives are essentially primitives that live purely inside of SOPs and contain one or more primitives with their own transforms. Packed primitives can easily contain other packed primtivies allowing you to have a full transform hierarchy completely inside of SOPs. Furthermore, contained packed primitives which happen to be duplicates will be properly instanced meaning that only one copy of the unique geometry will be stored in memory.

It's better, going foward, to have a real asset in mind. Here is our simple packed primitive asset:

HAPI_Instancing_PackedPrimAsset.png

The result being:

HAPI_Instancing_PackedPrimResult.png

Where the only thing to note is that each Copy SOP has its Pack Geometry Before Copying option turned on:

HAPI_Instancing_PackedPrimCopySetting.png

In Houdini Engine, packed primitives show up like any other primitive, as Parts Parts. The way you can tell if a part is a packed primitive is that the HAPI_PartInfo::type will be set to HAPI_PARTTYPE_INSTANCER. You can also tell if a part is being instanced by checking HAPI_PartInfo::isInstanced.

First thing to do is set the desired packed primitive mode for cooking. For this, there is HAPI_CookOptions::packedPrimInstancingMode. The modes are:

  • HAPI_PACKEDPRIM_INSTANCING_MODE_DISABLED which will flatten and bake every packed primitive tree into a giant unique mesh. Any instanced meshes will be combined into this unique mesh. This is equivalent to what happened before packed primitive support was added in Houdini Engine 2.0. With our example asset above, we'd get just two parts:
    1. unique containing all the boxes
    2. curve mesh containing all the curves
  • HAPI_PACKEDPRIM_INSTANCING_MODE_HIERARCHY which will bring each packed primitive tree whole, preserving the transform hierarchy inside. This means that you can get instancer parts instancing other instancer parts. With our example asset above, you'd get five parts:
    1. the center box as it's just merged in by itself
    2. instancer part representing the copy1 node instancing the copy2 node instancer part 4 times on the given grid1
    3. instancer part representing the copy2 node instancing the box-curve pair 4 times on the given grid2
    4. a box mesh part which is being instanced by the copy2 instancer
    5. a curve part which is being instanced by the copy2 instancer
  • HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT which will flatten each packed primitive tree so that any instancer parts will only instance regular non-instancer parts. With out example asset above, you'd get five parts:
    1. the center box as it's just merged in by itself
    2. an instancer instancing all the boxes 16 times in their baked positions
    3. a box mesh part which is being instanced by the box instancer
    4. an instancer instancing all the curves 16 times in their baked positions
    5. a curve part which is being instanced by the curve instancer

Next is getting finding out who is instancing who. Use each instancer part's HAPI_PartInfo::instancedPartCount to get the HAPI_PartId's of the parts being instanced with HAPI_GetInstancedPartIds(). With our asset above, for example, the instancer part corresponding to the copy2 node would have a HAPI_PartInfo::instancedPartCount of 2 and HAPI_GetInstancedPartIds() would give you the HAPI_PartId's of the instanced box and curve parts.

Finally, we need to know where to instance our instanced parts. Use each instancer part's HAPI_PartInfo::instanceCount to get the instance transforms with HAPI_GetInstancerPartTransforms(). With our asset above, for example, the instancer part corresponding to the copy2 node would have a HAPI_PartInfo::instanceCount of 4 and HAPI_GetInstancerPartTransforms() would return the points (transforms) of grid2. Here's an example that parses a packed primitive part hierachy of the example asset above and gets all the instance transforms:

// Load the library from file.
HAPI_AssetLibraryId library_id = -1;
hapiTestSession, "HAPI_Test_Instancing_Pack_Simple.otl",
false, &library_id );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( library_id >= 0 );
// Instantiate the asset.
HAPI_NodeId node_id = -1;
result = HAPI_CreateNode(
hapiTestSession, -1, "Object/HAPI_Test_Instancing_Pack_Simple",
nullptr, true, &node_id );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
// Set the packed prim mode to hierarchy and cook.
cook_options.packedPrimInstancingMode =
result = HAPI_CookNode( hapiTestSession, node_id, &cook_options );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
// Get the geo info.
result = HAPI_GetDisplayGeoInfo( hapiTestSession, node_id, &geo_info );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( geo_info.partCount == 5 );
const HAPI_NodeId geo_node_id = geo_info.nodeId;
// The 1st part should be just a regular box mesh. This is not being
// instanced.
// The 2nd part should be the 2nd instancer instancing the 1st instancer.
// First, get the part info.
HAPI_PartInfo instancer_instancer_part_info;
result = HAPI_GetPartInfo(
hapiTestSession, geo_node_id, 1, &instancer_instancer_part_info );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( instancer_instancer_part_info.instanceCount == 4 );
// Now, get the transforms of the instancer instances and check the first
// instance position against expected values.
std::vector< HAPI_Transform > instancer_transforms(
instancer_instancer_part_info.instanceCount );
hapiTestSession, geo_node_id, 1,
HAPI_SRT, instancer_transforms.data(),
0, instancer_instancer_part_info.instanceCount );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( approx( instancer_transforms[ 0 ].position[ 0 ], -6.0f ) );
HAPI_TEST_ASSERT( approx( instancer_transforms[ 0 ].position[ 1 ], 0.0f ) );
HAPI_TEST_ASSERT( approx( instancer_transforms[ 0 ].position[ 2 ], -6.0f ) );
// The 3rd part should be the 1st instancer instancing the 2-part geo.
// First, get the part info.
HAPI_PartInfo geo_instancer_part_info;
result = HAPI_GetPartInfo(
hapiTestSession, geo_node_id, 2, &geo_instancer_part_info );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( geo_instancer_part_info.instanceCount == 4 );
// Now, get the transforms of the geo instances and check the first
// instance position against expected values.
std::vector< HAPI_Transform > geo_transforms(
geo_instancer_part_info.instanceCount );
hapiTestSession, geo_node_id, 2, HAPI_SRT, geo_transforms.data(),
0, geo_instancer_part_info.instanceCount );
HAPI_TEST_ASSERT( result == HAPI_RESULT_SUCCESS );
HAPI_TEST_ASSERT( approx( geo_transforms[ 0 ].position[ 0 ], -1.5f ) );
HAPI_TEST_ASSERT( approx( geo_transforms[ 0 ].position[ 1 ], 0.0f ) );
HAPI_TEST_ASSERT( approx( geo_transforms[ 0 ].position[ 2 ], -1.5f ) );
// 4th part is the instanced box.
// 5th part is the instanced curve.

Instance OBJ

Another way to handle instancing in Houdini and Houdini Engine is by the use of Instance objects. An Instance object is an object level node with some special properties. Inside Houdini, an Instance node looks like the following:

HAPI_Instancing_Instancer.jpg

The first thing is to make sure the Point Instancing parameter is set to Full point instancing as shown in the screenshot.

If the Instance Object parameter is filled out, Houdini will try to create instances of the specified object. If the instance object is something that resides within the same asset, then it will be brought in along with the rest of the asset into the host environment, and the HAPI_ObjectInfo::objectToInstanceId will be set to the object id of that object. The host can then look up the object by its id and instance it.

Underneath the instance object (or inside it, rather) is another network of nodes, where geometry is being computed (a SOP network, in Houdini speak). Only in this case, the geometry isn't in the form of triangles and normals that you'd display directly, but rather it is a point cloud. Each point of the this point cloud has a position and several other attributes on it that indicate the proper orientation of the object to be instanced ("N", "up", "scale"). Here is a picture of what that point cloud might look like:

HAPI_Instancing_Points.jpg

Once objects have been instanced onto the points, here is what Houdini Engine would give a host application:

HAPI_Instancing_Result.png

To find the transform to use at each point, use ::HAPI_GetInstanceTransforms(). This function takes into account the reserved attributes (such as "N" and "up") and returns a transform for every single point in the point cloud. You could then apply this transform in the host environment to the object you wish to instance.

The instance object field is simple, but it has a shortcoming: it assumes only one object is to be instanced. In many cases, such as the pictures above, each point might have a different geometry instanced to it. In this case, we can make use of a special attribute with the name instance:

HAPI_Instancing_Attributes.png

As you can see from the figure above, the per point attribute instance is determining the model to be used at each point. This information can be extracted in the host environment through the attribute query functions covered in Query Attribute Information.