HDK
|
In the Houdini geometry model, there are four entities that may be associated with attributes:
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.
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.
Mostly, attributes are created calling the convenience functions:
GEO_Detail::addFloatTuple(const char *name, int tuplesize, ...);
GEO_Detail::addIntTuple(const char *name, int tuplesize, ...);
GEO_Detail::addStringTuple(const char *name, int tuplesize, ...);
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.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.
Attributes can store meta data. This data is stored in a GA_AttributeOptions class. There are several methods which manipulate this data:
GA_Attribute::getTypeInfo()
GA_Attribute::setTypeInfo()
GA_Attribute::isNonTransforming()
GA_Attribute::setNonTransforming()
But it's also possible to access the options directly using GA_Attribute::getOptions
.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.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:
To traverse all the public primitive attributes in a well-defined (alpabetic) order:
It is safe to destroy attributes during traversal.
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:
A GA_AttributeFilter object is a handle to an underlying implementation which will be cleaned up automatically.
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).
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.
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:
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:
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:
However, it is also possible to copy data between differing details by binding a source detail.
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.
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:
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.
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:
calcArea()
to compute the area. Obviously, there's no way to set the area, so the intrinsic attribute is marked as read-only.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:
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:
Once you have the GA_LocalIntrinsic identifier, you can use this to access the intrinsic for any primitives of the same type. For example: