GA Porting Guide: Table Of Contents
Introduction to GA
Houdini 12.0 has a new underlying geometry library. One of the design constraints for the design of the GA library was to minimize effort required to port code from the previous GB incarnation. Thus, though the underlying data structures are very different, the GA library can provide interfaces which make it look similar (not exact), to the older GB library.
The GB library was an "object centric" view of geometry. There were point, vertex, and primitive objects (or "elements").
- Each object owned its own attribute data
- All objects of the same type had the same kind of attribute data
- Points could be shared between primitives
- Primitives used vertex objects to reference points
While the GA library keeps the concepts of points, vertices and primitives, there are no longer any individual objects (with the current exception of primitives). Instead of individual objects, the geometry stores arrays of attribute data for each class of object.
GA Terms and Definitions
- GA_Index/GA_Offset
The GA_Index represents the "ordered" of the element in the array. This corresponds to the getNum() concept found in the old GB elements.
GA_Offset represents the offset of an element into the attribute arrays. This is immutable over the lifetime of the geometry object (though it may be altered under the defragment operation).
When an object is deleted, the following objects in the array will have their GA_Offsets unchanged, but their GA_Index will be changed.
- ATI - Attribute Type Implementation
An ATI (Attribute Type Implementation) is responsible for managing an array of data for an attribute. Put simply, an ATI implements an attribute type.
- AIF - Attribute Interface
An AIF (Attribute Interface) provides a way of accessing data in an ATI. Not all ATI's will support all AIF's. For example, the GA_AIFTuple interface provides access to an attribute as an array of tuple data. Numeric attributes should provide this AIF, while string attributes may not provide this attribute interface.
- RTI - Range Type Interface
Ranges provide a unified, generic way of specifying ranges of elements. For example, there can be a GA_Range for all points, for all the points in a particular group, or for all the vertices contained in a particular primitive. Each RTI is a sub-class of GA_RangeTypeInterface and implements a way of getting at the contents of the range.
GA Blind Data (GB_ATTRIB_MIXED)
In earlier versions of geometry code, each element (point, vertex, etc.) contained a pointer to attribute data. It was possible to allocate a chunk of blind data for each element by allocating an attribute of type GB_ATTRIB_MIXED
. Since the GA library maintains arrays of attribute data (indexed by the element offset), blind data must be allocated in a different fashion.
The GA library comes with an ATI (Attribute Type Implementation) named "blinddata"
. This ATI provides the GA_AIFBlindData interface to its data.
Note that unless you create a sub-class of GA_ATIBlindData (or a new ATI which implements GA_AIFBlindData), there isn't any easy way of constructing/destructing blind data. Please see GA Blind Blob Data for a process to add reference counted arbitrary blobs of data to objects.
- Adding Blind Data
class Struct { ... };
"attrib_name",
NULL, NULL,
"blinddata");
{
{
Struct default_value();
}
}
- Writing Values
"attrib_name");
{
{
#if 0
Struct pt_value();
pt_value.setValueFor(*it);
#else
pt_value.setValueFor(*it);
#endif
}
}
- Reading Values
"attrib_name");
{
{
...
}
}
GA Blind Blob Data
When using GA_ATIBlindData a chunk of memory will always be allocated for each element.
For some algorithms, you may need to allocate a "blob" of data for some elements, but not others. GA_ATIBlob is an ATI (Attribute Type Implementation) which allocates a table of shared blobs. Blobs are reference counted as they are attached/detatched to elements. The blobs are automatically cleaned up. The blobs may contain arbitrary data, but must be sub-classed off GA_BlobData. For example, to implement strings using ATIBlob, you might have something like:
{
public:
StringBlob()
: myString()
{}
StringBlob(const char *str)
{}
virtual ~StringBlob() {}
virtual uint hash()
const {
return myString.hash(); }
{
s = dynamic_cast<const StringBlob *>(&blob);
return s && (s->myString == myString);
}
private:
};
With a class like this, you can add "string" objects to elements. For example.
"blob_name",
NULL, NULL,
"blob");
{
GA_BlobRef strblob(
new StringBlob(getStringValue(*it)));
}
You can access blobs in a similar fashion:
To delete a blob on an element, just set the blob to an empty blob handle:
Porting Cookbook
This section covers porting recipes for common code patterns in GB.
Simple Name Translation
Many classes have simple prefix renaming for similar classes in GA
GB_PointRef ==> GA_PointRef
GB_PointRedirectArray ==> GA_PointRedirectArray
Non-Intuitive Name Translation
Other classes/enums have less intuitive equivalents in GA
GB_BaseGroup ==> GA_Group
GB_Edge ==> GA_EdgeGroup::Entry
GB_AttribType
There isn't a perfect mapping from GB_AttribType to the GA library. The closest is probably GA_StorageClass (which gives a generic float, int or string storage). The "type" part of the qualifier has been split out into GA_TypeInfo.
GB_AttributeRef
N = addPointAttrib(
"N",
sizeof(
float)*3, GA_ATTRIB_VECTOR);
GB_AttributeRef Cd = addPointAttrib("Cd", sizeof(float)*3, GA_ATTRIB_FLOAT);
==>
GB_ElementList
GB had arrays of pointers to objects. As these objects no longer exist, code using element arrays should likely be re-written to be more efficient. The GA version of element lists return by value (not by reference).
GEO_PointList &pnts = gdp->points();
==>
GEO_PointList pnts = gdp->points();
GB Macros (FOR_ALL*)
FOR_ALL_POINTS(gdp,ppt) ==> GA_FOR_ALL_POINTS(gdp,ppt)
etc.
Copying Point/Vertex Attributes
vtx->copyAttributeValues(src_vertex, gdp->vertexAttribs()),
ppt->copyAttributeValues(src_point, pt_dict);
==>
vtx->copyAttributeValues(src_vertex, gdp->getAttributes());
ppt->copyAttributeValues(*src_point, gdp->getAttributes());
wranglers.getVertex().copyAttributeValues(vtx->getMapOffset(), src_vertex.getMapOffset());
wranglers.getPoint().copyAttributeValues(ppt->getMapOffset(), src_point->getMapOffset());
Point/Vertex setPos()
==>
ppt->setPos(P3);
gdp->setPos3(ppt->getMapOffset(), P3);
gdp->setPw(ppt->getMapOffset(), 1);
Point/Vertex getPos()
The GB library had methods to return a non-const reference to the UT_Vector4 storing the point's position. This is no longer possible in GA. Code which uses this technique must be changed to use getPos()/setPos()
Destroying All Points In A Group
gdp.deletePoint(*group, [fast], [remove_degenerate]);
==>
Destroying Unused Points
gdp.removeUnusedPoints(group);
==>
gdp.destroyUnusedPoints(group);
Checking Whether a Point is Referenced
Check whether a point is referenced by any primitives. This will use the topology attributes if they exist
gdp.isPointUsed(ppt)
==>
gdp.isPointUsed(ppt->getMapOffset());
Destroying Degenerate Primitives
gdp.removeDegeneratePrimitives()
==>
gdp.destroyDegeneratePrimitives();
Destroying Unused Groups
gdp.removeUnusedGroups();
==>
gdp.destroyAllEmptyGroups();
Destroy Unused Points & Degenerate Primitives
gdp.removeUnused(
const GB_PrimitiveGroup *prims,
const GB_PointGroup *
points);
==>
gdp.destroyDegeneratePrimitives(prims);
gdp.destroyUnusedPoints(points);
Iterating Over Points
In GA, iteration can be done using a GA_Range and GA_Iterator. There are also macros to encapsulate many typical types of iteration.
FOR_ALL_GPOINTS(gdp, ppt)
avg += ppt->getPos();
avg /= gdp->getNumPoints();
==>
avg += P_h(ptoff);
avg += P_h(*it);
for (
GA_Iterator pageI(gdp->getPointRange()); pageI.blockAdvance(block_start, block_end); )
{
for (
GA_Offset ptoff = blk_start; ptoff < blk_end; ++ptoff)
avg += P_h(ptoff);
}
Iterating Over Primitives
Similar to iteration over points, use a GA_Iterator. However, the iterator gives you a GA_Offset, and often when iterating over primitives, you want the actual GEO_Primitive.
FOR_ALL_PRIMITIVES(gpd, prim)
processPrimitive(prim);
==>
processPrimitive(prim);
processPrimitive(gdp->getGEOPrimitive(*it));
Vertex Iteration of a Polygon
GEO_Vertex is now a "handle" instead of a pointer. Primitives no longer contain vertex objects.
GEO_FOR_FACE_VERTICES(poly, vtx, idx) {}
==>
GEO_FOR_FACE_VERTICES(poly, vtx, idx) {}
Generic Vertex Iteration
Random access GEO_Primitive::getVertexElement() and GA_Primitive::getVertexOffset() methods exist, but it is best to use an explicit iterator.
for (
GA_Size i = 0; i < prim->getVertexCount(); i++)
{ doSomething(prim->getVertexOffset(i)); }
==>
GA_Primitive::const_iterator it;
for (prim->beginVertex(it); !it.atEnd(); ++it)
{ doSomething(it.getVertexOffset()); }
Vertex Pointers
Primitives no longer store vertex objects, so they return vertex objects (rather than references to objects). Some methods still require GEO_Vertex pointers. In these cases, you will need to create a temporary object and pass the address of the temporary. gcc will warn if you attempt to take the address of a temporary. You should pay attention to this warning.
myCurVtx[0] = &prim->getVertex(i);
==>
myCurVtxObject[0] = prim->getVertex(i);
myCurVtx[0] = &myCurVtxObject[0];
GBclearAttributeRef
GB_AttributeRef attrib_ref;
GBclearAttributeRef(attrib_ref);
==>
GBisAttributeRefValid
GB_AttributeRef attrib_ref;
if (!GBisAttributeRefValid(attrib_ref)) return false;
==>
if (!attrib_ref.
isValid())
return false;
GB_AttributeDict::appendClone()
GB_AttributeDict::appendClone(atr)
==>
gdp->addPointAttrib(atr);
gdp->addVertexAttrib(atr);
gdp->addPrimAttrib(atr);
gdp->addGlobalAttrib(atr);
getAttributeDict()
getAttributeDict() now returns a reference instead of a pointer
gdp->getAttributeDict(owner)
==>
&gdp->getAttributeDict(owner)
Renaming Attributes
Destroying a point
gdp.deletePoint(ppt->
getNum(), [fast]);
==>
gdp.deletePoint(*ppt);
Attribute Data As a const pointer
The GA library allows for data to be stored as fpreal16, fpreal32 or fpreal64 (or int8, int16, int32, int64). So, there isn't always an easy way to get a fpreal32 pointer. Data may or may not have to be marshalled out of the buffer
GB_AttributeElem &pt;
const float *data_ptr;
data_ptr = pt.getPointer(h, buffer, nfloats);
==>
const float *data_ptr;
data = pt.getPointer(h, buffer, nfloats);
internalN attribute
The viewport (and some other few places) might have created an internal attribute named internalN
to store normals for rendering. In GB, this was "hidden" by making the attribute GB_ATTRIB_MIXED. In GA, there are convenience methods to deal with this attribute defined on GEO_Detail
gdp->findPointAttrib("internalN", sizeof(float)*3, GB_ATTRIB_MIXED);
gdp->destroyPointAttrib("internalN");
==>
gdp->findInternalNormalAttribute();
gdp->destroyInternalNormalAttribute()
Finding Attributes
Note: In the GB library size was specified in bytes. GA specifies size in components.
Also, because the type is part of the method name, this does not suit code which defines standard attributes like:
#define SIM_ATT_MASS GEO_STD_ATTRIB_MASS,sizeof(float),GB_ATTRIB_FLOAT
Adding Attributes
Note: In the GB library size was specified in bytes. GA specifies size in components.
The GB library only allowed a tuple size of 1 for string attributes.
Also, because the type is part of the method name, this does not suit code which defines standard attributes like:
#define SIM_ATT_MASS GEO_STD_ATTRIB_MASS,sizeof(float),GB_ATTRIB_FLOAT
Destroying Attribute
==>
destroyPointAttrib(name);
The GB_ATTRIB_INDEX String Type
The GB_ATTRIB_INDEX stored strings using a table of shared strings where each string had a unique integer associated with it. The attribute stored an integer (-1 for no string) for each element.
If an attribute in GA provides the GA_AIFSharedStringTuple interface, similar operations can be done.
static int minus_one = -1;
GB_AttributeRef h = gdp->addPrimAttrib("tex", sizeof(int), GA_ATTRIB_INDEX, &minus_one);
GB_Attribute *atr = gdp->primitiveAttribs().findByOffset(h);
int idx;
idx = atr->addIndex("foo");
prim->setValue<
int>(
h, idx);
atr->renameIndex(idx, "bar");
==>
prim->setString(h, "foo");
int idx = prim->getStringHandle(h);
Enumerating Attributes
Note: the GA point attributes includes the "P" attribute, as well as any boolean attributes used to store group membership.
destroyPointAttrib(name);
==>
gdp->getAttributes().getDict(GA_ATTRIB_POINT).entries()
gdp->getAttributes().getDict(GA_ATTRIB_VERTEX).entries()
gdp->getAttributes().getDict(GA_ATTRIB_PRIMITIVE).entries()
Testing for any attributes
vtx->hasAllocatedAttributeValues()
==>
gdp->getAttributes().getDict(GA_ATTRIB_VERTEX).entries() > 0
Iterating Over Attributes
Use iterator objects
GB_ConstAttributeDictOffsetIterator it;
for (atr = dict.getHead(); atr; atr = atr->next()) {}
==>
{ atr = it.attrib(); ... }
GB_ATTRIB_VECTOR
The GB library used the attribute type GB_ATTRIB_VECTOR
to represent normal attributes. The GA library now stores more complete type information on attributes.
- GA_TYPE_POINT
Represents a position
- GA_TYPE_VECTOR
Represents a direction vector
- GA_TYPE_NORMAL
Represents a normal vector
- GA_TYPE_COLOR
Represents a color
- etc.
findPointAttrib(name, 3*sizeof(float), GB_ATTRIB_VECTOR);
findVertexAttrib(name, 3*sizeof(float), GB_ATTRIB_VECTOR);
findPrimAttrib(name, 3*sizeof(float), GB_ATTRIB_VECTOR);
==>
Finding a GB_AttributeRef from a GB_Attribute pointer
GB_AttributeRef
r = gdp->pointAttribs().getOffset(
attribute);
==>
Finding a GB_Attribute from a GB_AttributeRef
The GA_ROAttributeRef/GA_RWAttributeRef objects now hold a pointer to the attribute
GB_Attribute *a = gdp.pointAttribs().findByOffset(aref);
==>
Detail Attributes Using Objects
gdp->attribs().getElement().getValue(aref, ...);
gdp->attribs().getElement().setValue(aref, ...);
==>
gdp.element().getValue(ref...);
gdp.element().setValue(ref...);
Duplicating Attribute Definition Between Details 1
In GB, GEO_AttribDict
or GB_AttributeDict
could be used to clone attributes. In GA, there is GA_AttributeDict.
GEO_PointAttribDict &pattrib = gdp->pointAttribs();
for (GB_Attribute *atr = (GB_Attribute*)
head(); atr;
atr = (GB_Attribute*)atr->next())
==>
{
dst_gdp->getAttributes().cloneAttribute(GA_ATTRIB_POINT, a->
getName(), *
a,
true);
}
Duplicating Attribute Definition Between Details 2
In GB, GEO_Detail::sortAllAttributes()
and GEO_Detail::mergeDetailAttributes()
could be used to clone attributes. In GA, there is GA_Detail::cloneMissingAttributes()
, though, unlike GEO_Detail::mergeDetailAttributes()
, it only clones attribute definitions and does not copy attribute values.
gdp->sortAllAttributes(*src_gdp);
...
gdp->mergeDetailAttributes(*src_gdp, original_point_count);
==>
gdp->cloneMissingAttributes(*src_gdp, GA_ATTRIB_POINT,
filter);
gdp->cloneMissingAttributes(*src_gdp, GA_ATTRIB_VERTEX,
filter);
gdp->cloneMissingAttributes(*src_gdp, GA_ATTRIB_PRIMITIVE,
filter);
Checking the attribute type (now called storage)
In GB, GB_Attribute::getType() would return a numerical type of an attribute. In GA, GA_Attribute::getType() returns a GA_AttributeType object, which contains overall information about an attribute, like a name and uniqe ID, rather than just a numeric type. To find out the numeric type, the simplest way is to call GA_Attribute::getStorageClass(). GA_StorageClass is a convenience enumeration that is based on the GA_Storeage and it is GA_Storage that is used for setting the underlying storage.
if (atr->
getType() == GB_ATTRIB_INT)
...
atr->setType(GB_ATTRIB_FLOAT);
==>
...
Adding Index Pair Attributes
gdp->addPtIndexPairAttribute(name, npairs*sizeof(float32));
==>
GB_AttributeIndexPairs
GB_AttributeIndexPairs<int, float> attrib(ppt, ref);
==>
Working With Point Capture Attributes
const char *name = GEO_Detail::getPointCaptureIndexAttribName(
type);
GB_Attribute *path_attrib = gdp->attribs().find(name, sizeof(int), GB_ATTRIB_INDEX);
if(path_attrib)
{
int num_paths = path_attrib->getIndexSize();
for (int i = 0; i < num_paths; ++i)
{
<tab>
const char *
path = path_attrib->getIndex(i);
<tab>
}
}
==>
if(path_attrib.isValid())
{
int num_paths = path_attrib.getNumPaths();
for (int i = 0; i < num_paths; ++i)
{
<tab>
const char *
path = path_attrib.getPath(i);
<tab>
}
}
GEOPRIMPOLY: Primitive Ids
In GB, the some primitive ids were a bit mask, others were not.
There are two ways to port querying primitive types.
For compatibility, there is a GEO_PrimTypeCompat namespace containing an object for each of the old named bitfields. These compatibility objects work much in the same way as the previous defines. That is, you can or
them together as bit fields, etc. Using them as simple as pre-pending GEO_PrimTypeCompat:
:
The alternative is to switch to the new code. Rather than using prim->getPrimitiveId()
, you would use prim->getTypeId()
instead.
The primitive type ID provides a simple integer (not a bit field mask). The new IDs for well-known primitives add an underscore to the old name. For example GEO_PRIMPOLY
.
switch (prim->getPrimitiveId()) {
case GEOPRIMPOLY: ... }
==>
FOR_MASK_PRIMITIVES
Traversing Edges
FOR_ALL_GROUP_EDGES(edgelist(), e) {}
==>
Edges And Primitive Pointers
The optional primitive is now stored in the group entry and not the edge object.
FOR_ALL_GROUP_EDGES(group, edge) { processPrimitive(edge->prim()); }
==>
processPrimitive(it.getPrimitive());
Edge Points
==>
gdp->getGEOPoint(edge.
p0());
Edge Point Position
Edge Hash
Edge hashes are constructed with the point offsets
GB_EdgeHash(pt0, pt1)
==>
Iterating Over Groups
Use iterator objects
for (grp = gdp->primitiveGroups().head(); grp; grp = grp->next())
==>
Iterating Over Groups
Note: It is not safe to delete groups from within the macro traversal
FOR_ALL_POINTGROUPS, FOR_ALL_PRIMGROUPS
==>
Creating a GB_Edge
You likely won't have to allocate a new edge now as edges are no longer linked list nodes themselves. You should be able to create edges off the stack.
new GB_Edge(p0, p1);
==>
GA_Edge(p0->getMapOffset(), p1->getMapOffset());
Querying Group Type
The GB defines have been changed to more consistent names for GA:
- GBGROUP ==> GA_GROUP_TYPE_MASK
- GBPOINTGROUP ==> GA_GROUP_POINT
- GBPRIMITIVEGROUP ==> GA_GROUP_PRIMITIVE
- GBEDGEGROUP ==> GA_GROUP_EDGE
- GBBREAKPOINTGROUP ==> GA_GROUP_BREAKPOINT
- GBVERTEXGROUP ==> GA_GROUP_VERTEX
if (group()->classType() == GBPRIMITIVEGROUP)
==>
Adding to a group and making it ordered.
Making a group ordered is no longer part of adding/removing elements. The creation of ordered groups should be done outside of any loops
group->add(element, 1);
==>
group->makeOrdered();
group->add(element);
Checking if a Group is Ordered
group->ordered()
==>
group->getOrdered() != NULL
Copying A Group
gdp.copyGroup(existing, new_name);
==>
grp = gdp.createInternalElementGroup(GA_ATTRIB_PRIMITIVE);
grp = gdp.createElementGroup(GA_ATTRIB_POINT, new_name);
grp->copyMembership(existing);
Copying Group Membership
group->setFlags(source_group)
==>
group->copyMembership(*source_group)
Testing group membership
group->contains(prim->getNum())
==>
group->contains(*prim);
group->containsIndex(prim->getNum());
Optimizing tests of group membership
prim->getGroups().isset(group->getOffset(), group->getMask());
==>
group->contains(prim);
Removing From A Group
group->remove(prim->getNum())
==>
group->remove(*prim);
group->removeIndex(prim->getNum());
Adding To A Group
group->add(prim->getNum())
==>
group->add(*prim);
group->addIndex(prim->getNum());
Destroying A Group
group->destroyGroup(group);
==>
gdp->destroyElementGroup(group);
gdp->destroyGroup(group);
Vertex Group Iteration
GB_VertexGroupIterator it(group);
GB_VertexData vtxdata;
int valid;
for (valid = it.head(vtxdata); valid; valid = it.next(vtxdata))
{
GB_Primitive *prim = vtxdata.prim();
int index = vtxdata.linearIndex();
...
}
==>
GA_GBVertexGroupIterator it(group);
for (it.rewind(); !it.atEnd(); it.advance())
{
int index = it.getLinearIndex();
...
}
GB_Basis
GB_Basis::breakCount()
GB_Basis::interpolatesEnds() const
GB_Basis::interpolatesEnds(
value)
GB_Basis::multiplicity()
==>
GB_NUBBasis
GB_NUBBasis::knotsEqualSpace()
GB_NUBBasis::knotsAveraging()
GB_NUBBasis::knotsSpreading()
GB_NUBBasis::knotsBreakpoints()
GB_NUBBasis::breakpnt(
int/
fpreal)
GB_NUBBasis::periodic()
GB_NUBBasis::validInterval()
==>
Wrapping GB_NUBBasis
GB_NUBBasis::wrap();
GB_NUBBasis::unwrap();
==>
Getting Basis Data
Binomial Coefficients
==>
GA_Basis::theBinomal[i][
j]
Break Points
GB_Breakpoint::evaluate(pos)
==>
Break Point Groups
==>
breakpoint_group.add(original_bkpt)
Barycentric Coordinates
vtx.getPt()->baryAttributeValues(v0->getPt(),
v1->getPt(),
v2->getPt(), u,
v);
vtx.baryAttributeValues(v0,
v1,
v2, u,
v);
==>
map.append(GA_AttributeFilter::selectAll(), GA_ATTRIB_VERTEX);
map.append(GA_AttributeFilter::selectAll(), GA_ATTRIB_POINT);
gah.barycentric(v0.getMapOffset(),
v1.getMapOffset(),
v2.getMapOffset(), u,
v);
Bilinear Interpolation
vtx.getPt()->bilinearAttributeValues(v0->getPt(),
v1->getPt(),
v2->getPt(), u,
v);
vtx.bilinearAttributeValues(v0,
v1,
v2, u,
v);
==>
GA_MultiAttributeHandle map(gdp);
map.append(GA_AttributeFilter::selectAll(), GA_ATTRIB_VERTEX);
map.append(GA_AttributeFilter::selectAll(), GA_ATTRIB_POINT);
gah.bilinear(v0.getMapOffset(),
v1.getMapOffset(),
v2.getMapOffset(), u,
v);
Enumerating Profile Curves
In GA, primitives can have multiple secondary details (rather than a single detail). Thus, there is an additional query getNumSecondaryDetails()
.
You can also query primitives in a secondary detail using GA_Offset using prim->getSecondaryByOffset(detail_index,offset)
.
prim->numSecondary();
prim->getSecondary(index);
==>
Profile Curve Groups
group->addMix(prim, profile);
==>
group->addMix(prim, &sec);
Iterating Over Profiles
==>
'FOR_ALL_MIX_GROUP_PRIMITIVES()
Setting Crease Weights
GA_Edge no longer has a reference to a primitive. Thus, you need to specify the primitive explicitly
const GB_AttributeRef &vtxoff,
const GB_AttributeRef &ptoff);
==>
Setting Local Variables in SOPs
SOP_Node no longer has a myCurPt[] or myCurPrim[]. Instead, the myCurPrimOff[] and myCurPtOff[] are used.
myCurPt[0] = ppt;
myCurPt[0] = 0;
==>