HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Geometry Attributes 101

Overview of attributes

In the Houdini geometry model, there are four entities that may be associated with attributes:

  • Points
  • Primitives
  • Vertices
  • Detail

A point represents a position in space. A primitive represents geometry. Each primitive has vertices which reference points. Points may be shared, vertices are unique per primitive. The detail is the container for the geometry.

An attribute object encapsulates both the definition and all the data associated with that attribute.

Attribute References

GA_[RO|WO|RW]AttributeRef are classes that were mostly used with older interfaces that are now deprecated, but some attribute creation and finding functions still return them. A GA_ROAttributeRef can be implicitly cast to a const GA_Attribute *, and a GA_RWAttributeRef can be implicitly cast to a GA_Attribute *, so you can safely use pointers to GA_Attribute in your code, instead.

Creating New Attributes

Mostly, attributes are created calling the convenience functions:

These convenience functions are wrappers on the more generic GEO_Detail::addAttribute() method which allows much finer grained control over creating attributes (including ways to create non-standard attributes).

In most cases, the add*Tuple methods are easy and sufficient. However, in some special cases, you may need finer control. In this case you may need to call the generic GEO_Detail::addAttribute() function which takes the arguments:

  • const char *name The name of the attribute.
  • const UT_Options *creation_args Optional collection of arguments used by the factory allocator. For example, tuple_size and a default value can be passed in this object when creating a "numeric" attribute.
  • const GA_AttributeOptions *attribute_options Optional collection of settings inherited by the attribute.
  • const char *style The type of attribute. Current standard types are: "numeric", "string", "indexpair", "blob", "blinddata", "arraydata", "stringarray", and "blobarray".
  • GA_AttributeOwner owner This is one of: GA_ATTRIB_POINT, GA_ATTRIB_VERTEX, GA_ATTRIB_PRIMITIVE, GA_ATTRIB_DETAIL.
  • GA_AttributeScope scope This is one of: GA_SCOPE_PUBLIC, GA_SCOPE_PRIVATE, GA_SCOPE_GROUP.

Attribute Defaults

With addFloatTuple and addIntTuple, one of the defaulted arguments is a GA_Defaults argument. The GA_Defaults class is used to specify what the defaults are for the attribute.

String attributes are stored as an indexed array, the default value for the index is -1, indicating an undefined (empty) string. There's currently no way to change the default string.

Once an attribute has been created, it's currently not very easy to change the defaults of the attribute. This is because the paged data arrays rely on the default values for compression. The easiest way to change a default would be to create a new attribute and copy over values.

Attribute Meta Data

Attributes can store meta data. This data is stored in a GA_AttributeOptions class. There are several methods which manipulate this data:

Deleting Attributes

Attributes are deleted by calling GEO_Detail::destroyAttribute()

This function takes arguments:

  • GA_AttributeOwner owner This is one of: GA_ATTRIB_POINT, GA_ATTRIB_VERTEX, GA_ATTRIB_PRIMITIVE, GA_ATTRIB_DETAIL.
  • GA_AttributeScope scope This is one of: GA_SCOPE_PUBLIC, GA_SCOPE_PRIVATE, GA_SCOPE_GROUP.
  • const char *name The name of the attribute.
  • const GA_AttributeFilter *filter An optional filter, that if provided, will be used to ensure that only an attribute matched by the filter will be destroyed.

Traversing Attributes

Each GA_AttributeOwner has its own dictionary of attributes that can be searched or traversed independently. Aside from the public attributes, a dictionary can contain private and group attributes, so be careful about scope during traversal.

For example, to traverse all the public primitive attributes in an undefined order:

for (GA_AttributeDict::iterator it = gdp->getAttributeDict(GA_ATTRIB_PRIMITIVE).begin(GA_SCOPE_PUBLIC); !it.atEnd(); it.advance())
{
GA_Attribute *attrib = it.attrib();
...
}

To traverse all the public primitive attributes in a well-defined (alpabetic) order:

for (GA_AttributeDict::ordered_iterator it = gdp->getAttributeDict(GA_ATTRIB_PRIMITIVE).obegin(GA_SCOPE_PUBLIC); !it.atEnd(); it.advance())
{
GA_Attribute *attrib = it.attrib();
...
}

It is safe to destroy attributes during traversal.

Filtering Attributes

When traversing or searching attribute dictionaries, it is often useful to identify attributes having some specific criteria. The easiest way to do this is to use a GA_AttributeFilter. You can implement your own custom filters, but GA_AttributeFilter provides an assortment of static methods for pre-defined filter types.

