Houdini HDK Development: A Graphics Researcher’s Perspective
Geometry Libraries
The main geometry libraries in Houdini are:
- GA: Geometry Attributes. Base classes for all geometry.
- GD: Geometry 2D. A 2D sub-class of the GA library.
- GEO: Geometry 3D. A 3D sub-class of the GA library.
- GU: Geometry Utilities. A sub-classed of the GEO library.

Container Class
Geometry is stored in a container class (GA_Detail, GEO_Detail, GD_Detail, GU_Detail).
Container contains attributes data arrays for points (shared between primitives), vertices (unique), primitives, and the container itself. The detail container maintains a list of GA_Primitive objects, one for each primitive in the detail. Each primitive maintains a list of vertices. Each vertex has a single reference to a point. Unlike primitives, points and vertices are described entirely by their attribute values and have no separate allocations per point or vertex.
Elements Offset/Index
- GA_Offset: NOT CHANGE if elements are added or destroyed (except under a defragment operation).
- GA_Index: WILL CHANGE if elements before it are deleted.
The offset/index mapping is maintained by the detail in a GA_IndexMap structure.
the GA_Offset for a primitive is also used to lookup the GA_Primitive object in the detail’s primitive list.
ATI Attributes
ATI: Atribute Type Implementation
The attribute data for each element is accessed using its GA_Offset. That is, the GA_Offset represents the offset in the ATI array.
The detail class stores the attributes in an attribute set object. This object consists of four dictionaries (vertex, point, primitive and detail). Attributes are uniquely identified by NAME within each dictionary.
Attributes have a “scope” to determine visibility. Only attributes with a public scope are presented to the user.
Topological Information
In all cases, you can find out which vertices are referenced by a primitive and which points are referenced by a vertex. However, with minimal topological information (usually present in Houdini), you can also find out which vertices reference a point (multiple vertices can reference the same shared point) and also which primitive a vertex belongs to.
Geometry Structures
The main types/classes for Houdini geometry are:
- GU_Detail: The container class.
- GA_Attribute: The base class for all attribute type implementations (ATIs).
- GA_Offset: The internal offset for an element’s data in the attribute data pages.
- GA_Index: The allocation order for an element.
- GA_IndexMap: A class managing the GA_Index <-> GA_Offset association for a particular element type: point, vertex, primitive or detail.
- GEO_Primitive: A primitive object. Examples of these are GEO_PrimPoly, GEO_PrimSphere. Each primitive manages a list of zero or more vertices (GA_Offsets in the vertex GA_IndexMap). Each vertex has an associated reference to a point (GA_Offset in the point GA_IndexMap) which may be shared across multiple vertices.

GA_Detail
- Points: GA_Detail::getPointMap()
- Vertices: GA_Detail::getVertexMap()
- Primitives: GA_Detail::getPrimitiveMap()
- Detail itself (Trivial): GA_Detail::getGlobalMap()
This sharing of points provides an opportunity for the vertices in a primitive to have both shared attribute data (point attributes) and unique attribute data (vertex attributes).
Geometry Attributes
A geometry attribute is an instance of a subclass of GA_Attribute
. These subclasses are typically referred to as ATIs (Attribute Type Implementations) because they implement a particular GA_AttributeType
.
ATIs— Attribute Type Implementations
Each ATI has a common interface inherited from GA_Attribute
:
- name: The name of the attribute (i.e. P or N or temperature)
- scope: A scope (
GA_SCOPE_PUBLIC
,GA_SCOPE_PRIVATE
,GA_SCOPE_GROUP
) identifies a name space for the attribute as well as some behaviors. SeeGA_AttributeScope
. - type: A reference to the factory object which allocated it.
- type info: A hint on how to interpret the stored data. See
GA_TypeInfo
. - options: A
UT_Options
object that can store various name, value pairs. - AIFs accessors: Virtual methods to access Attribute Interfaces (AIFs) for manipulating the attribute data. Examples include:
GA_AIFCopyData
,GA_AIFTuple
,GA_AIFMath
.
The list of attributes are maintained by the GA_Detail
. Because the attributes are maintained at the global level, attributes are uniform across element types. That is, if one point has the Cd
attribute, then all points in the detail will have the Cd
attribute. That is not to say, however, that the attribute necessarily allocates memory for every point.
Attributes Order
Generally it’s typical to prefer finer grained attributes over coarser grained variants (i.e. vertex
before point
before primitive
before detail
). By default, however, trying to create a point attribute with the same name as a vertex attribute or vice versa will promote the original to the new type, to reduce problems that can emerge when point and vertex attributes have the same name. This behaviour can be overridden using a GA_ReuseStrategy
.
Attribute Types
New attribute types may be created with the HDK, and so attribute types are identified by a unique token.
- “numeric”: Numeric tuple data. Supported storage types are:
GA_STORE_UINT8
,GA_STORE_INT8
,GA_STORE_INT16
,GA_STORE_INT32
,GA_STORE_REAL16
,GA_STORE_REAL32
andGA_STORE_REAL64
. Attributes of this type support theGA_AIFTuple
interface. However, for most uses, handles likeGA_ROHandleV3
,GA_RWHandleV3
,GA_ROHandleF
, andGA_RWHandleF
are easier and faster to use. Integer storage types, by default, are set asGA_TYPE_NONARITHMETIC_INTEGER
. - “arraydata”: Variable-length arrays of numeric tuples. This implements
GA_AIFNumericArray
, but is easier to access with handles likeGA_ROHandleFA
andGA_RWHandleFA
, for simple operations. - “string”: An integer handle to a shared table of strings is stored on each geometric element. Attributes of this type support both the
GA_AIFStringTuple
andGA_AIFSharedStringTuple
interfaces. However, for most uses,GA_ROHandleS
, GA_RWHandleS, orGA_RWBatchHandleS
are easier and faster to use. - “stringarray”: Variable-length arrays of string tuples. This implements
GA_AIFStringArray
andGA_AIFSharedStringArray
, but is easier to access withGA_ROHandleSA
andGA_RWHandleSA
, for simple operations. - “indexpair”: Associated with each element a series of
<index, value>
pairs. The index refers to an index into a shared table of “objects” and the values can be numeric tuples. For example, in the “boneCapture” attribute, the objects are capture region paths, and the values are weights. Attributes of this type support theGA_AIFIndexPair
interface, but can also be manipulated using theGA_AIFTuple
interface. - “blob”: A generalization of the “
string
” attribute where the shared table consists of arbitrary objects instead of strings. In fact, “string
” attributes are just a specialization of “blob
” attributes. Attributes of this type support theGA_AIFBlob
interface. - “blobarray”: Variable-length arrays of blob tuples. This implements
GA_AIFBlobArray
. - “blinddata”: Each element is allocated a chunk of typeless data which can be used to store arbitrary data. Attributes of this type support the
GA_AIFBlindData
interface.
String Attributes
String attributes implement two main interfaces: GA_AIFStringTuple
and GA_AIFSharedStringTuple
. Either may be used, but it is generally more efficient to use the latter as it allows code to make assumptions about how the strings are managed. That said, many operations fall back to GA_AIFStringTuple
when GA_AIFSharedStringTuple
is not available, making it possible to define a custom string attribute type that does not use a shared table.
For simple operations, GA_ROHandleS
, GA_RWHandleS
, or GA_RWBatchHandleS
are much easier to use, and faster, avoiding the virtual calls of the AIFs.
SOP Local Variables
Users may note that some attributes magically create local variables in SOPs. This is not so magical.
GEO_Detail::addVariableName()
creates a mapping between an attribute and a variable name. This mapping is kept as a detail string attribute. You can also call GEO_Detail::removeVariableName()
to remove a mapping, or GEO_Detail::getVariableNameMap()
to retrieve the list of all mappings in place. An example of this can be found in SOP/SOP_BrushHairLen.C
.
You can use GEO_Detail::getVariableNameMap()
and parse the results to perform local variable mapping, or, you can let the methods on SOP_Node do the work for you. SOP/SOP_Flatten.C
makes use of the SOP_Node::setVariableOrder()
, SOP_Node::setCurGdh()
and SOP_Node::setupLocalVars()
, SOP_Node::resetLocalVarRefs()
methods to automatically bind attributes to their local variables.
Attribute Interpolation
There are various methods in the geometry libraries to interpolate attribute point or vertex attribute values within a primitive.
Many of these interfaces require a destination vertex offset, even if there are only point attributes being interpolated, which would necessitate creating and destroying a temporary vertex referencing that point, which can be slow, and may require copying the entire detail if the detail is const. This approach is no longer recommended.
GEO_Primitive::computeInteriorPointWeights()
makes it possible to get an array of the source vertex offsets and an array of their corresponding weights for the interpolation. This makes it possible to do just the desired interpolation without modifying the source detail. The disadvantage is that this will not work for interpolating point positions in primitives like spheres, tubes, and volumes, where positions cannot be represented as linear combinations of the point position, so a special case is needed for interpolating positions if those types of primitives may be present. GEO_Primitive::evaluateInteriorPoint(UT_Vector4 &pos, fpreal u, fpreal v, fpreal w = 0)
can be used for this.
UT_Vector3 sampleNormal(const GEO_Detail &gdp, GA_Offset primoff, UT_Vector3 &primuvw, const GA_ROHandleV3 &normalattrib)
{
// Get the primitive, and the interpolation vertex offsets and weights.
const GEO_Primitive *prim = gdp.getGEOPrimitive(primoff);
UT_Array<GA_Offset> offsetarray;
UT_FloatArray weightarray;
prim->computeInteriorPointWeights(offsetarray, weightarray, primuvw.x(), primuvw.y(), primuvw.z());
// Do the weighted average.
GA_AttributeOwner owner = normalattrib->getOwner();
UT_Vector3 normal(0,0,0);
for (exint i = 0; i < offsetarray.size(); ++i)
{
// Assuming either a point or vertex normal attribute
GA_Offset offset = offsetarray(i);
if (owner == GA_ATTRIB_POINT)
offset = gdp.vertexPoint(offset);
normal += weightarray(i) * normalattrib.get(offset);
}
normal.normalize();
return normal;
}
void interpolatePoint(const GEO_Detail &source, const GEO_Primitive *srcprim, UT_Vector3 &primuvw,
GEO_Detail &dest, GA_Offset destptoff, const GA_AttributeRefMap &refmap)
{
// Get the interpolation vertex offsets and weights.
UT_Array<GA_Offset> offsetarray;
UT_FloatArray weightarray;
srcprim->computeInteriorPointWeights(offsetarray, weightarray, primuvw.x(), primuvw.y(), primuvw.z());
GA_WeightedSum sumpt;
sumpt.rewind();
refmap.startSum(sumpt, GA_ATTRIB_POINT, destptoff);
for (exint i = 0; i < offsetarray.size(); ++i)
{
refmap.addSumValue(sumpt, GA_ATTRIB_POINT, destptoff, GA_ATTRIB_POINT, source.vertexPoint(offsetarray(i)), weightarray(i));
}
refmap.finishSum(sumpt, GA_ATTRIB_POINT, destptoff);
// Special case for point position on quadrics, volumes, etc.,
// by excluding the types we know are fine.
GA_PrimitiveTypeId type = srcprim->getTypeId();
if (!( type == GA_PRIMPOLY || type == GA_PRIMPOLYSOUP ||
type == GA_PRIMBEZCURVE || type == GA_PRIMBEZSURF ||
type == GA_PRIMMESH || type == GA_PRIMNURBCURVE ||
type == GA_PRIMNURBSURF || type == GA_PRIMPART ||
type == GA_PRIMTRISTRIP || type == GA_PRIMTRIFAN ||
type == GA_PRIMTRIBEZIER || type == GA_PRIMTETRAHEDRON))
{
UT_Vector4 ptpos;
srcprim->evaluateInteriorPoint(ptpos, primuvw.x(), primuvw.y(), primuvw.z());
dest.setPos4(destptoff, ptpos);
}
}
Attribute Caveats
- The attributes of a single type of element (
point
,vertex
,primitive
,detail
) in a single scope (public
,private
,group
) must all have UNIQUE names. - At the current time, attribute names are restricted to being valid C style variable names. That means that the first character must be a letter or an underscore.
- At the current time, the same attribute cannot exist on point and and vertex elements simultaneously. For example, if there is a point attribute “
Cd
” and you try to add a vertex attribute “Cd
”, then the point attribute will be converted/promoted to a vertex attribute, regardless of the type. An exception is if aGA_ReuseStrategy
is used to specifically override this behaviour.
Geometry Primitives
There are several sub-classes of GEO_Primitive
.
- Face Primitives
- Polygon Soup Primitives
- Patch Primitives
- Quadric Primitives
- Volume Primitives
- Particle Primitives
- Metaball Primitives
- Metaball Expressions