For example, to find all of the floating point tuple types among the public primitive attributes: order:

for (GA_AttributeDict::iterator it = gdp->getAttributeDict(GA_ATTRIB_PRIMITIVE).begin(GA_SCOPE_PUBLIC); !it.atEnd(); it.advance())
{
if (float_filter.match(it.attrib()))
...
}

A GA_AttributeFilter object is a handle to an underlying implementation which will be cleaned up automatically.

The Many Ways To Access Attribute Data

Specialized GA_Handle Accessors

The most common way of accessing attributes in Houdini uses a set of specialized handle classes that validate attribute types on binding. Typically such handles support a single attribute type.

For example, GA_[RO|RW]HandleV3 can only be bound to a GA_ATINumeric attribute of tuple size at least 3, while GA_[RO|RW]HandleS can only be bound to a GA_ATIString attribute of tuple size at least 1.

A numeric handle bound to an attribute whose storage type does not match the handle's storage type, e.g. a GA_ROHandleF (32-bit floating-point) bound to an attribute whose storage type is 64-bit floating-point, will automatically convert between the types, at the expense of the conversion time, and having to call a function pointer, (like a virtual function call).

GA_RWHandleV3 N_h(gdp, GA_ATTRIB_POINT, "N");
if (N_h.isValid())
{
for (GA_Iterator it(gdp->getPointRange()); !it.atEnd(); ++it)
{
GA_Offset offset = *it;
UT_Vector3 N = N_h.get(offset);
N.normalize();
N_h.set(offset, N);
}
}

Specialized GA_PageHandle Accessors

The specialized handle classes eliminate some of the overhead associated with each individual access, but they still work with one individual element at a time. To further amortize data access cost, one can work on contiguous blocks of elements. Since the buffers returned by the page handle are guaranteed to be contiguous, this can allow you to use vector operations (i.e. SSE) on the data.

Note that page handles may keep a marshalled buffer of data if the attribute type doesn't match exactly the handle type. This means that it's not safe to use the same page handle object in multiple threads at the same time. However, if each thread has its own page handle, and writes only to offsets in pages that are not written-to by any other thread, it should be threadsafe.

GA_Attribute *attrib = gdp->findFloatTuple(GA_ATTRIB_POINT, "N", 3);
GA_RWPageHandleV3 N_ph(attrib);
if (N_ph.isValid())
{
for (GA_Iterator it(gdp->getPointRange()); it.blockAdvance(start, end); )
{
N_ph.setPage(start);
for (GA_Offset offset = start; offset < end; ++offset)
N_ph.value(offset).normalize(); // direct data access
}
}

Attribute Interfaces (AIFs)

Houdini provides many abstract interfaces for manipulating attributes. All a newly defined attribute type needs to do is implement the subset of those interfaces that makes sense, and any operation that uses those interfaces will automatically be able to work with that new attribute type.

There is a cost associated with this versatility. Because such interfaces are implemented with virtual methods that take the attribute as an argument, a performance hit is unavoidable. Regardless, AIFs represent the most abstract and generic way to manipulate attributes.

Some common AIFs are:

GA_Attribute *attrib = gdp->findFloatTuple(GA_ATTRIB_POINT, "N", 3);
const GA_AIFTuple *tuple = attrib->getAIFTuple();
if (tuple)
{
for (GA_Iterator it(gdp->getPointRange()); !it.atEnd(); it.advance())
{
GA_Offset offset = it.getOffset();
tuple->get(attrib; offset, N.data(), 3);
N.normalize();
tuple->set(attrib, offset, N.data(), 3);
}
}

Attribute Reference Maps

An attribute reference map (GA_AttributeRefMap) provides a mechanism for performing operations on a set of attributes. For example, an attribute reference map is supplied when evaluating an interior point on a primitive to control which attributes are evaluated.

At the highest level, an attribute reference map represents a list of destination attributes from a single detail, each tied to a source attribute, all from the same detail, which can differ from the destination detail. Moreover, an attribute reference map can cross element boundaries, containing a mix of point, vertex, primitive and even detail attributes.

Since attribute reference maps have to evaluate multiple attributes simultaneously, they sometimes need to have temporary storage for intermediate computations. This is usually done using the GA_WorkVertexBuffer class.

For example:

refmap.bindDetail(*gdp, *gdp);
// Add all floating point vertex attributes
refmap.append(filter, GA_ATTRIB_VERTEX);
// Add all floating point point attributes
refmap.append(filter, GA_ATTRIB_POINT);
// Get the interpolation vertex offsets and weights of the primitive
// at the parametric coordinates (u,v,w). w is zero for polygon
// primitives.
UT_Array<GA_Offset> offsetarray;
UT_FloatArray weightarray;
srcprim->computeInteriorPointWeights(offsetarray, weightarray, primuvw.x(), primuvw.y(), primuvw.z());
sum.rewind();
refmap.startSum(sum, GA_ATTRIB_VERTEX, destvtxoff);
for (exint i = 0; i < offsetarray.size(); ++i)
{
refmap.addSumValue(sum, GA_ATTRIB_VERTEX, destvtxoff, GA_ATTRIB_VERTEX, offsetarray(i), weightarray(i));
}
refmap.finishSum(sum, GA_ATTRIB_VERTEX, destvtxoff);
// 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);
}
// Do something with destvtxoff
...

In typical usage, attribute handles are used to perform operations on objects within a single piece of geometry. For example, to copy attributes from one point to another:

GA_AttributeRefMap refmap(*gdp);
refmap.append(GA_AttributeFilter::selectFloatTuple(), GA_ATTRIB_POINT);
refmap.copyValue(GA_ATTRIB_POINT, gdp->pointOffset(0), // destination
GA_ATTRIB_POINT, gdp->pointOffset(1)); // source

However, it is also possible to copy data between differing details by binding a source detail.

GA_AttributeRefMap refmap(*dest_gdp, src_gdp);
refmap.append(GA_AttributeFilter::selectFloatTuple(), GA_ATTRIB_POINT);
refmap.copyValue(GA_ATTRIB_POINT, gdp->pointOffset(0), // destination
GA_ATTRIB_POINT, gdp->pointOffset(1)); // source

String Attributes

String attributes in GA_ATIString are implemented using a shared string table. If multiple elements store the same string value, only a single copy of the string is stored, but the string is referenced multiple times. When strings are no longer referenced by elements, they are automatically deleted from the string table.

Each string is given a unique integer identifier. The string attribute actually stores an array of integers (rather than pointers to strings). The easiest way to access string data is to use a GA_RWHandleS.

GA_RWHandleS h(gdp, GA_ATTRIB_POINT, "name");
if (!h.isValid())
return;
for (GA_Iterator it(gdp.getPointRange()); !it.atEnd(); ++it)
{
GA_Offset offset = *it;
const char *string_value = h.get(offset);
int string_index = h.getIndex(offset);
}

However, using the GA_AIFSharedStringTuple interface, it's also possible to get more information out of the attribute. For example, to extract all the strings and their indices:

const GA_Attribute *attrib = gdp.findStringTuple("name");
if (!attrib)
return;
if (!stuple)
return;
stuple->extractStrings(attrib, strings, indices);

Note, that because strings can be added and deleted, it's possible that there are "holes" in the index list. Calling compactStorage() should remove the holes, but it's important to realize that the indices can have values larger than the number of strings.

Intrinsic Attributes

Many primitives store member data which define "intrinsic" properties of primitives. For example, metaball primitives have a metaball kernel and a metaball weight (in addition to the inherited quadric transform). The GA library allows the designer of a primitive to provide generic access methods to get/set integer, float or string "intrinsic" data.

The low level primitive interface provides methods to register, evaluate and optionally set intrinsic attributes. The file GA_IntrinsicMacros.h also provides a set of macros which streamlines the interface to define and evaluate intrinsic attributes.

It's likely simplest to use the macros defined in GA_IntrinsicMacros.h, but if you want to avoid using the macros, you can still use the raw interfaces. The raw method interface consists of:

  • GA_Primitive::registerIntrinsics
    Define the name, type and read-only status of all intrinsics for the primitive.
  • int GA_Primitive::localIntrinsicTupleSize(const GA_IntrinsicEval &) const
    Return the tuple size (extent, number of elements) in the intrinsic attribute. This does not have to be constant, and may have different values for each primitive.
  • int localGetIntrinsicI(const GA_IntrinsicEval &, int64 *, GA_Size) const
    int localGetIntrinsicF(const GA_IntrinsicEval &, fpreal64 *, GA_Size) const
    int localGetIntrinsicS(const GA_IntrinsicEval &, UT_String &) const
    int localGetIntrinsicSA(const GA_IntrinsicEval &, UT_StringArray &) const
    Get the value for an intrinsic attribute. These methods should return the number of values set by the method. The size passed in is the buffer size and the method should not write past the end of the array.
  • int localSetIntrinsicI(const GA_IntrinsicEval &, int64 *, GA_Size)
    int localSetIntrinsicF(const GA_IntrinsicEval &, fpreal64 *, GA_Size)
    int localSetIntrinsicSS(const GA_IntrinsicEval &, const char **, GA_Size)
    int localSetIntrinsicSA(const GA_IntrinsicEval &, const UT_StringArray &)
    These methods allow a user to set intrinsic data. Not all intrinsic attributes need to provide these methods. For example, the GEO_Primitive base class provides an intrinsic attribute ("measuredarea") which calls calcArea() to compute the area. Obviously, there's no way to set the area, so the intrinsic attribute is marked as read-only.

Defining Intrinsic Attributes

This section is primarily for developers who are writing custom primitive types for Houdini.

The section above discusses the raw methods to define intrinsic attributes. However, using the macros defined in GA_IntrinsicMacros.h makes the whole process much more streamlined.

In the class definition of your primitive, you need to declare that the primitive has intrinsic attributes. You can do this by adding the line:

This will add the declaration of the raw intrinsic methods to your class. The GA_NO_OVERRIDE indicates that they will be created without the override keyword. To declare as override methods, use instead:

In the implementation of your class, you can use the other macros to provide access to intrinsic properties of your class. The code pattern looks kind of like:

enum {
geo_INTRINSIC_SCALAR1,
geo_INTRINSIC_TUPLE1,
...
}
namespace
{
// Put all evaluation methods into an anonymous namespace.
// You can look at the comments in GA_IntrinsicMacros.h for
// details on the expected signatures for the evaluation
// callbacks
static fpreal
evalScalar1(const ClassName *prim)
{
return computeProperty1(...);
}
static void
setScalar1(ClassName *prim, fpreal value)
{
...
}
static int
evalTuple1(const ClassName *prim, int64 *v, GA_Size tuple_size)
{
// Ensure we don't have any buffer overrun
tuple_size = SYSmin(tuple_size, TUPLE_SIZE_1);
for (int i = 0; i < tuple_size; ++i)
v[i] = getTupleComponent(prim, i);
return tuple_size; // Return number of items retrieved
}
}
// Start intrinsic definitions for our class. Here, ClassName is
// the name of the class.
// Declare methods to access intrinsic values
GA_INTRINSIC_F(ClassName, geo_INTRINSIC_SCALAR1,
"scalarname1", evalScalar1)
GA_INTRINSIC_TUPLE_F(ClassName, geo_INTRINSIC_TUPLE1,
"tuplename1", 3, evalTuple1)
// Declare methods to set intrinsic values
GA_INTRINSIC_SET_F(ClassName, geo_INTRINSIC_SCALAR1, setScalar1)
// Now, finish the intrinsic defintions.
// Here, BaseClassName is the name of the GEO_Primitive class we
// inherit from (i.e. GEO_Primitive or GEO_TriMesh).
GA_END_INTRINSIC_DEF(ClassName, BaseClassName)

Using Intrinsic Attributes

There are two steps to evaluating an intrinsic for a given primitive. The first is to look up the local intrinsic identifier for the given primitive type. The identifiers for the same intrinsic attribute name may be different for different primitive types. For example the "transform" intrinsic might be 7 for a GEO_PrimSphere, but 18 for a GEO_PrimVolume. However, the identifier will be the same value for all GEO_PrimSphere objects. To look up the GA_LocalIntrinsic identifier for a primitive, you can call:

findIntrinsicIdentifier(const GEO_Primitive &prim, const char *name)
{
if (!GAisValidLocalIntrinsic(i))
throw EvaluationError("Invalid intrinsic name");
return i;
}

Once you have the GA_LocalIntrinsic identifier, you can use this to access the intrinsic for any primitives of the same type. For example:

void
dumpIntrinsicValue(const GEO_Primitive &prim, GA_LocalIntrinsic id)
{
bool readonly = prim.getIntrinsicReadOnly(id);
exint tuplesize = prim.getIntrinsicTupleSize(id);
const char *name = prim.getIntrinsicName(id);
const UT_Options *getIntrinsicOptions(id);
switch (storage)
{
{
exint evalsize = prim.getIntrinsic(id, values, tuplesize);
UT_ASSERT(evalsize == tuplesize);
dumpValues(name, options, readonly, storage, values, evalsize);
}
break;
{
exint evalsize = prim.getIntrinsic(id, values, tuplesize);
UT_ASSERT(evalsize == tuplesize);
dumpValues(name, options, readonly, storage, values, evalsize);
}
break;
{
exint evalsize = prim.getIntrinsic(id, values);
UT_ASSERT(evalsize == tuplesize);
UT_ASSERT(evalsize == values.entries());
dumpValues(name, options, readonly, storage, values, evalsize);
}
break;
}
}